blob: ba004e927fde624d54678da9235c99b61841d4ba [file] [log] [blame]
/*
* Copyright (C) 2009 Alex Milowski (alex@milowski.com). All rights reserved.
* Copyright (C) 2010 Apple Inc. All rights reserved.
* Copyright (C) 2016 Igalia S.L.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "MathMLPresentationElement.h"
#if ENABLE(MATHML)
#include "ElementIterator.h"
#include "HTMLHtmlElement.h"
#include "HTMLMapElement.h"
#include "HTMLNames.h"
#include "HTMLParserIdioms.h"
#include "MathMLMathElement.h"
#include "MathMLNames.h"
#include "RenderMathMLBlock.h"
#include "RenderTableCell.h"
#include "SVGSVGElement.h"
#include <wtf/IsoMallocInlines.h>
namespace WebCore {
WTF_MAKE_ISO_ALLOCATED_IMPL(MathMLPresentationElement);
using namespace MathMLNames;
MathMLPresentationElement::MathMLPresentationElement(const QualifiedName& tagName, Document& document)
: MathMLElement(tagName, document)
{
}
Ref<MathMLPresentationElement> MathMLPresentationElement::create(const QualifiedName& tagName, Document& document)
{
return adoptRef(*new MathMLPresentationElement(tagName, document));
}
RenderPtr<RenderElement> MathMLPresentationElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition& insertionPosition)
{
if (hasTagName(mtableTag))
return createRenderer<RenderMathMLTable>(*this, WTFMove(style));
return MathMLElement::createElementRenderer(WTFMove(style), insertionPosition);
}
bool MathMLPresentationElement::isPhrasingContent(const Node& node)
{
// Phrasing content is described in the HTML 5 specification:
// http://www.w3.org/TR/html5/dom.html#phrasing-content.
if (!node.isElementNode())
return node.isTextNode();
if (is<MathMLElement>(node)) {
auto& mathmlElement = downcast<MathMLElement>(node);
return is<MathMLMathElement>(mathmlElement);
}
if (is<SVGElement>(node)) {
auto& svgElement = downcast<SVGElement>(node);
return is<SVGSVGElement>(svgElement);
}
if (is<HTMLElement>(node)) {
// FIXME: add the <data> and <time> tags when they are implemented.
auto& htmlElement = downcast<HTMLElement>(node);
return htmlElement.hasTagName(HTMLNames::aTag)
|| htmlElement.hasTagName(HTMLNames::abbrTag)
|| (htmlElement.hasTagName(HTMLNames::areaTag) && ancestorsOfType<HTMLMapElement>(htmlElement).first())
|| htmlElement.hasTagName(HTMLNames::audioTag)
|| htmlElement.hasTagName(HTMLNames::bTag)
|| htmlElement.hasTagName(HTMLNames::bdiTag)
|| htmlElement.hasTagName(HTMLNames::bdoTag)
|| htmlElement.hasTagName(HTMLNames::brTag)
|| htmlElement.hasTagName(HTMLNames::buttonTag)
|| htmlElement.hasTagName(HTMLNames::canvasTag)
|| htmlElement.hasTagName(HTMLNames::citeTag)
|| htmlElement.hasTagName(HTMLNames::codeTag)
|| htmlElement.hasTagName(HTMLNames::datalistTag)
|| htmlElement.hasTagName(HTMLNames::delTag)
|| htmlElement.hasTagName(HTMLNames::dfnTag)
|| htmlElement.hasTagName(HTMLNames::emTag)
|| htmlElement.hasTagName(HTMLNames::embedTag)
|| htmlElement.hasTagName(HTMLNames::iTag)
|| htmlElement.hasTagName(HTMLNames::iframeTag)
|| htmlElement.hasTagName(HTMLNames::imgTag)
|| htmlElement.hasTagName(HTMLNames::inputTag)
|| htmlElement.hasTagName(HTMLNames::insTag)
|| htmlElement.hasTagName(HTMLNames::kbdTag)
|| htmlElement.hasTagName(HTMLNames::keygenTag)
|| htmlElement.hasTagName(HTMLNames::labelTag)
|| htmlElement.hasTagName(HTMLNames::mapTag)
|| htmlElement.hasTagName(HTMLNames::markTag)
|| htmlElement.hasTagName(HTMLNames::meterTag)
|| htmlElement.hasTagName(HTMLNames::noscriptTag)
|| htmlElement.hasTagName(HTMLNames::objectTag)
|| htmlElement.hasTagName(HTMLNames::outputTag)
|| htmlElement.hasTagName(HTMLNames::progressTag)
|| htmlElement.hasTagName(HTMLNames::qTag)
|| htmlElement.hasTagName(HTMLNames::rubyTag)
|| htmlElement.hasTagName(HTMLNames::sTag)
|| htmlElement.hasTagName(HTMLNames::sampTag)
|| htmlElement.hasTagName(HTMLNames::scriptTag)
|| htmlElement.hasTagName(HTMLNames::selectTag)
|| htmlElement.hasTagName(HTMLNames::smallTag)
|| htmlElement.hasTagName(HTMLNames::spanTag)
|| htmlElement.hasTagName(HTMLNames::strongTag)
|| htmlElement.hasTagName(HTMLNames::subTag)
|| htmlElement.hasTagName(HTMLNames::supTag)
|| htmlElement.hasTagName(HTMLNames::templateTag)
|| htmlElement.hasTagName(HTMLNames::textareaTag)
|| htmlElement.hasTagName(HTMLNames::uTag)
|| htmlElement.hasTagName(HTMLNames::varTag)
|| htmlElement.hasTagName(HTMLNames::videoTag)
|| htmlElement.hasTagName(HTMLNames::wbrTag);
}
return false;
}
bool MathMLPresentationElement::isFlowContent(const Node& node)
{
// Flow content is described in the HTML 5 specification:
// http://www.w3.org/TR/html5/dom.html#flow-content
if (isPhrasingContent(node))
return true;
if (!is<HTMLElement>(node))
return false;
auto& htmlElement = downcast<HTMLElement>(node);
// FIXME add the <dialog> tag when it is implemented.
return htmlElement.hasTagName(HTMLNames::addressTag)
|| htmlElement.hasTagName(HTMLNames::articleTag)
|| htmlElement.hasTagName(HTMLNames::asideTag)
|| htmlElement.hasTagName(HTMLNames::blockquoteTag)
|| htmlElement.hasTagName(HTMLNames::detailsTag)
|| htmlElement.hasTagName(HTMLNames::divTag)
|| htmlElement.hasTagName(HTMLNames::dlTag)
|| htmlElement.hasTagName(HTMLNames::fieldsetTag)
|| htmlElement.hasTagName(HTMLNames::figureTag)
|| htmlElement.hasTagName(HTMLNames::footerTag)
|| htmlElement.hasTagName(HTMLNames::formTag)
|| htmlElement.hasTagName(HTMLNames::h1Tag)
|| htmlElement.hasTagName(HTMLNames::h2Tag)
|| htmlElement.hasTagName(HTMLNames::h3Tag)
|| htmlElement.hasTagName(HTMLNames::h4Tag)
|| htmlElement.hasTagName(HTMLNames::h5Tag)
|| htmlElement.hasTagName(HTMLNames::h6Tag)
|| htmlElement.hasTagName(HTMLNames::headerTag)
|| htmlElement.hasTagName(HTMLNames::hrTag)
|| htmlElement.hasTagName(HTMLNames::mainTag)
|| htmlElement.hasTagName(HTMLNames::navTag)
|| htmlElement.hasTagName(HTMLNames::olTag)
|| htmlElement.hasTagName(HTMLNames::pTag)
|| htmlElement.hasTagName(HTMLNames::preTag)
|| htmlElement.hasTagName(HTMLNames::sectionTag)
|| (htmlElement.hasTagName(HTMLNames::styleTag) && htmlElement.hasAttribute("scoped"))
|| htmlElement.hasTagName(HTMLNames::tableTag)
|| htmlElement.hasTagName(HTMLNames::ulTag);
}
const MathMLElement::BooleanValue& MathMLPresentationElement::cachedBooleanAttribute(const QualifiedName& name, std::optional<BooleanValue>& attribute)
{
if (attribute)
return attribute.value();
// In MathML, attribute values are case-sensitive.
const AtomicString& value = attributeWithoutSynchronization(name);
if (value == "true")
attribute = BooleanValue::True;
else if (value == "false")
attribute = BooleanValue::False;
else
attribute = BooleanValue::Default;
return attribute.value();
}
MathMLElement::Length MathMLPresentationElement::parseNumberAndUnit(const StringView& string)
{
LengthType lengthType = LengthType::UnitLess;
unsigned stringLength = string.length();
UChar lastChar = string[stringLength - 1];
if (lastChar == '%') {
lengthType = LengthType::Percentage;
stringLength--;
} else if (stringLength >= 2) {
UChar penultimateChar = string[stringLength - 2];
if (penultimateChar == 'c' && lastChar == 'm')
lengthType = LengthType::Cm;
if (penultimateChar == 'e' && lastChar == 'm')
lengthType = LengthType::Em;
else if (penultimateChar == 'e' && lastChar == 'x')
lengthType = LengthType::Ex;
else if (penultimateChar == 'i' && lastChar == 'n')
lengthType = LengthType::In;
else if (penultimateChar == 'm' && lastChar == 'm')
lengthType = LengthType::Mm;
else if (penultimateChar == 'p' && lastChar == 'c')
lengthType = LengthType::Pc;
else if (penultimateChar == 'p' && lastChar == 't')
lengthType = LengthType::Pt;
else if (penultimateChar == 'p' && lastChar == 'x')
lengthType = LengthType::Px;
if (lengthType != LengthType::UnitLess)
stringLength -= 2;
}
bool ok;
float lengthValue = string.substring(0, stringLength).toFloat(ok);
if (!ok)
return Length();
Length length;
length.type = lengthType;
length.value = lengthValue;
return length;
}
MathMLElement::Length MathMLPresentationElement::parseNamedSpace(const StringView& string)
{
// Named space values are case-sensitive.
int namedSpaceValue;
if (string == "veryverythinmathspace")
namedSpaceValue = 1;
else if (string == "verythinmathspace")
namedSpaceValue = 2;
else if (string == "thinmathspace")
namedSpaceValue = 3;
else if (string == "mediummathspace")
namedSpaceValue = 4;
else if (string == "thickmathspace")
namedSpaceValue = 5;
else if (string == "verythickmathspace")
namedSpaceValue = 6;
else if (string == "veryverythickmathspace")
namedSpaceValue = 7;
else if (string == "negativeveryverythinmathspace")
namedSpaceValue = -1;
else if (string == "negativeverythinmathspace")
namedSpaceValue = -2;
else if (string == "negativethinmathspace")
namedSpaceValue = -3;
else if (string == "negativemediummathspace")
namedSpaceValue = -4;
else if (string == "negativethickmathspace")
namedSpaceValue = -5;
else if (string == "negativeverythickmathspace")
namedSpaceValue = -6;
else if (string == "negativeveryverythickmathspace")
namedSpaceValue = -7;
else
return Length();
Length length;
length.type = LengthType::MathUnit;
length.value = namedSpaceValue;
return length;
}
MathMLElement::Length MathMLPresentationElement::parseMathMLLength(const String& string)
{
// The regular expression from the MathML Relax NG schema is as follows:
//
// pattern = '\s*((-?[0-9]*([0-9]\.?|\.[0-9])[0-9]*(e[mx]|in|cm|mm|p[xtc]|%)?)|(negative)?((very){0,2}thi(n|ck)|medium)mathspace)\s*'
//
// We do not perform a strict verification of the syntax of whitespaces and number.
// Instead, we just use isHTMLSpace and toFloat to parse these parts.
// We first skip whitespace from both ends of the string.
StringView stringView = stripLeadingAndTrailingWhitespace(string);
if (stringView.isEmpty())
return Length();
// We consider the most typical case: a number followed by an optional unit.
UChar firstChar = stringView[0];
if (isASCIIDigit(firstChar) || firstChar == '-' || firstChar == '.')
return parseNumberAndUnit(stringView);
// Otherwise, we try and parse a named space.
return parseNamedSpace(stringView);
}
const MathMLElement::Length& MathMLPresentationElement::cachedMathMLLength(const QualifiedName& name, std::optional<Length>& length)
{
if (length)
return length.value();
length = parseMathMLLength(attributeWithoutSynchronization(name));
return length.value();
}
bool MathMLPresentationElement::acceptsDisplayStyleAttribute()
{
return hasTagName(mtableTag);
}
std::optional<bool> MathMLPresentationElement::specifiedDisplayStyle()
{
if (!acceptsDisplayStyleAttribute())
return std::nullopt;
const MathMLElement::BooleanValue& specifiedDisplayStyle = cachedBooleanAttribute(displaystyleAttr, m_displayStyle);
return toOptionalBool(specifiedDisplayStyle);
}
MathMLElement::MathVariant MathMLPresentationElement::parseMathVariantAttribute(const AtomicString& attributeValue)
{
// The mathvariant attribute values is case-sensitive.
if (attributeValue == "normal")
return MathVariant::Normal;
if (attributeValue == "bold")
return MathVariant::Bold;
if (attributeValue == "italic")
return MathVariant::Italic;
if (attributeValue == "bold-italic")
return MathVariant::BoldItalic;
if (attributeValue == "double-struck")
return MathVariant::DoubleStruck;
if (attributeValue == "bold-fraktur")
return MathVariant::BoldFraktur;
if (attributeValue == "script")
return MathVariant::Script;
if (attributeValue == "bold-script")
return MathVariant::BoldScript;
if (attributeValue == "fraktur")
return MathVariant::Fraktur;
if (attributeValue == "sans-serif")
return MathVariant::SansSerif;
if (attributeValue == "bold-sans-serif")
return MathVariant::BoldSansSerif;
if (attributeValue == "sans-serif-italic")
return MathVariant::SansSerifItalic;
if (attributeValue == "sans-serif-bold-italic")
return MathVariant::SansSerifBoldItalic;
if (attributeValue == "monospace")
return MathVariant::Monospace;
if (attributeValue == "initial")
return MathVariant::Initial;
if (attributeValue == "tailed")
return MathVariant::Tailed;
if (attributeValue == "looped")
return MathVariant::Looped;
if (attributeValue == "stretched")
return MathVariant::Stretched;
return MathVariant::None;
}
std::optional<MathMLElement::MathVariant> MathMLPresentationElement::specifiedMathVariant()
{
if (!acceptsMathVariantAttribute())
return std::nullopt;
if (!m_mathVariant)
m_mathVariant = parseMathVariantAttribute(attributeWithoutSynchronization(mathvariantAttr));
return m_mathVariant.value() == MathVariant::None ? std::nullopt : m_mathVariant;
}
void MathMLPresentationElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
{
bool displayStyleAttribute = name == displaystyleAttr && acceptsDisplayStyleAttribute();
bool mathVariantAttribute = name == mathvariantAttr && acceptsMathVariantAttribute();
if (displayStyleAttribute)
m_displayStyle = std::nullopt;
if (mathVariantAttribute)
m_mathVariant = std::nullopt;
if ((displayStyleAttribute || mathVariantAttribute) && renderer())
MathMLStyle::resolveMathMLStyleTree(renderer());
MathMLElement::parseAttribute(name, value);
}
}
#endif // ENABLE(MATHML)