diff options
Diffstat (limited to 'Source/WebCore/rendering/mathml')
32 files changed, 4310 insertions, 1874 deletions
diff --git a/Source/WebCore/rendering/mathml/MathMLStyle.cpp b/Source/WebCore/rendering/mathml/MathMLStyle.cpp new file mode 100644 index 000000000..6162acbb9 --- /dev/null +++ b/Source/WebCore/rendering/mathml/MathMLStyle.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2016 Igalia S.L. All rights reserved. + * + * 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 "MathMLStyle.h" + +#if ENABLE(MATHML) + +#include "MathMLElement.h" +#include "MathMLNames.h" +#include "RenderMathMLBlock.h" +#include "RenderMathMLFraction.h" +#include "RenderMathMLMath.h" +#include "RenderMathMLRoot.h" +#include "RenderMathMLScripts.h" +#include "RenderMathMLToken.h" +#include "RenderMathMLUnderOver.h" + +namespace WebCore { + +using namespace MathMLNames; + +Ref<MathMLStyle> MathMLStyle::create() +{ + return adoptRef(*new MathMLStyle()); +} + +const MathMLStyle* MathMLStyle::getMathMLStyle(RenderObject* renderer) +{ + // FIXME: Should we make RenderMathMLTable derive from RenderMathMLBlock in order to simplify this? + if (is<RenderMathMLTable>(renderer)) + return &downcast<RenderMathMLTable>(*renderer).mathMLStyle(); + if (is<RenderMathMLBlock>(renderer)) + return &downcast<RenderMathMLBlock>(*renderer).mathMLStyle(); + return nullptr; +} + +void MathMLStyle::resolveMathMLStyleTree(RenderObject* renderer) +{ + for (auto* child = renderer; child; child = child->nextInPreOrder(renderer)) { + // FIXME: Should we make RenderMathMLTable derive from RenderMathMLBlock in order to simplify this? + if (is<RenderMathMLTable>(child)) + downcast<RenderMathMLTable>(*child).mathMLStyle().resolveMathMLStyle(child); + else if (is<RenderMathMLBlock>(child)) + downcast<RenderMathMLBlock>(*child).mathMLStyle().resolveMathMLStyle(child); + } +} + +RenderObject* MathMLStyle::getMathMLParentNode(RenderObject* renderer) +{ + auto* parentRenderer = renderer->parent(); + + while (parentRenderer && !(is<RenderMathMLTable>(parentRenderer) || is<RenderMathMLBlock>(parentRenderer))) + parentRenderer = parentRenderer->parent(); + + return parentRenderer; +} + +void MathMLStyle::updateStyleIfNeeded(RenderObject* renderer, bool oldDisplayStyle, MathMLElement::MathVariant oldMathVariant) +{ + if (oldDisplayStyle != m_displayStyle) { + renderer->setNeedsLayoutAndPrefWidthsRecalc(); + if (is<RenderMathMLToken>(renderer)) + downcast<RenderMathMLToken>(renderer)->updateTokenContent(); + else if (is<RenderMathMLFraction>(renderer)) + downcast<RenderMathMLFraction>(renderer)->updateFromElement(); + } + if (oldMathVariant != m_mathVariant) { + if (is<RenderMathMLToken>(renderer)) + downcast<RenderMathMLToken>(renderer)->updateTokenContent(); + } +} + +void MathMLStyle::resolveMathMLStyle(RenderObject* renderer) +{ + ASSERT(renderer); + + bool oldDisplayStyle = m_displayStyle; + MathMLElement::MathVariant oldMathVariant = m_mathVariant; + auto* parentRenderer = getMathMLParentNode(renderer); + const MathMLStyle* parentStyle = getMathMLStyle(parentRenderer); + + // By default, we just inherit the style from our parent. + m_displayStyle = false; + m_mathVariant = MathMLElement::MathVariant::None; + if (parentStyle) { + setDisplayStyle(parentStyle->displayStyle()); + setMathVariant(parentStyle->mathVariant()); + } + + // Early return for anonymous renderers. + if (renderer->isAnonymous()) { + updateStyleIfNeeded(renderer, oldDisplayStyle, oldMathVariant); + return; + } + + if (is<RenderMathMLMath>(renderer) || is<RenderMathMLTable>(renderer)) + m_displayStyle = false; // The default displaystyle of <math> and <mtable> is false. + else if (parentRenderer) { + if (is<RenderMathMLFraction>(parentRenderer)) + m_displayStyle = false; // <mfrac> sets displaystyle to false within its numerator and denominator. + else if ((is<RenderMathMLRoot>(parentRenderer) && !parentRenderer->isRenderMathMLSquareRoot()) || is<RenderMathMLScripts>(parentRenderer) || is<RenderMathMLUnderOver>(parentRenderer)) { + // <mroot>, <msub>, <msup>, <msubsup>, <mmultiscripts>, <munder>, <mover> and <munderover> elements set displaystyle to false within their scripts. + auto* base = downcast<RenderBox>(parentRenderer)->firstChildBox(); + if (renderer != base) + m_displayStyle = false; + } + } + + // The displaystyle and mathvariant attributes override the default behavior. + auto* element = downcast<RenderElement>(renderer)->element(); + if (is<MathMLElement>(element)) { + std::optional<bool> displayStyle = downcast<MathMLElement>(element)->specifiedDisplayStyle(); + if (displayStyle) + m_displayStyle = displayStyle.value(); + std::optional<MathMLElement::MathVariant> mathVariant = downcast<MathMLElement>(element)->specifiedMathVariant(); + if (mathVariant) + m_mathVariant = mathVariant.value(); + } + updateStyleIfNeeded(renderer, oldDisplayStyle, oldMathVariant); +} + +} + +#endif // ENABLE(MATHML) diff --git a/Source/WebCore/rendering/mathml/RenderMathMLSquareRoot.h b/Source/WebCore/rendering/mathml/MathMLStyle.h index a9e3cffbd..8f4240408 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLSquareRoot.h +++ b/Source/WebCore/rendering/mathml/MathMLStyle.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 Alex Milowski (alex@milowski.com). All rights reserved. + * Copyright (C) 2016 Igalia S.L. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -22,33 +22,41 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -#ifndef RenderMathMLSquareRoot_h -#define RenderMathMLSquareRoot_h -#if ENABLE(MATHML) +#pragma once -#include "RenderMathMLRoot.h" +#if ENABLE(MATHML) +#include "Element.h" +#include "MathMLElement.h" +#include <wtf/RefCounted.h> namespace WebCore { -class RenderMathMLMenclose; - -// Render sqrt(base), using radical notation. -class RenderMathMLSquareRoot final : public RenderMathMLRoot { +class MathMLStyle: public RefCounted<MathMLStyle> { public: - RenderMathMLSquareRoot(Element&, PassRef<RenderStyle>); - RenderMathMLSquareRoot(Document&, PassRef<RenderStyle>); - static RenderPtr<RenderMathMLSquareRoot> createAnonymousWithParentRenderer(RenderMathMLMenclose&); - + MathMLStyle() { } + static Ref<MathMLStyle> create(); + + bool displayStyle() const { return m_displayStyle; } + void setDisplayStyle(bool displayStyle) { m_displayStyle = displayStyle; } + + MathMLElement::MathVariant mathVariant() const { return m_mathVariant; } + void setMathVariant(MathMLElement::MathVariant mathvariant) { m_mathVariant = mathvariant; } + + void resolveMathMLStyle(RenderObject*); + static void resolveMathMLStyleTree(RenderObject*); + private: - virtual bool isRenderMathMLSquareRoot() const override { return true; } - virtual const char* renderName() const override { return "RenderMathMLSquareRoot"; } + bool isDisplayStyleAlwaysFalse(RenderObject*); + const MathMLStyle* getMathMLStyle(RenderObject* renderer); + RenderObject* getMathMLParentNode(RenderObject*); + void updateStyleIfNeeded(RenderObject*, bool, MathMLElement::MathVariant); + + bool m_displayStyle { false }; + MathMLElement::MathVariant m_mathVariant { MathMLElement::MathVariant::None }; }; - + } #endif // ENABLE(MATHML) - -#endif // RenderMathMLSquareRoot_h diff --git a/Source/WebCore/rendering/mathml/MathOperator.cpp b/Source/WebCore/rendering/mathml/MathOperator.cpp new file mode 100644 index 000000000..032109080 --- /dev/null +++ b/Source/WebCore/rendering/mathml/MathOperator.cpp @@ -0,0 +1,746 @@ +/* + * Copyright (C) 2016 Igalia S.L. All rights reserved. + * Copyright (C) 2016 Apple Inc. All rights reserved. + * + * 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 "MathOperator.h" + +#if ENABLE(MATHML) + +#include "RenderStyle.h" +#include "StyleInheritedData.h" + +static const unsigned kRadicalOperator = 0x221A; +static const unsigned kMaximumExtensionCount = 128; + +namespace WebCore { + +static inline FloatRect boundsForGlyph(const GlyphData& data) +{ + return data.font ? data.font->boundsForGlyph(data.glyph) : FloatRect(); +} + +static inline float heightForGlyph(const GlyphData& data) +{ + return boundsForGlyph(data).height(); +} + +static inline void getAscentAndDescentForGlyph(const GlyphData& data, LayoutUnit& ascent, LayoutUnit& descent) +{ + FloatRect bounds = boundsForGlyph(data); + ascent = -bounds.y(); + descent = bounds.maxY(); +} + +static inline float advanceWidthForGlyph(const GlyphData& data) +{ + return data.font ? data.font->widthForGlyph(data.glyph) : 0; +} + +// FIXME: This hardcoded data can be removed when OpenType MATH font are widely available (http://wkbug/156837). +struct StretchyCharacter { + UChar32 character; + UChar topChar; + UChar extensionChar; + UChar bottomChar; + UChar middleChar; +}; +// The first leftRightPairsCount pairs correspond to left/right fences that can easily be mirrored in RTL. +static const short leftRightPairsCount = 5; +static const StretchyCharacter stretchyCharacters[14] = { + { 0x28 , 0x239b, 0x239c, 0x239d, 0x0 }, // left parenthesis + { 0x29 , 0x239e, 0x239f, 0x23a0, 0x0 }, // right parenthesis + { 0x5b , 0x23a1, 0x23a2, 0x23a3, 0x0 }, // left square bracket + { 0x5d , 0x23a4, 0x23a5, 0x23a6, 0x0 }, // right square bracket + { 0x7b , 0x23a7, 0x23aa, 0x23a9, 0x23a8 }, // left curly bracket + { 0x7d , 0x23ab, 0x23aa, 0x23ad, 0x23ac }, // right curly bracket + { 0x2308, 0x23a1, 0x23a2, 0x23a2, 0x0 }, // left ceiling + { 0x2309, 0x23a4, 0x23a5, 0x23a5, 0x0 }, // right ceiling + { 0x230a, 0x23a2, 0x23a2, 0x23a3, 0x0 }, // left floor + { 0x230b, 0x23a5, 0x23a5, 0x23a6, 0x0 }, // right floor + { 0x7c , 0x7c, 0x7c, 0x7c, 0x0 }, // vertical bar + { 0x2016, 0x2016, 0x2016, 0x2016, 0x0 }, // double vertical line + { 0x2225, 0x2225, 0x2225, 0x2225, 0x0 }, // parallel to + { 0x222b, 0x2320, 0x23ae, 0x2321, 0x0 } // integral sign +}; + +void MathOperator::GlyphAssemblyData::initialize() +{ + topOrRightCodePoint = 0; + topOrRightFallbackGlyph = 0; + extensionCodePoint = 0; + extensionFallbackGlyph = 0; + bottomOrLeftCodePoint = 0; + bottomOrLeftFallbackGlyph = 0; + middleCodePoint = 0; + middleFallbackGlyph = 0; +} + +MathOperator::MathOperator() +{ + m_assembly.initialize(); + m_variantGlyph = 0; +} + +void MathOperator::setOperator(const RenderStyle& style, UChar32 baseCharacter, Type operatorType) +{ + m_baseCharacter = baseCharacter; + m_operatorType = operatorType; + reset(style); +} + +void MathOperator::reset(const RenderStyle& style) +{ + m_stretchType = StretchType::Unstretched; + m_maxPreferredWidth = 0; + m_width = 0; + m_ascent = 0; + m_descent = 0; + m_italicCorrection = 0; + m_radicalVerticalScale = 1; + + // We use the base size for the calculation of the preferred width. + GlyphData baseGlyph; + if (!getBaseGlyph(style, baseGlyph)) + return; + m_maxPreferredWidth = m_width = advanceWidthForGlyph(baseGlyph); + getAscentAndDescentForGlyph(baseGlyph, m_ascent, m_descent); + + if (m_operatorType == Type::VerticalOperator) + calculateStretchyData(style, true); // We also take into account the width of larger sizes for the calculation of the preferred width. + else if (m_operatorType == Type::DisplayOperator) + calculateDisplayStyleLargeOperator(style); // We can directly select the size variant and determine the final metrics. +} + +LayoutUnit MathOperator::stretchSize() const +{ + ASSERT(m_operatorType == Type::VerticalOperator || m_operatorType == Type::HorizontalOperator); + return m_operatorType == Type::VerticalOperator ? m_ascent + m_descent : m_width; +} + +bool MathOperator::getGlyph(const RenderStyle& style, UChar32 character, GlyphData& glyph) const +{ + glyph = style.fontCascade().glyphDataForCharacter(character, !style.isLeftToRightDirection()); + return glyph.font && glyph.font == &style.fontCascade().primaryFont(); +} + +void MathOperator::setSizeVariant(const GlyphData& sizeVariant) +{ + ASSERT(sizeVariant.font); + ASSERT(sizeVariant.font->mathData()); + m_stretchType = StretchType::SizeVariant; + m_variantGlyph = sizeVariant.glyph; + m_width = advanceWidthForGlyph(sizeVariant); + getAscentAndDescentForGlyph(sizeVariant, m_ascent, m_descent); +} + +static GlyphData glyphDataForCodePointOrFallbackGlyph(const RenderStyle& style, UChar32 codePoint, Glyph fallbackGlyph) +{ + if (codePoint) + return style.fontCascade().glyphDataForCharacter(codePoint, false); + + GlyphData fallback; + + if (fallbackGlyph) { + fallback.glyph = fallbackGlyph; + fallback.font = &style.fontCascade().primaryFont(); + } + + return fallback; +} + +void MathOperator::setGlyphAssembly(const RenderStyle& style, const GlyphAssemblyData& assemblyData) +{ + ASSERT(m_operatorType == Type::VerticalOperator || m_operatorType == Type::HorizontalOperator); + m_stretchType = StretchType::GlyphAssembly; + m_assembly = assemblyData; + + auto topOrRight = glyphDataForCodePointOrFallbackGlyph(style, m_assembly.topOrRightCodePoint, m_assembly.topOrRightFallbackGlyph); + auto extension = glyphDataForCodePointOrFallbackGlyph(style, m_assembly.extensionCodePoint, m_assembly.extensionFallbackGlyph); + auto middle = glyphDataForCodePointOrFallbackGlyph(style, m_assembly.middleCodePoint, m_assembly.middleFallbackGlyph); + auto bottomOrLeft = glyphDataForCodePointOrFallbackGlyph(style, m_assembly.bottomOrLeftCodePoint, m_assembly.bottomOrLeftFallbackGlyph); + + if (m_operatorType == Type::VerticalOperator) { + m_width = 0; + m_width = std::max<LayoutUnit>(m_width, advanceWidthForGlyph(topOrRight)); + m_width = std::max<LayoutUnit>(m_width, advanceWidthForGlyph(extension)); + m_width = std::max<LayoutUnit>(m_width, advanceWidthForGlyph(bottomOrLeft)); + m_width = std::max<LayoutUnit>(m_width, advanceWidthForGlyph(middle)); + } else { + m_ascent = 0; + m_descent = 0; + LayoutUnit ascent, descent; + getAscentAndDescentForGlyph(bottomOrLeft, ascent, descent); + m_ascent = std::max(m_ascent, ascent); + m_descent = std::max(m_descent, descent); + getAscentAndDescentForGlyph(extension, ascent, descent); + m_ascent = std::max(m_ascent, ascent); + m_descent = std::max(m_descent, descent); + getAscentAndDescentForGlyph(topOrRight, ascent, descent); + m_ascent = std::max(m_ascent, ascent); + m_descent = std::max(m_descent, descent); + getAscentAndDescentForGlyph(middle, ascent, descent); + m_ascent = std::max(m_ascent, ascent); + m_descent = std::max(m_descent, descent); + } +} + +// The MathML specification recommends avoiding combining characters. +// See https://www.w3.org/TR/MathML/chapter7.html#chars.comb-chars +// However, many math fonts do not provide constructions for the non-combining equivalent. +const unsigned maxFallbackPerCharacter = 3; +static const UChar32 characterFallback[][maxFallbackPerCharacter] = { + { 0x005E, 0x0302, 0 }, // CIRCUMFLEX ACCENT + { 0x005F, 0x0332, 0 }, // LOW LINE + { 0x007E, 0x0303, 0 }, // TILDE + { 0x00AF, 0x0304, 0x0305 }, // MACRON + { 0x02C6, 0x0302, 0 }, // MODIFIER LETTER CIRCUMFLEX ACCENT + { 0x02C7, 0x030C, 0 } // CARON +}; +const unsigned characterFallbackSize = WTF_ARRAY_LENGTH(characterFallback); + +void MathOperator::getMathVariantsWithFallback(const RenderStyle& style, bool isVertical, Vector<Glyph>& sizeVariants, Vector<OpenTypeMathData::AssemblyPart>& assemblyParts) +{ + // In general, we first try and find contruction for the base glyph. + GlyphData baseGlyph; + if (!getBaseGlyph(style, baseGlyph) || !baseGlyph.font->mathData()) + return; + baseGlyph.font->mathData()->getMathVariants(baseGlyph.glyph, isVertical, sizeVariants, assemblyParts); + if (!sizeVariants.isEmpty() || !assemblyParts.isEmpty()) + return; + + // Otherwise, we try and find fallback constructions using similar characters. + for (unsigned i = 0; i < characterFallbackSize; i++) { + unsigned j = 0; + if (characterFallback[i][j] == m_baseCharacter) { + for (j++; j < maxFallbackPerCharacter && characterFallback[i][j]; j++) { + GlyphData glyphData; + if (!getGlyph(style, characterFallback[i][j], glyphData)) + continue; + glyphData.font->mathData()->getMathVariants(glyphData.glyph, isVertical, sizeVariants, assemblyParts); + if (!sizeVariants.isEmpty() || !assemblyParts.isEmpty()) + return; + } + break; + } + } +} + +void MathOperator::calculateDisplayStyleLargeOperator(const RenderStyle& style) +{ + ASSERT(m_operatorType == Type::DisplayOperator); + + GlyphData baseGlyph; + if (!getBaseGlyph(style, baseGlyph) || !baseGlyph.font->mathData()) + return; + + // The value of displayOperatorMinHeight is sometimes too small, so we ensure that it is at least \sqrt{2} times the size of the base glyph. + float displayOperatorMinHeight = std::max(heightForGlyph(baseGlyph) * sqrtOfTwoFloat, baseGlyph.font->mathData()->getMathConstant(*baseGlyph.font, OpenTypeMathData::DisplayOperatorMinHeight)); + + Vector<Glyph> sizeVariants; + Vector<OpenTypeMathData::AssemblyPart> assemblyParts; + baseGlyph.font->mathData()->getMathVariants(baseGlyph.glyph, true, sizeVariants, assemblyParts); + + // We choose the first size variant that is larger than the expected displayOperatorMinHeight and otherwise fallback to the largest variant. + for (auto& sizeVariant : sizeVariants) { + GlyphData glyphData(sizeVariant, baseGlyph.font); + setSizeVariant(glyphData); + m_maxPreferredWidth = m_width; + m_italicCorrection = glyphData.font->mathData()->getItalicCorrection(*glyphData.font, glyphData.glyph); + if (heightForGlyph(glyphData) >= displayOperatorMinHeight) + break; + } +} + +bool MathOperator::calculateGlyphAssemblyFallback(const Vector<OpenTypeMathData::AssemblyPart>& assemblyParts, GlyphAssemblyData& assemblyData) const +{ + // The structure of the Open Type Math table is a bit more general than the one currently used by the MathOperator code, so we try to fallback in a reasonable way. + // FIXME: MathOperator should support the most general format (https://bugs.webkit.org/show_bug.cgi?id=130327). + // We use the approach of the copyComponents function in github.com/mathjax/MathJax-dev/blob/master/fonts/OpenTypeMath/fontUtil.py + + // We count the number of non extender pieces. + int nonExtenderCount = 0; + for (auto& part : assemblyParts) { + if (!part.isExtender) + nonExtenderCount++; + } + if (nonExtenderCount > 3) + return false; // This is not supported: there are too many pieces. + + // We now browse the list of pieces from left to right for horizontal operators and from bottom to top for vertical operators. + enum PartType { + Start, + ExtenderBetweenStartAndMiddle, + Middle, + ExtenderBetweenMiddleAndEnd, + End, + None + }; + PartType expectedPartType = Start; + assemblyData.extensionCodePoint = 0; + assemblyData.extensionFallbackGlyph = 0; + assemblyData.middleCodePoint = 0; + assemblyData.middleFallbackGlyph = 0; + for (auto& part : assemblyParts) { + if (nonExtenderCount < 3) { + // If we only have at most two non-extenders then we skip the middle glyph. + if (expectedPartType == ExtenderBetweenStartAndMiddle) + expectedPartType = ExtenderBetweenMiddleAndEnd; + else if (expectedPartType == Middle) + expectedPartType = End; + } + if (part.isExtender) { + if (!assemblyData.extensionFallbackGlyph) + assemblyData.extensionFallbackGlyph = part.glyph; // We copy the extender part. + else if (assemblyData.extensionFallbackGlyph != part.glyph) + return false; // This is not supported: the assembly has different extenders. + + switch (expectedPartType) { + case Start: + // We ignore the left/bottom part. + expectedPartType = ExtenderBetweenStartAndMiddle; + continue; + case Middle: + // We ignore the middle part. + expectedPartType = ExtenderBetweenMiddleAndEnd; + continue; + case End: + case None: + // This is not supported: we got an unexpected extender. + return false; + case ExtenderBetweenStartAndMiddle: + case ExtenderBetweenMiddleAndEnd: + // We ignore multiple consecutive extenders. + continue; + } + } + + switch (expectedPartType) { + case Start: + // We copy the left/bottom part. + assemblyData.bottomOrLeftFallbackGlyph = part.glyph; + assemblyData.bottomOrLeftCodePoint = 0; + expectedPartType = ExtenderBetweenStartAndMiddle; + continue; + case ExtenderBetweenStartAndMiddle: + case Middle: + // We copy the middle part. + assemblyData.middleFallbackGlyph = part.glyph; + expectedPartType = ExtenderBetweenMiddleAndEnd; + continue; + case ExtenderBetweenMiddleAndEnd: + case End: + // We copy the right/top part. + assemblyData.topOrRightFallbackGlyph = part.glyph; + assemblyData.topOrRightCodePoint = 0; + expectedPartType = None; + continue; + case None: + // This is not supported: we got an unexpected non-extender part. + return false; + } + } + + if (!assemblyData.hasExtension()) + return false; // This is not supported: we always assume that we have an extension glyph. + + // If we don't have top/bottom glyphs, we use the extension glyph. + if (!assemblyData.topOrRightCodePoint && !assemblyData.topOrRightFallbackGlyph) + assemblyData.topOrRightFallbackGlyph = assemblyData.extensionFallbackGlyph; + if (!assemblyData.bottomOrLeftCodePoint && !assemblyData.bottomOrLeftFallbackGlyph) + assemblyData.bottomOrLeftFallbackGlyph = assemblyData.extensionFallbackGlyph; + + return true; +} + +void MathOperator::calculateStretchyData(const RenderStyle& style, bool calculateMaxPreferredWidth, LayoutUnit targetSize) +{ + ASSERT(m_operatorType == Type::VerticalOperator || m_operatorType == Type::HorizontalOperator); + ASSERT(!calculateMaxPreferredWidth || m_operatorType == Type::VerticalOperator); + bool isVertical = m_operatorType == Type::VerticalOperator; + + GlyphData baseGlyph; + if (!getBaseGlyph(style, baseGlyph)) + return; + + if (!calculateMaxPreferredWidth) { + // We do not stretch if the base glyph is large enough. + float baseSize = isVertical ? heightForGlyph(baseGlyph) : advanceWidthForGlyph(baseGlyph); + if (targetSize <= baseSize) + return; + } + + GlyphAssemblyData assemblyData; + if (baseGlyph.font->mathData()) { + Vector<Glyph> sizeVariants; + Vector<OpenTypeMathData::AssemblyPart> assemblyParts; + getMathVariantsWithFallback(style, isVertical, sizeVariants, assemblyParts); + // We verify the size variants. + for (auto& sizeVariant : sizeVariants) { + GlyphData glyphData(sizeVariant, baseGlyph.font); + if (calculateMaxPreferredWidth) + m_maxPreferredWidth = std::max<LayoutUnit>(m_maxPreferredWidth, advanceWidthForGlyph(glyphData)); + else { + setSizeVariant(glyphData); + LayoutUnit size = isVertical ? heightForGlyph(glyphData) : advanceWidthForGlyph(glyphData); + if (size >= targetSize) + return; + } + } + + // We verify if there is a construction. + if (!calculateGlyphAssemblyFallback(assemblyParts, assemblyData)) + return; + } else { + if (!isVertical) + return; + + // If the font does not have a MATH table, we fallback to the Unicode-only constructions. + const StretchyCharacter* stretchyCharacter = nullptr; + const unsigned maxIndex = WTF_ARRAY_LENGTH(stretchyCharacters); + for (unsigned index = 0; index < maxIndex; ++index) { + if (stretchyCharacters[index].character == m_baseCharacter) { + stretchyCharacter = &stretchyCharacters[index]; + if (!style.isLeftToRightDirection() && index < leftRightPairsCount * 2) { + // If we are in right-to-left direction we select the mirrored form by adding -1 or +1 according to the parity of index. + index += index % 2 ? -1 : 1; + } + break; + } + } + + // Unicode contains U+23B7 RADICAL SYMBOL BOTTOM but it is generally not provided by fonts without a MATH table. + // Moreover, it's not clear what the proper vertical extender or top hook would be. + // Hence we fallback to scaling the base glyph vertically. + if (!calculateMaxPreferredWidth && m_baseCharacter == kRadicalOperator) { + LayoutUnit height = m_ascent + m_descent; + if (height > 0 && height < targetSize) { + m_radicalVerticalScale = targetSize.toFloat() / height; + m_ascent *= m_radicalVerticalScale; + m_descent *= m_radicalVerticalScale; + } + return; + } + + // If we didn't find a stretchy character set for this character, we don't know how to stretch it. + if (!stretchyCharacter) + return; + + // We convert the list of Unicode characters into a list of glyph data. + assemblyData.topOrRightCodePoint = stretchyCharacter->topChar; + assemblyData.extensionCodePoint = stretchyCharacter->extensionChar; + assemblyData.bottomOrLeftCodePoint = stretchyCharacter->bottomChar; + assemblyData.middleCodePoint = stretchyCharacter->middleChar; + } + + auto topOrRight = glyphDataForCodePointOrFallbackGlyph(style, assemblyData.topOrRightCodePoint, assemblyData.topOrRightFallbackGlyph); + auto extension = glyphDataForCodePointOrFallbackGlyph(style, assemblyData.extensionCodePoint, assemblyData.extensionFallbackGlyph); + auto middle = glyphDataForCodePointOrFallbackGlyph(style, assemblyData.middleCodePoint, assemblyData.middleFallbackGlyph); + auto bottomOrLeft = glyphDataForCodePointOrFallbackGlyph(style, assemblyData.bottomOrLeftCodePoint, assemblyData.bottomOrLeftFallbackGlyph); + + // If we are measuring the maximum width, verify each component. + if (calculateMaxPreferredWidth) { + m_maxPreferredWidth = std::max<LayoutUnit>(m_maxPreferredWidth, advanceWidthForGlyph(topOrRight)); + m_maxPreferredWidth = std::max<LayoutUnit>(m_maxPreferredWidth, advanceWidthForGlyph(extension)); + m_maxPreferredWidth = std::max<LayoutUnit>(m_maxPreferredWidth, advanceWidthForGlyph(middle)); + m_maxPreferredWidth = std::max<LayoutUnit>(m_maxPreferredWidth, advanceWidthForGlyph(bottomOrLeft)); + return; + } + + // We ensure that the size is large enough to avoid glyph overlaps. + float minSize = isVertical ? + heightForGlyph(topOrRight) + heightForGlyph(middle) + heightForGlyph(bottomOrLeft) + : advanceWidthForGlyph(bottomOrLeft) + advanceWidthForGlyph(middle) + advanceWidthForGlyph(topOrRight); + if (minSize > targetSize) + return; + + setGlyphAssembly(style, assemblyData); +} + +void MathOperator::stretchTo(const RenderStyle& style, LayoutUnit targetSize) +{ + ASSERT(m_operatorType == Type::VerticalOperator || m_operatorType == Type::HorizontalOperator); + calculateStretchyData(style, false, targetSize); + if (m_stretchType == StretchType::GlyphAssembly) { + if (m_operatorType == Type::VerticalOperator) { + m_ascent = targetSize; + m_descent = 0; + } else + m_width = targetSize; + } +} + +LayoutRect MathOperator::paintGlyph(const RenderStyle& style, PaintInfo& info, const GlyphData& data, const LayoutPoint& origin, GlyphPaintTrimming trim) +{ + FloatRect glyphBounds = boundsForGlyph(data); + + LayoutRect glyphPaintRect(origin, LayoutSize(glyphBounds.x() + glyphBounds.width(), glyphBounds.height())); + glyphPaintRect.setY(origin.y() + glyphBounds.y()); + + // In order to have glyphs fit snugly with one another we snap the connecting edges to pixel boundaries + // and trim off one pixel. The pixel trim is to account for fonts that have edge pixels that have less + // than full coverage. These edge pixels can introduce small seams between connected glyphs. + FloatRect clipBounds = info.rect; + switch (trim) { + case TrimTop: + glyphPaintRect.shiftYEdgeTo(glyphPaintRect.y().ceil() + 1); + clipBounds.shiftYEdgeTo(glyphPaintRect.y()); + break; + case TrimBottom: + glyphPaintRect.shiftMaxYEdgeTo(glyphPaintRect.maxY().floor() - 1); + clipBounds.shiftMaxYEdgeTo(glyphPaintRect.maxY()); + break; + case TrimTopAndBottom: + glyphPaintRect.shiftYEdgeTo(glyphPaintRect.y().ceil() + 1); + glyphPaintRect.shiftMaxYEdgeTo(glyphPaintRect.maxY().floor() - 1); + clipBounds.shiftYEdgeTo(glyphPaintRect.y()); + clipBounds.shiftMaxYEdgeTo(glyphPaintRect.maxY()); + break; + case TrimLeft: + glyphPaintRect.shiftXEdgeTo(glyphPaintRect.x().ceil() + 1); + clipBounds.shiftXEdgeTo(glyphPaintRect.x()); + break; + case TrimRight: + glyphPaintRect.shiftMaxXEdgeTo(glyphPaintRect.maxX().floor() - 1); + clipBounds.shiftMaxXEdgeTo(glyphPaintRect.maxX()); + break; + case TrimLeftAndRight: + glyphPaintRect.shiftXEdgeTo(glyphPaintRect.x().ceil() + 1); + glyphPaintRect.shiftMaxXEdgeTo(glyphPaintRect.maxX().floor() - 1); + clipBounds.shiftXEdgeTo(glyphPaintRect.x()); + clipBounds.shiftMaxXEdgeTo(glyphPaintRect.maxX()); + } + + // Clipping the enclosing IntRect avoids any potential issues at joined edges. + GraphicsContextStateSaver stateSaver(info.context()); + info.context().clip(clipBounds); + + GlyphBuffer buffer; + buffer.add(data.glyph, data.font, advanceWidthForGlyph(data)); + info.context().drawGlyphs(style.fontCascade(), *data.font, buffer, 0, 1, origin); + + return glyphPaintRect; +} + +void MathOperator::fillWithVerticalExtensionGlyph(const RenderStyle& style, PaintInfo& info, const LayoutPoint& from, const LayoutPoint& to) +{ + ASSERT(m_operatorType == Type::VerticalOperator); + ASSERT(m_stretchType == StretchType::GlyphAssembly); + + auto extension = glyphDataForCodePointOrFallbackGlyph(style, m_assembly.extensionCodePoint, m_assembly.extensionFallbackGlyph); + + ASSERT(extension.font); + ASSERT(from.y() <= to.y()); + + // If there is no space for the extension glyph, we don't need to do anything. + if (from.y() == to.y()) + return; + + GraphicsContextStateSaver stateSaver(info.context()); + + FloatRect glyphBounds = boundsForGlyph(extension); + + // Clipping the extender region here allows us to draw the bottom extender glyph into the + // regions of the bottom glyph without worrying about overdraw (hairy pixels) and simplifies later clipping. + LayoutRect clipBounds = info.rect; + clipBounds.shiftYEdgeTo(from.y()); + clipBounds.shiftMaxYEdgeTo(to.y()); + info.context().clip(clipBounds); + + // Trimming may remove up to two pixels from the top of the extender glyph, so we move it up by two pixels. + float offsetToGlyphTop = glyphBounds.y() + 2; + LayoutPoint glyphOrigin = LayoutPoint(from.x(), from.y() - offsetToGlyphTop); + FloatRect lastPaintedGlyphRect(from, FloatSize()); + + // In practice, only small stretch sizes are requested but we limit the number of glyphs to avoid hangs. + for (unsigned extensionCount = 0; lastPaintedGlyphRect.maxY() < to.y() && extensionCount < kMaximumExtensionCount; extensionCount++) { + lastPaintedGlyphRect = paintGlyph(style, info, extension, glyphOrigin, TrimTopAndBottom); + glyphOrigin.setY(glyphOrigin.y() + lastPaintedGlyphRect.height()); + + // There's a chance that if the font size is small enough the glue glyph has been reduced to an empty rectangle + // with trimming. In that case we just draw nothing. + if (lastPaintedGlyphRect.isEmpty()) + break; + } +} + +void MathOperator::fillWithHorizontalExtensionGlyph(const RenderStyle& style, PaintInfo& info, const LayoutPoint& from, const LayoutPoint& to) +{ + ASSERT(m_operatorType == Type::HorizontalOperator); + ASSERT(m_stretchType == StretchType::GlyphAssembly); + + auto extension = glyphDataForCodePointOrFallbackGlyph(style, m_assembly.extensionCodePoint, m_assembly.extensionFallbackGlyph); + + ASSERT(extension.font); + ASSERT(from.x() <= to.x()); + ASSERT(from.y() == to.y()); + + // If there is no space for the extension glyph, we don't need to do anything. + if (from.x() == to.x()) + return; + + GraphicsContextStateSaver stateSaver(info.context()); + + // Clipping the extender region here allows us to draw the bottom extender glyph into the + // regions of the bottom glyph without worrying about overdraw (hairy pixels) and simplifies later clipping. + LayoutRect clipBounds = info.rect; + clipBounds.shiftXEdgeTo(from.x()); + clipBounds.shiftMaxXEdgeTo(to.x()); + info.context().clip(clipBounds); + + // Trimming may remove up to two pixels from the left of the extender glyph, so we move it left by two pixels. + float offsetToGlyphLeft = -2; + LayoutPoint glyphOrigin = LayoutPoint(from.x() + offsetToGlyphLeft, from.y()); + FloatRect lastPaintedGlyphRect(from, FloatSize()); + + // In practice, only small stretch sizes are requested but we limit the number of glyphs to avoid hangs. + for (unsigned extensionCount = 0; lastPaintedGlyphRect.maxX() < to.x() && extensionCount < kMaximumExtensionCount; extensionCount++) { + lastPaintedGlyphRect = paintGlyph(style, info, extension, glyphOrigin, TrimLeftAndRight); + glyphOrigin.setX(glyphOrigin.x() + lastPaintedGlyphRect.width()); + + // There's a chance that if the font size is small enough the glue glyph has been reduced to an empty rectangle + // with trimming. In that case we just draw nothing. + if (lastPaintedGlyphRect.isEmpty()) + break; + } +} + +void MathOperator::paintVerticalGlyphAssembly(const RenderStyle& style, PaintInfo& info, const LayoutPoint& paintOffset) +{ + ASSERT(m_operatorType == Type::VerticalOperator); + ASSERT(m_stretchType == StretchType::GlyphAssembly); + + auto topOrRight = glyphDataForCodePointOrFallbackGlyph(style, m_assembly.topOrRightCodePoint, m_assembly.topOrRightFallbackGlyph); + auto bottomOrLeft = glyphDataForCodePointOrFallbackGlyph(style, m_assembly.bottomOrLeftCodePoint, m_assembly.bottomOrLeftFallbackGlyph); + + ASSERT(topOrRight.font); + ASSERT(bottomOrLeft.font); + + // We are positioning the glyphs so that the edge of the tight glyph bounds line up exactly with the edges of our paint box. + LayoutPoint operatorTopLeft = paintOffset; + FloatRect topGlyphBounds = boundsForGlyph(topOrRight); + LayoutPoint topGlyphOrigin(operatorTopLeft.x(), operatorTopLeft.y() - topGlyphBounds.y()); + LayoutRect topGlyphPaintRect = paintGlyph(style, info, topOrRight, topGlyphOrigin, TrimBottom); + + FloatRect bottomGlyphBounds = boundsForGlyph(bottomOrLeft); + LayoutPoint bottomGlyphOrigin(operatorTopLeft.x(), operatorTopLeft.y() + stretchSize() - (bottomGlyphBounds.height() + bottomGlyphBounds.y())); + LayoutRect bottomGlyphPaintRect = paintGlyph(style, info, bottomOrLeft, bottomGlyphOrigin, TrimTop); + + if (m_assembly.hasMiddle()) { + auto middle = glyphDataForCodePointOrFallbackGlyph(style, m_assembly.middleCodePoint, m_assembly.middleFallbackGlyph); + + // Center the glyph origin between the start and end glyph paint extents. Then shift it half the paint height toward the bottom glyph. + FloatRect middleGlyphBounds = boundsForGlyph(middle); + LayoutPoint middleGlyphOrigin(operatorTopLeft.x(), topGlyphOrigin.y()); + middleGlyphOrigin.moveBy(LayoutPoint(0, (bottomGlyphPaintRect.y() - topGlyphPaintRect.maxY()) / 2.0)); + middleGlyphOrigin.moveBy(LayoutPoint(0, middleGlyphBounds.height() / 2.0)); + + LayoutRect middleGlyphPaintRect = paintGlyph(style, info, middle, middleGlyphOrigin, TrimTopAndBottom); + fillWithVerticalExtensionGlyph(style, info, topGlyphPaintRect.minXMaxYCorner(), middleGlyphPaintRect.minXMinYCorner()); + fillWithVerticalExtensionGlyph(style, info, middleGlyphPaintRect.minXMaxYCorner(), bottomGlyphPaintRect.minXMinYCorner()); + } else + fillWithVerticalExtensionGlyph(style, info, topGlyphPaintRect.minXMaxYCorner(), bottomGlyphPaintRect.minXMinYCorner()); +} + +void MathOperator::paintHorizontalGlyphAssembly(const RenderStyle& style, PaintInfo& info, const LayoutPoint& paintOffset) +{ + ASSERT(m_operatorType == Type::HorizontalOperator); + ASSERT(m_stretchType == StretchType::GlyphAssembly); + + auto topOrRight = glyphDataForCodePointOrFallbackGlyph(style, m_assembly.topOrRightCodePoint, m_assembly.topOrRightFallbackGlyph); + auto bottomOrLeft = glyphDataForCodePointOrFallbackGlyph(style, m_assembly.bottomOrLeftCodePoint, m_assembly.bottomOrLeftFallbackGlyph); + + ASSERT(bottomOrLeft.font); + ASSERT(topOrRight.font); + + // We are positioning the glyphs so that the edge of the tight glyph bounds line up exactly with the edges of our paint box. + LayoutPoint operatorTopLeft = paintOffset; + LayoutUnit baselineY = operatorTopLeft.y() + m_ascent; + LayoutPoint leftGlyphOrigin(operatorTopLeft.x(), baselineY); + LayoutRect leftGlyphPaintRect = paintGlyph(style, info, bottomOrLeft, leftGlyphOrigin, TrimRight); + + FloatRect rightGlyphBounds = boundsForGlyph(topOrRight); + LayoutPoint rightGlyphOrigin(operatorTopLeft.x() + stretchSize() - rightGlyphBounds.width(), baselineY); + LayoutRect rightGlyphPaintRect = paintGlyph(style, info, topOrRight, rightGlyphOrigin, TrimLeft); + + if (m_assembly.hasMiddle()) { + auto middle = glyphDataForCodePointOrFallbackGlyph(style, m_assembly.middleCodePoint, m_assembly.middleFallbackGlyph); + + // Center the glyph origin between the start and end glyph paint extents. + LayoutPoint middleGlyphOrigin(operatorTopLeft.x(), baselineY); + middleGlyphOrigin.moveBy(LayoutPoint((rightGlyphPaintRect.x() - leftGlyphPaintRect.maxX()) / 2.0, 0)); + LayoutRect middleGlyphPaintRect = paintGlyph(style, info, middle, middleGlyphOrigin, TrimLeftAndRight); + fillWithHorizontalExtensionGlyph(style, info, LayoutPoint(leftGlyphPaintRect.maxX(), baselineY), LayoutPoint(middleGlyphPaintRect.x(), baselineY)); + fillWithHorizontalExtensionGlyph(style, info, LayoutPoint(middleGlyphPaintRect.maxX(), baselineY), LayoutPoint(rightGlyphPaintRect.x(), baselineY)); + } else + fillWithHorizontalExtensionGlyph(style, info, LayoutPoint(leftGlyphPaintRect.maxX(), baselineY), LayoutPoint(rightGlyphPaintRect.x(), baselineY)); +} + +void MathOperator::paint(const RenderStyle& style, PaintInfo& info, const LayoutPoint& paintOffset) +{ + if (info.context().paintingDisabled() || info.phase != PaintPhaseForeground || style.visibility() != VISIBLE) + return; + + // Make a copy of the PaintInfo because applyTransform will modify its rect. + PaintInfo paintInfo(info); + GraphicsContextStateSaver stateSaver(paintInfo.context()); + paintInfo.context().setFillColor(style.visitedDependentColor(CSSPropertyColor)); + + // For a radical character, we may need some scale transform to stretch it vertically or mirror it. + if (m_baseCharacter == kRadicalOperator) { + float radicalHorizontalScale = style.isLeftToRightDirection() ? 1 : -1; + if (radicalHorizontalScale == -1 || m_radicalVerticalScale > 1) { + LayoutPoint scaleOrigin = paintOffset; + scaleOrigin.move(m_width / 2, 0); + paintInfo.applyTransform(AffineTransform().translate(scaleOrigin).scale(radicalHorizontalScale, m_radicalVerticalScale).translate(-scaleOrigin)); + } + } + + if (m_stretchType == StretchType::GlyphAssembly) { + if (m_operatorType == Type::VerticalOperator) + paintVerticalGlyphAssembly(style, info, paintOffset); + else + paintHorizontalGlyphAssembly(style, info, paintOffset); + return; + } + + GlyphData glyphData; + ASSERT(m_stretchType == StretchType::Unstretched || m_stretchType == StretchType::SizeVariant); + if (!getBaseGlyph(style, glyphData)) + return; + if (m_stretchType == StretchType::SizeVariant) + glyphData.glyph = m_variantGlyph; + + GlyphBuffer buffer; + buffer.add(glyphData.glyph, glyphData.font, advanceWidthForGlyph(glyphData)); + LayoutPoint operatorTopLeft = paintOffset; + FloatRect glyphBounds = boundsForGlyph(glyphData); + LayoutPoint operatorOrigin(operatorTopLeft.x(), operatorTopLeft.y() - glyphBounds.y()); + paintInfo.context().drawGlyphs(style.fontCascade(), *glyphData.font, buffer, 0, 1, operatorOrigin); +} + +} + +#endif diff --git a/Source/WebCore/rendering/mathml/MathOperator.h b/Source/WebCore/rendering/mathml/MathOperator.h new file mode 100644 index 000000000..735eb6a63 --- /dev/null +++ b/Source/WebCore/rendering/mathml/MathOperator.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2016 Igalia S.L. All rights reserved. + * Copyright (C) 2016 Apple Inc. All rights reserved. + * + * 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. + */ + +#pragma once + +#if ENABLE(MATHML) + +#include "GlyphPage.h" +#include "LayoutUnit.h" +#include "OpenTypeMathData.h" +#include "PaintInfo.h" +#include <unicode/utypes.h> + +namespace WebCore { + +class RenderStyle; + +class MathOperator { +public: + MathOperator(); + enum class Type { NormalOperator, DisplayOperator, VerticalOperator, HorizontalOperator }; + void setOperator(const RenderStyle&, UChar32 baseCharacter, Type); + void reset(const RenderStyle&); + + LayoutUnit width() const { return m_width; } + LayoutUnit maxPreferredWidth() const { return m_maxPreferredWidth; } + LayoutUnit ascent() const { return m_ascent; } + LayoutUnit descent() const { return m_descent; } + LayoutUnit italicCorrection() const { return m_italicCorrection; } + + void stretchTo(const RenderStyle&, LayoutUnit ascent, LayoutUnit descent); + void stretchTo(const RenderStyle&, LayoutUnit width); + + void paint(const RenderStyle&, PaintInfo&, const LayoutPoint&); + +private: + struct GlyphAssemblyData { + UChar32 topOrRightCodePoint { 0 }; + Glyph topOrRightFallbackGlyph { 0 }; + UChar32 extensionCodePoint { 0 }; + Glyph extensionFallbackGlyph { 0 }; + UChar32 bottomOrLeftCodePoint { 0 }; + Glyph bottomOrLeftFallbackGlyph { 0 }; + UChar32 middleCodePoint { 0 }; + Glyph middleFallbackGlyph { 0 }; + + bool hasExtension() const { return extensionCodePoint || extensionFallbackGlyph; } + bool hasMiddle() const { return middleCodePoint || middleFallbackGlyph; } + void initialize(); + }; + enum class StretchType { Unstretched, SizeVariant, GlyphAssembly }; + enum GlyphPaintTrimming { + TrimTop, + TrimBottom, + TrimTopAndBottom, + TrimLeft, + TrimRight, + TrimLeftAndRight + }; + + LayoutUnit stretchSize() const; + bool getGlyph(const RenderStyle&, UChar32 character, GlyphData&) const; + bool getBaseGlyph(const RenderStyle& style, GlyphData& baseGlyph) const { return getGlyph(style, m_baseCharacter, baseGlyph); } + void setSizeVariant(const GlyphData&); + void setGlyphAssembly(const RenderStyle&, const GlyphAssemblyData&); + void getMathVariantsWithFallback(const RenderStyle&, bool isVertical, Vector<Glyph>&, Vector<OpenTypeMathData::AssemblyPart>&); + void calculateDisplayStyleLargeOperator(const RenderStyle&); + void calculateStretchyData(const RenderStyle&, bool calculateMaxPreferredWidth, LayoutUnit targetSize = 0); + bool calculateGlyphAssemblyFallback(const Vector<OpenTypeMathData::AssemblyPart>&, GlyphAssemblyData&) const; + + LayoutRect paintGlyph(const RenderStyle&, PaintInfo&, const GlyphData&, const LayoutPoint& origin, GlyphPaintTrimming); + void fillWithVerticalExtensionGlyph(const RenderStyle&, PaintInfo&, const LayoutPoint& from, const LayoutPoint& to); + void fillWithHorizontalExtensionGlyph(const RenderStyle&, PaintInfo&, const LayoutPoint& from, const LayoutPoint& to); + void paintVerticalGlyphAssembly(const RenderStyle&, PaintInfo&, const LayoutPoint&); + void paintHorizontalGlyphAssembly(const RenderStyle&, PaintInfo&, const LayoutPoint&); + + UChar32 m_baseCharacter { 0 }; + Type m_operatorType { Type::NormalOperator }; + StretchType m_stretchType { StretchType::Unstretched }; + union { + Glyph m_variantGlyph; + GlyphAssemblyData m_assembly; + }; + LayoutUnit m_maxPreferredWidth { 0 }; + LayoutUnit m_width { 0 }; + LayoutUnit m_ascent { 0 }; + LayoutUnit m_descent { 0 }; + LayoutUnit m_italicCorrection { 0 }; + float m_radicalVerticalScale { 1 }; +}; + +} + +#endif // ENABLE(MATHML) diff --git a/Source/WebCore/rendering/mathml/RenderMathMLBlock.cpp b/Source/WebCore/rendering/mathml/RenderMathMLBlock.cpp index 724311566..417e9c655 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLBlock.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLBlock.cpp @@ -25,46 +25,72 @@ */ #include "config.h" +#include "RenderMathMLBlock.h" #if ENABLE(MATHML) -#include "RenderMathMLBlock.h" - +#include "CSSHelper.h" #include "GraphicsContext.h" +#include "LayoutRepainter.h" +#include "MathMLElement.h" #include "MathMLNames.h" +#include "MathMLPresentationElement.h" #include "RenderView.h" -#include <wtf/text/StringBuilder.h> #if ENABLE(DEBUG_MATH_LAYOUT) #include "PaintInfo.h" #endif namespace WebCore { - + using namespace MathMLNames; - -RenderMathMLBlock::RenderMathMLBlock(Element& container, PassRef<RenderStyle> style) - : RenderFlexibleBox(container, std::move(style)) - , m_ignoreInAccessibilityTree(false) + +RenderMathMLBlock::RenderMathMLBlock(MathMLPresentationElement& container, RenderStyle&& style) + : RenderBlock(container, WTFMove(style), 0) + , m_mathMLStyle(MathMLStyle::create()) +{ + setChildrenInline(false); // All of our children must be block-level. +} + +RenderMathMLBlock::RenderMathMLBlock(Document& document, RenderStyle&& style) + : RenderBlock(document, WTFMove(style), 0) + , m_mathMLStyle(MathMLStyle::create()) { + setChildrenInline(false); // All of our children must be block-level. } -RenderMathMLBlock::RenderMathMLBlock(Document& document, PassRef<RenderStyle> style) - : RenderFlexibleBox(document, std::move(style)) - , m_ignoreInAccessibilityTree(false) +RenderMathMLBlock::~RenderMathMLBlock() { } bool RenderMathMLBlock::isChildAllowed(const RenderObject& child, const RenderStyle&) const { - return child.node() && isElement(*child.node()); + return is<Element>(child.node()); } -RenderPtr<RenderMathMLBlock> RenderMathMLBlock::createAnonymousMathMLBlock() +static LayoutUnit axisHeight(const RenderStyle& style) { - RenderPtr<RenderMathMLBlock> newBlock = createRenderer<RenderMathMLBlock>(document(), RenderStyle::createAnonymousStyleWithDisplay(&style(), FLEX)); - newBlock->initializeStyle(); - return newBlock; + // If we have a MATH table we just return the AxisHeight constant. + const auto& primaryFont = style.fontCascade().primaryFont(); + if (auto* mathData = primaryFont.mathData()) + return mathData->getMathConstant(primaryFont, OpenTypeMathData::AxisHeight); + + // Otherwise, the idea is to try and use the middle of operators as the math axis which we thus approximate by "half of the x-height". + // Note that Gecko has a slower but more accurate version that measures half of the height of U+2212 MINUS SIGN. + return style.fontMetrics().xHeight() / 2; +} + +LayoutUnit RenderMathMLBlock::mathAxisHeight() const +{ + return axisHeight(style()); +} + +LayoutUnit RenderMathMLBlock::mirrorIfNeeded(LayoutUnit horizontalOffset, LayoutUnit boxWidth) const +{ + if (style().direction() == RTL) + return logicalWidth() - boxWidth - horizontalOffset; + + return horizontalOffset; } int RenderMathMLBlock::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode direction, LinePositionMode linePositionMode) const @@ -73,242 +99,167 @@ int RenderMathMLBlock::baselinePosition(FontBaseline baselineType, bool firstLin // return 0 here to match our line-height. This matters when RootInlineBox::ascentAndDescentForBox is called on a RootInlineBox for an inline-block. if (linePositionMode == PositionOfInteriorLineBoxes) return 0; - - LayoutUnit baseline = firstLineBaseline(); // FIXME: This may be unnecessary after flex baselines are implemented (https://bugs.webkit.org/show_bug.cgi?id=96188). - if (baseline != -1) - return baseline; - - return RenderFlexibleBox::baselinePosition(baselineType, firstLine, direction, linePositionMode); -} -const char* RenderMathMLBlock::renderName() const -{ - EDisplay display = style().display(); - if (display == FLEX) - return isAnonymous() ? "RenderMathMLBlock (anonymous, flex)" : "RenderMathMLBlock (flex)"; - if (display == INLINE_FLEX) - return isAnonymous() ? "RenderMathMLBlock (anonymous, inline-flex)" : "RenderMathMLBlock (inline-flex)"; - // |display| should be one of the above. - ASSERT_NOT_REACHED(); - return isAnonymous() ? "RenderMathMLBlock (anonymous)" : "RenderMathMLBlock"; + return firstLineBaseline().value_or(RenderBlock::baselinePosition(baselineType, firstLine, direction, linePositionMode)); } #if ENABLE(DEBUG_MATH_LAYOUT) void RenderMathMLBlock::paint(PaintInfo& info, const LayoutPoint& paintOffset) { - RenderFlexibleBox::paint(info, paintOffset); - - if (info.context->paintingDisabled() || info.phase != PaintPhaseForeground) + RenderBlock::paint(info, paintOffset); + + if (info.context().paintingDisabled() || info.phase != PaintPhaseForeground) return; IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset + location()); - GraphicsContextStateSaver stateSaver(*info.context); - - info.context->setStrokeThickness(1.0f); - info.context->setStrokeStyle(SolidStroke); - info.context->setStrokeColor(Color(0, 0, 255), ColorSpaceSRGB); - - info.context->drawLine(adjustedPaintOffset, IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y())); - info.context->drawLine(IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y()), IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y() + pixelSnappedOffsetHeight())); - info.context->drawLine(IntPoint(adjustedPaintOffset.x(), adjustedPaintOffset.y() + pixelSnappedOffsetHeight()), IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y() + pixelSnappedOffsetHeight())); - info.context->drawLine(adjustedPaintOffset, IntPoint(adjustedPaintOffset.x(), adjustedPaintOffset.y() + pixelSnappedOffsetHeight())); - + GraphicsContextStateSaver stateSaver(info.context()); + + info.context().setStrokeThickness(1.0f); + info.context().setStrokeStyle(SolidStroke); + info.context().setStrokeColor(Color(0, 0, 255)); + + info.context().drawLine(adjustedPaintOffset, IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y())); + info.context().drawLine(IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y()), IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y() + pixelSnappedOffsetHeight())); + info.context().drawLine(IntPoint(adjustedPaintOffset.x(), adjustedPaintOffset.y() + pixelSnappedOffsetHeight()), IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y() + pixelSnappedOffsetHeight())); + info.context().drawLine(adjustedPaintOffset, IntPoint(adjustedPaintOffset.x(), adjustedPaintOffset.y() + pixelSnappedOffsetHeight())); + int topStart = paddingTop(); - - info.context->setStrokeColor(Color(0, 255, 0), ColorSpaceSRGB); - - info.context->drawLine(IntPoint(adjustedPaintOffset.x(), adjustedPaintOffset.y() + topStart), IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y() + topStart)); - + + info.context().setStrokeColor(Color(0, 255, 0)); + + info.context().drawLine(IntPoint(adjustedPaintOffset.x(), adjustedPaintOffset.y() + topStart), IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y() + topStart)); + int baseline = roundToInt(baselinePosition(AlphabeticBaseline, true, HorizontalLine)); - - info.context->setStrokeColor(Color(255, 0, 0), ColorSpaceSRGB); - - info.context->drawLine(IntPoint(adjustedPaintOffset.x(), adjustedPaintOffset.y() + baseline), IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y() + baseline)); + + info.context().setStrokeColor(Color(255, 0, 0)); + + info.context().drawLine(IntPoint(adjustedPaintOffset.x(), adjustedPaintOffset.y() + baseline), IntPoint(adjustedPaintOffset.x() + pixelSnappedOffsetWidth(), adjustedPaintOffset.y() + baseline)); } #endif // ENABLE(DEBUG_MATH_LAYOUT) -// -// The MathML specification says: -// (http://www.w3.org/TR/MathML/chapter2.html#fund.units) -// -// "Most presentation elements have attributes that accept values representing -// lengths to be used for size, spacing or similar properties. The syntax of a -// length is specified as -// -// number | number unit | namedspace -// -// There should be no space between the number and the unit of a length." -// -// "A trailing '%' represents a percent of the default value. The default -// value, or how it is obtained, is listed in the table of attributes for each -// element. [...] A number without a unit is intepreted as a multiple of the -// default value." -// -// "The possible units in MathML are: -// -// Unit Description -// em an em (font-relative unit traditionally used for horizontal lengths) -// ex an ex (font-relative unit traditionally used for vertical lengths) -// px pixels, or size of a pixel in the current display -// in inches (1 inch = 2.54 centimeters) -// cm centimeters -// mm millimeters -// pt points (1 point = 1/72 inch) -// pc picas (1 pica = 12 points) -// % percentage of default value" -// -// The numbers are defined that way: -// - unsigned-number: "a string of decimal digits with up to one decimal point -// (U+002E), representing a non-negative terminating decimal number (a type of -// rational number)" -// - number: "an optional prefix of '-' (U+002D), followed by an unsigned -// number, representing a terminating decimal number (a type of rational -// number)" -// -bool parseMathMLLength(const String& string, LayoutUnit& lengthValue, const RenderStyle* style, bool allowNegative) +LayoutUnit toUserUnits(const MathMLElement::Length& length, const RenderStyle& style, const LayoutUnit& referenceValue) { - String s = string.simplifyWhiteSpace(); + switch (length.type) { + case MathMLElement::LengthType::Cm: + return length.value * cssPixelsPerInch / 2.54f; + case MathMLElement::LengthType::Em: + return length.value * style.fontCascade().size(); + case MathMLElement::LengthType::Ex: + return length.value * style.fontMetrics().xHeight(); + case MathMLElement::LengthType::In: + return length.value * cssPixelsPerInch; + case MathMLElement::LengthType::MathUnit: + return length.value * style.fontCascade().size() / 18; + case MathMLElement::LengthType::Mm: + return length.value * cssPixelsPerInch / 25.4f; + case MathMLElement::LengthType::Pc: + return length.value * cssPixelsPerInch / 6; + case MathMLElement::LengthType::Percentage: + return referenceValue * length.value / 100; + case MathMLElement::LengthType::Pt: + return length.value * cssPixelsPerInch / 72; + case MathMLElement::LengthType::Px: + return length.value; + case MathMLElement::LengthType::UnitLess: + return referenceValue * length.value; + case MathMLElement::LengthType::ParsingFailed: + return referenceValue; + case MathMLElement::LengthType::Infinity: + return intMaxForLayoutUnit; + default: + ASSERT_NOT_REACHED(); + return referenceValue; + } +} - int stringLength = s.length(); - if (!stringLength) - return false; +std::optional<int> RenderMathMLTable::firstLineBaseline() const +{ + // By default the vertical center of <mtable> is aligned on the math axis. + // This is different than RenderTable::firstLineBoxBaseline, which returns the baseline of the first row of a <table>. + return std::optional<int>(logicalHeight() / 2 + axisHeight(style())); +} - if (parseMathMLNamedSpace(s, lengthValue, style, allowNegative)) - return true; +void RenderMathMLBlock::layoutItems(bool relayoutChildren) +{ + LayoutUnit verticalOffset = borderBefore() + paddingBefore(); + LayoutUnit horizontalOffset = borderStart() + paddingStart(); - StringBuilder number; - String unit; + LayoutUnit preferredHorizontalExtent = 0; + for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) { + LayoutUnit childHorizontalExtent = child->maxPreferredLogicalWidth() - child->horizontalBorderAndPaddingExtent(); + LayoutUnit childHorizontalMarginBoxExtent = child->horizontalBorderAndPaddingExtent() + childHorizontalExtent; + childHorizontalMarginBoxExtent += child->horizontalMarginExtent(); - // This verifies whether the negative sign is there. - int i = 0; - UChar c = s[0]; - if (c == '-') { - number.append(c); - i++; + preferredHorizontalExtent += childHorizontalMarginBoxExtent; } - // This gathers up characters that make up the number. - bool gotDot = false; - for ( ; i < stringLength; i++) { - c = s[i]; - // The string is invalid if it contains two dots. - if (gotDot && c == '.') - return false; - if (c == '.') - gotDot = true; - else if (!isASCIIDigit(c)) { - unit = s.substring(i, stringLength - i); - // Some authors leave blanks before the unit, but that shouldn't - // be allowed, so don't simplifyWhitespace on 'unit'. - break; - } - number.append(c); - } + LayoutUnit currentHorizontalExtent = contentLogicalWidth(); + for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) { + LayoutUnit childSize = child->maxPreferredLogicalWidth() - child->horizontalBorderAndPaddingExtent(); - // Convert number to floating point - bool ok; - float floatValue = number.toString().toFloat(&ok); - if (!ok) - return false; - if (floatValue < 0 && !allowNegative) - return false; - - if (unit.isEmpty()) { - // no explicit unit, this is a number that will act as a multiplier - lengthValue *= floatValue; - return true; - } - if (unit == "%") { - lengthValue *= floatValue / 100; - return true; - } - if (unit == "em") { - lengthValue = floatValue * style->font().size(); - return true; - } - if (unit == "ex") { - lengthValue = floatValue * style->fontMetrics().xHeight(); - return true; - } - if (unit == "px") { - lengthValue = floatValue; - return true; - } - if (unit == "pt") { - lengthValue = 4 * (floatValue / 3); - return true; - } - if (unit == "pc") { - lengthValue = 16 * floatValue; - return true; - } - if (unit == "in") { - lengthValue = 96 * floatValue; - return true; - } - if (unit == "cm") { - lengthValue = 96 * (floatValue / 2.54); - return true; - } - if (unit == "mm") { - lengthValue = 96 * (floatValue / 25.4); - return true; - } + if (preferredHorizontalExtent > currentHorizontalExtent) + childSize = currentHorizontalExtent; + + LayoutUnit childPreferredSize = childSize + child->horizontalBorderAndPaddingExtent(); + + if (childPreferredSize != child->width()) + child->setChildNeedsLayout(MarkOnlyThis); + + updateBlockChildDirtyBitsBeforeLayout(relayoutChildren, *child); + child->layoutIfNeeded(); + + LayoutUnit childVerticalMarginBoxExtent; + childVerticalMarginBoxExtent = child->height() + child->verticalMarginExtent(); + + setLogicalHeight(std::max(logicalHeight(), verticalOffset + borderAfter() + paddingAfter() + childVerticalMarginBoxExtent + horizontalScrollbarHeight())); - // unexpected unit - return false; + horizontalOffset += child->marginStart(); + + LayoutUnit childHorizontalExtent = child->width(); + LayoutPoint childLocation(style().isLeftToRightDirection() ? horizontalOffset : width() - horizontalOffset - childHorizontalExtent, + verticalOffset + child->marginBefore()); + + child->setLocation(childLocation); + horizontalOffset += childHorizontalExtent + child->marginEnd(); + } } -bool parseMathMLNamedSpace(const String& string, LayoutUnit& lengthValue, const RenderStyle* style, bool allowNegative) +void RenderMathMLBlock::layoutBlock(bool relayoutChildren, LayoutUnit) { - float length = 0; - // See if it is one of the namedspaces (ranging -7/18em, -6/18, ... 7/18em) - if (string == "veryverythinmathspace") - length = 1; - else if (string == "verythinmathspace") - length = 2; - else if (string == "thinmathspace") - length = 3; - else if (string == "mediummathspace") - length = 4; - else if (string == "thickmathspace") - length = 5; - else if (string == "verythickmathspace") - length = 6; - else if (string == "veryverythickmathspace") - length = 7; - else if (allowNegative) { - if (string == "negativeveryverythinmathspace") - length = -1; - else if (string == "negativeverythinmathspace") - length = -2; - else if (string == "negativethinmathspace") - length = -3; - else if (string == "negativemediummathspace") - length = -4; - else if (string == "negativethickmathspace") - length = -5; - else if (string == "negativeverythickmathspace") - length = -6; - else if (string == "negativeveryverythickmathspace") - length = -7; - } - if (length) { - lengthValue = length * style->font().size() / 18; - return true; - } - return false; + ASSERT(needsLayout()); + + if (!relayoutChildren && simplifiedLayout()) + return; + + LayoutRepainter repainter(*this, checkForRepaintDuringLayout()); + + if (recomputeLogicalWidth()) + relayoutChildren = true; + + setLogicalHeight(borderAndPaddingLogicalHeight() + scrollbarLogicalHeight()); + + layoutItems(relayoutChildren); + + updateLogicalHeight(); + + repainter.repaintAfterLayout(); + + clearNeedsLayout(); } -int RenderMathMLTable::firstLineBaseline() const +void RenderMathMLBlock::layoutInvalidMarkup() { - // In legal MathML, we'll have a MathML parent. That RenderFlexibleBox parent will use our firstLineBaseline() for baseline alignment, per - // http://dev.w3.org/csswg/css3-flexbox/#flex-baselines. We want to vertically center an <mtable>, such as a matrix. Essentially the whole <mtable> element fits on a - // single line, whose baseline gives this centering. This is different than RenderTable::firstLineBoxBaseline, which returns the baseline of the first row of a <table>. - return (logicalHeight() + style().fontMetrics().xHeight()) / 2; + // Invalid MathML subtrees are just renderered as empty boxes. + // FIXME: https://webkit.org/b/135460 - Should we display some "invalid" markup message instead? + ASSERT(needsLayout()); + for (auto child = firstChildBox(); child; child = child->nextSiblingBox()) + child->layoutIfNeeded(); + setLogicalWidth(0); + setLogicalHeight(0); + clearNeedsLayout(); } -} +} #endif diff --git a/Source/WebCore/rendering/mathml/RenderMathMLBlock.h b/Source/WebCore/rendering/mathml/RenderMathMLBlock.h index b1b44c9a9..61a12ca48 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLBlock.h +++ b/Source/WebCore/rendering/mathml/RenderMathMLBlock.h @@ -24,28 +24,31 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef RenderMathMLBlock_h -#define RenderMathMLBlock_h +#pragma once #if ENABLE(MATHML) -#include "RenderFlexibleBox.h" +#include "MathMLElement.h" +#include "MathMLStyle.h" +#include "RenderBlock.h" #include "RenderTable.h" #include "StyleInheritedData.h" -#define ENABLE_DEBUG_MATH_LAYOUT 0 - namespace WebCore { - + class RenderMathMLOperator; +class MathMLPresentationElement; -class RenderMathMLBlock : public RenderFlexibleBox { +class RenderMathMLBlock : public RenderBlock { public: - RenderMathMLBlock(Element&, PassRef<RenderStyle>); - RenderMathMLBlock(Document&, PassRef<RenderStyle>); + RenderMathMLBlock(MathMLPresentationElement&, RenderStyle&&); + RenderMathMLBlock(Document&, RenderStyle&&); + virtual ~RenderMathMLBlock(); + + MathMLStyle& mathMLStyle() const { return m_mathMLStyle; } + + bool isChildAllowed(const RenderObject&, const RenderStyle&) const override; - virtual bool isChildAllowed(const RenderObject&, const RenderStyle&) const override; - // MathML defines an "embellished operator" as roughly an <mo> that may have subscripts, // superscripts, underscripts, overscripts, or a denominator (as in d/dx, where "d" is some // differential operator). The padding, precedence, and stretchiness of the base <mo> should @@ -54,46 +57,68 @@ public: // FIXME: We don't yet handle all the cases in the MathML spec. See // https://bugs.webkit.org/show_bug.cgi?id=78617. virtual RenderMathMLOperator* unembellishedOperator() { return 0; } - - virtual int baselinePosition(FontBaseline, bool firstLine, LineDirectionMode, LinePositionMode = PositionOnContainingLine) const override; - + + int baselinePosition(FontBaseline, bool firstLine, LineDirectionMode, LinePositionMode = PositionOnContainingLine) const override; + #if ENABLE(DEBUG_MATH_LAYOUT) virtual void paint(PaintInfo&, const LayoutPoint&); #endif - - // Create a new RenderMathMLBlock, with a new style inheriting from this->style(). - RenderPtr<RenderMathMLBlock> createAnonymousMathMLBlock(); - - void setIgnoreInAccessibilityTree(bool flag) { m_ignoreInAccessibilityTree = flag; } - bool ignoreInAccessibilityTree() const { return m_ignoreInAccessibilityTree; } - + +protected: + LayoutUnit ruleThicknessFallback() const + { + // This function returns a value for the default rule thickness (TeX's \xi_8) to be used as a fallback when we lack a MATH table. + // This arbitrary value of 0.05em was used in early WebKit MathML implementations for the thickness of the fraction bars. + // Note that Gecko has a slower but more accurate version that measures the thickness of U+00AF MACRON to be more accurate and otherwise fallback to some arbitrary value. + return 0.05f * style().fontCascade().size(); + } + + LayoutUnit mathAxisHeight() const; + LayoutUnit mirrorIfNeeded(LayoutUnit horizontalOffset, LayoutUnit boxWidth = 0) const; + LayoutUnit mirrorIfNeeded(LayoutUnit horizontalOffset, const RenderBox& child) const { return mirrorIfNeeded(horizontalOffset, child.logicalWidth()); } + + static LayoutUnit ascentForChild(const RenderBox& child) + { + return child.firstLineBaseline().value_or(child.logicalHeight()); + } + + void layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight = 0) override; + void layoutInvalidMarkup(); + private: - virtual bool isRenderMathMLBlock() const override final { return true; } - virtual const char* renderName() const override; + bool isRenderMathMLBlock() const final { return true; } + const char* renderName() const override { return "RenderMathMLBlock"; } + bool avoidsFloats() const final { return true; } + bool canDropAnonymousBlockChild() const final { return false; } + void layoutItems(bool relayoutChildren); - bool m_ignoreInAccessibilityTree; + Ref<MathMLStyle> m_mathMLStyle; }; -template<> inline bool isRendererOfType<const RenderMathMLBlock>(const RenderObject& renderer) { return renderer.isRenderMathMLBlock(); } -RENDER_OBJECT_TYPE_CASTS(RenderMathMLBlock, isRenderMathMLBlock()) - class RenderMathMLTable final : public RenderTable { public: - explicit RenderMathMLTable(Element& element, PassRef<RenderStyle> style) - : RenderTable(element, std::move(style)) + explicit RenderMathMLTable(MathMLElement& element, RenderStyle&& style) + : RenderTable(element, WTFMove(style)) + , m_mathMLStyle(MathMLStyle::create()) { } - - virtual int firstLineBaseline() const override; - + + + MathMLStyle& mathMLStyle() const { return m_mathMLStyle; } + private: - virtual const char* renderName() const override { return "RenderMathMLTable"; } + bool isRenderMathMLTable() const final { return true; } + const char* renderName() const final { return "RenderMathMLTable"; } + std::optional<int> firstLineBaseline() const final; + + Ref<MathMLStyle> m_mathMLStyle; }; -// Parsing functions for MathML Length values -bool parseMathMLLength(const String&, LayoutUnit&, const RenderStyle*, bool allowNegative = true); -bool parseMathMLNamedSpace(const String&, LayoutUnit&, const RenderStyle*, bool allowNegative = true); -} +LayoutUnit toUserUnits(const MathMLElement::Length&, const RenderStyle&, const LayoutUnit& referenceValue); + +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_RENDER_OBJECT(RenderMathMLBlock, isRenderMathMLBlock()) +SPECIALIZE_TYPE_TRAITS_RENDER_OBJECT(RenderMathMLTable, isRenderMathMLTable()) #endif // ENABLE(MATHML) -#endif // RenderMathMLBlock_h diff --git a/Source/WebCore/rendering/mathml/RenderMathMLFenced.cpp b/Source/WebCore/rendering/mathml/RenderMathMLFenced.cpp index 2e009ae59..6f166a799 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLFenced.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLFenced.cpp @@ -24,31 +24,27 @@ */ #include "config.h" +#include "RenderMathMLFenced.h" #if ENABLE(MATHML) -#include "RenderMathMLFenced.h" - #include "FontSelector.h" #include "MathMLNames.h" +#include "MathMLRowElement.h" #include "RenderInline.h" -#include "RenderMathMLOperator.h" +#include "RenderMathMLFencedOperator.h" #include "RenderText.h" #include <wtf/text/StringBuilder.h> namespace WebCore { - + using namespace MathMLNames; - -enum Braces { OpeningBraceChar = 0x28, ClosingBraceChar = 0x29 }; - -static const float gSeparatorMarginEndEms = 0.25f; -static const float gFenceMarginEms = 0.1f; - -RenderMathMLFenced::RenderMathMLFenced(MathMLInlineContainerElement& element, PassRef<RenderStyle> style) - : RenderMathMLRow(element, std::move(style)) - , m_open(OpeningBraceChar) - , m_close(ClosingBraceChar) + +static const char* gOpeningBraceChar = "("; +static const char* gClosingBraceChar = ")"; + +RenderMathMLFenced::RenderMathMLFenced(MathMLRowElement& element, RenderStyle&& style) + : RenderMathMLRow(element, WTFMove(style)) , m_closeFenceRenderer(nullptr) { } @@ -56,16 +52,16 @@ RenderMathMLFenced::RenderMathMLFenced(MathMLInlineContainerElement& element, Pa void RenderMathMLFenced::updateFromElement() { const auto& fenced = element(); - - // FIXME: Handle open/close values with more than one character (they should be treated like text). - AtomicString openValue = fenced.getAttribute(MathMLNames::openAttr); - if (openValue.length() > 0) - m_open = openValue[0]; - AtomicString closeValue = fenced.getAttribute(MathMLNames::closeAttr); - if (closeValue.length() > 0) - m_close = closeValue[0]; - - AtomicString separators = fenced.getAttribute(MathMLNames::separatorsAttr); + + // The open operator defaults to a left parenthesis. + auto& open = fenced.attributeWithoutSynchronization(MathMLNames::openAttr); + m_open = open.isNull() ? gOpeningBraceChar : open; + + // The close operator defaults to a right parenthesis. + auto& close = fenced.attributeWithoutSynchronization(MathMLNames::closeAttr); + m_close = close.isNull() ? gClosingBraceChar : close; + + auto& separators = fenced.attributeWithoutSynchronization(MathMLNames::separatorsAttr); if (!separators.isNull()) { StringBuilder characters; for (unsigned int i = 0; i < separators.length(); i++) { @@ -77,48 +73,43 @@ void RenderMathMLFenced::updateFromElement() // The separator defaults to a single comma. m_separators = StringImpl::create(","); } - - if (isEmpty()) + + if (!firstChild()) makeFences(); + else { + // FIXME: The mfenced element fails to update dynamically when its open, close and separators attributes are changed (https://bugs.webkit.org/show_bug.cgi?id=57696). + if (is<RenderMathMLFencedOperator>(*firstChild())) + downcast<RenderMathMLFencedOperator>(*firstChild()).updateOperatorContent(m_open); + m_closeFenceRenderer->updateOperatorContent(m_close); + } } -RenderPtr<RenderMathMLOperator> RenderMathMLFenced::createMathMLOperator(UChar uChar, RenderMathMLOperator::OperatorType operatorType) +RenderPtr<RenderMathMLFencedOperator> RenderMathMLFenced::createMathMLOperator(const String& operatorString, MathMLOperatorDictionary::Form form, MathMLOperatorDictionary::Flag flag) { - auto newStyle = RenderStyle::createAnonymousStyleWithDisplay(&style(), FLEX); - newStyle.get().setFlexDirection(FlowColumn); - newStyle.get().setMarginEnd(Length((operatorType == RenderMathMLOperator::Fence ? gFenceMarginEms : gSeparatorMarginEndEms) * style().fontSize(), Fixed)); - if (operatorType == RenderMathMLOperator::Fence) - newStyle.get().setMarginStart(Length(gFenceMarginEms * style().fontSize(), Fixed)); - RenderPtr<RenderMathMLOperator> newOperator = createRenderer<RenderMathMLOperator>(element(), std::move(newStyle), uChar); - newOperator->setOperatorType(operatorType); + RenderPtr<RenderMathMLFencedOperator> newOperator = createRenderer<RenderMathMLFencedOperator>(document(), RenderStyle::createAnonymousStyleWithDisplay(style(), BLOCK), operatorString, form, flag); newOperator->initializeStyle(); return newOperator; } void RenderMathMLFenced::makeFences() { - RenderPtr<RenderMathMLOperator> openFence = createMathMLOperator(m_open, RenderMathMLOperator::Fence); - RenderMathMLOperator* openFencePtr = openFence.get(); + RenderPtr<RenderMathMLFencedOperator> openFence = createMathMLOperator(m_open, MathMLOperatorDictionary::Prefix, MathMLOperatorDictionary::Fence); RenderMathMLRow::addChild(openFence.leakPtr(), firstChild()); - RenderPtr<RenderMathMLOperator> closeFence = createMathMLOperator(m_close, RenderMathMLOperator::Fence); + RenderPtr<RenderMathMLFencedOperator> closeFence = createMathMLOperator(m_close, MathMLOperatorDictionary::Postfix, MathMLOperatorDictionary::Fence); m_closeFenceRenderer = closeFence.get(); RenderMathMLRow::addChild(closeFence.leakPtr()); - - openFencePtr->updateFromElement(); - m_closeFenceRenderer->updateFromElement(); } void RenderMathMLFenced::addChild(RenderObject* child, RenderObject* beforeChild) { // make the fences if the render object is empty - if (isEmpty()) + if (!firstChild()) updateFromElement(); - - // FIXME: Adding or removing a child should possibly cause all later separators to shift places if they're different, - // as later child positions change by +1 or -1. - - RenderPtr<RenderMathMLOperator> separatorRenderer; + + // FIXME: Adding or removing a child should possibly cause all later separators to shift places if they're different, as later child positions change by +1 or -1. This should also handle surrogate pairs. See https://bugs.webkit.org/show_bug.cgi?id=125938. + + RenderPtr<RenderMathMLFencedOperator> separatorRenderer; if (m_separators.get()) { unsigned int count = 0; for (Node* position = child->node(); position; position = position->previousSibling()) { @@ -130,20 +121,22 @@ void RenderMathMLFenced::addChild(RenderObject* child, RenderObject* beforeChild --count; } // |count| is now the number of element children that will be before our new separator, i.e. it's the 1-based index of the separator. - + if (count > 0) { UChar separator; - + // Use the last separator if we've run out of specified separators. if (count > m_separators.get()->length()) separator = (*m_separators.get())[m_separators.get()->length() - 1]; else separator = (*m_separators.get())[count - 1]; - - separatorRenderer = createMathMLOperator(separator, RenderMathMLOperator::Separator); + + StringBuilder builder; + builder.append(separator); + separatorRenderer = createMathMLOperator(builder.toString(), MathMLOperatorDictionary::Infix, MathMLOperatorDictionary::Separator); } } - + if (beforeChild) { // Adding |x| before an existing |y| e.g. in element (y) - first insert our new child |x|, then its separator, to get (x, y). RenderMathMLRow::addChild(child, beforeChild); @@ -157,26 +150,6 @@ void RenderMathMLFenced::addChild(RenderObject* child, RenderObject* beforeChild } } -// FIXME: Change createMathMLOperator() above to create an isAnonymous() operator, and remove this styleDidChange() function. -void RenderMathMLFenced::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) -{ - RenderMathMLBlock::styleDidChange(diff, oldStyle); - - for (RenderObject* child = firstChild(); child; child = child->nextSibling()) { - if (child->node() == &element()) { - ASSERT(child->style().refCount() == 1); - child->style().inheritFrom(&style()); - bool isFence = child == firstChild() || child == lastChild(); - child->style().setMarginEnd(Length((isFence ? gFenceMarginEms : gSeparatorMarginEndEms) * style().fontSize(), Fixed)); - if (isFence) { - RenderMathMLBlock* block = toRenderMathMLBlock(child); - toRenderMathMLOperator(block)->updateFromElement(); - child->style().setMarginStart(Length(gFenceMarginEms * style().fontSize(), Fixed)); - } - } - } } -} - #endif diff --git a/Source/WebCore/rendering/mathml/RenderMathMLFenced.h b/Source/WebCore/rendering/mathml/RenderMathMLFenced.h index 97263a334..dc0b9f9b6 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLFenced.h +++ b/Source/WebCore/rendering/mathml/RenderMathMLFenced.h @@ -23,42 +23,37 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef RenderMathMLFenced_h -#define RenderMathMLFenced_h +#pragma once #if ENABLE(MATHML) -#include "MathMLInlineContainerElement.h" -#include "RenderMathMLOperator.h" +#include "RenderMathMLFencedOperator.h" #include "RenderMathMLRow.h" namespace WebCore { - + +class MathMLRowElement; + class RenderMathMLFenced final : public RenderMathMLRow { public: - RenderMathMLFenced(MathMLInlineContainerElement&, PassRef<RenderStyle>); - MathMLInlineContainerElement& element() { return static_cast<MathMLInlineContainerElement&>(nodeForNonAnonymous()); } - -private: - virtual bool isRenderMathMLFenced() const override { return true; } - virtual const char* renderName() const override { return "RenderMathMLFenced"; } - virtual void addChild(RenderObject* child, RenderObject* beforeChild) override; - virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle) override; + RenderMathMLFenced(MathMLRowElement&, RenderStyle&&); - virtual void updateFromElement() override; +private: + bool isRenderMathMLFenced() const final { return true; } + const char* renderName() const final { return "RenderMathMLFenced"; } + void addChild(RenderObject* child, RenderObject* beforeChild) final; + void updateFromElement() final; - RenderPtr<RenderMathMLOperator> createMathMLOperator(UChar, RenderMathMLOperator::OperatorType); + RenderPtr<RenderMathMLFencedOperator> createMathMLOperator(const String& operatorString, MathMLOperatorDictionary::Form, MathMLOperatorDictionary::Flag); void makeFences(); - UChar m_open; - UChar m_close; + String m_open; + String m_close; RefPtr<StringImpl> m_separators; - - RenderMathMLOperator* m_closeFenceRenderer; + + RenderMathMLFencedOperator* m_closeFenceRenderer; }; - + } #endif // ENABLE(MATHML) - -#endif // RenderMathMLFenced_h diff --git a/Source/WebCore/rendering/mathml/RenderMathMLFencedOperator.cpp b/Source/WebCore/rendering/mathml/RenderMathMLFencedOperator.cpp new file mode 100644 index 000000000..f24417587 --- /dev/null +++ b/Source/WebCore/rendering/mathml/RenderMathMLFencedOperator.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2016 Igalia S.L. All rights reserved. + * + * 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 "RenderMathMLFencedOperator.h" + +#if ENABLE(MATHML) + +#include "MathMLOperatorDictionary.h" +#include "MathMLOperatorElement.h" + +namespace WebCore { + +using namespace MathMLOperatorDictionary; + +RenderMathMLFencedOperator::RenderMathMLFencedOperator(Document& document, RenderStyle&& style, const String& operatorString, MathMLOperatorDictionary::Form form, unsigned short flags) + : RenderMathMLOperator(document, WTFMove(style)) + , m_operatorForm(form) + , m_operatorFlags(flags) +{ + updateOperatorContent(operatorString); +} + +void RenderMathMLFencedOperator::updateOperatorContent(const String& operatorString) +{ + m_operatorChar = MathMLOperatorElement::parseOperatorChar(operatorString); + + // We try and read spacing and boolean properties from the operator dictionary. + // However we preserve the Fence and Separator properties specified in the constructor. + if (auto entry = search(m_operatorChar.character, m_operatorForm, true)) { + m_leadingSpaceInMathUnit = entry.value().leadingSpaceInMathUnit; + m_trailingSpaceInMathUnit = entry.value().trailingSpaceInMathUnit; + m_operatorFlags = (m_operatorFlags & (MathMLOperatorDictionary::Fence | MathMLOperatorDictionary::Separator)) | entry.value().flags; + } else { + m_operatorFlags &= MathMLOperatorDictionary::Fence | MathMLOperatorDictionary::Separator; // Flags are disabled by default. + m_leadingSpaceInMathUnit = 5; // Default spacing is thickmathspace. + m_trailingSpaceInMathUnit = 5; // Default spacing is thickmathspace. + } + + updateMathOperator(); +} + +LayoutUnit RenderMathMLFencedOperator::leadingSpace() const +{ + MathMLElement::Length leadingSpace; + leadingSpace.type = MathMLElement::LengthType::MathUnit; + leadingSpace.value = static_cast<float>(m_leadingSpaceInMathUnit); + return toUserUnits(leadingSpace, style(), 0); +} + +LayoutUnit RenderMathMLFencedOperator::trailingSpace() const +{ + MathMLElement::Length trailingSpace; + trailingSpace.type = MathMLElement::LengthType::MathUnit; + trailingSpace.value = static_cast<float>(m_trailingSpaceInMathUnit); + return toUserUnits(trailingSpace, style(), 0); +} + +} + +#endif diff --git a/Source/WebCore/rendering/mathml/RenderMathMLFencedOperator.h b/Source/WebCore/rendering/mathml/RenderMathMLFencedOperator.h new file mode 100644 index 000000000..dc466413b --- /dev/null +++ b/Source/WebCore/rendering/mathml/RenderMathMLFencedOperator.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2016 Igalia S.L. All rights reserved. + * + * 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. + */ + +#pragma once + +#if ENABLE(MATHML) + +#include "MathMLOperatorDictionary.h" +#include "MathMLOperatorElement.h" +#include "RenderMathMLOperator.h" + +namespace WebCore { + +class RenderMathMLFencedOperator final : public RenderMathMLOperator { +public: + RenderMathMLFencedOperator(Document&, RenderStyle&&, const String& operatorString, MathMLOperatorDictionary::Form, unsigned short flags = 0); + void updateOperatorContent(const String&); + +private: + bool isRenderMathMLFencedOperator() const final { return true; } + bool isVertical() const final { return m_operatorChar.isVertical; } + UChar32 textContent() const final { return m_operatorChar.character; } + LayoutUnit leadingSpace() const final; + LayoutUnit trailingSpace() const final; + + // minsize always has the default value "1em". + LayoutUnit minSize() const final { return style().fontCascade().size(); } + + // maxsize always has the default value "infinity". + LayoutUnit maxSize() const final { return intMaxForLayoutUnit; } + + bool hasOperatorFlag(MathMLOperatorDictionary::Flag flag) const final { return m_operatorFlags & flag; } + + // We always use the MathOperator class for anonymous mfenced operators, since they do not have text content in the DOM. + bool useMathOperator() const final { return true; } + + MathMLOperatorElement::OperatorChar m_operatorChar; + unsigned short m_leadingSpaceInMathUnit; + unsigned short m_trailingSpaceInMathUnit; + MathMLOperatorDictionary::Form m_operatorForm; + unsigned short m_operatorFlags; +}; + +}; // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_RENDER_OBJECT(RenderMathMLFencedOperator, isRenderMathMLFencedOperator()) + +#endif // ENABLE(MATHML) diff --git a/Source/WebCore/rendering/mathml/RenderMathMLFraction.cpp b/Source/WebCore/rendering/mathml/RenderMathMLFraction.cpp index 0655289df..a20bd122d 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLFraction.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLFraction.cpp @@ -1,6 +1,7 @@ /* * Copyright (C) 2009 Alex Milowski (alex@milowski.com). All rights reserved. * Copyright (C) 2010 François Sausset (sausset@gmail.com). 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 @@ -25,144 +26,226 @@ */ #include "config.h" +#include "RenderMathMLFraction.h" #if ENABLE(MATHML) -#include "RenderMathMLFraction.h" - #include "GraphicsContext.h" -#include "MathMLNames.h" +#include "MathMLFractionElement.h" #include "PaintInfo.h" +#include <cmath> namespace WebCore { - -using namespace MathMLNames; - -static const float gLineThin = 0.33f; -static const float gLineMedium = 1.f; -static const float gLineThick = 3.f; -static const float gFractionBarWidth = 0.05f; -RenderMathMLFraction::RenderMathMLFraction(MathMLInlineContainerElement& element, PassRef<RenderStyle> style) - : RenderMathMLBlock(element, std::move(style)) - , m_lineThickness(gLineMedium) +RenderMathMLFraction::RenderMathMLFraction(MathMLFractionElement& element, RenderStyle&& style) + : RenderMathMLBlock(element, WTFMove(style)) { } -void RenderMathMLFraction::fixChildStyle(RenderObject* child) +bool RenderMathMLFraction::isValid() const { - ASSERT(child->isAnonymous() && child->style().refCount() == 1); - child->style().setFlexDirection(FlowColumn); + // Verify whether the list of children is valid: + // <mfrac> numerator denominator </mfrac> + auto* child = firstChildBox(); + if (!child) + return false; + child = child->nextSiblingBox(); + return child && !child->nextSiblingBox(); } -// FIXME: It's cleaner to only call updateFromElement when an attribute has changed. Move parts -// of this to fixChildStyle or other methods, and call them when needed. -void RenderMathMLFraction::updateFromElement() +RenderBox& RenderMathMLFraction::numerator() const { - // FIXME: mfrac where bevelled=true will need to reorganize the descendants - if (isEmpty()) - return; + ASSERT(isValid()); + return *firstChildBox(); +} - RenderObject* numeratorWrapper = firstChild(); - RenderObject* denominatorWrapper = numeratorWrapper->nextSibling(); - if (!denominatorWrapper) - return; +RenderBox& RenderMathMLFraction::denominator() const +{ + ASSERT(isValid()); + return *firstChildBox()->nextSiblingBox(); +} - String thickness = element().getAttribute(MathMLNames::linethicknessAttr); - m_lineThickness = gLineMedium; - if (equalIgnoringCase(thickness, "thin")) - m_lineThickness = gLineThin; - else if (equalIgnoringCase(thickness, "medium")) - m_lineThickness = gLineMedium; - else if (equalIgnoringCase(thickness, "thick")) - m_lineThickness = gLineThick; - else { - // This function parses the thickness attribute using gLineMedium as - // the default value. If the parsing fails, m_lineThickness will not be - // modified i.e. the default value will be used. - parseMathMLLength(thickness, m_lineThickness, &style(), false); - } +void RenderMathMLFraction::updateLineThickness() +{ + // We first determine the default line thickness. + const auto& primaryFont = style().fontCascade().primaryFont(); + const auto* mathData = style().fontCascade().primaryFont().mathData(); + if (mathData) + m_defaultLineThickness = mathData->getMathConstant(primaryFont, OpenTypeMathData::FractionRuleThickness); + else + m_defaultLineThickness = ruleThicknessFallback(); - // Update the style for the padding of the denominator for the line thickness - lastChild()->style().setPaddingTop(Length(static_cast<int>(m_lineThickness), Fixed)); + // Next we resolve the thickness using m_defaultLineThickness as the default value. + m_lineThickness = toUserUnits(element().lineThickness(), style(), m_defaultLineThickness); + if (m_lineThickness < 0) + m_lineThickness = 0; } -void RenderMathMLFraction::addChild(RenderObject* child, RenderObject* /* beforeChild */) +RenderMathMLFraction::FractionParameters RenderMathMLFraction::fractionParameters() { - if (isEmpty()) { - RenderPtr<RenderMathMLBlock> numeratorWrapper = createAnonymousMathMLBlock(); - fixChildStyle(numeratorWrapper.get()); - RenderMathMLBlock::addChild(numeratorWrapper.leakPtr()); - - RenderPtr<RenderMathMLBlock> denominatorWrapper = createAnonymousMathMLBlock(); - fixChildStyle(denominatorWrapper.get()); - RenderMathMLBlock::addChild(denominatorWrapper.leakPtr()); + ASSERT(!isStack()); + FractionParameters parameters; + + // We try and read constants to draw the fraction from the OpenType MATH and use fallback values otherwise. + const auto& primaryFont = style().fontCascade().primaryFont(); + const auto* mathData = style().fontCascade().primaryFont().mathData(); + bool display = mathMLStyle().displayStyle(); + if (mathData) { + parameters.numeratorGapMin = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::FractionNumDisplayStyleGapMin : OpenTypeMathData::FractionNumeratorGapMin); + parameters.denominatorGapMin = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::FractionDenomDisplayStyleGapMin : OpenTypeMathData::FractionDenominatorGapMin); + parameters.numeratorMinShiftUp = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::FractionNumeratorDisplayStyleShiftUp : OpenTypeMathData::FractionNumeratorShiftUp); + parameters.denominatorMinShiftDown = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::FractionDenominatorDisplayStyleShiftDown : OpenTypeMathData::FractionDenominatorShiftDown); + } else { + // The MATH table specification suggests default rule thickness or (in displaystyle) 3 times default rule thickness for the gaps. + parameters.numeratorGapMin = display ? 3 * ruleThicknessFallback() : ruleThicknessFallback(); + parameters.denominatorGapMin = parameters.numeratorGapMin; + + // The MATH table specification does not suggest any values for shifts, so we leave them at zero. + parameters.numeratorMinShiftUp = 0; + parameters.denominatorMinShiftDown = 0; } - - if (firstChild()->isEmpty()) - toRenderElement(firstChild())->addChild(child); - else - toRenderElement(lastChild())->addChild(child); - - updateFromElement(); + + return parameters; } -void RenderMathMLFraction::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +RenderMathMLFraction::StackParameters RenderMathMLFraction::stackParameters() { - RenderMathMLBlock::styleDidChange(diff, oldStyle); + ASSERT(isStack()); + StackParameters parameters; - for (RenderObject* child = firstChild(); child; child = child->nextSibling()) - fixChildStyle(child); - updateFromElement(); + // We try and read constants to draw the stack from the OpenType MATH and use fallback values otherwise. + const auto& primaryFont = style().fontCascade().primaryFont(); + const auto* mathData = style().fontCascade().primaryFont().mathData(); + bool display = mathMLStyle().displayStyle(); + if (mathData) { + parameters.gapMin = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::StackDisplayStyleGapMin : OpenTypeMathData::StackGapMin); + parameters.topShiftUp = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::StackTopDisplayStyleShiftUp : OpenTypeMathData::StackTopShiftUp); + parameters.bottomShiftDown = mathData->getMathConstant(primaryFont, display ? OpenTypeMathData::StackBottomDisplayStyleShiftDown : OpenTypeMathData::StackBottomShiftDown); + } else { + // We use the values suggested in the MATH table specification. + parameters.gapMin = display ? 7 * ruleThicknessFallback() : 3 * ruleThicknessFallback(); + + // The MATH table specification does not suggest any values for shifts, so we leave them at zero. + parameters.topShiftUp = 0; + parameters.bottomShiftDown = 0; + } + + return parameters; } RenderMathMLOperator* RenderMathMLFraction::unembellishedOperator() { - RenderObject* numeratorWrapper = firstChild(); - if (!numeratorWrapper) - return 0; - RenderObject* numerator = numeratorWrapper->firstChildSlow(); - if (!numerator || !numerator->isRenderMathMLBlock()) - return 0; - return toRenderMathMLBlock(numerator)->unembellishedOperator(); + if (!isValid() || !is<RenderMathMLBlock>(numerator())) + return nullptr; + + return downcast<RenderMathMLBlock>(numerator()).unembellishedOperator(); } -void RenderMathMLFraction::layout() +void RenderMathMLFraction::computePreferredLogicalWidths() { - updateFromElement(); + ASSERT(preferredLogicalWidthsDirty()); + + m_minPreferredLogicalWidth = 0; + m_maxPreferredLogicalWidth = 0; - // Adjust the fraction line thickness for the zoom - if (lastChild() && lastChild()->isRenderBlock()) - m_lineThickness *= ceilf(gFractionBarWidth * style().fontSize()); + if (isValid()) { + LayoutUnit numeratorWidth = numerator().maxPreferredLogicalWidth(); + LayoutUnit denominatorWidth = denominator().maxPreferredLogicalWidth(); + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = std::max(numeratorWidth, denominatorWidth); + } - RenderMathMLBlock::layout(); + setPreferredLogicalWidthsDirty(false); +} + +LayoutUnit RenderMathMLFraction::horizontalOffset(RenderBox& child, MathMLFractionElement::FractionAlignment align) +{ + switch (align) { + case MathMLFractionElement::FractionAlignmentRight: + return LayoutUnit(logicalWidth() - child.logicalWidth()); + case MathMLFractionElement::FractionAlignmentCenter: + return LayoutUnit((logicalWidth() - child.logicalWidth()) / 2); + case MathMLFractionElement::FractionAlignmentLeft: + return LayoutUnit(0); + } + + ASSERT_NOT_REACHED(); + return LayoutUnit(0); +} + +void RenderMathMLFraction::layoutBlock(bool relayoutChildren, LayoutUnit) +{ + ASSERT(needsLayout()); + + if (!relayoutChildren && simplifiedLayout()) + return; + + if (!isValid()) { + layoutInvalidMarkup(); + return; + } + + numerator().layoutIfNeeded(); + denominator().layoutIfNeeded(); + + setLogicalWidth(std::max(numerator().logicalWidth(), denominator().logicalWidth())); + + updateLineThickness(); + LayoutUnit verticalOffset = 0; // This is the top of the renderer. + LayoutPoint numeratorLocation(horizontalOffset(numerator(), element().numeratorAlignment()), verticalOffset); + numerator().setLocation(numeratorLocation); + + LayoutUnit numeratorAscent = ascentForChild(numerator()); + LayoutUnit numeratorDescent = numerator().logicalHeight() - numeratorAscent; + LayoutUnit denominatorAscent = ascentForChild(denominator()); + LayoutUnit denominatorDescent = denominator().logicalHeight() - denominatorAscent; + if (isStack()) { + StackParameters parameters = stackParameters(); + LayoutUnit gap = parameters.topShiftUp - numeratorDescent + parameters.bottomShiftDown - denominatorAscent; + if (gap < parameters.gapMin) { + // If the gap is not large enough, we increase the shifts by the same value. + LayoutUnit delta = (parameters.gapMin - gap) / 2; + parameters.topShiftUp += delta; + parameters.bottomShiftDown += delta; + } + verticalOffset += numeratorAscent + parameters.topShiftUp; // This is the middle of the stack gap. + m_ascent = verticalOffset + mathAxisHeight(); + verticalOffset += parameters.bottomShiftDown - denominatorAscent; + } else { + FractionParameters parameters = fractionParameters(); + verticalOffset += std::max(numerator().logicalHeight() + parameters.numeratorGapMin + m_lineThickness / 2, numeratorAscent + parameters.numeratorMinShiftUp); // This is the middle of the fraction bar. + m_ascent = verticalOffset + mathAxisHeight(); + verticalOffset += std::max(m_lineThickness / 2 + parameters.denominatorGapMin, parameters.denominatorMinShiftDown - denominatorAscent); + } + + LayoutPoint denominatorLocation(horizontalOffset(denominator(), element().denominatorAlignment()), verticalOffset); + denominator().setLocation(denominatorLocation); + + verticalOffset = std::max(verticalOffset + denominator().logicalHeight(), m_ascent + denominatorDescent); // This is the bottom of our renderer. + setLogicalHeight(verticalOffset); + + clearNeedsLayout(); } void RenderMathMLFraction::paint(PaintInfo& info, const LayoutPoint& paintOffset) { RenderMathMLBlock::paint(info, paintOffset); - if (info.context->paintingDisabled() || info.phase != PaintPhaseForeground || style().visibility() != VISIBLE) - return; - - RenderBox* denominatorWrapper = lastChildBox(); - if (!denominatorWrapper || !m_lineThickness) + if (info.context().paintingDisabled() || info.phase != PaintPhaseForeground || style().visibility() != VISIBLE || !isValid() || isStack()) return; - IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset + location() + denominatorWrapper->location() + LayoutPoint(0, m_lineThickness / 2)); - - GraphicsContextStateSaver stateSaver(*info.context); - - info.context->setStrokeThickness(m_lineThickness); - info.context->setStrokeStyle(SolidStroke); - info.context->setStrokeColor(style().visitedDependentColor(CSSPropertyColor), ColorSpaceSRGB); - - info.context->drawLine(adjustedPaintOffset, IntPoint(adjustedPaintOffset.x() + denominatorWrapper->pixelSnappedOffsetWidth(), adjustedPaintOffset.y())); + IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset + location() + LayoutPoint(0, m_ascent - mathAxisHeight())); + + GraphicsContextStateSaver stateSaver(info.context()); + + info.context().setStrokeThickness(m_lineThickness); + info.context().setStrokeStyle(SolidStroke); + info.context().setStrokeColor(style().visitedDependentColor(CSSPropertyColor)); + info.context().drawLine(adjustedPaintOffset, roundedIntPoint(LayoutPoint(adjustedPaintOffset.x() + logicalWidth(), adjustedPaintOffset.y()))); } -int RenderMathMLFraction::firstLineBaseline() const +std::optional<int> RenderMathMLFraction::firstLineBaseline() const { - if (RenderBox* denominatorWrapper = lastChildBox()) - return denominatorWrapper->logicalTop() + static_cast<int>(lroundf((m_lineThickness + style().fontMetrics().xHeight()) / 2)); + if (isValid()) + return std::optional<int>(std::lround(static_cast<float>(m_ascent))); return RenderMathMLBlock::firstLineBaseline(); } diff --git a/Source/WebCore/rendering/mathml/RenderMathMLFraction.h b/Source/WebCore/rendering/mathml/RenderMathMLFraction.h index 9c21d9cb9..0ad9dffaa 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLFraction.h +++ b/Source/WebCore/rendering/mathml/RenderMathMLFraction.h @@ -1,6 +1,7 @@ /* * Copyright (C) 2009 Alex Milowski (alex@milowski.com). All rights reserved. * Copyright (C) 2010 François Sausset (sausset@gmail.com). 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 @@ -24,44 +25,62 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef RenderMathMLFraction_h -#define RenderMathMLFraction_h +#pragma once #if ENABLE(MATHML) -#include "MathMLInlineContainerElement.h" +#include "MathMLFractionElement.h" #include "RenderMathMLBlock.h" namespace WebCore { +class MathMLFractionElement; + class RenderMathMLFraction final : public RenderMathMLBlock { public: - RenderMathMLFraction(MathMLInlineContainerElement&, PassRef<RenderStyle>); + RenderMathMLFraction(MathMLFractionElement&, RenderStyle&&); - MathMLInlineContainerElement& element() { return static_cast<MathMLInlineContainerElement&>(nodeForNonAnonymous()); } - float lineThickness() const { return m_lineThickness; } + float relativeLineThickness() const { return m_defaultLineThickness ? m_lineThickness / m_defaultLineThickness : LayoutUnit(0); } private: - virtual bool isRenderMathMLFraction() const override { return true; } - virtual const char* renderName() const override { return "RenderMathMLFraction"; } + bool isRenderMathMLFraction() const final { return true; } + const char* renderName() const final { return "RenderMathMLFraction"; } + + void computePreferredLogicalWidths() final; + void layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight = 0) final; + std::optional<int> firstLineBaseline() const final; + void paint(PaintInfo&, const LayoutPoint&) final; + RenderMathMLOperator* unembellishedOperator() final; - virtual void addChild(RenderObject* child, RenderObject* beforeChild) override; - virtual void updateFromElement() override; - virtual int firstLineBaseline() const override; - virtual void paint(PaintInfo&, const LayoutPoint&) override; - virtual RenderMathMLOperator* unembellishedOperator() override; - virtual void layout() override; - virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle) override; + MathMLFractionElement& element() const { return static_cast<MathMLFractionElement&>(nodeForNonAnonymous()); } - void fixChildStyle(RenderObject*); - + bool isStack() const { return !m_lineThickness; } + bool isValid() const; + RenderBox& numerator() const; + RenderBox& denominator() const; + LayoutUnit horizontalOffset(RenderBox&, MathMLFractionElement::FractionAlignment); + void updateLineThickness(); + struct FractionParameters { + LayoutUnit numeratorGapMin; + LayoutUnit denominatorGapMin; + LayoutUnit numeratorMinShiftUp; + LayoutUnit denominatorMinShiftDown; + }; + FractionParameters fractionParameters(); + struct StackParameters { + LayoutUnit gapMin; + LayoutUnit topShiftUp; + LayoutUnit bottomShiftDown; + }; + StackParameters stackParameters(); + + LayoutUnit m_ascent; + LayoutUnit m_defaultLineThickness { 1 }; LayoutUnit m_lineThickness; }; -RENDER_OBJECT_TYPE_CASTS(RenderMathMLFraction, isRenderMathMLFraction()); +} // namespace WebCore -} +SPECIALIZE_TYPE_TRAITS_RENDER_OBJECT(RenderMathMLFraction, isRenderMathMLFraction()) #endif // ENABLE(MATHML) - -#endif // RenderMathMLFraction_h diff --git a/Source/WebCore/rendering/mathml/RenderMathMLMath.cpp b/Source/WebCore/rendering/mathml/RenderMathMLMath.cpp index e6e1a88a4..615c50bd0 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLMath.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLMath.cpp @@ -24,19 +24,19 @@ */ #include "config.h" +#include "RenderMathMLMath.h" #if ENABLE(MATHML) -#include "RenderMathMLMath.h" - #include "MathMLNames.h" +#include "MathMLRowElement.h" namespace WebCore { using namespace MathMLNames; -RenderMathMLMath::RenderMathMLMath(Element& element, PassRef<RenderStyle> style) - : RenderMathMLRow(element, std::move(style)) +RenderMathMLMath::RenderMathMLMath(MathMLRowElement& element, RenderStyle&& style) + : RenderMathMLRow(element, WTFMove(style)) { } diff --git a/Source/WebCore/rendering/mathml/RenderMathMLMath.h b/Source/WebCore/rendering/mathml/RenderMathMLMath.h index c51bb0b0b..11cc77693 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLMath.h +++ b/Source/WebCore/rendering/mathml/RenderMathMLMath.h @@ -23,25 +23,27 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef RenderMathMLMath_h -#define RenderMathMLMath_h +#pragma once #if ENABLE(MATHML) #include "RenderMathMLRow.h" namespace WebCore { - + +class MathMLRowElement; + class RenderMathMLMath final : public RenderMathMLRow { public: - RenderMathMLMath(Element&, PassRef<RenderStyle>); + RenderMathMLMath(MathMLRowElement&, RenderStyle&&); private: - virtual bool isRenderMathMLMath() const override { return true; } - virtual const char* renderName() const override { return "RenderMathMLMath"; } + bool isRenderMathMLMath() const final { return true; } + const char* renderName() const final { return "RenderMathMLMath"; } }; - + } +SPECIALIZE_TYPE_TRAITS_RENDER_OBJECT(RenderMathMLMath, isRenderMathMLMath()) + #endif // ENABLE(MATHML) -#endif // RenderMathMLMath_h diff --git a/Source/WebCore/rendering/mathml/RenderMathMLMenclose.cpp b/Source/WebCore/rendering/mathml/RenderMathMLMenclose.cpp index 5cb924f03..7dde7a97b 100755..100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLMenclose.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLMenclose.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Gurpreet Kaur (k.gurpreet@samsung.com). 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 @@ -23,136 +24,320 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#define _USE_MATH_DEFINES 1 #include "config.h" +#include "RenderMathMLMenclose.h" #if ENABLE(MATHML) -#include "RenderMathMLMenclose.h" #include "GraphicsContext.h" -#include "MathMLMencloseElement.h" +#include "MathMLNames.h" #include "PaintInfo.h" -#include "RenderMathMLSquareRoot.h" #include <wtf/MathExtras.h> namespace WebCore { using namespace MathMLNames; -RenderMathMLMenclose::RenderMathMLMenclose(Element& element, PassRef<RenderStyle> style) - : RenderMathMLRow(element, std::move(style)) +// The MathML in HTML5 implementation note suggests drawing the left part of longdiv with a parenthesis. +// For now, we use a Bezier curve and this somewhat arbitrary value. +const unsigned short longDivLeftSpace = 10; + +RenderMathMLMenclose::RenderMathMLMenclose(MathMLMencloseElement& element, RenderStyle&& style) + : RenderMathMLRow(element, WTFMove(style)) { } -void RenderMathMLMenclose::addChild(RenderObject* newChild, RenderObject* beforeChild) +// This arbitrary thickness value is used for the parameter \xi_8 from the MathML in HTML5 implementation note. +// For now, we take: +// - OverbarVerticalGap = UnderbarVerticalGap = 3\xi_8 +// - OverbarRuleThickness = UnderbarRuleThickness = \xi_8 +// - OverbarExtraAscender = UnderbarExtraAscender = \xi_8 +// FIXME: OverBar and UnderBar parameters should be read from the MATH tables. +// See https://bugs.webkit.org/show_bug.cgi?id=122297 +LayoutUnit RenderMathMLMenclose::ruleThickness() const { - MathMLMencloseElement* menclose = toMathMLMencloseElement(element()); - // Allow an anonymous RenderMathMLSquareRoot to handle drawing the radical - // notation, rather than duplicating the code needed to paint a root. - if (!firstChild() && menclose->isRadical()) - RenderMathMLBlock::addChild(RenderMathMLSquareRoot::createAnonymousWithParentRenderer(*this).leakPtr()); - - if (newChild) { - if (firstChild() && menclose->isRadical()) - toRenderElement(firstChild())->addChild(newChild, beforeChild && beforeChild->parent() == firstChild() ? beforeChild : nullptr); - else - RenderMathMLBlock::addChild(newChild, beforeChild); + return 0.05f * style().fontCascade().size(); +} + +RenderMathMLMenclose::SpaceAroundContent RenderMathMLMenclose::spaceAroundContent(LayoutUnit contentWidth, LayoutUnit contentHeight) const +{ + SpaceAroundContent space; + space.right = 0; + space.top = 0; + space.bottom = 0; + space.left = 0; + + LayoutUnit thickness = ruleThickness(); + // In the MathML in HTML5 implementation note, the "left" notation is described as follows: + // - left side is 3\xi_8 padding + \xi_8 border + \xi_8 margin = 5\xi_8 + // - top space is Overbar Vertical Gap + Overbar Rule Thickness = 3\xi_8 + \xi_8 = 4\xi_8 + // - bottom space is Underbar Vertical Gap + Underbar Rule Thickness = 3\xi_8 + \xi_8 = 4\xi_8 + // The "right" notation is symmetric. + if (hasNotation(MathMLMencloseElement::Left)) + space.left = std::max(space.left, 5 * thickness); + if (hasNotation(MathMLMencloseElement::Right)) + space.right = std::max(space.right, 5 * thickness); + if (hasNotation(MathMLMencloseElement::Left) || hasNotation(MathMLMencloseElement::Right)) { + LayoutUnit extraSpace = 4 * thickness; + space.top = std::max(space.top, extraSpace); + space.bottom = std::max(space.bottom, extraSpace); + } + + // In the MathML in HTML5 implementation note, the "top" notation is described as follows: + // - left and right space are 4\xi_8 + // - top side is Vertical Gap + Rule Thickness + Extra Ascender = 3\xi_8 + \xi_8 + \xi_8 = 5\xi_8 + // The "bottom" notation is symmetric. + if (hasNotation(MathMLMencloseElement::Top)) + space.top = std::max(space.top, 5 * thickness); + if (hasNotation(MathMLMencloseElement::Bottom)) + space.bottom = std::max(space.bottom, 5 * thickness); + if (hasNotation(MathMLMencloseElement::Top) || hasNotation(MathMLMencloseElement::Bottom)) { + LayoutUnit extraSpace = 4 * thickness; + space.left = std::max(space.left, extraSpace); + space.right = std::max(space.right, extraSpace); + } + + // For longdiv, we use our own rules for now: + // - top space is like "top" notation + // - bottom space is like "bottom" notation + // - right space is like "right" notation + // - left space is longDivLeftSpace * \xi_8 + if (hasNotation(MathMLMencloseElement::LongDiv)) { + space.top = std::max(space.top, 5 * thickness); + space.bottom = std::max(space.bottom, 5 * thickness); + space.left = std::max(space.left, longDivLeftSpace * thickness); + space.right = std::max(space.right, 4 * thickness); + } + + // In the MathML in HTML5 implementation note, the "rounded" notation is described as follows: + // - top/bottom/left/right side have 3\xi_8 padding + \xi_8 border + \xi_8 margin = 5\xi_8 + if (hasNotation(MathMLMencloseElement::RoundedBox)) { + LayoutUnit extraSpace = 5 * thickness; + space.left = std::max(space.left, extraSpace); + space.right = std::max(space.right, extraSpace); + space.top = std::max(space.top, extraSpace); + space.bottom = std::max(space.bottom, extraSpace); } + + // In the MathML in HTML5 implementation note, the "rounded" notation is described as follows: + // - top/bottom/left/right spaces are \xi_8/2 + if (hasNotation(MathMLMencloseElement::UpDiagonalStrike) || hasNotation(MathMLMencloseElement::DownDiagonalStrike)) { + LayoutUnit extraSpace = thickness / 2; + space.left = std::max(space.left, extraSpace); + space.right = std::max(space.right, extraSpace); + space.top = std::max(space.top, extraSpace); + space.bottom = std::max(space.bottom, extraSpace); + } + + // In the MathML in HTML5 implementation note, the "circle" notation is described as follows: + // - We draw the ellipse of axes the axes of symmetry of this ink box + // - The radii of the ellipse are \sqrt{2}contentWidth/2 and \sqrt{2}contentHeight/2 + // - The thickness of the ellipse is \xi_8 + // - We add extra margin of \xi_8 + // Then for example the top space is \sqrt{2}contentHeight/2 - contentHeight/2 + \xi_8/2 + \xi_8. + if (hasNotation(MathMLMencloseElement::Circle)) { + LayoutUnit extraSpace = (contentWidth * (sqrtOfTwoFloat - 1) + 3 * thickness) / 2; + space.left = std::max(space.left, extraSpace); + space.right = std::max(space.right, extraSpace); + extraSpace = (contentHeight * (sqrtOfTwoFloat - 1) + 3 * thickness) / 2; + space.top = std::max(space.top, extraSpace); + space.bottom = std::max(space.bottom, extraSpace); + } + + // In the MathML in HTML5 implementation note, the "vertical" and "horizontal" notations do not add space around the content. + + return space; } void RenderMathMLMenclose::computePreferredLogicalWidths() { ASSERT(preferredLogicalWidthsDirty()); - RenderMathMLBlock::computePreferredLogicalWidths(); + RenderMathMLRow::computePreferredLogicalWidths(); - MathMLMencloseElement* menclose = toMathMLMencloseElement(element()); - const Vector<String>& notationValues = menclose->notationValues(); - size_t notationalValueSize = notationValues.size(); - for (size_t i = 0; i < notationalValueSize; i++) { - if (notationValues[i] == "circle") { - m_minPreferredLogicalWidth = minPreferredLogicalWidth() * float(sqrtOfTwoDouble); - m_maxPreferredLogicalWidth = maxPreferredLogicalWidth() * float(sqrtOfTwoDouble); - } - } + LayoutUnit preferredWidth = m_maxPreferredLogicalWidth; + SpaceAroundContent space = spaceAroundContent(preferredWidth, 0); + m_maxPreferredLogicalWidth = space.left + preferredWidth + space.right; + m_maxPreferredLogicalWidth = m_minPreferredLogicalWidth; setPreferredLogicalWidthsDirty(false); } -void RenderMathMLMenclose::updateLogicalHeight() +void RenderMathMLMenclose::layoutBlock(bool relayoutChildren, LayoutUnit) +{ + ASSERT(needsLayout()); + + if (!relayoutChildren && simplifiedLayout()) + return; + + LayoutUnit contentAscent = 0; + LayoutUnit contentDescent = 0; + RenderMathMLRow::computeLineVerticalStretch(contentAscent, contentDescent); + RenderMathMLRow::layoutRowItems(contentAscent, contentDescent); + LayoutUnit contentWidth = logicalWidth(); + + SpaceAroundContent space = spaceAroundContent(contentWidth, contentAscent + contentDescent); + setLogicalWidth(space.left + contentWidth + space.right); + setLogicalHeight(space.top + contentAscent + contentDescent + space.bottom); + + LayoutPoint contentLocation(space.left, space.top); + for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) + child->setLocation(child->location() + contentLocation); + + m_contentRect = LayoutRect(space.left, space.top, contentWidth, contentAscent + contentDescent); + + clearNeedsLayout(); +} + +// GraphicsContext::drawLine does not seem appropriate to draw menclose lines. +// To avoid unexpected behaviors and inconsistency with other notations, we just use strokePath. +static void drawLine(PaintInfo& info, const LayoutUnit& xStart, const LayoutUnit& yStart, const LayoutUnit& xEnd, const LayoutUnit& yEnd) { - MathMLMencloseElement* menclose = toMathMLMencloseElement(element()); - const Vector<String>& notationValues = menclose->notationValues(); - size_t notationalValueSize = notationValues.size(); - for (size_t i = 0; i < notationalValueSize; i++) - if (notationValues[i] == "circle") - setLogicalHeight(logicalHeight() * float(sqrtOfTwoDouble)); + Path line; + line.moveTo(LayoutPoint(xStart, yStart)); + line.addLineTo(LayoutPoint(xEnd, yEnd)); + info.context().strokePath(line); } void RenderMathMLMenclose::paint(PaintInfo& info, const LayoutPoint& paintOffset) { - RenderMathMLBlock::paint(info, paintOffset); + RenderMathMLRow::paint(info, paintOffset); - if (info.context->paintingDisabled() || info.phase != PaintPhaseForeground || style().visibility() != VISIBLE) + if (info.context().paintingDisabled() || info.phase != PaintPhaseForeground || style().visibility() != VISIBLE) return; - - MathMLMencloseElement* menclose = toMathMLMencloseElement(element()); - const Vector<String>& notationValues = menclose->notationValues(); - size_t notationalValueSize = notationValues.size(); - bool isDefaultLongDiv = !notationalValueSize; - if (notationalValueSize && checkNotationalValuesValidity(notationValues)) { - IntRect rect = absoluteBoundingBoxRect(); - int left = rect.x(); - int top = rect.y(); - int boxWidth = rect.width(); - int boxHeight = rect.height(); - int halfboxWidth = rect.width() / 2; - int halfboxHeight = rect.height() / 2; - - GraphicsContextStateSaver stateSaver(*info.context); - info.context->setStrokeThickness(1); - info.context->setStrokeStyle(SolidStroke); - info.context->setStrokeColor(style().visitedDependentColor(CSSPropertyColor), ColorSpaceDeviceRGB); - // TODO add support for notation value updiagonalarrow https://bugs.webkit.org/show_bug.cgi?id=127466 - for (size_t i = 0; i < notationalValueSize; i++) { - if (notationValues[i] == "updiagonalstrike") - info.context->drawLine(IntPoint(left, top + boxHeight), IntPoint(left + boxWidth, top)); - else if (notationValues[i] == "downdiagonalstrike") - info.context->drawLine(IntPoint(left, top), IntPoint(left + boxWidth, top + boxHeight)); - else if (notationValues[i] == "verticalstrike") - info.context->drawLine(IntPoint(left + halfboxWidth, top), IntPoint(left + halfboxWidth, top + boxHeight)); - else if (notationValues[i] == "horizontalstrike") - info.context->drawLine(IntPoint(left, top + halfboxHeight), IntPoint(left + boxWidth, top + halfboxHeight)); - else if (notationValues[i] == "circle") { - info.context->setFillColor(Color::transparent, ColorSpaceDeviceRGB); - info.context->drawEllipse(rect); - } else if (notationValues[i] == "longdiv") - isDefaultLongDiv = true; - } - if (isDefaultLongDiv) { - Path root; - int midxPoint = 0; - root.moveTo(FloatPoint(left, top)); - int childLeft = firstChild() ? firstChild()->absoluteBoundingBoxRect().x() : 0; - if (childLeft) - midxPoint= childLeft - left; - else - midxPoint = style().paddingLeft().value(); - root.addBezierCurveTo(FloatPoint(left, top), FloatPoint(left + midxPoint, top + halfboxHeight), FloatPoint(left, top + boxHeight)); - info.context->strokePath(root); - } + + LayoutUnit thickness = ruleThickness(); + + // Make a copy of the PaintInfo because applyTransform will modify its rect. + PaintInfo paintInfo(info); + GraphicsContextStateSaver stateSaver(paintInfo.context()); + + paintInfo.context().setStrokeThickness(thickness); + paintInfo.context().setStrokeStyle(SolidStroke); + paintInfo.context().setStrokeColor(style().visitedDependentColor(CSSPropertyColor)); + paintInfo.context().setFillColor(Color::transparent); + paintInfo.applyTransform(AffineTransform().translate(paintOffset + location())); + + // In the MathML in HTML5 implementation note, the "left" notation is described as follows: + // - center of the left vertical bar is at 3\xi_8 padding + \xi_8 border/2 = 7\xi_8/2 + // - top space is Overbar Vertical Gap + Overbar Rule Thickness = 3\xi_8 + \xi_8 = 4\xi_8 + // - bottom space is Underbar Vertical Gap + Underbar Rule Thickness = 3\xi_8 + \xi_8 = 4\xi_8 + if (hasNotation(MathMLMencloseElement::Left)) { + LayoutUnit x = m_contentRect.x() - 7 * thickness / 2; + LayoutUnit yStart = m_contentRect.y() - 4 * thickness; + LayoutUnit yEnd = m_contentRect.maxY() + 4 * thickness; + drawLine(info, x, yStart, x, yEnd); } -} -bool RenderMathMLMenclose::checkNotationalValuesValidity(const Vector<String>& attr) const -{ - size_t attrSize = attr.size(); - for (size_t i = 0; i < attrSize; i++) { - if (attr[i] == "updiagonalstrike" || attr[i] == "downdiagonalstrike" || attr[i] == "horizontalstrike" || attr[i] == "verticalstrike" - || attr[i] == "circle" || attr[i] == "longdiv") - return true; + // In the MathML in HTML5 implementation note, the "right" notation is described as follows: + // - center of the right vertical bar is at 3\xi_8 padding + \xi_8 border/2 = 7\xi_8/2 + // - top space is Overbar Vertical Gap + Overbar Rule Thickness = 3\xi_8 + \xi_8 = 4\xi_8 + // - bottom space is Underbar Vertical Gap + Underbar Rule Thickness = 3\xi_8 + \xi_8 = 4\xi_8 + if (hasNotation(MathMLMencloseElement::Right)) { + LayoutUnit x = m_contentRect.maxX() + 7 * thickness / 2; + LayoutUnit yStart = m_contentRect.y() - 4 * thickness; + LayoutUnit yEnd = m_contentRect.maxY() + 4 * thickness; + drawLine(info, x, yStart, x, yEnd); + } + + // In the MathML in HTML5 implementation note, the "vertical" notation is horizontally centered. + if (hasNotation(MathMLMencloseElement::VerticalStrike)) { + LayoutUnit x = m_contentRect.x() + (m_contentRect.width() - thickness) / 2; + LayoutUnit yStart = m_contentRect.y(); + LayoutUnit yEnd = m_contentRect.maxY(); + drawLine(info, x, yStart, x, yEnd); + } + + // In the MathML in HTML5 implementation note, the "top" notation is described as follows: + // - middle of the top horizontal bar is at Vertical Gap + Rule Thickness / 2 = 7\xi_8/2 + // - left and right spaces have size 4\xi_8 + if (hasNotation(MathMLMencloseElement::Top)) { + LayoutUnit y = m_contentRect.y() - 7 * thickness / 2; + LayoutUnit xStart = m_contentRect.x() - 4 * thickness; + LayoutUnit xEnd = m_contentRect.maxX() + 4 * thickness; + drawLine(info, xStart, y, xEnd, y); + } + + // In the MathML in HTML5 implementation note, the "bottom" notation is described as follows: + // - middle of the bottom horizontal bar is at Vertical Gap + Rule Thickness / 2 = 7\xi_8/2 + // - left and right spaces have size 4\xi_8 + if (hasNotation(MathMLMencloseElement::Bottom)) { + LayoutUnit y = m_contentRect.maxY() + 7 * thickness / 2; + LayoutUnit xStart = m_contentRect.x() - 4 * thickness; + LayoutUnit xEnd = m_contentRect.maxX() + 4 * thickness; + drawLine(info, xStart, y, xEnd, y); + } + + // In the MathML in HTML5 implementation note, the "vertical" notation is vertically centered. + if (hasNotation(MathMLMencloseElement::HorizontalStrike)) { + LayoutUnit y = m_contentRect.y() + (m_contentRect.height() - thickness) / 2; + LayoutUnit xStart = m_contentRect.x(); + LayoutUnit xEnd = m_contentRect.maxX(); + drawLine(info, xStart, y, xEnd, y); + } + + // In the MathML in HTML5 implementation note, the "updiagonalstrike" goes from the bottom left corner + // to the top right corner. + if (hasNotation(MathMLMencloseElement::UpDiagonalStrike)) + drawLine(info, m_contentRect.x(), m_contentRect.maxY(), m_contentRect.maxX(), m_contentRect.y()); + + // In the MathML in HTML5 implementation note, the "downdiagonalstrike" goes from the top left corner + // to the bottom right corner. + if (hasNotation(MathMLMencloseElement::DownDiagonalStrike)) + drawLine(info, m_contentRect.x(), m_contentRect.y(), m_contentRect.maxX(), m_contentRect.maxY()); + + // In the MathML in HTML5 implementation note, the "roundedbox" has radii size 3\xi_8 and is obtained + // by inflating the content box by 3\xi_8 + \xi_8/2 = 7\xi_8/2 + if (hasNotation(MathMLMencloseElement::RoundedBox)) { + LayoutSize radiiSize(3 * thickness, 3 * thickness); + RoundedRect::Radii radii(radiiSize, radiiSize, radiiSize, radiiSize); + RoundedRect roundedRect(m_contentRect, radii); + roundedRect.inflate(7 * thickness / 2); + Path path; + path.addRoundedRect(roundedRect); + paintInfo.context().strokePath(path); + } + + // For longdiv, we use our own rules for now: + // - top space is like "top" notation + // - bottom space is like "bottom" notation + // - right space is like "right" notation + // - left space is longDivLeftSpace * \xi_8 + // - We subtract half of the thickness from these spaces to obtain "top", "bottom", "left" + // and "right" coordinates. + // - The top bar is drawn from "right" to "left" and positioned at vertical offset "top". + // - The left part is draw as a quadratic Bezier curve with end points going from "top" to + // "bottom" and positioned at horizontal offset "left". + // - In order to force the curvature of the left part, we use a middle point that is vertically + // centered and shifted towards the right by longDivLeftSpace * \xi_8 + if (hasNotation(MathMLMencloseElement::LongDiv)) { + LayoutUnit top = m_contentRect.y() - 7 * thickness / 2; + LayoutUnit bottom = m_contentRect.maxY() + 7 * thickness / 2; + LayoutUnit left = m_contentRect.x() - longDivLeftSpace * thickness + thickness / 2; + LayoutUnit right = m_contentRect.maxX() + 4 * thickness; + LayoutUnit midX = left + longDivLeftSpace * thickness; + LayoutUnit midY = (top + bottom) / 2; + Path path; + path.moveTo(LayoutPoint(right, top)); + path.addLineTo(LayoutPoint(left, top)); + path.addQuadCurveTo(LayoutPoint(midX, midY), FloatPoint(left, bottom)); + paintInfo.context().strokePath(path); + } + + // In the MathML in HTML5 implementation note, the "circle" notation is described as follows: + // - The center and axes are the same as the content bounding box. + // - The width of the bounding box is \xi_8/2 + contentWidth * \sqrt{2} + \xi_8/2 + // - The height is \xi_8/2 + contentHeight * \sqrt{2} + \xi_8/2 + if (hasNotation(MathMLMencloseElement::Circle)) { + LayoutRect ellipseRect; + ellipseRect.setWidth(m_contentRect.width() * sqrtOfTwoFloat + thickness); + ellipseRect.setHeight(m_contentRect.height() * sqrtOfTwoFloat + thickness); + ellipseRect.setX(m_contentRect.x() - (ellipseRect.width() - m_contentRect.width()) / 2); + ellipseRect.setY(m_contentRect.y() - (ellipseRect.height() - m_contentRect.height()) / 2); + Path path; + path.addEllipse(ellipseRect); + paintInfo.context().strokePath(path); } - return false; } } diff --git a/Source/WebCore/rendering/mathml/RenderMathMLMenclose.h b/Source/WebCore/rendering/mathml/RenderMathMLMenclose.h index 43b463481..e6168a634 100755..100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLMenclose.h +++ b/Source/WebCore/rendering/mathml/RenderMathMLMenclose.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Gurpreet Kaur (k.gurpreet@samsung.com). 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 @@ -23,28 +24,41 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef RenderMathMLMenclose_h -#define RenderMathMLMenclose_h +#pragma once #if ENABLE(MATHML) + +#include "MathMLMencloseElement.h" #include "RenderMathMLRow.h" namespace WebCore { - + class RenderMathMLMenclose final: public RenderMathMLRow { public: - RenderMathMLMenclose(Element&, PassRef<RenderStyle>); + RenderMathMLMenclose(MathMLMencloseElement&, RenderStyle&&); private: - virtual const char* renderName() const { return "RenderMathMLMenclose"; } - virtual void paint(PaintInfo&, const LayoutPoint&) override; - virtual void updateLogicalHeight() override; - virtual void addChild(RenderObject* newChild, RenderObject* beforeChild = nullptr) override; - virtual void computePreferredLogicalWidths() override; - bool checkNotationalValuesValidity(const Vector<String>&) const; + const char* renderName() const final { return "RenderMathMLMenclose"; } + void computePreferredLogicalWidths() final; + void layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight = 0) final; + void paint(PaintInfo&, const LayoutPoint&) final; + + LayoutUnit ruleThickness() const; + bool hasNotation(MathMLMencloseElement::MencloseNotationFlag notationFlag) const { return downcast<MathMLMencloseElement>(element()).hasNotation(notationFlag); } + + struct SpaceAroundContent { + LayoutUnit left; + LayoutUnit right; + LayoutUnit top; + LayoutUnit bottom; + }; + SpaceAroundContent spaceAroundContent(LayoutUnit contentWidth, LayoutUnit contentHeight) const; + + LayoutRect m_contentRect; }; - + } +SPECIALIZE_TYPE_TRAITS_RENDER_OBJECT(RenderMathMLMenclose, isRenderMathMLMenclose()) + #endif // ENABLE(MATHML) -#endif // RenderMathMLMenclose_h diff --git a/Source/WebCore/rendering/mathml/RenderMathMLOperator.cpp b/Source/WebCore/rendering/mathml/RenderMathMLOperator.cpp index 4fe93a58d..9f97926f2 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLOperator.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLOperator.cpp @@ -1,7 +1,7 @@ /* * Copyright (C) 2010 Alex Milowski (alex@milowski.com). All rights reserved. * Copyright (C) 2010 François Sausset (sausset@gmail.com). All rights reserved. - * Copyright (C) 2013 Igalia S.L. + * Copyright (C) 2013, 2016 Igalia S.L. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -26,379 +26,298 @@ */ #include "config.h" +#include "RenderMathMLOperator.h" #if ENABLE(MATHML) -#include "RenderMathMLOperator.h" - -#include "FontCache.h" #include "FontSelector.h" #include "MathMLNames.h" +#include "MathMLOperatorElement.h" #include "PaintInfo.h" #include "RenderBlockFlow.h" #include "RenderText.h" #include "ScaleTransformOperation.h" #include "TransformOperations.h" +#include <cmath> #include <wtf/MathExtras.h> +#include <wtf/unicode/CharacterNames.h> namespace WebCore { - + using namespace MathMLNames; -// FIXME: The OpenType MATH table contains information that should override this table (http://wkbug/122297). -static RenderMathMLOperator::StretchyCharacter stretchyCharacters[14] = { - { 0x28 , 0x239b, 0x239c, 0x239d, 0x0 }, // left parenthesis - { 0x29 , 0x239e, 0x239f, 0x23a0, 0x0 }, // right parenthesis - { 0x5b , 0x23a1, 0x23a2, 0x23a3, 0x0 }, // left square bracket - { 0x2308, 0x23a1, 0x23a2, 0x23a2, 0x0 }, // left ceiling - { 0x230a, 0x23a2, 0x23a2, 0x23a3, 0x0 }, // left floor - { 0x5d , 0x23a4, 0x23a5, 0x23a6, 0x0 }, // right square bracket - { 0x2309, 0x23a4, 0x23a5, 0x23a5, 0x0 }, // right ceiling - { 0x230b, 0x23a5, 0x23a5, 0x23a6, 0x0 }, // right floor - { 0x7b , 0x23a7, 0x23aa, 0x23a9, 0x23a8 }, // left curly bracket - { 0x7c , 0x7c, 0x7c, 0x7c, 0x0 }, // vertical bar - { 0x2016, 0x2016, 0x2016, 0x2016, 0x0 }, // double vertical line - { 0x2225, 0x2225, 0x2225, 0x2225, 0x0 }, // parallel to - { 0x7d , 0x23ab, 0x23aa, 0x23ad, 0x23ac }, // right curly bracket - { 0x222b, 0x2320, 0x23ae, 0x2321, 0x0 } // integral sign -}; - -RenderMathMLOperator::RenderMathMLOperator(MathMLElement& element, PassRef<RenderStyle> style) - : RenderMathMLBlock(element, std::move(style)) - , m_stretchHeight(0) - , m_operator(0) - , m_operatorType(Default) - , m_stretchyCharacter(nullptr) +RenderMathMLOperator::RenderMathMLOperator(MathMLOperatorElement& element, RenderStyle&& style) + : RenderMathMLToken(element, WTFMove(style)) { + updateTokenContent(); } -RenderMathMLOperator::RenderMathMLOperator(MathMLElement& element, PassRef<RenderStyle> style, UChar operatorChar) - : RenderMathMLBlock(element, std::move(style)) - , m_stretchHeight(0) - , m_operator(convertHyphenMinusToMinusSign(operatorChar)) - , m_operatorType(Default) - , m_stretchyCharacter(nullptr) +RenderMathMLOperator::RenderMathMLOperator(Document& document, RenderStyle&& style) + : RenderMathMLToken(document, WTFMove(style)) { } -bool RenderMathMLOperator::isChildAllowed(const RenderObject&, const RenderStyle&) const +MathMLOperatorElement& RenderMathMLOperator::element() const { - return false; + return static_cast<MathMLOperatorElement&>(nodeForNonAnonymous()); } -static const float gOperatorExpansion = 1.2f; +UChar32 RenderMathMLOperator::textContent() const +{ + return element().operatorChar().character; +} -float RenderMathMLOperator::expandedStretchHeight() const +bool RenderMathMLOperator::isInvisibleOperator() const { - return m_stretchHeight * gOperatorExpansion; + // The following operators are invisible: U+2061 FUNCTION APPLICATION, U+2062 INVISIBLE TIMES, U+2063 INVISIBLE SEPARATOR, U+2064 INVISIBLE PLUS. + UChar32 character = textContent(); + return 0x2061 <= character && character <= 0x2064; } -void RenderMathMLOperator::stretchToHeight(int height) +bool RenderMathMLOperator::hasOperatorFlag(MathMLOperatorDictionary::Flag flag) const { - if (m_stretchHeight == height) - return; + return element().hasProperty(flag); +} - m_stretchHeight = height; - updateStyle(); +LayoutUnit RenderMathMLOperator::leadingSpace() const +{ + // FIXME: Negative leading spaces must be implemented (https://webkit.org/b/124830). + LayoutUnit leadingSpace = toUserUnits(element().defaultLeadingSpace(), style(), 0); + leadingSpace = toUserUnits(element().leadingSpace(), style(), leadingSpace); + return std::max<LayoutUnit>(0, leadingSpace); } -void RenderMathMLOperator::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +LayoutUnit RenderMathMLOperator::trailingSpace() const { - RenderMathMLBlock::styleDidChange(diff, oldStyle); - updateFromElement(); + // FIXME: Negative trailing spaces must be implemented (https://webkit.org/b/124830). + LayoutUnit trailingSpace = toUserUnits(element().defaultTrailingSpace(), style(), 0); + trailingSpace = toUserUnits(element().trailingSpace(), style(), trailingSpace); + return std::max<LayoutUnit>(0, trailingSpace); } -FloatRect RenderMathMLOperator::glyphBoundsForCharacter(UChar character) +LayoutUnit RenderMathMLOperator::minSize() const { - GlyphData data = style().font().glyphDataForCharacter(character, false); - return data.fontData->boundsForGlyph(data.glyph); + LayoutUnit minSize = style().fontCascade().size(); // Default minsize is "1em". + minSize = toUserUnits(element().minSize(), style(), minSize); + return std::max<LayoutUnit>(0, minSize); } -float RenderMathMLOperator::glyphHeightForCharacter(UChar character) +LayoutUnit RenderMathMLOperator::maxSize() const { - return glyphBoundsForCharacter(character).height(); + LayoutUnit maxSize = intMaxForLayoutUnit; // Default maxsize is "infinity". + maxSize = toUserUnits(element().maxSize(), style(), maxSize); + return std::max<LayoutUnit>(0, maxSize); } -float RenderMathMLOperator::advanceForCharacter(UChar character) +bool RenderMathMLOperator::isVertical() const { - // Hyphen minus is always replaced by the minus sign in rendered text. - GlyphData data = style().font().glyphDataForCharacter(convertHyphenMinusToMinusSign(character), false); - return data.fontData->widthForGlyph(data.glyph); + return element().operatorChar().isVertical; } -void RenderMathMLOperator::computePreferredLogicalWidths() + +void RenderMathMLOperator::stretchTo(LayoutUnit heightAboveBaseline, LayoutUnit depthBelowBaseline) { - ASSERT(preferredLogicalWidthsDirty()); + ASSERT(isStretchy()); + ASSERT(isVertical()); - UChar stretchedCharacter; - bool allowStretching = shouldAllowStretching(stretchedCharacter); - if (!allowStretching) { - RenderMathMLBlock::computePreferredLogicalWidths(); + if (!isVertical() || (heightAboveBaseline == m_stretchHeightAboveBaseline && depthBelowBaseline == m_stretchDepthBelowBaseline)) return; - } - float maximumGlyphWidth = advanceForCharacter(stretchedCharacter); - for (unsigned index = 0; index < WTF_ARRAY_LENGTH(stretchyCharacters); ++index) { - if (stretchyCharacters[index].character != stretchedCharacter) - continue; - - StretchyCharacter& character = stretchyCharacters[index]; - if (character.topGlyph) - maximumGlyphWidth = std::max(maximumGlyphWidth, advanceForCharacter(character.topGlyph)); - if (character.extensionGlyph) - maximumGlyphWidth = std::max(maximumGlyphWidth, advanceForCharacter(character.extensionGlyph)); - if (character.bottomGlyph) - maximumGlyphWidth = std::max(maximumGlyphWidth, advanceForCharacter(character.bottomGlyph)); - if (character.middleGlyph) - maximumGlyphWidth = std::max(maximumGlyphWidth, advanceForCharacter(character.middleGlyph)); - m_maxPreferredLogicalWidth = m_minPreferredLogicalWidth = maximumGlyphWidth; - return; + m_stretchHeightAboveBaseline = heightAboveBaseline; + m_stretchDepthBelowBaseline = depthBelowBaseline; + + if (hasOperatorFlag(MathMLOperatorDictionary::Symmetric)) { + // We make the operator stretch symmetrically above and below the axis. + LayoutUnit axis = mathAxisHeight(); + LayoutUnit halfStretchSize = std::max(m_stretchHeightAboveBaseline - axis, m_stretchDepthBelowBaseline + axis); + m_stretchHeightAboveBaseline = halfStretchSize + axis; + m_stretchDepthBelowBaseline = halfStretchSize - axis; + } + // We try to honor the minsize/maxsize condition by increasing or decreasing both height and depth proportionately. + // The MathML specification does not indicate what to do when maxsize < minsize, so we follow Gecko and make minsize take precedence. + LayoutUnit size = stretchSize(); + float aspect = 1.0; + if (size > 0) { + LayoutUnit minSizeValue = minSize(); + if (size < minSizeValue) + aspect = minSizeValue.toFloat() / size; + else { + LayoutUnit maxSizeValue = maxSize(); + if (maxSizeValue < size) + aspect = maxSizeValue.toFloat() / size; + } } + m_stretchHeightAboveBaseline *= aspect; + m_stretchDepthBelowBaseline *= aspect; + + m_mathOperator.stretchTo(style(), m_stretchHeightAboveBaseline + m_stretchDepthBelowBaseline); - m_maxPreferredLogicalWidth = m_minPreferredLogicalWidth = maximumGlyphWidth; + setLogicalHeight(m_mathOperator.ascent() + m_mathOperator.descent()); } -// FIXME: It's cleaner to only call updateFromElement when an attribute has changed. The body of -// this method should probably be moved to a private stretchHeightChanged or checkStretchHeight -// method. Probably at the same time, addChild/removeChild methods should be made to work for -// dynamic DOM changes. -void RenderMathMLOperator::updateFromElement() +void RenderMathMLOperator::stretchTo(LayoutUnit width) { - RenderElement* savedRenderer = element().renderer(); - - // Destroy our current children - destroyLeftoverChildren(); - - // Since we share a node with our children, destroying our children may set our node's - // renderer to 0, so we need to restore it. - element().setRenderer(savedRenderer); + ASSERT(isStretchy()); + ASSERT(!isVertical()); - RenderPtr<RenderMathMLBlock> container = createRenderer<RenderMathMLBlock>(element(), RenderStyle::createAnonymousStyleWithDisplay(&style(), FLEX)); - // This container doesn't offer any useful information to accessibility. - container->setIgnoreInAccessibilityTree(true); - container->initializeStyle(); + if (isVertical() || m_stretchWidth == width) + return; - RenderPtr<RenderText> text; - if (m_operator) - text = createRenderer<RenderText>(document(), String(&m_operator, 1)); - else - text = createRenderer<RenderText>(document(), element().textContent().replace(hyphenMinus, minusSign).impl()); + m_stretchWidth = width; + m_mathOperator.stretchTo(style(), width); - container->addChild(text.leakPtr()); - addChild(container.leakPtr()); + setLogicalHeight(m_mathOperator.ascent() + m_mathOperator.descent()); +} - updateStyle(); - setNeedsLayoutAndPrefWidthsRecalc(); +void RenderMathMLOperator::resetStretchSize() +{ + if (isVertical()) { + m_stretchHeightAboveBaseline = 0; + m_stretchDepthBelowBaseline = 0; + } else + m_stretchWidth = 0; } -bool RenderMathMLOperator::shouldAllowStretching(UChar& stretchedCharacter) +void RenderMathMLOperator::computePreferredLogicalWidths() { - if (equalIgnoringCase(element().getAttribute(MathMLNames::stretchyAttr), "false")) - return false; + ASSERT(preferredLogicalWidthsDirty()); - if (m_operator) { - stretchedCharacter = m_operator; - return true; - } + LayoutUnit preferredWidth = 0; + + if (!useMathOperator()) { + RenderMathMLToken::computePreferredLogicalWidths(); + preferredWidth = m_maxPreferredLogicalWidth; + if (isInvisibleOperator()) { + // In some fonts, glyphs for invisible operators have nonzero width. Consequently, we subtract that width here to avoid wide gaps. + GlyphData data = style().fontCascade().glyphDataForCharacter(textContent(), false); + float glyphWidth = data.font ? data.font->widthForGlyph(data.glyph) : 0; + ASSERT(glyphWidth <= preferredWidth); + preferredWidth -= glyphWidth; + } + } else + preferredWidth = m_mathOperator.maxPreferredWidth(); - // FIXME: This does not handle surrogate pairs (http://wkbug.com/122296/). - String opText = element().textContent(); - stretchedCharacter = 0; - for (unsigned i = 0; i < opText.length(); ++i) { - // If there's more than one non-whitespace character in this node, then don't even try to stretch it. - if (stretchedCharacter && !isSpaceOrNewline(opText[i])) - return false; + // FIXME: The spacing should be added to the whole embellished operator (https://webkit.org/b/124831). + // FIXME: The spacing should only be added inside (perhaps inferred) mrow (http://www.w3.org/TR/MathML/chapter3.html#presm.opspacing). + preferredWidth = leadingSpace() + preferredWidth + trailingSpace(); - if (!isSpaceOrNewline(opText[i])) - stretchedCharacter = opText[i]; - } + m_maxPreferredLogicalWidth = m_minPreferredLogicalWidth = preferredWidth; - return stretchedCharacter; + setPreferredLogicalWidthsDirty(false); } -// FIXME: We should also look at alternate characters defined in the OpenType MATH table (http://wkbug/122297). -RenderMathMLOperator::StretchyCharacter* RenderMathMLOperator::findAcceptableStretchyCharacter(UChar character) +void RenderMathMLOperator::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight) { - StretchyCharacter* stretchyCharacter = 0; - const int maxIndex = WTF_ARRAY_LENGTH(stretchyCharacters); - for (int index = 0; index < maxIndex; ++index) { - if (stretchyCharacters[index].character == character) { - stretchyCharacter = &stretchyCharacters[index]; - break; - } - } + ASSERT(needsLayout()); - // If we didn't find a stretchy character set for this character, we don't know how to stretch it. - if (!stretchyCharacter) - return 0; - - float height = glyphHeightForCharacter(stretchyCharacter->topGlyph) + glyphHeightForCharacter(stretchyCharacter->bottomGlyph); - if (stretchyCharacter->middleGlyph) - height += glyphHeightForCharacter(stretchyCharacter->middleGlyph); + if (!relayoutChildren && simplifiedLayout()) + return; - if (height > expandedStretchHeight()) - return 0; + LayoutUnit leadingSpaceValue = leadingSpace(); + LayoutUnit trailingSpaceValue = trailingSpace(); + + if (useMathOperator()) { + for (auto child = firstChildBox(); child; child = child->nextSiblingBox()) + child->layoutIfNeeded(); + setLogicalWidth(leadingSpaceValue + m_mathOperator.width() + trailingSpaceValue); + setLogicalHeight(m_mathOperator.ascent() + m_mathOperator.descent()); + } else { + // We first do the normal layout without spacing. + recomputeLogicalWidth(); + LayoutUnit width = logicalWidth(); + setLogicalWidth(width - leadingSpaceValue - trailingSpaceValue); + RenderMathMLToken::layoutBlock(relayoutChildren, pageLogicalHeight); + setLogicalWidth(width); + + // We then move the children to take spacing into account. + LayoutPoint horizontalShift(style().direction() == LTR ? leadingSpaceValue : -leadingSpaceValue, 0); + for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) + child->setLocation(child->location() + horizontalShift); + } - return stretchyCharacter; + clearNeedsLayout(); } -void RenderMathMLOperator::updateStyle() +void RenderMathMLOperator::updateMathOperator() { - ASSERT(firstChild()); - if (!firstChild()) - return; - - UChar stretchedCharacter; - bool allowStretching = shouldAllowStretching(stretchedCharacter); - - float stretchedCharacterHeight = style().fontMetrics().floatHeight(); - m_isStretched = allowStretching && expandedStretchHeight() > stretchedCharacterHeight; + ASSERT(useMathOperator()); + MathOperator::Type type; + if (isStretchy()) + type = isVertical() ? MathOperator::Type::VerticalOperator : MathOperator::Type::HorizontalOperator; + else if (textContent() && isLargeOperatorInDisplayStyle()) + type = MathOperator::Type::DisplayOperator; + else + type = MathOperator::Type::NormalOperator; - // Sometimes we cannot stretch an operator properly, so in that case, we should just use the original size. - m_stretchyCharacter = m_isStretched ? findAcceptableStretchyCharacter(stretchedCharacter) : 0; - if (!m_stretchyCharacter) - m_isStretched = false; + m_mathOperator.setOperator(style(), textContent(), type); } -int RenderMathMLOperator::firstLineBaseline() const +void RenderMathMLOperator::updateTokenContent() { - if (m_isStretched) - return expandedStretchHeight() * 2 / 3 - (expandedStretchHeight() - m_stretchHeight) / 2; - return RenderMathMLBlock::firstLineBaseline(); + ASSERT(!isAnonymous()); + RenderMathMLToken::updateTokenContent(); + if (useMathOperator()) + updateMathOperator(); } -void RenderMathMLOperator::computeLogicalHeight(LayoutUnit logicalHeight, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const +void RenderMathMLOperator::updateFromElement() { - if (m_isStretched) - logicalHeight = expandedStretchHeight(); - RenderBox::computeLogicalHeight(logicalHeight, logicalTop, computedValues); + updateTokenContent(); } -LayoutRect RenderMathMLOperator::paintCharacter(PaintInfo& info, UChar character, const LayoutPoint& origin, CharacterPaintTrimming trim) +bool RenderMathMLOperator::useMathOperator() const { - GlyphData data = style().font().glyphDataForCharacter(character, false); - FloatRect glyphBounds = data.fontData->boundsForGlyph(data.glyph); - - LayoutRect glyphPaintRect(origin, LayoutSize(glyphBounds.x() + glyphBounds.width(), glyphBounds.height())); - glyphPaintRect.setY(origin.y() + glyphBounds.y()); - - // In order to have glyphs fit snugly with one another we snap the connecting edges to pixel boundaries - // and trim off one pixel. The pixel trim is to account for fonts that have edge pixels that have less - // than full coverage. These edge pixels can introduce small seams between connected glyphs - FloatRect clipBounds = info.rect; - switch (trim) { - case TrimTop: - glyphPaintRect.shiftYEdgeTo(glyphPaintRect.y().ceil() + 1); - clipBounds.shiftYEdgeTo(glyphPaintRect.y()); - break; - case TrimBottom: - glyphPaintRect.shiftMaxYEdgeTo(glyphPaintRect.maxY().floor() - 1); - clipBounds.shiftMaxYEdgeTo(glyphPaintRect.maxY()); - break; - case TrimTopAndBottom: - LayoutUnit temp = glyphPaintRect.y() + 1; - glyphPaintRect.shiftYEdgeTo(temp.ceil()); - glyphPaintRect.shiftMaxYEdgeTo(glyphPaintRect.maxY().floor() - 1); - clipBounds.shiftYEdgeTo(glyphPaintRect.y()); - clipBounds.shiftMaxYEdgeTo(glyphPaintRect.maxY()); - break; - } - - // Clipping the enclosing IntRect avoids any potential issues at joined edges. - GraphicsContextStateSaver stateSaver(*info.context); - info.context->clip(clipBounds); - - info.context->drawText(style().font(), TextRun(&character, 1), origin); - - return glyphPaintRect; + // We use the MathOperator class to handle the following cases: + // 1) Stretchy and large operators, since they require special painting. + // 2) The minus sign, since it can be obtained from a hyphen in the DOM. + return isStretchy() || (textContent() && isLargeOperatorInDisplayStyle()) || textContent() == minusSign; } -void RenderMathMLOperator::fillWithExtensionGlyph(PaintInfo& info, const LayoutPoint& from, const LayoutPoint& to) +void RenderMathMLOperator::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) { - ASSERT(m_stretchyCharacter); - ASSERT(m_stretchyCharacter->extensionGlyph); - ASSERT(from.y() <= to.y()); - - // If there is no space for the extension glyph, we don't need to do anything. - if (from.y() == to.y()) - return; - - GraphicsContextStateSaver stateSaver(*info.context); - - FloatRect glyphBounds = glyphBoundsForCharacter(m_stretchyCharacter->extensionGlyph); - - // Clipping the extender region here allows us to draw the bottom extender glyph into the - // regions of the bottom glyph without worrying about overdraw (hairy pixels) and simplifies later clipping. - LayoutRect clipBounds = info.rect; - clipBounds.shiftYEdgeTo(from.y()); - clipBounds.shiftMaxYEdgeTo(to.y()); - info.context->clip(clipBounds); + RenderMathMLBlock::styleDidChange(diff, oldStyle); + m_mathOperator.reset(style()); +} - // Trimming may remove up to two pixels from the top of the extender glyph, so we move it up by two pixels. - float offsetToGlyphTop = glyphBounds.y() + 2; - LayoutPoint glyphOrigin = LayoutPoint(from.x(), from.y() - offsetToGlyphTop); - FloatRect lastPaintedGlyphRect(from, FloatSize()); +LayoutUnit RenderMathMLOperator::verticalStretchedOperatorShift() const +{ + if (!isVertical() || !stretchSize()) + return 0; - while (lastPaintedGlyphRect.maxY() < to.y()) { - lastPaintedGlyphRect = paintCharacter(info, m_stretchyCharacter->extensionGlyph, glyphOrigin, TrimTopAndBottom); - glyphOrigin.setY(glyphOrigin.y() + lastPaintedGlyphRect.height()); + return (m_stretchDepthBelowBaseline - m_stretchHeightAboveBaseline - m_mathOperator.descent() + m_mathOperator.ascent()) / 2; +} - // There's a chance that if the font size is small enough the glue glyph has been reduced to an empty rectangle - // with trimming. In that case we just draw nothing. - if (lastPaintedGlyphRect.isEmpty()) - break; - } +std::optional<int> RenderMathMLOperator::firstLineBaseline() const +{ + if (useMathOperator()) + return std::optional<int>(std::lround(static_cast<float>(m_mathOperator.ascent() - verticalStretchedOperatorShift()))); + return RenderMathMLToken::firstLineBaseline(); } void RenderMathMLOperator::paint(PaintInfo& info, const LayoutPoint& paintOffset) { - RenderMathMLBlock::paint(info, paintOffset); - - if (info.context->paintingDisabled() || info.phase != PaintPhaseForeground || style().visibility() != VISIBLE) - return; - - if (!m_isStretched && !m_stretchyCharacter) { - RenderMathMLBlock::paint(info, paintOffset); + RenderMathMLToken::paint(info, paintOffset); + if (!useMathOperator()) return; - } - - GraphicsContextStateSaver stateSaver(*info.context); - info.context->setFillColor(style().visitedDependentColor(CSSPropertyColor), style().colorSpace()); - ASSERT(m_stretchyCharacter->topGlyph); - ASSERT(m_stretchyCharacter->bottomGlyph); + LayoutPoint operatorTopLeft = paintOffset + location(); + operatorTopLeft.move(style().isLeftToRightDirection() ? leadingSpace() : trailingSpace(), 0); - // We are positioning the glyphs so that the edge of the tight glyph bounds line up exactly with the edges of our paint box. - LayoutPoint operatorTopLeft = ceiledIntPoint(paintOffset + location()); - FloatRect topGlyphBounds = glyphBoundsForCharacter(m_stretchyCharacter->topGlyph); - LayoutPoint topGlyphOrigin(operatorTopLeft.x(), operatorTopLeft.y() - topGlyphBounds.y()); - LayoutRect topGlyphPaintRect = paintCharacter(info, m_stretchyCharacter->topGlyph, topGlyphOrigin, TrimBottom); + // Center horizontal operators. + if (!isVertical()) + operatorTopLeft.move(-(m_mathOperator.width() - width()) / 2, 0); - FloatRect bottomGlyphBounds = glyphBoundsForCharacter(m_stretchyCharacter->bottomGlyph); - LayoutPoint bottomGlyphOrigin(operatorTopLeft.x(), operatorTopLeft.y() + offsetHeight() - (bottomGlyphBounds.height() + bottomGlyphBounds.y())); - LayoutRect bottomGlyphPaintRect = paintCharacter(info, m_stretchyCharacter->bottomGlyph, bottomGlyphOrigin, TrimTop); - - if (m_stretchyCharacter->middleGlyph) { - // Center the glyph origin between the start and end glyph paint extents. Then shift it half the paint height toward the bottom glyph. - FloatRect middleGlyphBounds = glyphBoundsForCharacter(m_stretchyCharacter->middleGlyph); - LayoutPoint middleGlyphOrigin(operatorTopLeft.x(), topGlyphOrigin.y()); - middleGlyphOrigin.moveBy(LayoutPoint(0, (bottomGlyphPaintRect.y() - topGlyphPaintRect.maxY()) / 2.0)); - middleGlyphOrigin.moveBy(LayoutPoint(0, middleGlyphBounds.height() / 2.0)); - - LayoutRect middleGlyphPaintRect = paintCharacter(info, m_stretchyCharacter->middleGlyph, middleGlyphOrigin, TrimTopAndBottom); - fillWithExtensionGlyph(info, topGlyphPaintRect.minXMaxYCorner(), middleGlyphPaintRect.minXMinYCorner()); - fillWithExtensionGlyph(info, middleGlyphPaintRect.minXMaxYCorner(), bottomGlyphPaintRect.minXMinYCorner()); - } else - fillWithExtensionGlyph(info, topGlyphPaintRect.minXMaxYCorner(), bottomGlyphPaintRect.minXMinYCorner()); + m_mathOperator.paint(style(), info, operatorTopLeft); } void RenderMathMLOperator::paintChildren(PaintInfo& paintInfo, const LayoutPoint& paintOffset, PaintInfo& paintInfoForChild, bool usePrintRect) { - if (m_isStretched) + // We skip painting for invisible operators too to avoid some "missing character" glyph to appear if appropriate math fonts are not available. + if (useMathOperator() || isInvisibleOperator()) return; - RenderMathMLBlock::paintChildren(paintInfo, paintOffset, paintInfoForChild, usePrintRect); + RenderMathMLToken::paintChildren(paintInfo, paintOffset, paintInfoForChild, usePrintRect); } - + } #endif diff --git a/Source/WebCore/rendering/mathml/RenderMathMLOperator.h b/Source/WebCore/rendering/mathml/RenderMathMLOperator.h index 505277114..02fd44e55 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLOperator.h +++ b/Source/WebCore/rendering/mathml/RenderMathMLOperator.h @@ -23,94 +23,74 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef RenderMathMLOperator_h -#define RenderMathMLOperator_h +#pragma once #if ENABLE(MATHML) #include "MathMLElement.h" -#include "RenderMathMLBlock.h" -#include <wtf/unicode/CharacterNames.h> +#include "MathMLOperatorDictionary.h" +#include "MathOperator.h" +#include "RenderMathMLToken.h" namespace WebCore { - -class RenderMathMLOperator final : public RenderMathMLBlock { -public: - RenderMathMLOperator(MathMLElement&, PassRef<RenderStyle>); - RenderMathMLOperator(MathMLElement&, PassRef<RenderStyle>, UChar operatorChar); - MathMLElement& element() { return toMathMLElement(nodeForNonAnonymous()); } +class MathMLOperatorElement; - void stretchToHeight(int pixelHeight); - int stretchHeight() { return m_stretchHeight; } - float expandedStretchHeight() const; - - enum OperatorType { Default, Separator, Fence }; - void setOperatorType(OperatorType type) { m_operatorType = type; } - OperatorType operatorType() const { return m_operatorType; } - void updateStyle(); +class RenderMathMLOperator : public RenderMathMLToken { +public: + RenderMathMLOperator(MathMLOperatorElement&, RenderStyle&&); + RenderMathMLOperator(Document&, RenderStyle&&); + MathMLOperatorElement& element() const; + + void stretchTo(LayoutUnit heightAboveBaseline, LayoutUnit depthBelowBaseline); + void stretchTo(LayoutUnit width); + LayoutUnit stretchSize() const { return isVertical() ? m_stretchHeightAboveBaseline + m_stretchDepthBelowBaseline : m_stretchWidth; } + void resetStretchSize(); + + virtual bool hasOperatorFlag(MathMLOperatorDictionary::Flag) const; + bool isLargeOperatorInDisplayStyle() const { return !hasOperatorFlag(MathMLOperatorDictionary::Stretchy) && hasOperatorFlag(MathMLOperatorDictionary::LargeOp) && mathMLStyle().displayStyle(); } + bool shouldMoveLimits() const { return hasOperatorFlag(MathMLOperatorDictionary::MovableLimits) && !mathMLStyle().displayStyle(); } + virtual bool isVertical() const; + LayoutUnit italicCorrection() const { return m_mathOperator.italicCorrection(); } + + void updateTokenContent() final; + void updateFromElement() final; + virtual UChar32 textContent() const; + bool isStretchy() const { return textContent() && hasOperatorFlag(MathMLOperatorDictionary::Stretchy); } + +protected: + virtual void updateMathOperator(); + virtual LayoutUnit leadingSpace() const; + virtual LayoutUnit trailingSpace() const; + virtual LayoutUnit minSize() const; + virtual LayoutUnit maxSize() const; + virtual bool useMathOperator() const; - void paint(PaintInfo&, const LayoutPoint&); +private: + void styleDidChange(StyleDifference, const RenderStyle* oldStyle) final; + void computePreferredLogicalWidths() final; + void layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight = 0) final; + void paint(PaintInfo&, const LayoutPoint&) final; - struct StretchyCharacter { - UChar character; - UChar topGlyph; - UChar extensionGlyph; - UChar bottomGlyph; - UChar middleGlyph; - }; + const char* renderName() const final { return isAnonymous() ? "RenderMathMLOperator (anonymous)" : "RenderMathMLOperator"; } + void paintChildren(PaintInfo& forSelf, const LayoutPoint&, PaintInfo& forChild, bool usePrintRect) final; + bool isRenderMathMLOperator() const final { return true; } + bool isInvisibleOperator() const; - virtual void updateFromElement() override; + std::optional<int> firstLineBaseline() const final; + RenderMathMLOperator* unembellishedOperator() final { return this; } -private: - virtual const char* renderName() const override { return isAnonymous() ? "RenderMathMLOperator (anonymous)" : "RenderMathMLOperator"; } - virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle) override; - virtual void paintChildren(PaintInfo& forSelf, const LayoutPoint&, PaintInfo& forChild, bool usePrintRect) override; - virtual bool isRenderMathMLOperator() const override { return true; } - virtual bool isChildAllowed(const RenderObject&, const RenderStyle&) const override; - virtual void computePreferredLogicalWidths() override; - virtual void computeLogicalHeight(LayoutUnit logicalHeight, LayoutUnit logicalTop, LogicalExtentComputedValues&) const override; - virtual int firstLineBaseline() const override; - virtual RenderMathMLOperator* unembellishedOperator() override { return this; } - - bool shouldAllowStretching(UChar& characterForStretching); - StretchyCharacter* findAcceptableStretchyCharacter(UChar); - - FloatRect glyphBoundsForCharacter(UChar); - float glyphHeightForCharacter(UChar); - float advanceForCharacter(UChar); - - enum CharacterPaintTrimming { - TrimTop, - TrimBottom, - TrimTopAndBottom, - }; - - LayoutRect paintCharacter(PaintInfo&, UChar, const LayoutPoint& origin, CharacterPaintTrimming); - void fillWithExtensionGlyph(PaintInfo&, const LayoutPoint& from, const LayoutPoint& to); - - int m_stretchHeight; - bool m_isStretched; - - UChar m_operator; - OperatorType m_operatorType; - StretchyCharacter* m_stretchyCharacter; -}; + LayoutUnit verticalStretchedOperatorShift() const; -RENDER_OBJECT_TYPE_CASTS(RenderMathMLOperator, isRenderMathMLOperator()); + LayoutUnit m_stretchHeightAboveBaseline { 0 }; + LayoutUnit m_stretchDepthBelowBaseline { 0 }; + LayoutUnit m_stretchWidth; -template<> inline bool isRendererOfType<const RenderMathMLOperator>(const RenderObject& renderer) { return renderer.isRenderMathMLOperator(); } + MathOperator m_mathOperator; +}; -inline UChar convertHyphenMinusToMinusSign(UChar glyph) -{ - // When rendered as a mathematical operator, minus glyph should be larger. - if (glyph == hyphenMinus) - return minusSign; - - return glyph; -} +} // namespace WebCore -} +SPECIALIZE_TYPE_TRAITS_RENDER_OBJECT(RenderMathMLOperator, isRenderMathMLOperator()) #endif // ENABLE(MATHML) -#endif // RenderMathMLOperator_h diff --git a/Source/WebCore/rendering/mathml/RenderMathMLPadded.cpp b/Source/WebCore/rendering/mathml/RenderMathMLPadded.cpp new file mode 100644 index 000000000..59b2b01df --- /dev/null +++ b/Source/WebCore/rendering/mathml/RenderMathMLPadded.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2016 Igalia S.L. All rights reserved. + * + * 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 "RenderMathMLPadded.h" + +#if ENABLE(MATHML) + +#include <cmath> + +namespace WebCore { + +RenderMathMLPadded::RenderMathMLPadded(MathMLPaddedElement& element, RenderStyle&& style) + : RenderMathMLRow(element, WTFMove(style)) +{ +} + +LayoutUnit RenderMathMLPadded::voffset() const +{ + return toUserUnits(element().voffset(), style(), 0); +} + +LayoutUnit RenderMathMLPadded::lspace() const +{ + LayoutUnit lspace = toUserUnits(element().lspace(), style(), 0); + // FIXME: Negative lspace values are not supported yet (https://bugs.webkit.org/show_bug.cgi?id=85730). + return std::max<LayoutUnit>(0, lspace); +} + +LayoutUnit RenderMathMLPadded::mpaddedWidth(LayoutUnit contentWidth) const +{ + return std::max<LayoutUnit>(0, toUserUnits(element().width(), style(), contentWidth)); +} + +LayoutUnit RenderMathMLPadded::mpaddedHeight(LayoutUnit contentHeight) const +{ + return std::max<LayoutUnit>(0, toUserUnits(element().height(), style(), contentHeight)); +} + +LayoutUnit RenderMathMLPadded::mpaddedDepth(LayoutUnit contentDepth) const +{ + return std::max<LayoutUnit>(0, toUserUnits(element().depth(), style(), contentDepth)); +} + +void RenderMathMLPadded::computePreferredLogicalWidths() +{ + ASSERT(preferredLogicalWidthsDirty()); + + // Determine the intrinsic width of the content. + RenderMathMLRow::computePreferredLogicalWidths(); + + // Only the width attribute should modify the width. + // We parse it using the preferred width of the content as its default value. + m_maxPreferredLogicalWidth = mpaddedWidth(m_maxPreferredLogicalWidth); + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth; + + setPreferredLogicalWidthsDirty(false); +} + +void RenderMathMLPadded::layoutBlock(bool relayoutChildren, LayoutUnit) +{ + ASSERT(needsLayout()); + + if (!relayoutChildren && simplifiedLayout()) + return; + + // We first layout our children as a normal <mrow> element. + LayoutUnit contentAscent, contentDescent, contentWidth; + contentAscent = contentDescent = 0; + RenderMathMLRow::computeLineVerticalStretch(contentAscent, contentDescent); + RenderMathMLRow::layoutRowItems(contentAscent, contentDescent); + contentWidth = logicalWidth(); + + // We parse the mpadded attributes using the content metrics as the default value. + LayoutUnit width = mpaddedWidth(contentWidth); + LayoutUnit ascent = mpaddedHeight(contentAscent); + LayoutUnit descent = mpaddedDepth(contentDescent); + + // Align children on the new baseline and shift them by (lspace, -voffset) + LayoutPoint contentLocation(lspace(), ascent - contentAscent - voffset()); + for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) + child->setLocation(child->location() + contentLocation); + + // Set the final metrics. + setLogicalWidth(width); + setLogicalHeight(ascent + descent); + + clearNeedsLayout(); +} + +std::optional<int> RenderMathMLPadded::firstLineBaseline() const +{ + // We try and calculate the baseline from the position of the first child. + LayoutUnit ascent; + if (auto* baselineChild = firstChildBox()) + ascent = ascentForChild(*baselineChild) + baselineChild->logicalTop() + voffset(); + else + ascent = mpaddedHeight(0); + return std::optional<int>(std::lround(static_cast<float>(ascent))); +} + +} + +#endif diff --git a/Source/WebCore/rendering/mathml/RenderMathMLSquareRoot.cpp b/Source/WebCore/rendering/mathml/RenderMathMLPadded.h index 6a8bceb3e..b1598d8ea 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLSquareRoot.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLPadded.h @@ -1,6 +1,5 @@ /* - * Copyright (C) 2009 Alex Milowski (alex@milowski.com). All rights reserved. - * Copyright (C) 2010 François Sausset (sausset@gmail.com). All rights reserved. + * Copyright (C) 2016 Igalia S.L. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -24,32 +23,37 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "config.h" +#pragma once #if ENABLE(MATHML) -#include "RenderMathMLSquareRoot.h" - -#include "RenderMathMLMenclose.h" +#include "MathMLPaddedElement.h" +#include "RenderMathMLRow.h" namespace WebCore { - -RenderMathMLSquareRoot::RenderMathMLSquareRoot(Element& element, PassRef<RenderStyle> style) - : RenderMathMLRoot(element, std::move(style)) -{ -} - -RenderMathMLSquareRoot::RenderMathMLSquareRoot(Document& document, PassRef<RenderStyle> style) - : RenderMathMLRoot(document, std::move(style)) -{ -} - -RenderPtr<RenderMathMLSquareRoot> RenderMathMLSquareRoot::createAnonymousWithParentRenderer(RenderMathMLMenclose& parent) -{ - RenderPtr<RenderMathMLSquareRoot> squareRoot = createRenderer<RenderMathMLSquareRoot>(parent.document(), RenderStyle::createAnonymousStyleWithDisplay(&parent.style(), FLEX)); - squareRoot->initializeStyle(); - return squareRoot; -} - -} + +class RenderMathMLPadded final : public RenderMathMLRow { +public: + RenderMathMLPadded(MathMLPaddedElement&, RenderStyle&&); + +private: + const char* renderName() const final { return "RenderMathMLPadded"; } + bool isRenderMathMLPadded() const final { return true; } + + void computePreferredLogicalWidths() final; + void layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight = 0) final; + std::optional<int> firstLineBaseline() const final; + + MathMLPaddedElement& element() const { return static_cast<MathMLPaddedElement&>(nodeForNonAnonymous()); } + LayoutUnit voffset() const; + LayoutUnit lspace() const; + LayoutUnit mpaddedWidth(LayoutUnit contentWidth) const; + LayoutUnit mpaddedHeight(LayoutUnit contentHeight) const; + LayoutUnit mpaddedDepth(LayoutUnit contentDepth) const; +}; + +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_RENDER_OBJECT(RenderMathMLPadded, isRenderMathMLPadded()) + #endif // ENABLE(MATHML) diff --git a/Source/WebCore/rendering/mathml/RenderMathMLRoot.cpp b/Source/WebCore/rendering/mathml/RenderMathMLRoot.cpp index 0e975c730..3d757a7a8 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLRoot.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLRoot.cpp @@ -1,6 +1,7 @@ /* * Copyright (C) 2009 Alex Milowski (alex@milowski.com). All rights reserved. * Copyright (C) 2010 François Sausset (sausset@gmail.com). 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 @@ -25,285 +26,268 @@ */ #include "config.h" +#include "RenderMathMLRoot.h" #if ENABLE(MATHML) -#include "RenderMathMLRoot.h" - +#include "FontCache.h" #include "GraphicsContext.h" +#include "MathMLNames.h" +#include "MathMLRowElement.h" #include "PaintInfo.h" #include "RenderIterator.h" -#include "RenderMathMLRow.h" +#include "RenderMathMLMenclose.h" +#include "RenderMathMLOperator.h" + +static const UChar gRadicalCharacter = 0x221A; namespace WebCore { - -// FIXME: This whole file should be changed to work with various writing modes. See https://bugs.webkit.org/show_bug.cgi?id=48951. - -// Threshold above which the radical shape is modified to look nice with big bases (em) -const float gThresholdBaseHeightEms = 1.5f; -// Normal width of the front of the radical sign, before the base & overbar (em) -const float gFrontWidthEms = 0.75f; -// Gap between the base and overbar (em) -const float gSpaceAboveEms = 0.2f; -// Horizontal position of the bottom point of the radical (* frontWidth) -const float gRadicalBottomPointXFront = 0.5f; -// Lower the radical sign's bottom point (px) -const int gRadicalBottomPointLower = 3; -// Horizontal position of the top left point of the radical "dip" (* frontWidth) -const float gRadicalDipLeftPointXFront = 0.8f; -// Vertical position of the top left point of a sqrt radical "dip" (* baseHeight) -const float gSqrtRadicalDipLeftPointYPos = 0.5f; -// Vertical position of the top left point of an nth root radical "dip" (* baseHeight) -const float gRootRadicalDipLeftPointYPos = 0.625f; -// Vertical shift of the left end point of the radical (em) -const float gRadicalLeftEndYShiftEms = 0.05f; -// Additional bottom root padding if baseHeight > threshold (em) -const float gBigRootBottomPaddingEms = 0.2f; - -// Radical line thickness (em) -const float gRadicalLineThicknessEms = 0.02f; -// Radical thick line thickness (em) -const float gRadicalThickLineThicknessEms = 0.1f; - -RenderMathMLRoot::RenderMathMLRoot(Element& element, PassRef<RenderStyle> style) - : RenderMathMLBlock(element, std::move(style)) - , m_intrinsicPaddingBefore(0) - , m_intrinsicPaddingAfter(0) - , m_intrinsicPaddingStart(0) - , m_intrinsicPaddingEnd(0) + +RenderMathMLRoot::RenderMathMLRoot(MathMLRowElement& element, RenderStyle&& style) + : RenderMathMLRow(element, WTFMove(style)) { + // Determine what kind of expression we have by element name + if (element.hasTagName(MathMLNames::msqrtTag)) + m_kind = SquareRoot; + else if (element.hasTagName(MathMLNames::mrootTag)) + m_kind = RootWithIndex; + + m_radicalOperator.setOperator(RenderMathMLRoot::style(), gRadicalCharacter, MathOperator::Type::VerticalOperator); } -RenderMathMLRoot::RenderMathMLRoot(Document& document, PassRef<RenderStyle> style) - : RenderMathMLBlock(document, std::move(style)) - , m_intrinsicPaddingBefore(0) - , m_intrinsicPaddingAfter(0) - , m_intrinsicPaddingStart(0) - , m_intrinsicPaddingEnd(0) +bool RenderMathMLRoot::isValid() const { + // Verify whether the list of children is valid: + // <msqrt> child1 child2 ... childN </msqrt> + // <mroot> base index </mroot> + if (m_kind == SquareRoot) + return true; + + ASSERT(m_kind == RootWithIndex); + auto* child = firstChildBox(); + if (!child) + return false; + child = child->nextSiblingBox(); + return child && !child->nextSiblingBox(); } -LayoutUnit RenderMathMLRoot::paddingTop() const + +RenderBox& RenderMathMLRoot::getBase() const { - LayoutUnit result = computedCSSPaddingTop(); - switch (style().writingMode()) { - case TopToBottomWritingMode: - return result + m_intrinsicPaddingBefore; - case BottomToTopWritingMode: - return result + m_intrinsicPaddingAfter; - case LeftToRightWritingMode: - case RightToLeftWritingMode: - return result + (style().isLeftToRightDirection() ? m_intrinsicPaddingStart : m_intrinsicPaddingEnd); - } - ASSERT_NOT_REACHED(); - return result; + ASSERT(isValid()); + ASSERT(m_kind == RootWithIndex); + return *firstChildBox(); } -LayoutUnit RenderMathMLRoot::paddingBottom() const +RenderBox& RenderMathMLRoot::getIndex() const { - LayoutUnit result = computedCSSPaddingBottom(); - switch (style().writingMode()) { - case TopToBottomWritingMode: - return result + m_intrinsicPaddingAfter; - case BottomToTopWritingMode: - return result + m_intrinsicPaddingBefore; - case LeftToRightWritingMode: - case RightToLeftWritingMode: - return result + (style().isLeftToRightDirection() ? m_intrinsicPaddingEnd : m_intrinsicPaddingStart); - } - ASSERT_NOT_REACHED(); - return result; + ASSERT(isValid()); + ASSERT(m_kind == RootWithIndex); + return *firstChildBox()->nextSiblingBox(); } -LayoutUnit RenderMathMLRoot::paddingLeft() const +void RenderMathMLRoot::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) { - LayoutUnit result = computedCSSPaddingLeft(); - switch (style().writingMode()) { - case LeftToRightWritingMode: - return result + m_intrinsicPaddingBefore; - case RightToLeftWritingMode: - return result + m_intrinsicPaddingAfter; - case TopToBottomWritingMode: - case BottomToTopWritingMode: - return result + (style().isLeftToRightDirection() ? m_intrinsicPaddingStart : m_intrinsicPaddingEnd); - } - ASSERT_NOT_REACHED(); - return result; + RenderMathMLRow::styleDidChange(diff, oldStyle); + m_radicalOperator.reset(style()); } -LayoutUnit RenderMathMLRoot::paddingRight() const +RenderMathMLRoot::HorizontalParameters RenderMathMLRoot::horizontalParameters() { - LayoutUnit result = computedCSSPaddingRight(); - switch (style().writingMode()) { - case RightToLeftWritingMode: - return result + m_intrinsicPaddingBefore; - case LeftToRightWritingMode: - return result + m_intrinsicPaddingAfter; - case TopToBottomWritingMode: - case BottomToTopWritingMode: - return result + (style().isLeftToRightDirection() ? m_intrinsicPaddingEnd : m_intrinsicPaddingStart); + HorizontalParameters parameters; + + // Square roots do not require horizontal parameters. + if (m_kind == SquareRoot) + return parameters; + + // We try and read constants to draw the radical from the OpenType MATH and use fallback values otherwise. + const auto& primaryFont = style().fontCascade().primaryFont(); + if (auto* mathData = style().fontCascade().primaryFont().mathData()) { + parameters.kernBeforeDegree = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalKernBeforeDegree); + parameters.kernAfterDegree = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalKernAfterDegree); + } else { + // RadicalKernBeforeDegree: No suggested value provided. OT Math Illuminated mentions 5/18 em, Gecko uses 0. + // RadicalKernAfterDegree: Suggested value is -10/18 of em. + parameters.kernBeforeDegree = 5 * style().fontCascade().size() / 18; + parameters.kernAfterDegree = -10 * style().fontCascade().size() / 18; } - ASSERT_NOT_REACHED(); - return result; + return parameters; } -LayoutUnit RenderMathMLRoot::paddingBefore() const +RenderMathMLRoot::VerticalParameters RenderMathMLRoot::verticalParameters() { - return computedCSSPaddingBefore() + m_intrinsicPaddingBefore; -} + VerticalParameters parameters; + // We try and read constants to draw the radical from the OpenType MATH and use fallback values otherwise. + const auto& primaryFont = style().fontCascade().primaryFont(); + if (auto* mathData = style().fontCascade().primaryFont().mathData()) { + parameters.ruleThickness = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalRuleThickness); + parameters.verticalGap = mathData->getMathConstant(primaryFont, mathMLStyle().displayStyle() ? OpenTypeMathData::RadicalDisplayStyleVerticalGap : OpenTypeMathData::RadicalVerticalGap); + parameters.extraAscender = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalExtraAscender); + if (m_kind == RootWithIndex) + parameters.degreeBottomRaisePercent = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalDegreeBottomRaisePercent); + } else { + // RadicalVerticalGap: Suggested value is 5/4 default rule thickness. + // RadicalDisplayStyleVerticalGap: Suggested value is default rule thickness + 1/4 x-height. + // RadicalRuleThickness: Suggested value is default rule thickness. + // RadicalExtraAscender: Suggested value is RadicalRuleThickness. + // RadicalDegreeBottomRaisePercent: Suggested value is 60%. + parameters.ruleThickness = ruleThicknessFallback(); + if (mathMLStyle().displayStyle()) + parameters.verticalGap = parameters.ruleThickness + style().fontMetrics().xHeight() / 4; + else + parameters.verticalGap = 5 * parameters.ruleThickness / 4; -LayoutUnit RenderMathMLRoot::paddingAfter() const -{ - return computedCSSPaddingAfter() + m_intrinsicPaddingAfter; + if (m_kind == RootWithIndex) { + parameters.extraAscender = parameters.ruleThickness; + parameters.degreeBottomRaisePercent = 0.6f; + } + } + return parameters; } -LayoutUnit RenderMathMLRoot::paddingStart() const +void RenderMathMLRoot::computePreferredLogicalWidths() { - return computedCSSPaddingStart() + m_intrinsicPaddingStart; -} + ASSERT(preferredLogicalWidthsDirty()); -LayoutUnit RenderMathMLRoot::paddingEnd() const -{ - return computedCSSPaddingEnd() + m_intrinsicPaddingEnd; -} + if (!isValid()) { + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = 0; + setPreferredLogicalWidthsDirty(false); + return; + } -void RenderMathMLRoot::addChild(RenderObject* newChild, RenderObject* beforeChild) -{ - // Insert an implicit <mrow> for <mroot> as well as <msqrt>, to ensure firstChild() will have a box - // to measure and store a glyph-based height for preferredLogicalHeightAfterSizing. - if (!firstChild()) - RenderMathMLBlock::addChild(RenderMathMLRow::createAnonymousWithParentRenderer(*this).leakPtr()); - - // An <mroot>'s index has { position: absolute }. - if (newChild->style().position() == AbsolutePosition) - RenderMathMLBlock::addChild(newChild); - else - toRenderElement(firstChild())->addChild(newChild, beforeChild && beforeChild->parent() == firstChild() ? beforeChild : 0); -} + LayoutUnit preferredWidth = 0; + if (m_kind == SquareRoot) { + preferredWidth += m_radicalOperator.maxPreferredWidth(); + setPreferredLogicalWidthsDirty(true); + RenderMathMLRow::computePreferredLogicalWidths(); + preferredWidth += m_maxPreferredLogicalWidth; + } else { + ASSERT(m_kind == RootWithIndex); + auto horizontal = horizontalParameters(); + preferredWidth += horizontal.kernBeforeDegree; + preferredWidth += getIndex().maxPreferredLogicalWidth(); + preferredWidth += horizontal.kernAfterDegree; + preferredWidth += m_radicalOperator.maxPreferredWidth(); + preferredWidth += getBase().maxPreferredLogicalWidth(); + } -RenderBox* RenderMathMLRoot::index() const -{ - if (!firstChild()) - return 0; - RenderObject* index = firstChild()->nextSibling(); - if (!index || !index->isBox()) - return 0; - return toRenderBox(index); + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = preferredWidth; + setPreferredLogicalWidthsDirty(false); } -void RenderMathMLRoot::layout() +void RenderMathMLRoot::layoutBlock(bool relayoutChildren, LayoutUnit) { - for (auto& box : childrenOfType<RenderBox>(*this)) - box.layoutIfNeeded(); - - int baseHeight = firstChild() && firstChild()->isBox() ? roundToInt(toRenderBox(firstChild())->logicalHeight()) : style().fontSize(); - int frontWidth = lroundf(gFrontWidthEms * style().fontSize()); - - // Base height above which the shape of the root changes - float thresholdHeight = gThresholdBaseHeightEms * style().fontSize(); - if (baseHeight > thresholdHeight && thresholdHeight) { - float shift = std::min<float>((baseHeight - thresholdHeight) / thresholdHeight, 1.0f); - m_overbarLeftPointShift = static_cast<int>(shift * gRadicalBottomPointXFront * frontWidth); - m_intrinsicPaddingAfter = lroundf(gBigRootBottomPaddingEms * style().fontSize()); + ASSERT(needsLayout()); + + if (!relayoutChildren && simplifiedLayout()) + return; + + m_radicalOperatorTop = 0; + m_baseWidth = 0; + + if (!isValid()) { + layoutInvalidMarkup(); + return; + } + + // We layout the children, determine the vertical metrics of the base and set the logical width. + // Note: Per the MathML specification, the children of <msqrt> are wrapped in an inferred <mrow>, which is the desired base. + LayoutUnit baseAscent, baseDescent; + recomputeLogicalWidth(); + if (m_kind == SquareRoot) { + baseAscent = baseDescent; + RenderMathMLRow::computeLineVerticalStretch(baseAscent, baseDescent); + RenderMathMLRow::layoutRowItems(baseAscent, baseDescent); + m_baseWidth = logicalWidth(); } else { - m_overbarLeftPointShift = 0; - m_intrinsicPaddingAfter = 0; + getBase().layoutIfNeeded(); + m_baseWidth = getBase().logicalWidth(); + baseAscent = ascentForChild(getBase()); + baseDescent = getBase().logicalHeight() - baseAscent; + getIndex().layoutIfNeeded(); } - - int rootPad = lroundf(gSpaceAboveEms * style().fontSize()); - m_intrinsicPaddingBefore = rootPad; - m_indexTop = 0; - if (RenderBox* index = this->index()) { - m_intrinsicPaddingStart = roundToInt(index->maxPreferredLogicalWidth()) + m_overbarLeftPointShift; - - int indexHeight = roundToInt(index->logicalHeight()); - int partDipHeight = lroundf((1 - gRootRadicalDipLeftPointYPos) * baseHeight); - int rootExtraTop = partDipHeight + indexHeight - (baseHeight + rootPad); - if (rootExtraTop > 0) - m_intrinsicPaddingBefore += rootExtraTop; - else - m_indexTop = - rootExtraTop; - } else - m_intrinsicPaddingStart = frontWidth; - // FIXME: We should rewrite RenderMathMLRoot to rewrite -webkit-flex-order to get rid of the need - // for intrinsic padding. See https://bugs.webkit.org/show_bug.cgi?id=107151#c2. - // FIXME: We should make it so that the preferred width of RenderMathMLRoots doesn't change during layout. - // Technically, we currently only need to set the dirty bit here if one of the member variables above changes. - setPreferredLogicalWidthsDirty(true); + auto horizontal = horizontalParameters(); + auto vertical = verticalParameters(); - RenderMathMLBlock::layout(); + // Stretch the radical operator to cover the base height. + // We can then determine the metrics of the radical operator + the base. + m_radicalOperator.stretchTo(style(), baseAscent + baseDescent); + LayoutUnit radicalOperatorHeight = m_radicalOperator.ascent() + m_radicalOperator.descent(); + LayoutUnit indexBottomRaise = vertical.degreeBottomRaisePercent * radicalOperatorHeight; + LayoutUnit radicalAscent = baseAscent + vertical.verticalGap + vertical.ruleThickness + vertical.extraAscender; + LayoutUnit radicalDescent = std::max<LayoutUnit>(baseDescent, radicalOperatorHeight + vertical.extraAscender - radicalAscent); + LayoutUnit descent = radicalDescent; + LayoutUnit ascent = radicalAscent; - if (RenderBox* index = this->index()) - index->setLogicalTop(m_indexTop); + // We set the logical width. + if (m_kind == SquareRoot) + setLogicalWidth(m_radicalOperator.width() + m_baseWidth); + else { + ASSERT(m_kind == RootWithIndex); + setLogicalWidth(horizontal.kernBeforeDegree + getIndex().logicalWidth() + horizontal.kernAfterDegree + m_radicalOperator.width() + m_baseWidth); + } + + // For <mroot>, we update the metrics to take into account the index. + LayoutUnit indexAscent, indexDescent; + if (m_kind == RootWithIndex) { + indexAscent = ascentForChild(getIndex()); + indexDescent = getIndex().logicalHeight() - indexAscent; + ascent = std::max<LayoutUnit>(radicalAscent, indexBottomRaise + indexDescent + indexAscent - descent); + } + + // We set the final position of children. + m_radicalOperatorTop = ascent - radicalAscent + vertical.extraAscender; + LayoutUnit horizontalOffset = m_radicalOperator.width(); + if (m_kind == RootWithIndex) + horizontalOffset += horizontal.kernBeforeDegree + getIndex().logicalWidth() + horizontal.kernAfterDegree; + LayoutPoint baseLocation(mirrorIfNeeded(horizontalOffset, m_baseWidth), ascent - baseAscent); + if (m_kind == SquareRoot) { + for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) + child->setLocation(child->location() + baseLocation); + } else { + ASSERT(m_kind == RootWithIndex); + getBase().setLocation(baseLocation); + LayoutPoint indexLocation(mirrorIfNeeded(horizontal.kernBeforeDegree, getIndex()), ascent + descent - indexBottomRaise - indexDescent - indexAscent); + getIndex().setLocation(indexLocation); + } + + setLogicalHeight(ascent + descent); + clearNeedsLayout(); } void RenderMathMLRoot::paint(PaintInfo& info, const LayoutPoint& paintOffset) { - RenderMathMLBlock::paint(info, paintOffset); - - if (info.context->paintingDisabled() || style().visibility() != VISIBLE) + RenderMathMLRow::paint(info, paintOffset); + + if (!firstChild() || info.context().paintingDisabled() || style().visibility() != VISIBLE || !isValid()) + return; + + // We draw the radical operator. + LayoutPoint radicalOperatorTopLeft = paintOffset + location(); + LayoutUnit horizontalOffset = 0; + if (m_kind == RootWithIndex) { + auto horizontal = horizontalParameters(); + horizontalOffset = horizontal.kernBeforeDegree + getIndex().logicalWidth() + horizontal.kernAfterDegree; + } + radicalOperatorTopLeft.move(mirrorIfNeeded(horizontalOffset, m_radicalOperator.width()), m_radicalOperatorTop); + m_radicalOperator.paint(style(), info, radicalOperatorTopLeft); + + // We draw the radical line. + LayoutUnit ruleThickness = verticalParameters().ruleThickness; + if (!ruleThickness) return; - - IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset + location() + contentBoxRect().location()); - - int startX = adjustedPaintOffset.x(); - int frontWidth = lroundf(gFrontWidthEms * style().fontSize()); - int overbarWidth = roundToInt(contentLogicalWidth()) + m_overbarLeftPointShift; - - int baseHeight = roundToInt(contentLogicalHeight()); - int rootPad = lroundf(gSpaceAboveEms * style().fontSize()); - adjustedPaintOffset.setY(adjustedPaintOffset.y() - rootPad); - - float radicalDipLeftPointYPos = (index() ? gRootRadicalDipLeftPointYPos : gSqrtRadicalDipLeftPointYPos) * baseHeight; - - FloatPoint overbarLeftPoint(startX - m_overbarLeftPointShift, adjustedPaintOffset.y()); - FloatPoint bottomPoint(startX - gRadicalBottomPointXFront * frontWidth, adjustedPaintOffset.y() + baseHeight + gRadicalBottomPointLower); - FloatPoint dipLeftPoint(startX - gRadicalDipLeftPointXFront * frontWidth, adjustedPaintOffset.y() + radicalDipLeftPointYPos); - FloatPoint leftEnd(startX - frontWidth, dipLeftPoint.y() + gRadicalLeftEndYShiftEms * style().fontSize()); - - GraphicsContextStateSaver stateSaver(*info.context); - - info.context->setStrokeThickness(gRadicalLineThicknessEms * style().fontSize()); - info.context->setStrokeStyle(SolidStroke); - info.context->setStrokeColor(style().visitedDependentColor(CSSPropertyColor), ColorSpaceDeviceRGB); - info.context->setLineJoin(MiterJoin); - info.context->setMiterLimit(style().fontSize()); - - Path root; - - root.moveTo(FloatPoint(overbarLeftPoint.x() + overbarWidth, adjustedPaintOffset.y())); - // draw top - root.addLineTo(overbarLeftPoint); - // draw from top left corner to bottom point of radical - root.addLineTo(bottomPoint); - // draw from bottom point to top of left part of radical base "dip" - root.addLineTo(dipLeftPoint); - // draw to end - root.addLineTo(leftEnd); - - info.context->strokePath(root); - - GraphicsContextStateSaver maskStateSaver(*info.context); - - // Build a mask to draw the thick part of the root. - Path mask; - - mask.moveTo(overbarLeftPoint); - mask.addLineTo(bottomPoint); - mask.addLineTo(dipLeftPoint); - mask.addLineTo(FloatPoint(2 * dipLeftPoint.x() - leftEnd.x(), 2 * dipLeftPoint.y() - leftEnd.y())); - - info.context->clip(mask); - - // Draw the thick part of the root. - info.context->setStrokeThickness(gRadicalThickLineThicknessEms * style().fontSize()); - info.context->setLineCap(SquareCap); - - Path line; - line.moveTo(bottomPoint); - line.addLineTo(dipLeftPoint); - - info.context->strokePath(line); + GraphicsContextStateSaver stateSaver(info.context()); + + info.context().setStrokeThickness(ruleThickness); + info.context().setStrokeStyle(SolidStroke); + info.context().setStrokeColor(style().visitedDependentColor(CSSPropertyColor)); + LayoutPoint ruleOffsetFrom = paintOffset + location() + LayoutPoint(0, m_radicalOperatorTop + ruleThickness / 2); + LayoutPoint ruleOffsetTo = ruleOffsetFrom; + horizontalOffset += m_radicalOperator.width(); + ruleOffsetFrom.move(mirrorIfNeeded(horizontalOffset), 0); + horizontalOffset += m_baseWidth; + ruleOffsetTo.move(mirrorIfNeeded(horizontalOffset), 0); + info.context().drawLine(ruleOffsetFrom, ruleOffsetTo); } } diff --git a/Source/WebCore/rendering/mathml/RenderMathMLRoot.h b/Source/WebCore/rendering/mathml/RenderMathMLRoot.h index 4d5d731ef..a816e1261 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLRoot.h +++ b/Source/WebCore/rendering/mathml/RenderMathMLRoot.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2009 Alex Milowski (alex@milowski.com). 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 @@ -23,54 +24,62 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef RenderMathMLRoot_h -#define RenderMathMLRoot_h +#pragma once #if ENABLE(MATHML) +#include "MathOperator.h" #include "RenderMathMLBlock.h" +#include "RenderMathMLRow.h" namespace WebCore { - -// Render base^(1/index), or sqrt(base) via the derived class RenderMathMLSquareRoot, using radical notation. -class RenderMathMLRoot : public RenderMathMLBlock { + +class MathMLRowElement; + +// Render base^(1/index), or sqrt(base) using radical notation. +class RenderMathMLRoot final : public RenderMathMLRow { + public: - RenderMathMLRoot(Element&, PassRef<RenderStyle>); - RenderMathMLRoot(Document&, PassRef<RenderStyle>); - - virtual LayoutUnit paddingTop() const override; - virtual LayoutUnit paddingBottom() const override; - virtual LayoutUnit paddingLeft() const override; - virtual LayoutUnit paddingRight() const override; - virtual LayoutUnit paddingBefore() const override; - virtual LayoutUnit paddingAfter() const override; - virtual LayoutUnit paddingStart() const override; - virtual LayoutUnit paddingEnd() const override; - - virtual void addChild(RenderObject* newChild, RenderObject* beforeChild = 0) override; - -protected: - virtual void layout() override; - - virtual void paint(PaintInfo&, const LayoutPoint&) override; + RenderMathMLRoot(MathMLRowElement&, RenderStyle&&); + void updateStyle(); private: - virtual bool isRenderMathMLRoot() const override final { return true; } - virtual const char* renderName() const override { return "RenderMathMLRoot"; } - - // This may return 0 for a non-MathML index (which won't occur in valid MathML). - RenderBox* index() const; - - int m_intrinsicPaddingBefore; - int m_intrinsicPaddingAfter; - int m_intrinsicPaddingStart; - int m_intrinsicPaddingEnd; - int m_overbarLeftPointShift; - int m_indexTop; + bool isValid() const; + RenderBox& getBase() const; + RenderBox& getIndex() const; + bool isRenderMathMLRoot() const final { return true; } + const char* renderName() const final { return "RenderMathMLRoot"; } + + void styleDidChange(StyleDifference, const RenderStyle* oldStyle) final; + + void computePreferredLogicalWidths() final; + void layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight = 0) final; + void paint(PaintInfo&, const LayoutPoint&) final; + + struct HorizontalParameters { + LayoutUnit kernBeforeDegree; + LayoutUnit kernAfterDegree; + }; + HorizontalParameters horizontalParameters(); + struct VerticalParameters { + LayoutUnit verticalGap; + LayoutUnit ruleThickness; + LayoutUnit extraAscender; + float degreeBottomRaisePercent; + }; + VerticalParameters verticalParameters(); + + MathOperator m_radicalOperator; + LayoutUnit m_radicalOperatorTop; + LayoutUnit m_baseWidth; + + enum RootType { SquareRoot, RootWithIndex }; + RootType m_kind; + bool isRenderMathMLSquareRoot() const final { return m_kind == SquareRoot; } }; - -} -#endif // ENABLE(MATHML) +} // namespace WebCore -#endif // RenderMathMLRoot_h +SPECIALIZE_TYPE_TRAITS_RENDER_OBJECT(RenderMathMLRoot, isRenderMathMLRoot()) + +#endif // ENABLE(MATHML) diff --git a/Source/WebCore/rendering/mathml/RenderMathMLRow.cpp b/Source/WebCore/rendering/mathml/RenderMathMLRow.cpp index 02ef296cd..c4bebc2a2 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLRow.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLRow.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 Alex Milowski (alex@milowski.com). 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 @@ -24,12 +25,12 @@ */ #include "config.h" +#include "RenderMathMLRow.h" #if ENABLE(MATHML) -#include "RenderMathMLRow.h" - #include "MathMLNames.h" +#include "MathMLRowElement.h" #include "RenderIterator.h" #include "RenderMathMLOperator.h" #include "RenderMathMLRoot.h" @@ -38,47 +39,152 @@ namespace WebCore { using namespace MathMLNames; -RenderMathMLRow::RenderMathMLRow(Element& element, PassRef<RenderStyle> style) - : RenderMathMLBlock(element, std::move(style)) +RenderMathMLRow::RenderMathMLRow(MathMLRowElement& element, RenderStyle&& style) + : RenderMathMLBlock(element, WTFMove(style)) { } -RenderMathMLRow::RenderMathMLRow(Document& document, PassRef<RenderStyle> style) - : RenderMathMLBlock(document, std::move(style)) +MathMLRowElement& RenderMathMLRow::element() const { + return static_cast<MathMLRowElement&>(nodeForNonAnonymous()); } -RenderPtr<RenderMathMLRow> RenderMathMLRow::createAnonymousWithParentRenderer(RenderMathMLRoot& parent) +std::optional<int> RenderMathMLRow::firstLineBaseline() const { - RenderPtr<RenderMathMLRow> newMRow = createRenderer<RenderMathMLRow>(parent.document(), RenderStyle::createAnonymousStyleWithDisplay(&parent.style(), FLEX)); - newMRow->initializeStyle(); - return newMRow; + auto* baselineChild = firstChildBox(); + if (!baselineChild) + return std::optional<int>(); + + return std::optional<int>(static_cast<int>(lroundf(ascentForChild(*baselineChild) + baselineChild->logicalTop()))); } -void RenderMathMLRow::layout() +void RenderMathMLRow::computeLineVerticalStretch(LayoutUnit& ascent, LayoutUnit& descent) { - int stretchLogicalHeight = 0; - for (RenderObject* child = firstChild(); child; child = child->nextSibling()) { - if (child->needsLayout()) - toRenderElement(child)->layout(); - // FIXME: Only skip renderMo if it is stretchy. - if (child->isRenderMathMLBlock() && toRenderMathMLBlock(child)->unembellishedOperator()) + for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) { + if (is<RenderMathMLBlock>(child)) { + auto* renderOperator = downcast<RenderMathMLBlock>(child)->unembellishedOperator(); + if (renderOperator && renderOperator->isStretchy()) + continue; + } + + child->layoutIfNeeded(); + + LayoutUnit childHeightAboveBaseline = ascentForChild(*child); + LayoutUnit childDepthBelowBaseline = child->logicalHeight() - childHeightAboveBaseline; + + ascent = std::max(ascent, childHeightAboveBaseline); + descent = std::max(descent, childDepthBelowBaseline); + } + + // We ensure a minimal stretch size. + if (ascent + descent <= 0) { + ascent = style().fontSize(); + descent = 0; + } +} + +void RenderMathMLRow::computePreferredLogicalWidths() +{ + ASSERT(preferredLogicalWidthsDirty()); + + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = 0; + + LayoutUnit preferredWidth = 0; + for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) + preferredWidth += child->maxPreferredLogicalWidth() + child->marginLogicalWidth(); + + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = preferredWidth + borderAndPaddingLogicalWidth(); + + setPreferredLogicalWidthsDirty(false); +} + +void RenderMathMLRow::layoutRowItems(LayoutUnit& ascent, LayoutUnit& descent) +{ + // We first stretch the vertical operators. + // For inline formulas, we can then calculate the logical width. + LayoutUnit width = borderAndPaddingStart(); + for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) { + if (child->isOutOfFlowPositioned()) continue; - if (child->isBox()) - stretchLogicalHeight = std::max<int>(stretchLogicalHeight, roundToInt(toRenderBox(child)->logicalHeight())); + + if (is<RenderMathMLBlock>(child)) { + auto renderOperator = downcast<RenderMathMLBlock>(child)->unembellishedOperator(); + if (renderOperator && renderOperator->isStretchy() && renderOperator->isVertical()) + renderOperator->stretchTo(ascent, descent); + } + + child->layoutIfNeeded(); + + width += child->marginStart() + child->logicalWidth() + child->marginEnd(); } - if (!stretchLogicalHeight) - stretchLogicalHeight = style().fontSize(); - - // Set the sizes of (possibly embellished) stretchy operator children. - for (auto& child : childrenOfType<RenderMathMLBlock>(*this)) { - if (auto renderMo = child.unembellishedOperator()) { - if (renderMo->stretchHeight() != stretchLogicalHeight) - renderMo->stretchToHeight(stretchLogicalHeight); + + width += borderEnd() + paddingEnd(); + if ((!isRenderMathMLMath() || style().display() == INLINE)) + setLogicalWidth(width); + + LayoutUnit verticalOffset = borderTop() + paddingTop(); + LayoutUnit maxAscent = 0, maxDescent = 0; // Used baseline alignment. + LayoutUnit horizontalOffset = borderAndPaddingStart(); + bool shouldFlipHorizontal = !style().isLeftToRightDirection(); + for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) { + if (child->isOutOfFlowPositioned()) { + child->containingBlock()->insertPositionedObject(*child); + continue; } + LayoutUnit childHorizontalExtent = child->logicalWidth(); + LayoutUnit ascent = ascentForChild(*child); + LayoutUnit descent = child->verticalMarginExtent() + child->logicalHeight() - ascent; + maxAscent = std::max(maxAscent, ascent); + maxDescent = std::max(maxDescent, descent); + LayoutUnit childVerticalMarginBoxExtent = maxAscent + maxDescent; + + horizontalOffset += child->marginStart(); + + setLogicalHeight(std::max(logicalHeight(), verticalOffset + borderBottom() + paddingBottom() + childVerticalMarginBoxExtent + horizontalScrollbarHeight())); + + LayoutPoint childLocation(shouldFlipHorizontal ? logicalWidth() - horizontalOffset - childHorizontalExtent : horizontalOffset, verticalOffset + child->marginTop()); + child->setLocation(childLocation); + + horizontalOffset += childHorizontalExtent + child->marginEnd(); } - RenderMathMLBlock::layout(); + LayoutUnit centerBlockOffset = 0; + if (style().display() == BLOCK) + centerBlockOffset = std::max<LayoutUnit>(0, (logicalWidth() - (horizontalOffset + borderEnd() + paddingEnd())) / 2); + + if (shouldFlipHorizontal && centerBlockOffset > 0) + centerBlockOffset = -centerBlockOffset; + + for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) { + LayoutUnit ascent = ascentForChild(*child); + LayoutUnit startOffset = maxAscent - ascent; + child->setLocation(child->location() + LayoutPoint(centerBlockOffset, startOffset)); + } + + ascent = maxAscent; + descent = maxDescent; +} + +void RenderMathMLRow::layoutBlock(bool relayoutChildren, LayoutUnit) +{ + ASSERT(needsLayout()); + + if (!relayoutChildren && simplifiedLayout()) + return; + + LayoutUnit ascent = 0; + LayoutUnit descent = 0; + computeLineVerticalStretch(ascent, descent); + + recomputeLogicalWidth(); + + setLogicalHeight(borderAndPaddingLogicalHeight() + scrollbarLogicalHeight()); + + layoutRowItems(ascent, descent); + + updateLogicalHeight(); + + clearNeedsLayout(); } } diff --git a/Source/WebCore/rendering/mathml/RenderMathMLRow.h b/Source/WebCore/rendering/mathml/RenderMathMLRow.h index 16ddfcdea..f9c16789b 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLRow.h +++ b/Source/WebCore/rendering/mathml/RenderMathMLRow.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 Alex Milowski (alex@milowski.com). 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 @@ -23,8 +24,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef RenderMathMLRow_h -#define RenderMathMLRow_h +#pragma once #if ENABLE(MATHML) @@ -32,24 +32,28 @@ namespace WebCore { -class RenderMathMLRoot; +class MathMLRowElement; class RenderMathMLRow : public RenderMathMLBlock { public: - RenderMathMLRow(Element&, PassRef<RenderStyle>); - RenderMathMLRow(Document&, PassRef<RenderStyle>); - - static RenderPtr<RenderMathMLRow> createAnonymousWithParentRenderer(RenderMathMLRoot&); + RenderMathMLRow(MathMLRowElement&, RenderStyle&&); + MathMLRowElement& element() const; protected: - virtual void layout(); + void layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight = 0) override; + std::optional<int> firstLineBaseline() const override; + + void layoutRowItems(LayoutUnit& ascent, LayoutUnit& descent); + void computeLineVerticalStretch(LayoutUnit& ascent, LayoutUnit& descent); + void computePreferredLogicalWidths() override; private: - virtual bool isRenderMathMLRow() const override final { return true; } - virtual const char* renderName() const override { return isAnonymous() ? "RenderMathMLRow (anonymous)" : "RenderMathMLRow"; } + bool isRenderMathMLRow() const final { return true; } + const char* renderName() const override { return "RenderMathMLRow"; } }; -} +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_RENDER_OBJECT(RenderMathMLRow, isRenderMathMLRow()) #endif // ENABLE(MATHML) -#endif // RenderMathMLRow_h diff --git a/Source/WebCore/rendering/mathml/RenderMathMLScripts.cpp b/Source/WebCore/rendering/mathml/RenderMathMLScripts.cpp index 9f3bb8eff..05d50e0c9 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLScripts.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLScripts.cpp @@ -1,6 +1,7 @@ /* * Copyright (C) 2010 Alex Milowski (alex@milowski.com). All rights reserved. * Copyright (C) 2013 The MathJax Consortium. + * 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 @@ -25,498 +26,460 @@ */ #include "config.h" +#include "RenderMathMLScripts.h" #if ENABLE(MATHML) -#include "RenderMathMLScripts.h" - -#include "MathMLNames.h" +#include "MathMLElement.h" +#include "MathMLScriptsElement.h" +#include "RenderMathMLOperator.h" namespace WebCore { - -using namespace MathMLNames; - -// RenderMathMLScripts implements various MathML elements drawing scripts attached to a base. For valid MathML elements, the structure of the render tree should be: -// -// - msub, msup, msubsup: BaseWrapper SubSupPairWrapper -// - mmultiscripts: BaseWrapper SubSupPairWrapper* (mprescripts SubSupPairWrapper*)* -// -// where BaseWrapper and SubSupPairWrapper do not contain any <mprescripts/> children. In addition, BaseWrapper must have one child and SubSupPairWrapper must have either one child (msub, msup) or two children (msubsup, mmultiscripts). -// -// In order to accept invalid markup and to handle the script elements consistently and uniformly, we will use a more general structure that encompasses both valid and invalid elements: -// -// BaseWrapper SubSupPairWrapper* (mprescripts SubSupPairWrapper*)* -// -// where BaseWrapper can now be empty and SubSupPairWrapper can now have one or two elements. -// - -static bool isPrescript(RenderObject* renderObject) + +static bool isPrescriptDelimiter(const RenderObject& renderObject) { - ASSERT(renderObject); - return renderObject->node() && renderObject->node()->hasTagName(MathMLNames::mprescriptsTag); + return renderObject.node() && renderObject.node()->hasTagName(MathMLNames::mprescriptsTag); } -RenderMathMLScripts::RenderMathMLScripts(Element& element, PassRef<RenderStyle> style) - : RenderMathMLBlock(element, std::move(style)) - , m_baseWrapper(0) +RenderMathMLScripts::RenderMathMLScripts(MathMLScriptsElement& element, RenderStyle&& style) + : RenderMathMLBlock(element, WTFMove(style)) { // Determine what kind of sub/sup expression we have by element name - if (element.hasLocalName(MathMLNames::msubTag)) - m_kind = Sub; - else if (element.hasLocalName(MathMLNames::msupTag)) - m_kind = Super; - else if (element.hasLocalName(MathMLNames::msubsupTag)) - m_kind = SubSup; + if (element.hasTagName(MathMLNames::msubTag)) + m_scriptType = Sub; + else if (element.hasTagName(MathMLNames::msupTag)) + m_scriptType = Super; + else if (element.hasTagName(MathMLNames::msubsupTag)) + m_scriptType = SubSup; + else if (element.hasTagName(MathMLNames::munderTag)) + m_scriptType = Under; + else if (element.hasTagName(MathMLNames::moverTag)) + m_scriptType = Over; + else if (element.hasTagName(MathMLNames::munderoverTag)) + m_scriptType = UnderOver; else { - ASSERT(element.hasLocalName(MathMLNames::mmultiscriptsTag)); - m_kind = Multiscripts; + ASSERT(element.hasTagName(MathMLNames::mmultiscriptsTag)); + m_scriptType = Multiscripts; } } -RenderBoxModelObject* RenderMathMLScripts::base() const +MathMLScriptsElement& RenderMathMLScripts::element() const { - if (!m_baseWrapper) - return 0; - RenderObject* base = m_baseWrapper->firstChild(); - if (!base || !base->isBoxModelObject()) - return 0; - return toRenderBoxModelObject(base); + return static_cast<MathMLScriptsElement&>(nodeForNonAnonymous()); } -void RenderMathMLScripts::fixAnonymousStyleForSubSupPair(RenderObject* subSupPair, bool isPostScript) +RenderMathMLOperator* RenderMathMLScripts::unembellishedOperator() { - ASSERT(subSupPair && subSupPair->style().refCount() == 1); - RenderStyle& scriptsStyle = subSupPair->style(); - - // subSup pairs are drawn in column from bottom (subscript) to top (superscript). - scriptsStyle.setFlexDirection(FlowColumnReverse); - - // The MathML specification does not specify horizontal alignment of - // scripts. We align the bottom (respectively top) edge of the subscript - // (respectively superscript) with the bottom (respectively top) edge of - // the flex container. Note that for valid <msub> and <msup> elements, the - // subSupPair should actually have only one script. - scriptsStyle.setJustifyContent(m_kind == Sub ? JustifyFlexStart : m_kind == Super ? JustifyFlexEnd : JustifySpaceBetween); - - // The MathML specification does not specify vertical alignment of scripts. - // Let's right align prescripts and left align postscripts. - // See http://lists.w3.org/Archives/Public/www-math/2012Aug/0006.html - scriptsStyle.setAlignItems(isPostScript ? AlignFlexStart : AlignFlexEnd); - - // We set the order property so that the prescripts are drawn before the base. - scriptsStyle.setOrder(isPostScript ? 0 : -1); - - // We set this wrapper's font-size for its line-height. - LayoutUnit scriptSize = static_cast<int>(0.75 * style().fontSize()); - scriptsStyle.setFontSize(scriptSize); + auto base = firstChildBox(); + if (!is<RenderMathMLBlock>(base)) + return nullptr; + return downcast<RenderMathMLBlock>(base)->unembellishedOperator(); } -void RenderMathMLScripts::fixAnonymousStyles() +std::optional<RenderMathMLScripts::ReferenceChildren> RenderMathMLScripts::validateAndGetReferenceChildren() { - // We set the base wrapper's style so that baseHeight in layout() will be an unstretched height. - ASSERT(m_baseWrapper && m_baseWrapper->style().hasOneRef()); - m_baseWrapper->style().setAlignSelf(AlignFlexStart); - - // This sets the style for postscript pairs. - RenderObject* subSupPair = m_baseWrapper; - for (subSupPair = subSupPair->nextSibling(); subSupPair && !isPrescript(subSupPair); subSupPair = subSupPair->nextSibling()) - fixAnonymousStyleForSubSupPair(subSupPair, true); - - if (subSupPair && m_kind == Multiscripts) { - // This sets the style for prescript pairs. - for (subSupPair = subSupPair->nextSibling(); subSupPair && !isPrescript(subSupPair); subSupPair = subSupPair->nextSibling()) - fixAnonymousStyleForSubSupPair(subSupPair, false); - } - - // This resets style for extra subSup pairs. - for (; subSupPair; subSupPair = subSupPair->nextSibling()) { - if (!isPrescript(subSupPair)) { - ASSERT(subSupPair && subSupPair->style().refCount() == 1); - RenderStyle& scriptsStyle = subSupPair->style(); - scriptsStyle.setFlexDirection(FlowRow); - scriptsStyle.setJustifyContent(JustifyFlexStart); - scriptsStyle.setAlignItems(AlignCenter); - scriptsStyle.setOrder(0); - scriptsStyle.setFontSize(style().fontSize()); - } + // All scripted elements must have at least one child. + // The first child is the base. + auto base = firstChildBox(); + if (!base) + return std::nullopt; + + ReferenceChildren reference; + reference.base = base; + reference.firstPostScript = nullptr; + reference.firstPreScript = nullptr; + reference.prescriptDelimiter = nullptr; + + switch (m_scriptType) { + case Sub: + case Super: + case Under: + case Over: { + // These elements must have exactly two children. + // The second child is a postscript and there are no prescripts. + // <msub> base subscript </msub> + // <msup> base superscript </msup> + // <munder> base underscript </munder> + // <mover> base overscript </mover> + auto script = base->nextSiblingBox(); + if (!script || isPrescriptDelimiter(*script) || script->nextSiblingBox()) + return std::nullopt; + reference.firstPostScript = script; + return reference; } -} - -void RenderMathMLScripts::addChildInternal(bool doNotRestructure, RenderObject* child, RenderObject* beforeChild) -{ - if (doNotRestructure) { - RenderMathMLBlock::addChild(child, beforeChild); - return; + case SubSup: + case UnderOver: { + // These elements must have exactly three children. + // The second and third children are postscripts and there are no prescripts. + // <msubsup> base subscript superscript </msubsup> + // <munderover> base subscript superscript </munderover> + auto subScript = base->nextSiblingBox(); + if (!subScript || isPrescriptDelimiter(*subScript)) + return std::nullopt; + auto superScript = subScript->nextSiblingBox(); + if (!superScript || isPrescriptDelimiter(*superScript) || superScript->nextSiblingBox()) + return std::nullopt; + reference.firstPostScript = subScript; + return reference; } - - if (beforeChild) { - // beforeChild may be a grandchild, so we call the addChild function of the corresponding wrapper instead. - RenderObject* parent = beforeChild->parent(); - if (parent != this) { - RenderMathMLBlock* parentBlock = toRenderMathMLBlock(parent); - if (parentBlock->isRenderMathMLScriptsWrapper()) { - RenderMathMLScriptsWrapper* wrapper = toRenderMathMLScriptsWrapper(parentBlock); - wrapper->addChildInternal(false, child, beforeChild); - return; + case Multiscripts: { + // This element accepts the following syntax: + // + // <mmultiscripts> + // base + // (subscript superscript)* + // [ <mprescripts/> (presubscript presuperscript)* ] + // </mmultiscripts> + // + // https://www.w3.org/TR/MathML3/chapter3.html#presm.mmultiscripts + // + // We set the first postscript, unless (subscript superscript)* is empty. + if (base->nextSiblingBox() && !isPrescriptDelimiter(*base->nextSiblingBox())) + reference.firstPostScript = base->nextSiblingBox(); + + // We browse the children in order to + // 1) Set the first prescript, unless (presubscript presuperscript)* is empty. + // 2) Ensure the validity of the element i.e. + // a) That the list of postscripts can be grouped into pairs of subscript/superscript. + // b) That the list of prescripts can be grouped into pairs of subscript/superscript. + // c) That there is at most one <mprescripts/>. + bool numberOfScriptIsEven = true; + for (auto script = base->nextSiblingBox(); script; script = script->nextSiblingBox()) { + if (isPrescriptDelimiter(*script)) { + // This is a <mprescripts/>. Let's check 2a) and 2c). + if (!numberOfScriptIsEven || reference.firstPreScript) + return std::nullopt; + reference.firstPreScript = script->nextSiblingBox(); // We do 1). + reference.prescriptDelimiter = script; + continue; } + numberOfScriptIsEven = !numberOfScriptIsEven; } + return numberOfScriptIsEven ? std::optional<ReferenceChildren>(reference) : std::nullopt; // We verify 2b). } - - if (beforeChild == m_baseWrapper) { - // This is like inserting the child at the beginning of the base wrapper. - m_baseWrapper->addChildInternal(false, child, m_baseWrapper->firstChild()); - return; - } - - if (isPrescript(child)) { - // The new child becomes an <mprescripts/> separator. - RenderMathMLBlock::addChild(child, beforeChild); - return; - } - - if (!beforeChild || isPrescript(beforeChild)) { - // We are at the end of a sequence of subSup pairs. - RenderMathMLBlock* previousSibling = toRenderMathMLBlock(beforeChild ? beforeChild->previousSibling() : lastChild()); - if (previousSibling && previousSibling->isRenderMathMLScriptsWrapper()) { - RenderMathMLScriptsWrapper* wrapper = toRenderMathMLScriptsWrapper(previousSibling); - if ((wrapper->m_kind == RenderMathMLScriptsWrapper::Base && wrapper->isEmpty()) || (wrapper->m_kind == RenderMathMLScriptsWrapper::SubSupPair && !wrapper->firstChild()->nextSibling())) { - // The previous sibling is either an empty base or a SubSup pair with a single child so we can insert the new child into that wrapper. - wrapper->addChildInternal(true, child); - return; - } - } - // Otherwise we create a new subSupPair to store the new child. - RenderMathMLScriptsWrapper* subSupPair = RenderMathMLScriptsWrapper::createAnonymousWrapper(this, RenderMathMLScriptsWrapper::SubSupPair); - subSupPair->addChildInternal(true, child); - RenderMathMLBlock::addChild(subSupPair, beforeChild); - return; } - // beforeChild is a subSup pair. This is like inserting the new child at the beginning of the subSup wrapper. - RenderMathMLScriptsWrapper* wrapper = toRenderMathMLScriptsWrapper(beforeChild); - ASSERT(wrapper->m_kind == RenderMathMLScriptsWrapper::SubSupPair); - ASSERT(!(m_baseWrapper->isEmpty() && m_baseWrapper->nextSibling() == beforeChild)); - wrapper->addChildInternal(false, child, wrapper->firstChild()); + ASSERT_NOT_REACHED(); + return std::nullopt; } -void RenderMathMLScripts::removeChildInternal(bool doNotRestructure, RenderObject& child) +LayoutUnit RenderMathMLScripts::spaceAfterScript() { - if (doNotRestructure) { - RenderMathMLBlock::removeChild(child); - return; - } - - ASSERT(isPrescript(&child)); - - RenderObject* previousSibling = child.previousSibling(); - RenderObject* nextSibling = child.nextSibling(); - ASSERT(previousSibling); - - if (nextSibling && !isPrescript(previousSibling) && !isPrescript(nextSibling)) { - RenderMathMLScriptsWrapper* previousWrapper = toRenderMathMLScriptsWrapper(previousSibling); - RenderMathMLScriptsWrapper* nextWrapper = toRenderMathMLScriptsWrapper(nextSibling); - ASSERT(nextWrapper->m_kind == RenderMathMLScriptsWrapper::SubSupPair && !nextWrapper->isEmpty()); - if ((previousWrapper->m_kind == RenderMathMLScriptsWrapper::Base && previousWrapper->isEmpty()) || (previousWrapper->m_kind == RenderMathMLScriptsWrapper::SubSupPair && !previousWrapper->firstChild()->nextSibling())) { - RenderObject* script = nextWrapper->firstChild(); - nextWrapper->removeChildInternal(false, *script); - previousWrapper->addChildInternal(true, script); - } - } - - RenderMathMLBlock::removeChild(child); + const auto& primaryFont = style().fontCascade().primaryFont(); + if (auto* mathData = primaryFont.mathData()) + return mathData->getMathConstant(primaryFont, OpenTypeMathData::SpaceAfterScript); + return style().fontCascade().size() / 5; } -void RenderMathMLScripts::addChild(RenderObject* child, RenderObject* beforeChild) +LayoutUnit RenderMathMLScripts::italicCorrection(const ReferenceChildren& reference) { - if (isEmpty()) { - m_baseWrapper = RenderMathMLScriptsWrapper::createAnonymousWrapper(this, RenderMathMLScriptsWrapper::Base); - RenderMathMLBlock::addChild(m_baseWrapper); + if (is<RenderMathMLBlock>(*reference.base)) { + if (auto* renderOperator = downcast<RenderMathMLBlock>(*reference.base).unembellishedOperator()) + return renderOperator->italicCorrection(); } - - addChildInternal(false, child, beforeChild); - - fixAnonymousStyles(); + return 0; } -void RenderMathMLScripts::removeChild(RenderObject& child) +void RenderMathMLScripts::computePreferredLogicalWidths() { - if (beingDestroyed() || documentBeingDestroyed()) { - // The renderer is being destroyed so we remove the child normally. - RenderMathMLBlock::removeChild(child); - return; - } + ASSERT(preferredLogicalWidthsDirty()); - removeChildInternal(false, child); - - fixAnonymousStyles(); -} + m_minPreferredLogicalWidth = 0; + m_maxPreferredLogicalWidth = 0; -void RenderMathMLScripts::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) -{ - RenderMathMLBlock::styleDidChange(diff, oldStyle); - - if (!isEmpty()) - fixAnonymousStyles(); -} - -RenderMathMLOperator* RenderMathMLScripts::unembellishedOperator() -{ - RenderBoxModelObject* base = this->base(); - if (!base || !base->isRenderMathMLBlock()) - return 0; - return toRenderMathMLBlock(base)->unembellishedOperator(); -} - -void RenderMathMLScripts::layout() -{ - RenderMathMLBlock::layout(); - - if (!m_baseWrapper) - return; - RenderBox* base = m_baseWrapper->firstChildBox(); - if (!base) + auto possibleReference = validateAndGetReferenceChildren(); + if (!possibleReference) { + setPreferredLogicalWidthsDirty(false); return; - - // Our layout rules include: Don't let the superscript go below the "axis" (half x-height above the - // baseline), or the subscript above the axis. Also, don't let the superscript's top edge be - // below the base's top edge, or the subscript's bottom edge above the base's bottom edge. - - LayoutUnit baseHeight = base->logicalHeight(); - LayoutUnit baseBaseline = base->firstLineBaseline(); - if (baseBaseline == -1) - baseBaseline = baseHeight; - LayoutUnit axis = style().fontMetrics().xHeight() / 2; - int fontSize = style().fontSize(); - - ASSERT(m_baseWrapper->style().hasOneRef()); - bool needsSecondLayout = false; - - LayoutUnit topPadding = 0; - LayoutUnit bottomPadding = 0; - - Element* scriptElement = element(); - LayoutUnit superscriptShiftValue = 0; - LayoutUnit subscriptShiftValue = 0; - if (m_kind == Sub || m_kind == SubSup || m_kind == Multiscripts) - parseMathMLLength(scriptElement->fastGetAttribute(MathMLNames::subscriptshiftAttr), subscriptShiftValue, &style(), false); - if (m_kind == Super || m_kind == SubSup || m_kind == Multiscripts) - parseMathMLLength(scriptElement->fastGetAttribute(MathMLNames::superscriptshiftAttr), superscriptShiftValue, &style(), false); - - bool isPostScript = true; - RenderMathMLBlock* subSupPair = toRenderMathMLBlock(m_baseWrapper->nextSibling()); - for (; subSupPair; subSupPair = toRenderMathMLBlock(subSupPair->nextSibling())) { - - // We skip the base and <mprescripts/> elements. - if (isPrescript(subSupPair)) { - if (!isPostScript) - break; - isPostScript = false; - continue; - } - - if (RenderBox* superscript = m_kind == Sub ? 0 : subSupPair->lastChildBox()) { - LayoutUnit superscriptHeight = superscript->logicalHeight(); - LayoutUnit superscriptBaseline = superscript->firstLineBaseline(); - if (superscriptBaseline == -1) - superscriptBaseline = superscriptHeight; - LayoutUnit minBaseline = std::max<LayoutUnit>(fontSize / 3 + 1 + superscriptBaseline, superscriptHeight + axis + superscriptShiftValue); - - topPadding = std::max<LayoutUnit>(topPadding, minBaseline - baseBaseline); + } + auto& reference = possibleReference.value(); + + LayoutUnit baseItalicCorrection = std::min(reference.base->maxPreferredLogicalWidth(), italicCorrection(reference)); + LayoutUnit space = spaceAfterScript(); + + switch (m_scriptType) { + case Sub: + case Under: + m_maxPreferredLogicalWidth += reference.base->maxPreferredLogicalWidth(); + m_maxPreferredLogicalWidth += std::max(LayoutUnit(0), reference.firstPostScript->maxPreferredLogicalWidth() - baseItalicCorrection + space); + break; + case Super: + case Over: + m_maxPreferredLogicalWidth += reference.base->maxPreferredLogicalWidth(); + m_maxPreferredLogicalWidth += std::max(LayoutUnit(0), reference.firstPostScript->maxPreferredLogicalWidth() + space); + break; + case SubSup: + case UnderOver: + case Multiscripts: { + auto subScript = reference.firstPreScript; + while (subScript) { + auto supScript = subScript->nextSiblingBox(); + ASSERT(supScript); + LayoutUnit subSupPairWidth = std::max(subScript->maxPreferredLogicalWidth(), supScript->maxPreferredLogicalWidth()); + m_maxPreferredLogicalWidth += subSupPairWidth + space; + subScript = supScript->nextSiblingBox(); } - - if (RenderBox* subscript = m_kind == Super ? 0 : subSupPair->firstChildBox()) { - LayoutUnit subscriptHeight = subscript->logicalHeight(); - LayoutUnit subscriptBaseline = subscript->firstLineBaseline(); - if (subscriptBaseline == -1) - subscriptBaseline = subscriptHeight; - LayoutUnit baseExtendUnderBaseline = baseHeight - baseBaseline; - LayoutUnit subscriptUnderItsBaseline = subscriptHeight - subscriptBaseline; - LayoutUnit minExtendUnderBaseline = std::max<LayoutUnit>(fontSize / 5 + 1 + subscriptUnderItsBaseline, subscriptHeight + subscriptShiftValue - axis); - - bottomPadding = std::max<LayoutUnit>(bottomPadding, minExtendUnderBaseline - baseExtendUnderBaseline); + m_maxPreferredLogicalWidth += reference.base->maxPreferredLogicalWidth(); + subScript = reference.firstPostScript; + while (subScript && subScript != reference.prescriptDelimiter) { + auto supScript = subScript->nextSiblingBox(); + ASSERT(supScript); + LayoutUnit subSupPairWidth = std::max(std::max(LayoutUnit(0), subScript->maxPreferredLogicalWidth() - baseItalicCorrection), supScript->maxPreferredLogicalWidth()); + m_maxPreferredLogicalWidth += subSupPairWidth + space; + subScript = supScript->nextSiblingBox(); } } - - Length newPadding(topPadding, Fixed); - if (newPadding != m_baseWrapper->style().paddingTop()) { - m_baseWrapper->style().setPaddingTop(newPadding); - needsSecondLayout = true; - } - - newPadding = Length(bottomPadding, Fixed); - if (newPadding != m_baseWrapper->style().paddingBottom()) { - m_baseWrapper->style().setPaddingBottom(newPadding); - needsSecondLayout = true; } - if (!needsSecondLayout) - return; - - setNeedsLayout(MarkOnlyThis); - m_baseWrapper->setChildNeedsLayout(MarkOnlyThis); + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth; - RenderMathMLBlock::layout(); + setPreferredLogicalWidthsDirty(false); } -int RenderMathMLScripts::firstLineBaseline() const +auto RenderMathMLScripts::verticalParameters() const -> VerticalParameters { - if (m_baseWrapper) { - LayoutUnit baseline = m_baseWrapper->firstLineBaseline(); - if (baseline != -1) - return baseline; + VerticalParameters parameters; + const auto& primaryFont = style().fontCascade().primaryFont(); + if (auto* mathData = primaryFont.mathData()) { + parameters.subscriptShiftDown = mathData->getMathConstant(primaryFont, OpenTypeMathData::SubscriptShiftDown); + parameters.superscriptShiftUp = mathData->getMathConstant(primaryFont, OpenTypeMathData::SuperscriptShiftUp); + parameters.subscriptBaselineDropMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::SubscriptBaselineDropMin); + parameters.superScriptBaselineDropMax = mathData->getMathConstant(primaryFont, OpenTypeMathData::SuperscriptBaselineDropMax); + parameters.subSuperscriptGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::SubSuperscriptGapMin); + parameters.superscriptBottomMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::SuperscriptBottomMin); + parameters.subscriptTopMax = mathData->getMathConstant(primaryFont, OpenTypeMathData::SubscriptTopMax); + parameters.superscriptBottomMaxWithSubscript = mathData->getMathConstant(primaryFont, OpenTypeMathData::SuperscriptBottomMaxWithSubscript); + } else { + // Default heuristic values when you do not have a font. + parameters.subscriptShiftDown = style().fontMetrics().xHeight() / 3; + parameters.superscriptShiftUp = style().fontMetrics().xHeight(); + parameters.subscriptBaselineDropMin = style().fontMetrics().xHeight() / 2; + parameters.superScriptBaselineDropMax = style().fontMetrics().xHeight() / 2; + parameters.subSuperscriptGapMin = style().fontCascade().size() / 5; + parameters.superscriptBottomMin = style().fontMetrics().xHeight() / 4; + parameters.subscriptTopMax = 4 * style().fontMetrics().xHeight() / 5; + parameters.superscriptBottomMaxWithSubscript = 4 * style().fontMetrics().xHeight() / 5; } - return RenderMathMLBlock::firstLineBaseline(); + return parameters; } -RenderMathMLScriptsWrapper* RenderMathMLScriptsWrapper::createAnonymousWrapper(RenderMathMLScripts* renderObject, WrapperType type) +RenderMathMLScripts::VerticalMetrics RenderMathMLScripts::verticalMetrics(const ReferenceChildren& reference) { - RenderMathMLScriptsWrapper* newBlock = new RenderMathMLScriptsWrapper(renderObject->document(), RenderStyle::createAnonymousStyleWithDisplay(&renderObject->style(), FLEX), type); - newBlock->initializeStyle(); - return newBlock; -} - -void RenderMathMLScriptsWrapper::addChildInternal(bool doNotRestructure, RenderObject* child, RenderObject* beforeChild) -{ - if (doNotRestructure) { - RenderMathMLBlock::addChild(child, beforeChild); - return; - } - - RenderMathMLScripts* parentNode = toRenderMathMLScripts(parent()); - - if (m_kind == Base) { - RenderObject* sibling = nextSibling(); - - if (!isEmpty() && !beforeChild) { - // This is like inserting the child after the base wrapper. - parentNode->addChildInternal(false, sibling); - return; + VerticalParameters parameters = verticalParameters(); + VerticalMetrics metrics = { 0, 0, 0, 0 }; + + LayoutUnit baseAscent = ascentForChild(*reference.base); + LayoutUnit baseDescent = reference.base->logicalHeight() - baseAscent; + if (m_scriptType == Sub || m_scriptType == SubSup || m_scriptType == Multiscripts || m_scriptType == Under || m_scriptType == UnderOver) { + metrics.subShift = std::max(parameters.subscriptShiftDown, baseDescent + parameters.subscriptBaselineDropMin); + if (!isRenderMathMLUnderOver()) { + // It is not clear how to interpret the default shift and it is not available yet anyway. + // Hence we just pass 0 as the default value used by toUserUnits. + LayoutUnit specifiedMinSubShift = toUserUnits(element().subscriptShift(), style(), 0); + metrics.subShift = std::max(metrics.subShift, specifiedMinSubShift); } - - // The old base (if any) becomes a script ; the new child becomes either the base or an <mprescripts> separator. - RenderObject* oldBase = firstChild(); - if (oldBase) - RenderMathMLBlock::removeChild(*oldBase); - if (isPrescript(child)) - parentNode->addChildInternal(true, child, sibling); - else - RenderMathMLBlock::addChild(child); - if (oldBase) - parentNode->addChildInternal(false, oldBase, sibling); - return; } - - if (isPrescript(child)) { - // We insert an <mprescripts> element. - if (!beforeChild) - parentNode->addChildInternal(true, child, nextSibling()); - else if (beforeChild == firstChild()) - parentNode->addChildInternal(true, child, this); - else { - // We insert the <mprescripts> in the middle of a subSup pair so we must split that pair. - RenderObject* sibling = nextSibling(); - parentNode->removeChildInternal(true, *this); - parentNode->addChildInternal(true, child, sibling); - - RenderObject* script = firstChild(); - RenderMathMLBlock::removeChild(*script); - parentNode->addChildInternal(false, script, child); - - script = beforeChild; - RenderMathMLBlock::removeChild(*script); - parentNode->addChildInternal(false, script, sibling); - destroy(); + if (m_scriptType == Super || m_scriptType == SubSup || m_scriptType == Multiscripts || m_scriptType == Over || m_scriptType == UnderOver) { + metrics.supShift = std::max(parameters.superscriptShiftUp, baseAscent - parameters.superScriptBaselineDropMax); + if (!isRenderMathMLUnderOver()) { + // It is not clear how to interpret the default shift and it is not available yet anyway. + // Hence we just pass 0 as the default value used by toUserUnits. + LayoutUnit specifiedMinSupShift = toUserUnits(element().superscriptShift(), style(), 0); + metrics.supShift = std::max(metrics.supShift, specifiedMinSupShift); } - return; } - // We first move to the last subSup pair in the curent sequence of scripts. - RenderMathMLScriptsWrapper* subSupPair = this; - while (subSupPair->nextSibling() && !isPrescript(subSupPair->nextSibling())) - subSupPair = toRenderMathMLScriptsWrapper(subSupPair->nextSibling()); - if (subSupPair->firstChild()->nextSibling()) { - // The last pair has two children so we need to create a new pair to leave room for the new child. - RenderMathMLScriptsWrapper* newPair = createAnonymousWrapper(parentNode, RenderMathMLScriptsWrapper::SubSupPair); - parentNode->addChildInternal(true, newPair, subSupPair->nextSibling()); - subSupPair = newPair; + switch (m_scriptType) { + case Sub: + case Under: { + LayoutUnit subAscent = ascentForChild(*reference.firstPostScript); + LayoutUnit subDescent = reference.firstPostScript->logicalHeight() - subAscent; + metrics.descent = subDescent; + metrics.subShift = std::max(metrics.subShift, subAscent - parameters.subscriptTopMax); } + break; + case Super: + case Over: { + LayoutUnit supAscent = ascentForChild(*reference.firstPostScript); + LayoutUnit supDescent = reference.firstPostScript->logicalHeight() - supAscent; + metrics.ascent = supAscent; + metrics.supShift = std::max(metrics.supShift, parameters.superscriptBottomMin + supDescent); + } + break; + case SubSup: + case UnderOver: + case Multiscripts: { + // FIXME: We should move the code updating VerticalMetrics for each sub/sup pair in a helper + // function. That way, SubSup/UnderOver can just make one call and the loop for Multiscripts + // can be rewritten in a more readable. + auto subScript = reference.firstPostScript ? reference.firstPostScript : reference.firstPreScript; + while (subScript) { + auto supScript = subScript->nextSiblingBox(); + ASSERT(supScript); + LayoutUnit subAscent = ascentForChild(*subScript); + LayoutUnit subDescent = subScript->logicalHeight() - subAscent; + LayoutUnit supAscent = ascentForChild(*supScript); + LayoutUnit supDescent = supScript->logicalHeight() - supAscent; + metrics.ascent = std::max(metrics.ascent, supAscent); + metrics.descent = std::max(metrics.descent, subDescent); + LayoutUnit subScriptShift = std::max(parameters.subscriptShiftDown, baseDescent + parameters.subscriptBaselineDropMin); + subScriptShift = std::max(subScriptShift, subAscent - parameters.subscriptTopMax); + LayoutUnit supScriptShift = std::max(parameters.superscriptShiftUp, baseAscent - parameters.superScriptBaselineDropMax); + supScriptShift = std::max(supScriptShift, parameters.superscriptBottomMin + supDescent); + + LayoutUnit subSuperscriptGap = (subScriptShift - subAscent) + (supScriptShift - supDescent); + if (subSuperscriptGap < parameters.subSuperscriptGapMin) { + // First, we try and push the superscript up. + LayoutUnit delta = parameters.superscriptBottomMaxWithSubscript - (supScriptShift - supDescent); + if (delta > 0) { + delta = std::min(delta, parameters.subSuperscriptGapMin - subSuperscriptGap); + supScriptShift += delta; + subSuperscriptGap += delta; + } + // If that is not enough, we push the subscript down. + if (subSuperscriptGap < parameters.subSuperscriptGapMin) + subScriptShift += parameters.subSuperscriptGapMin - subSuperscriptGap; + } + + metrics.subShift = std::max(metrics.subShift, subScriptShift); + metrics.supShift = std::max(metrics.supShift, supScriptShift); - // We shift the successors in the current sequence of scripts. - for (RenderObject* previousSibling = subSupPair->previousSibling(); subSupPair != this; previousSibling = previousSibling->previousSibling()) { - RenderMathMLScriptsWrapper* previousSubSupPair = toRenderMathMLScriptsWrapper(previousSibling); - RenderObject* script = previousSubSupPair->lastChild(); - previousSubSupPair->removeChildInternal(true, *script); - subSupPair->addChildInternal(true, script, subSupPair->firstChild()); - subSupPair = toRenderMathMLScriptsWrapper(previousSibling); + subScript = supScript->nextSiblingBox(); + if (subScript == reference.prescriptDelimiter) + subScript = reference.firstPreScript; + } + } } - // This subSup pair now contain one element which is either beforeChild or the script that was before. Hence we can insert the new child before of after that element. - RenderMathMLBlock::addChild(child, firstChild() == beforeChild ? beforeChild : 0); + return metrics; } -void RenderMathMLScriptsWrapper::addChild(RenderObject* child, RenderObject* beforeChild) +void RenderMathMLScripts::layoutBlock(bool relayoutChildren, LayoutUnit) { - RenderMathMLScripts* parentNode = toRenderMathMLScripts(parent()); - - addChildInternal(false, child, beforeChild); + ASSERT(needsLayout()); - parentNode->fixAnonymousStyles(); -} + if (!relayoutChildren && simplifiedLayout()) + return; -void RenderMathMLScriptsWrapper::removeChildInternal(bool doNotRestructure, RenderObject& child) -{ - if (doNotRestructure) { - RenderMathMLBlock::removeChild(child); + auto possibleReference = validateAndGetReferenceChildren(); + if (!possibleReference) { + layoutInvalidMarkup(); return; } - - RenderMathMLScripts* parentNode = toRenderMathMLScripts(parent()); - - if (m_kind == Base) { - // We remove the child from the base wrapper. - RenderObject* sibling = nextSibling(); - RenderMathMLBlock::removeChild(child); - if (sibling && !isPrescript(sibling)) { - // If there are postscripts, the first one becomes the base. - RenderMathMLScriptsWrapper* wrapper = toRenderMathMLScriptsWrapper(sibling); - RenderObject* script = wrapper->firstChild(); - wrapper->removeChildInternal(false, *script); - RenderMathMLBlock::addChild(script); + auto& reference = possibleReference.value(); + + recomputeLogicalWidth(); + for (auto child = firstChildBox(); child; child = child->nextSiblingBox()) + child->layoutIfNeeded(); + + LayoutUnit space = spaceAfterScript(); + + // We determine the minimal shift/size of each script and take the maximum of the values. + VerticalMetrics metrics = verticalMetrics(reference); + + LayoutUnit baseAscent = ascentForChild(*reference.base); + LayoutUnit baseDescent = reference.base->logicalHeight() - baseAscent; + LayoutUnit baseItalicCorrection = std::min(reference.base->logicalWidth(), italicCorrection(reference)); + LayoutUnit horizontalOffset = 0; + + LayoutUnit ascent = std::max(baseAscent, metrics.ascent + metrics.supShift); + LayoutUnit descent = std::max(baseDescent, metrics.descent + metrics.subShift); + setLogicalHeight(ascent + descent); + + switch (m_scriptType) { + case Sub: + case Under: { + setLogicalWidth(reference.base->logicalWidth() + std::max(LayoutUnit(0), reference.firstPostScript->logicalWidth() - baseItalicCorrection + space)); + LayoutPoint baseLocation(mirrorIfNeeded(horizontalOffset, *reference.base), ascent - baseAscent); + reference.base->setLocation(baseLocation); + horizontalOffset += reference.base->logicalWidth(); + LayoutUnit scriptAscent = ascentForChild(*reference.firstPostScript); + LayoutPoint scriptLocation(mirrorIfNeeded(horizontalOffset - baseItalicCorrection, *reference.firstPostScript), ascent + metrics.subShift - scriptAscent); + reference.firstPostScript->setLocation(scriptLocation); + } + break; + case Super: + case Over: { + setLogicalWidth(reference.base->logicalWidth() + std::max(LayoutUnit(0), reference.firstPostScript->logicalWidth() + space)); + LayoutPoint baseLocation(mirrorIfNeeded(horizontalOffset, *reference.base), ascent - baseAscent); + reference.base->setLocation(baseLocation); + horizontalOffset += reference.base->logicalWidth(); + LayoutUnit scriptAscent = ascentForChild(*reference.firstPostScript); + LayoutPoint scriptLocation(mirrorIfNeeded(horizontalOffset, *reference.firstPostScript), ascent - metrics.supShift - scriptAscent); + reference.firstPostScript->setLocation(scriptLocation); + } + break; + case SubSup: + case UnderOver: + case Multiscripts: { + // Calculate the logical width. + LayoutUnit logicalWidth = 0; + auto subScript = reference.firstPreScript; + while (subScript) { + auto supScript = subScript->nextSiblingBox(); + ASSERT(supScript); + LayoutUnit subSupPairWidth = std::max(subScript->logicalWidth(), supScript->logicalWidth()); + logicalWidth += subSupPairWidth + space; + subScript = supScript->nextSiblingBox(); + } + logicalWidth += reference.base->logicalWidth(); + subScript = reference.firstPostScript; + while (subScript && subScript != reference.prescriptDelimiter) { + auto supScript = subScript->nextSiblingBox(); + ASSERT(supScript); + LayoutUnit subSupPairWidth = std::max(std::max(LayoutUnit(0), subScript->logicalWidth() - baseItalicCorrection), supScript->logicalWidth()); + logicalWidth += subSupPairWidth + space; + subScript = supScript->nextSiblingBox(); + } + setLogicalWidth(logicalWidth); + + subScript = reference.firstPreScript; + while (subScript) { + auto supScript = subScript->nextSiblingBox(); + ASSERT(supScript); + LayoutUnit subSupPairWidth = std::max(subScript->logicalWidth(), supScript->logicalWidth()); + horizontalOffset += space + subSupPairWidth; + LayoutUnit subAscent = ascentForChild(*subScript); + LayoutPoint subScriptLocation(mirrorIfNeeded(horizontalOffset - subScript->logicalWidth(), *subScript), ascent + metrics.subShift - subAscent); + subScript->setLocation(subScriptLocation); + LayoutUnit supAscent = ascentForChild(*supScript); + LayoutPoint supScriptLocation(mirrorIfNeeded(horizontalOffset - supScript->logicalWidth(), *supScript), ascent - metrics.supShift - supAscent); + supScript->setLocation(supScriptLocation); + subScript = supScript->nextSiblingBox(); + } + LayoutPoint baseLocation(mirrorIfNeeded(horizontalOffset, *reference.base), ascent - baseAscent); + reference.base->setLocation(baseLocation); + horizontalOffset += reference.base->logicalWidth(); + subScript = reference.firstPostScript; + while (subScript && subScript != reference.prescriptDelimiter) { + auto supScript = subScript->nextSiblingBox(); + ASSERT(supScript); + LayoutUnit subAscent = ascentForChild(*subScript); + LayoutPoint subScriptLocation(mirrorIfNeeded(horizontalOffset - baseItalicCorrection, *subScript), ascent + metrics.subShift - subAscent); + subScript->setLocation(subScriptLocation); + LayoutUnit supAscent = ascentForChild(*supScript); + LayoutPoint supScriptLocation(mirrorIfNeeded(horizontalOffset, *supScript), ascent - metrics.supShift - supAscent); + supScript->setLocation(supScriptLocation); + + LayoutUnit subSupPairWidth = std::max(subScript->logicalWidth(), supScript->logicalWidth()); + horizontalOffset += subSupPairWidth + space; + subScript = supScript->nextSiblingBox(); } - return; } - - // We remove the child and shift the successors in the current sequence of scripts. - RenderMathMLBlock::removeChild(child); - RenderMathMLScriptsWrapper* subSupPair = this; - for (RenderObject* nextSibling = subSupPair->nextSibling(); nextSibling && !isPrescript(nextSibling); nextSibling = nextSibling->nextSibling()) { - RenderMathMLScriptsWrapper* nextSubSupPair = toRenderMathMLScriptsWrapper(nextSibling); - RenderObject* script = nextSubSupPair->firstChild(); - nextSubSupPair->removeChildInternal(true, *script); - subSupPair->addChildInternal(true, script); - subSupPair = toRenderMathMLScriptsWrapper(nextSibling); } - // We remove the last subSup pair if it became empty. - if (subSupPair->isEmpty()) { - parentNode->removeChildInternal(true, *subSupPair); - subSupPair->destroy(); - } + clearNeedsLayout(); } -void RenderMathMLScriptsWrapper::removeChild(RenderObject& child) +std::optional<int> RenderMathMLScripts::firstLineBaseline() const { - if (beingDestroyed() || documentBeingDestroyed()) { - // The renderer is being destroyed so we remove the child normally. - RenderMathMLBlock::removeChild(child); - return; - } - - RenderMathMLScripts* parentNode = toRenderMathMLScripts(parent()); - removeChildInternal(false, child); - parentNode->fixAnonymousStyles(); + ASSERT(!needsLayout()); + auto* base = firstChildBox(); + if (!base) + return std::optional<int>(); + return std::optional<int>(static_cast<int>(lroundf(ascentForChild(*base) + base->logicalTop()))); } -} +} #endif // ENABLE(MATHML) diff --git a/Source/WebCore/rendering/mathml/RenderMathMLScripts.h b/Source/WebCore/rendering/mathml/RenderMathMLScripts.h index e3225b843..ff04e86c5 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLScripts.h +++ b/Source/WebCore/rendering/mathml/RenderMathMLScripts.h @@ -1,6 +1,7 @@ /* * Copyright (C) 2010 Alex Milowski (alex@milowski.com). All rights reserved. * Copyright (C) 2013 The MathJax Consortium. + * 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 @@ -24,91 +25,65 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef RenderMathMLScripts_h -#define RenderMathMLScripts_h +#pragma once #if ENABLE(MATHML) #include "RenderMathMLBlock.h" namespace WebCore { - -class RenderMathMLScripts; -class RenderMathMLScriptsWrapper : public RenderMathMLBlock { - -friend class RenderMathMLScripts; - -public: - enum WrapperType { Base, SubSupPair }; - - virtual void addChild(RenderObject* child, RenderObject* beforeChild = 0) override; - virtual void removeChild(RenderObject&) override; - -private: - RenderMathMLScriptsWrapper(Document& document, PassRef<RenderStyle> style, WrapperType kind) - : RenderMathMLBlock(document, std::move(style)) - , m_kind(kind) - { - } - - static RenderMathMLScriptsWrapper* createAnonymousWrapper(RenderMathMLScripts* renderObject, WrapperType); - - void addChildInternal(bool normalInsertion, RenderObject* child, RenderObject* beforeChild = 0); - void removeChildInternal(bool normalRemoval, RenderObject& child); - - virtual const char* renderName() const override { return m_kind == Base ? "Base Wrapper" : "SubSupPair Wrapper"; } - virtual bool isRenderMathMLScriptsWrapper() const override final { return true; } - - RenderMathMLScripts* parentMathMLScripts(); - - WrapperType m_kind; -}; - -RENDER_OBJECT_TYPE_CASTS(RenderMathMLScriptsWrapper, isRenderMathMLScriptsWrapper()); +class MathMLScriptsElement; // Render a base with scripts. class RenderMathMLScripts : public RenderMathMLBlock { - -friend class RenderMathMLScriptsWrapper; - public: - RenderMathMLScripts(Element&, PassRef<RenderStyle>); - virtual void addChild(RenderObject* child, RenderObject* beforeChild = 0) override; - virtual void removeChild(RenderObject&) override; - - virtual RenderMathMLOperator* unembellishedOperator(); - virtual int firstLineBaseline() const override; + RenderMathMLScripts(MathMLScriptsElement&, RenderStyle&&); + RenderMathMLOperator* unembellishedOperator() final; protected: - virtual void layout(); - -private: - void addChildInternal(bool normalInsertion, RenderObject* child, RenderObject* beforeChild = 0); - void removeChildInternal(bool normalRemoval, RenderObject& child); - - virtual bool isRenderMathMLScripts() const override final { return true; } - virtual const char* renderName() const override { return "RenderMathMLScripts"; } + bool isRenderMathMLScripts() const override { return true; } + const char* renderName() const override { return "RenderMathMLScripts"; } + void computePreferredLogicalWidths() override; + void layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight = 0) override; - void fixAnonymousStyleForSubSupPair(RenderObject* subSupPair, bool isPostScript); - void fixAnonymousStyles(); + enum ScriptsType { Sub, Super, SubSup, Multiscripts, Under, Over, UnderOver }; + ScriptsType m_scriptType; - virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle) override; - - // Omit our subscript and/or superscript. This may return 0 for a non-MathML base (which - // won't occur in valid MathML). - RenderBoxModelObject* base() const; - - enum ScriptsType { Sub, Super, SubSup, Multiscripts }; - - ScriptsType m_kind; - RenderMathMLScriptsWrapper* m_baseWrapper; +private: + MathMLScriptsElement& element() const; + std::optional<int> firstLineBaseline() const final; + struct ReferenceChildren { + RenderBox* base; + RenderBox* prescriptDelimiter; + RenderBox* firstPostScript; + RenderBox* firstPreScript; + }; + std::optional<ReferenceChildren> validateAndGetReferenceChildren(); + LayoutUnit spaceAfterScript(); + LayoutUnit italicCorrection(const ReferenceChildren&); + struct VerticalParameters { + LayoutUnit subscriptShiftDown; + LayoutUnit superscriptShiftUp; + LayoutUnit subscriptBaselineDropMin; + LayoutUnit superScriptBaselineDropMax; + LayoutUnit subSuperscriptGapMin; + LayoutUnit superscriptBottomMin; + LayoutUnit subscriptTopMax; + LayoutUnit superscriptBottomMaxWithSubscript; + }; + VerticalParameters verticalParameters() const; + struct VerticalMetrics { + LayoutUnit subShift; + LayoutUnit supShift; + LayoutUnit ascent; + LayoutUnit descent; + }; + VerticalMetrics verticalMetrics(const ReferenceChildren&); }; -RENDER_OBJECT_TYPE_CASTS(RenderMathMLScripts, isRenderMathMLScripts()); +} // namespace WebCore -} +SPECIALIZE_TYPE_TRAITS_RENDER_OBJECT(RenderMathMLScripts, isRenderMathMLScripts()) #endif // ENABLE(MATHML) - -#endif // RenderMathMLScripts_h diff --git a/Source/WebCore/rendering/mathml/RenderMathMLSpace.cpp b/Source/WebCore/rendering/mathml/RenderMathMLSpace.cpp index 288e6622e..292c83553 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLSpace.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLSpace.cpp @@ -29,71 +29,63 @@ #if ENABLE(MATHML) #include "GraphicsContext.h" -#include "MathMLNames.h" -#include "PaintInfo.h" namespace WebCore { - -using namespace MathMLNames; - -RenderMathMLSpace::RenderMathMLSpace(MathMLTextElement& element, PassRef<RenderStyle> style) - : RenderMathMLBlock(element, std::move(style)) - , m_width(0) - , m_height(0) - , m_depth(0) -{ -} -void RenderMathMLSpace::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const +RenderMathMLSpace::RenderMathMLSpace(MathMLSpaceElement& element, RenderStyle&& style) + : RenderMathMLBlock(element, WTFMove(style)) { - minLogicalWidth = m_width; - maxLogicalWidth = m_width; } -void RenderMathMLSpace::updateFromElement() +void RenderMathMLSpace::computePreferredLogicalWidths() { - const auto& spaceElement = element(); - - // This parses the mspace attributes, using 0 as the default values. - m_width = 0; - m_height = 0; - m_depth = 0; - parseMathMLLength(spaceElement.getAttribute(MathMLNames::widthAttr), m_width, &style()); - parseMathMLLength(spaceElement.getAttribute(MathMLNames::heightAttr), m_height, &style()); - parseMathMLLength(spaceElement.getAttribute(MathMLNames::depthAttr), m_depth, &style()); + ASSERT(preferredLogicalWidthsDirty()); - // FIXME: Negative width values should be accepted. - if (m_width < 0) - m_width = 0; - - // If the total height is negative, set vertical dimensions to 0. - if (m_height + m_depth < 0) { - m_height = 0; - m_depth = 0; - } + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = spaceWidth(); - setNeedsLayoutAndPrefWidthsRecalc(); + setPreferredLogicalWidthsDirty(false); } -void RenderMathMLSpace::updateLogicalWidth() +LayoutUnit RenderMathMLSpace::spaceWidth() const { - setLogicalWidth(m_width); + auto& spaceElement = element(); + // FIXME: Negative width values are not supported yet. + return std::max<LayoutUnit>(0, toUserUnits(spaceElement.width(), style(), 0)); } -void RenderMathMLSpace::updateLogicalHeight() +void RenderMathMLSpace::getSpaceHeightAndDepth(LayoutUnit& height, LayoutUnit& depth) const { - setLogicalHeight(m_height + m_depth); + auto& spaceElement = element(); + height = toUserUnits(spaceElement.height(), style(), 0); + depth = toUserUnits(spaceElement.depth(), style(), 0); + + // If the total height is negative, set vertical dimensions to 0. + if (height + depth < 0) { + height = 0; + depth = 0; + } } -void RenderMathMLSpace::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +void RenderMathMLSpace::layoutBlock(bool relayoutChildren, LayoutUnit) { - RenderMathMLBlock::styleDidChange(diff, oldStyle); - updateFromElement(); + ASSERT(needsLayout()); + + if (!relayoutChildren && simplifiedLayout()) + return; + + setLogicalWidth(spaceWidth()); + LayoutUnit height, depth; + getSpaceHeightAndDepth(height, depth); + setLogicalHeight(height + depth); + + clearNeedsLayout(); } -int RenderMathMLSpace::firstLineBaseline() const +std::optional<int> RenderMathMLSpace::firstLineBaseline() const { - return m_height; + LayoutUnit height, depth; + getSpaceHeightAndDepth(height, depth); + return std::optional<int>(height); } } diff --git a/Source/WebCore/rendering/mathml/RenderMathMLSpace.h b/Source/WebCore/rendering/mathml/RenderMathMLSpace.h index 48a4e2fe4..fd19be2d6 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLSpace.h +++ b/Source/WebCore/rendering/mathml/RenderMathMLSpace.h @@ -23,40 +23,34 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef RenderMathMLSpace_h -#define RenderMathMLSpace_h +#pragma once #if ENABLE(MATHML) -#include "MathMLTextElement.h" +#include "MathMLSpaceElement.h" #include "RenderMathMLBlock.h" namespace WebCore { - + class RenderMathMLSpace final : public RenderMathMLBlock { public: - RenderMathMLSpace(MathMLTextElement&, PassRef<RenderStyle>); - MathMLTextElement& element() { return static_cast<MathMLTextElement&>(nodeForNonAnonymous()); } + RenderMathMLSpace(MathMLSpaceElement&, RenderStyle&&); + MathMLSpaceElement& element() const { return static_cast<MathMLSpaceElement&>(nodeForNonAnonymous()); } private: - virtual const char* renderName() const override { return isAnonymous() ? "RenderMathMLSpace (anonymous)" : "RenderMathMLSpace"; } - virtual bool isRenderMathMLSpace() const override { return true; } - virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle) override; - virtual bool isChildAllowed(const RenderObject&, const RenderStyle&) const override { return false; } - virtual void computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const override; - virtual void updateFromElement() override; - virtual int firstLineBaseline() const override; - virtual void updateLogicalWidth() override; - virtual void updateLogicalHeight() override; - - LayoutUnit m_width; - LayoutUnit m_height; - LayoutUnit m_depth; + const char* renderName() const final { return "RenderMathMLSpace"; } + bool isRenderMathMLSpace() const final { return true; } + bool isChildAllowed(const RenderObject&, const RenderStyle&) const final { return false; } + void computePreferredLogicalWidths() final; + void layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight = 0) final; + std::optional<int> firstLineBaseline() const final; + + LayoutUnit spaceWidth() const; + void getSpaceHeightAndDepth(LayoutUnit& height, LayoutUnit& depth) const; }; -RENDER_OBJECT_TYPE_CASTS(RenderMathMLSpace, isRenderMathMLSpace()); +} // namespace WebCore -} +SPECIALIZE_TYPE_TRAITS_RENDER_OBJECT(RenderMathMLSpace, isRenderMathMLSpace()) #endif // ENABLE(MATHML) -#endif // RenderMathMLSpace_h diff --git a/Source/WebCore/rendering/mathml/RenderMathMLToken.cpp b/Source/WebCore/rendering/mathml/RenderMathMLToken.cpp new file mode 100644 index 000000000..a5c4c1c0d --- /dev/null +++ b/Source/WebCore/rendering/mathml/RenderMathMLToken.cpp @@ -0,0 +1,623 @@ +/* + * Copyright (C) 2014 Frédéric Wang (fred.wang@free.fr). All rights reserved. + * Copyright (C) 2016 Igalia S.L. + * Copyright (C) 2016 Apple Inc. All rights reserved. + * + * 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 "RenderMathMLToken.h" + +#if ENABLE(MATHML) + +#include "MathMLElement.h" +#include "MathMLNames.h" +#include "MathMLTokenElement.h" +#include "PaintInfo.h" +#include "RenderElement.h" +#include "RenderIterator.h" + +namespace WebCore { + +using namespace MathMLNames; + +RenderMathMLToken::RenderMathMLToken(MathMLTokenElement& element, RenderStyle&& style) + : RenderMathMLBlock(element, WTFMove(style)) +{ +} + +RenderMathMLToken::RenderMathMLToken(Document& document, RenderStyle&& style) + : RenderMathMLBlock(document, WTFMove(style)) +{ +} + +MathMLTokenElement& RenderMathMLToken::element() +{ + return static_cast<MathMLTokenElement&>(nodeForNonAnonymous()); +} + +void RenderMathMLToken::updateTokenContent() +{ + RenderMathMLBlock::updateFromElement(); + setMathVariantGlyphDirty(); +} + +// Entries for the mathvariant lookup tables. +// 'key' represents the Unicode character to be transformed and is used for searching the tables. +// 'replacement' represents the mapped mathvariant Unicode character. +struct MathVariantMapping { + uint32_t key; + uint32_t replacement; +}; +static inline UChar32 ExtractKey(const MathVariantMapping* entry) { return entry->key; } +static UChar32 MathVariantMappingSearch(uint32_t key, const MathVariantMapping* table, size_t tableLength) +{ + if (const auto* entry = tryBinarySearch<const MathVariantMapping, UChar32>(table, tableLength, key, ExtractKey)) + return entry->replacement; + + return 0; +} + +// Lookup tables for use with mathvariant mappings to transform a unicode character point to another unicode character that indicates the proper output. +// key represents one of two concepts. +// 1. In the Latin table it represents a hole in the mathematical alphanumeric block, where the character that should occupy that position is located elsewhere. +// 2. It represents an Arabic letter. +// As a replacement, 0 is reserved to indicate no mapping was found. +static const MathVariantMapping arabicInitialMapTable[] = { + { 0x628, 0x1EE21 }, + { 0x62A, 0x1EE35 }, + { 0x62B, 0x1EE36 }, + { 0x62C, 0x1EE22 }, + { 0x62D, 0x1EE27 }, + { 0x62E, 0x1EE37 }, + { 0x633, 0x1EE2E }, + { 0x634, 0x1EE34 }, + { 0x635, 0x1EE31 }, + { 0x636, 0x1EE39 }, + { 0x639, 0x1EE2F }, + { 0x63A, 0x1EE3B }, + { 0x641, 0x1EE30 }, + { 0x642, 0x1EE32 }, + { 0x643, 0x1EE2A }, + { 0x644, 0x1EE2B }, + { 0x645, 0x1EE2C }, + { 0x646, 0x1EE2D }, + { 0x647, 0x1EE24 }, + { 0x64A, 0x1EE29 } +}; + +static const MathVariantMapping arabicTailedMapTable[] = { + { 0x62C, 0x1EE42 }, + { 0x62D, 0x1EE47 }, + { 0x62E, 0x1EE57 }, + { 0x633, 0x1EE4E }, + { 0x634, 0x1EE54 }, + { 0x635, 0x1EE51 }, + { 0x636, 0x1EE59 }, + { 0x639, 0x1EE4F }, + { 0x63A, 0x1EE5B }, + { 0x642, 0x1EE52 }, + { 0x644, 0x1EE4B }, + { 0x646, 0x1EE4D }, + { 0x64A, 0x1EE49 }, + { 0x66F, 0x1EE5F }, + { 0x6BA, 0x1EE5D } +}; + +static const MathVariantMapping arabicStretchedMapTable[] = { + { 0x628, 0x1EE61 }, + { 0x62A, 0x1EE75 }, + { 0x62B, 0x1EE76 }, + { 0x62C, 0x1EE62 }, + { 0x62D, 0x1EE67 }, + { 0x62E, 0x1EE77 }, + { 0x633, 0x1EE6E }, + { 0x634, 0x1EE74 }, + { 0x635, 0x1EE71 }, + { 0x636, 0x1EE79 }, + { 0x637, 0x1EE68 }, + { 0x638, 0x1EE7A }, + { 0x639, 0x1EE6F }, + { 0x63A, 0x1EE7B }, + { 0x641, 0x1EE70 }, + { 0x642, 0x1EE72 }, + { 0x643, 0x1EE6A }, + { 0x645, 0x1EE6C }, + { 0x646, 0x1EE6D }, + { 0x647, 0x1EE64 }, + { 0x64A, 0x1EE69 }, + { 0x66E, 0x1EE7C }, + { 0x6A1, 0x1EE7E } +}; + +static const MathVariantMapping arabicLoopedMapTable[] = { + { 0x627, 0x1EE80 }, + { 0x628, 0x1EE81 }, + { 0x62A, 0x1EE95 }, + { 0x62B, 0x1EE96 }, + { 0x62C, 0x1EE82 }, + { 0x62D, 0x1EE87 }, + { 0x62E, 0x1EE97 }, + { 0x62F, 0x1EE83 }, + { 0x630, 0x1EE98 }, + { 0x631, 0x1EE93 }, + { 0x632, 0x1EE86 }, + { 0x633, 0x1EE8E }, + { 0x634, 0x1EE94 }, + { 0x635, 0x1EE91 }, + { 0x636, 0x1EE99 }, + { 0x637, 0x1EE88 }, + { 0x638, 0x1EE9A }, + { 0x639, 0x1EE8F }, + { 0x63A, 0x1EE9B }, + { 0x641, 0x1EE90 }, + { 0x642, 0x1EE92 }, + { 0x644, 0x1EE8B }, + { 0x645, 0x1EE8C }, + { 0x646, 0x1EE8D }, + { 0x647, 0x1EE84 }, + { 0x648, 0x1EE85 }, + { 0x64A, 0x1EE89 } +}; + +static const MathVariantMapping arabicDoubleMapTable[] = { + { 0x628, 0x1EEA1 }, + { 0x62A, 0x1EEB5 }, + { 0x62B, 0x1EEB6 }, + { 0x62C, 0x1EEA2 }, + { 0x62D, 0x1EEA7 }, + { 0x62E, 0x1EEB7 }, + { 0x62F, 0x1EEA3 }, + { 0x630, 0x1EEB8 }, + { 0x631, 0x1EEB3 }, + { 0x632, 0x1EEA6 }, + { 0x633, 0x1EEAE }, + { 0x634, 0x1EEB4 }, + { 0x635, 0x1EEB1 }, + { 0x636, 0x1EEB9 }, + { 0x637, 0x1EEA8 }, + { 0x638, 0x1EEBA }, + { 0x639, 0x1EEAF }, + { 0x63A, 0x1EEBB }, + { 0x641, 0x1EEB0 }, + { 0x642, 0x1EEB2 }, + { 0x644, 0x1EEAB }, + { 0x645, 0x1EEAC }, + { 0x646, 0x1EEAD }, + { 0x648, 0x1EEA5 }, + { 0x64A, 0x1EEA9 } +}; + +static const MathVariantMapping latinExceptionMapTable[] = { + { 0x1D455, 0x210E }, + { 0x1D49D, 0x212C }, + { 0x1D4A0, 0x2130 }, + { 0x1D4A1, 0x2131 }, + { 0x1D4A3, 0x210B }, + { 0x1D4A4, 0x2110 }, + { 0x1D4A7, 0x2112 }, + { 0x1D4A8, 0x2133 }, + { 0x1D4AD, 0x211B }, + { 0x1D4BA, 0x212F }, + { 0x1D4BC, 0x210A }, + { 0x1D4C4, 0x2134 }, + { 0x1D506, 0x212D }, + { 0x1D50B, 0x210C }, + { 0x1D50C, 0x2111 }, + { 0x1D515, 0x211C }, + { 0x1D51D, 0x2128 }, + { 0x1D53A, 0x2102 }, + { 0x1D53F, 0x210D }, + { 0x1D545, 0x2115 }, + { 0x1D547, 0x2119 }, + { 0x1D548, 0x211A }, + { 0x1D549, 0x211D }, + { 0x1D551, 0x2124 } +}; + +const UChar32 greekUpperTheta = 0x03F4; +const UChar32 holeGreekUpperTheta = 0x03A2; +const UChar32 nabla = 0x2207; +const UChar32 partialDifferential = 0x2202; +const UChar32 greekUpperAlpha = 0x0391; +const UChar32 greekUpperOmega = 0x03A9; +const UChar32 greekLowerAlpha = 0x03B1; +const UChar32 greekLowerOmega = 0x03C9; +const UChar32 greekLunateEpsilonSymbol = 0x03F5; +const UChar32 greekThetaSymbol = 0x03D1; +const UChar32 greekKappaSymbol = 0x03F0; +const UChar32 greekPhiSymbol = 0x03D5; +const UChar32 greekRhoSymbol = 0x03F1; +const UChar32 greekPiSymbol = 0x03D6; +const UChar32 greekLetterDigamma = 0x03DC; +const UChar32 greekSmallLetterDigamma = 0x03DD; +const UChar32 mathBoldCapitalDigamma = 0x1D7CA; +const UChar32 mathBoldSmallDigamma = 0x1D7CB; + +const UChar32 latinSmallLetterDotlessI = 0x0131; +const UChar32 latinSmallLetterDotlessJ = 0x0237; + +const UChar32 mathItalicSmallDotlessI = 0x1D6A4; +const UChar32 mathItalicSmallDotlessJ = 0x1D6A5; + +const UChar32 mathBoldUpperA = 0x1D400; +const UChar32 mathItalicUpperA = 0x1D434; +const UChar32 mathBoldSmallA = 0x1D41A; +const UChar32 mathBoldUpperAlpha = 0x1D6A8; +const UChar32 mathBoldSmallAlpha = 0x1D6C2; +const UChar32 mathItalicUpperAlpha = 0x1D6E2; +const UChar32 mathBoldDigitZero = 0x1D7CE; +const UChar32 mathDoubleStruckZero = 0x1D7D8; + +const UChar32 mathBoldUpperTheta = 0x1D6B9; +const UChar32 mathBoldNabla = 0x1D6C1; +const UChar32 mathBoldPartialDifferential = 0x1D6DB; +const UChar32 mathBoldEpsilonSymbol = 0x1D6DC; +const UChar32 mathBoldThetaSymbol = 0x1D6DD; +const UChar32 mathBoldKappaSymbol = 0x1D6DE; +const UChar32 mathBoldPhiSymbol = 0x1D6DF; +const UChar32 mathBoldRhoSymbol = 0x1D6E0; +const UChar32 mathBoldPiSymbol = 0x1D6E1; + +// Performs the character mapping needed to implement MathML's mathvariant attribute. +// It takes a unicode character and maps it to its appropriate mathvariant counterpart specified by mathvariant. +// The mapped character is typically located within Unicode's mathematical blocks (0x1D***, 0x1EE**) but there are exceptions which this function accounts for. +// Characters without a valid mapping or valid aMathvar value are returned +// unaltered. +// Characters already in the mathematical blocks (or are one of the exceptions) are never transformed. +// Acceptable values for mathvariant are specified in MathMLElement.h +// The transformable characters can be found at: +// http://lists.w3.org/Archives/Public/www-math/2013Sep/0012.html and +// https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols +static UChar32 mathVariant(UChar32 codePoint, MathMLElement::MathVariant mathvariant) +{ + ASSERT(mathvariant >= MathMLElement::MathVariant::Normal && mathvariant <= MathMLElement::MathVariant::Stretched); + + if (mathvariant == MathMLElement::MathVariant::Normal) + return codePoint; // Nothing to do here. + + // Exceptional characters with at most one possible transformation. + if (codePoint == holeGreekUpperTheta) + return codePoint; // Nothing at this code point is transformed + if (codePoint == greekLetterDigamma) { + if (mathvariant == MathMLElement::MathVariant::Bold) + return mathBoldCapitalDigamma; + return codePoint; + } + if (codePoint == greekSmallLetterDigamma) { + if (mathvariant == MathMLElement::MathVariant::Bold) + return mathBoldSmallDigamma; + return codePoint; + } + if (codePoint == latinSmallLetterDotlessI) { + if (mathvariant == MathMLElement::MathVariant::Italic) + return mathItalicSmallDotlessI; + return codePoint; + } + if (codePoint == latinSmallLetterDotlessJ) { + if (mathvariant == MathMLElement::MathVariant::Italic) + return mathItalicSmallDotlessJ; + return codePoint; + } + + // The Unicode mathematical blocks are divided into four segments: Latin, Greek, numbers and Arabic. + // In the case of the first three baseChar represents the relative order in which the characters are encoded in the Unicode mathematical block, normalised to the first character of that sequence. + UChar32 baseChar = 0; + enum CharacterType { + Latin, + Greekish, + Number, + Arabic + }; + CharacterType varType; + if (isASCIIUpper(codePoint)) { + baseChar = codePoint - 'A'; + varType = Latin; + } else if (isASCIILower(codePoint)) { + // Lowercase characters are placed immediately after the uppercase characters in the Unicode mathematical block. + // The constant subtraction represents the number of characters between the start of the sequence (capital A) and the first lowercase letter. + baseChar = mathBoldSmallA - mathBoldUpperA + codePoint - 'a'; + varType = Latin; + } else if (isASCIIDigit(codePoint)) { + baseChar = codePoint - '0'; + varType = Number; + } else if (greekUpperAlpha <= codePoint && codePoint <= greekUpperOmega) { + baseChar = codePoint - greekUpperAlpha; + varType = Greekish; + } else if (greekLowerAlpha <= codePoint && codePoint <= greekLowerOmega) { + // Lowercase Greek comes after uppercase Greek. + // Note in this instance the presence of an additional character (Nabla) between the end of the uppercase Greek characters and the lowercase ones. + baseChar = mathBoldSmallAlpha - mathBoldUpperAlpha + codePoint - greekLowerAlpha; + varType = Greekish; + } else if (0x0600 <= codePoint && codePoint <= 0x06FF) { + // Arabic characters are defined within this range + varType = Arabic; + } else { + switch (codePoint) { + case greekUpperTheta: + baseChar = mathBoldUpperTheta - mathBoldUpperAlpha; + break; + case nabla: + baseChar = mathBoldNabla - mathBoldUpperAlpha; + break; + case partialDifferential: + baseChar = mathBoldPartialDifferential - mathBoldUpperAlpha; + break; + case greekLunateEpsilonSymbol: + baseChar = mathBoldEpsilonSymbol - mathBoldUpperAlpha; + break; + case greekThetaSymbol: + baseChar = mathBoldThetaSymbol - mathBoldUpperAlpha; + break; + case greekKappaSymbol: + baseChar = mathBoldKappaSymbol - mathBoldUpperAlpha; + break; + case greekPhiSymbol: + baseChar = mathBoldPhiSymbol - mathBoldUpperAlpha; + break; + case greekRhoSymbol: + baseChar = mathBoldRhoSymbol - mathBoldUpperAlpha; + break; + case greekPiSymbol: + baseChar = mathBoldPiSymbol - mathBoldUpperAlpha; + break; + default: + return codePoint; + } + varType = Greekish; + } + + int8_t multiplier; + if (varType == Number) { + // Each possible number mathvariant is encoded in a single, contiguous block. + // For example the beginning of the double struck number range follows immediately after the end of the bold number range. + // multiplier represents the order of the sequences relative to the first one. + switch (mathvariant) { + case MathMLElement::MathVariant::Bold: + multiplier = 0; + break; + case MathMLElement::MathVariant::DoubleStruck: + multiplier = 1; + break; + case MathMLElement::MathVariant::SansSerif: + multiplier = 2; + break; + case MathMLElement::MathVariant::BoldSansSerif: + multiplier = 3; + break; + case MathMLElement::MathVariant::Monospace: + multiplier = 4; + break; + default: + // This mathvariant isn't defined for numbers or is otherwise normal. + return codePoint; + } + // As the ranges are contiguous, to find the desired mathvariant range it is sufficient to + // multiply the position within the sequence order (multiplier) with the period of the sequence (which is constant for all number sequences) + // and to add the character point of the first character within the number mathvariant range. + // To this the baseChar calculated earlier is added to obtain the final code point. + return baseChar + multiplier * (mathDoubleStruckZero - mathBoldDigitZero) + mathBoldDigitZero; + } + if (varType == Greekish) { + switch (mathvariant) { + case MathMLElement::MathVariant::Bold: + multiplier = 0; + break; + case MathMLElement::MathVariant::Italic: + multiplier = 1; + break; + case MathMLElement::MathVariant::BoldItalic: + multiplier = 2; + break; + case MathMLElement::MathVariant::BoldSansSerif: + multiplier = 3; + break; + case MathMLElement::MathVariant::SansSerifBoldItalic: + multiplier = 4; + break; + default: + // This mathvariant isn't defined for Greek or is otherwise normal. + return codePoint; + } + // See the Number case for an explanation of the following calculation. + return baseChar + mathBoldUpperAlpha + multiplier * (mathItalicUpperAlpha - mathBoldUpperAlpha); + } + + UChar32 tempChar = 0; + UChar32 newChar; + if (varType == Arabic) { + // The Arabic mathematical block is not continuous, nor does it have a monotonic mapping to the unencoded characters, requiring the use of a lookup table. + const MathVariantMapping* mapTable; + size_t tableLength; + switch (mathvariant) { + case MathMLElement::MathVariant::Initial: + mapTable = arabicInitialMapTable; + tableLength = WTF_ARRAY_LENGTH(arabicInitialMapTable); + break; + case MathMLElement::MathVariant::Tailed: + mapTable = arabicTailedMapTable; + tableLength = WTF_ARRAY_LENGTH(arabicTailedMapTable); + break; + case MathMLElement::MathVariant::Stretched: + mapTable = arabicStretchedMapTable; + tableLength = WTF_ARRAY_LENGTH(arabicStretchedMapTable); + break; + case MathMLElement::MathVariant::Looped: + mapTable = arabicLoopedMapTable; + tableLength = WTF_ARRAY_LENGTH(arabicLoopedMapTable); + break; + case MathMLElement::MathVariant::DoubleStruck: + mapTable = arabicDoubleMapTable; + tableLength = WTF_ARRAY_LENGTH(arabicDoubleMapTable); + break; + default: + return codePoint; // No valid transformations exist. + } + newChar = MathVariantMappingSearch(codePoint, mapTable, tableLength); + } else { + // Must be Latin + if (mathvariant > MathMLElement::MathVariant::Monospace) + return codePoint; // Latin doesn't support the Arabic mathvariants + multiplier = static_cast<int>(mathvariant) - 2; + // This is possible because the values for NS_MATHML_MATHVARIANT_* are chosen to coincide with the order in which the encoded mathvariant characters are located within their unicode block (less an offset to avoid None and Normal variants) + // See the Number case for an explanation of the following calculation + tempChar = baseChar + mathBoldUpperA + multiplier * (mathItalicUpperA - mathBoldUpperA); + // There are roughly twenty characters that are located outside of the mathematical block, so the spaces where they ought to be are used as keys for a lookup table containing the correct character mappings. + newChar = MathVariantMappingSearch(tempChar, latinExceptionMapTable, WTF_ARRAY_LENGTH(latinExceptionMapTable)); + } + + if (newChar) + return newChar; + if (varType == Latin) + return tempChar; + return codePoint; // This is an Arabic character without a corresponding mapping. +} + +void RenderMathMLToken::computePreferredLogicalWidths() +{ + ASSERT(preferredLogicalWidthsDirty()); + + if (m_mathVariantGlyphDirty) + updateMathVariantGlyph(); + + if (m_mathVariantCodePoint) { + auto mathVariantGlyph = style().fontCascade().glyphDataForCharacter(m_mathVariantCodePoint.value(), m_mathVariantIsMirrored); + if (mathVariantGlyph.font) { + m_maxPreferredLogicalWidth = m_minPreferredLogicalWidth = mathVariantGlyph.font->widthForGlyph(mathVariantGlyph.glyph); + setPreferredLogicalWidthsDirty(false); + return; + } + } + + RenderMathMLBlock::computePreferredLogicalWidths(); +} + +void RenderMathMLToken::updateMathVariantGlyph() +{ + ASSERT(m_mathVariantGlyphDirty); + + m_mathVariantCodePoint = std::nullopt; + m_mathVariantGlyphDirty = false; + + // Early return if the token element contains RenderElements. + // Note that the renderers corresponding to the children of the token element are wrapped inside an anonymous RenderBlock. + if (const auto& block = downcast<RenderElement>(firstChild())) { + if (childrenOfType<RenderElement>(*block).first()) + return; + } + + const auto& tokenElement = element(); + if (auto codePoint = MathMLTokenElement::convertToSingleCodePoint(element().textContent())) { + MathMLElement::MathVariant mathvariant = mathMLStyle().mathVariant(); + if (mathvariant == MathMLElement::MathVariant::None) + mathvariant = tokenElement.hasTagName(MathMLNames::miTag) ? MathMLElement::MathVariant::Italic : MathMLElement::MathVariant::Normal; + UChar32 transformedCodePoint = mathVariant(codePoint.value(), mathvariant); + if (transformedCodePoint != codePoint.value()) { + m_mathVariantCodePoint = mathVariant(codePoint.value(), mathvariant); + m_mathVariantIsMirrored = !style().isLeftToRightDirection(); + } + } +} + +void RenderMathMLToken::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderMathMLBlock::styleDidChange(diff, oldStyle); + setMathVariantGlyphDirty(); +} + +void RenderMathMLToken::updateFromElement() +{ + RenderMathMLBlock::updateFromElement(); + setMathVariantGlyphDirty(); +} + +std::optional<int> RenderMathMLToken::firstLineBaseline() const +{ + if (m_mathVariantCodePoint) { + auto mathVariantGlyph = style().fontCascade().glyphDataForCharacter(m_mathVariantCodePoint.value(), m_mathVariantIsMirrored); + if (mathVariantGlyph.font) + return std::optional<int>(static_cast<int>(lroundf(-mathVariantGlyph.font->boundsForGlyph(mathVariantGlyph.glyph).y()))); + } + return RenderMathMLBlock::firstLineBaseline(); +} + +void RenderMathMLToken::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight) +{ + ASSERT(needsLayout()); + + if (!relayoutChildren && simplifiedLayout()) + return; + + GlyphData mathVariantGlyph; + if (m_mathVariantCodePoint) + mathVariantGlyph = style().fontCascade().glyphDataForCharacter(m_mathVariantCodePoint.value(), m_mathVariantIsMirrored); + + if (!mathVariantGlyph.font) { + RenderMathMLBlock::layoutBlock(relayoutChildren, pageLogicalHeight); + return; + } + + for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) + child->layoutIfNeeded(); + + setLogicalWidth(mathVariantGlyph.font->widthForGlyph(mathVariantGlyph.glyph)); + setLogicalHeight(mathVariantGlyph.font->boundsForGlyph(mathVariantGlyph.glyph).height()); + + clearNeedsLayout(); +} + +void RenderMathMLToken::paint(PaintInfo& info, const LayoutPoint& paintOffset) +{ + RenderMathMLBlock::paint(info, paintOffset); + + // FIXME: Instead of using DrawGlyph, we may consider using the more general TextPainter so that we can apply mathvariant to strings with an arbitrary number of characters and preserve advanced CSS effects (text-shadow, etc). + if (info.context().paintingDisabled() || info.phase != PaintPhaseForeground || style().visibility() != VISIBLE || !m_mathVariantCodePoint) + return; + + auto mathVariantGlyph = style().fontCascade().glyphDataForCharacter(m_mathVariantCodePoint.value(), m_mathVariantIsMirrored); + if (!mathVariantGlyph.font) + return; + + GraphicsContextStateSaver stateSaver(info.context()); + info.context().setFillColor(style().visitedDependentColor(CSSPropertyColor)); + + GlyphBuffer buffer; + buffer.add(mathVariantGlyph.glyph, mathVariantGlyph.font, mathVariantGlyph.font->widthForGlyph(mathVariantGlyph.glyph)); + LayoutUnit glyphAscent = static_cast<int>(lroundf(-mathVariantGlyph.font->boundsForGlyph(mathVariantGlyph.glyph).y())); + info.context().drawGlyphs(style().fontCascade(), *mathVariantGlyph.font, buffer, 0, 1, paintOffset + location() + LayoutPoint(0, glyphAscent)); +} + +void RenderMathMLToken::paintChildren(PaintInfo& paintInfo, const LayoutPoint& paintOffset, PaintInfo& paintInfoForChild, bool usePrintRect) +{ + if (m_mathVariantCodePoint) { + auto mathVariantGlyph = style().fontCascade().glyphDataForCharacter(m_mathVariantCodePoint.value(), m_mathVariantIsMirrored); + if (mathVariantGlyph.font) + return; + } + + RenderMathMLBlock::paintChildren(paintInfo, paintOffset, paintInfoForChild, usePrintRect); +} + +} + +#endif // ENABLE(MATHML) diff --git a/Source/WebCore/rendering/mathml/RenderMathMLToken.h b/Source/WebCore/rendering/mathml/RenderMathMLToken.h new file mode 100644 index 000000000..5101d6af1 --- /dev/null +++ b/Source/WebCore/rendering/mathml/RenderMathMLToken.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2014 Frédéric Wang (fred.wang@free.fr). All rights reserved. + * Copyright (C) 2016 Igalia S.L. + * Copyright (C) 2016 Apple Inc. All rights reserved. + * + * 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. + */ + +#pragma once + +#if ENABLE(MATHML) + +#include "RenderMathMLBlock.h" + +namespace WebCore { + +class MathMLTokenElement; + +class RenderMathMLToken : public RenderMathMLBlock { +public: + RenderMathMLToken(MathMLTokenElement&, RenderStyle&&); + RenderMathMLToken(Document&, RenderStyle&&); + + MathMLTokenElement& element(); + + virtual void updateTokenContent(); + void updateFromElement() override; + +protected: + void paint(PaintInfo&, const LayoutPoint&) override; + void paintChildren(PaintInfo& forSelf, const LayoutPoint&, PaintInfo& forChild, bool usePrintRect) override; + std::optional<int> firstLineBaseline() const override; + void layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight = 0) override; + void computePreferredLogicalWidths() override; + +private: + bool isRenderMathMLToken() const final { return true; } + const char* renderName() const override { return "RenderMathMLToken"; } + bool isChildAllowed(const RenderObject&, const RenderStyle&) const final { return true; }; + void styleDidChange(StyleDifference, const RenderStyle* oldStyle) override; + void updateMathVariantGlyph(); + void setMathVariantGlyphDirty() + { + m_mathVariantGlyphDirty = true; + setNeedsLayoutAndPrefWidthsRecalc(); + } + std::optional<UChar32> m_mathVariantCodePoint { std::nullopt }; + bool m_mathVariantIsMirrored { false }; + bool m_mathVariantGlyphDirty { false }; +}; + +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_RENDER_OBJECT(RenderMathMLToken, isRenderMathMLToken()) + +#endif // ENABLE(MATHML) diff --git a/Source/WebCore/rendering/mathml/RenderMathMLUnderOver.cpp b/Source/WebCore/rendering/mathml/RenderMathMLUnderOver.cpp index 34af852db..2ab8cc5c6 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLUnderOver.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLUnderOver.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2009 Alex Milowski (alex@milowski.com). 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 @@ -24,48 +25,290 @@ */ #include "config.h" +#include "RenderMathMLUnderOver.h" #if ENABLE(MATHML) -#include "RenderMathMLUnderOver.h" - -#include "MathMLNames.h" +#include "MathMLElement.h" +#include "MathMLOperatorDictionary.h" +#include "MathMLUnderOverElement.h" +#include "RenderIterator.h" +#include "RenderMathMLOperator.h" namespace WebCore { -using namespace MathMLNames; - -RenderMathMLUnderOver::RenderMathMLUnderOver(Element& element, PassRef<RenderStyle> style) - : RenderMathMLBlock(element, std::move(style)) +RenderMathMLUnderOver::RenderMathMLUnderOver(MathMLUnderOverElement& element, RenderStyle&& style) + : RenderMathMLScripts(element, WTFMove(style)) +{ +} + +MathMLUnderOverElement& RenderMathMLUnderOver::element() const +{ + return static_cast<MathMLUnderOverElement&>(nodeForNonAnonymous()); +} + +void RenderMathMLUnderOver::computeOperatorsHorizontalStretch() { - // Determine what kind of under/over expression we have by element name - if (element.hasLocalName(MathMLNames::munderTag)) - m_kind = Under; - else if (element.hasLocalName(MathMLNames::moverTag)) - m_kind = Over; - else { - ASSERT(element.hasLocalName(MathMLNames::munderoverTag)); - m_kind = UnderOver; + LayoutUnit stretchWidth = 0; + Vector<RenderMathMLOperator*, 2> renderOperators; + + for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) { + if (child->needsLayout()) { + if (is<RenderMathMLBlock>(child)) { + if (auto renderOperator = downcast<RenderMathMLBlock>(*child).unembellishedOperator()) { + if (renderOperator->isStretchy() && !renderOperator->isVertical()) { + renderOperator->resetStretchSize(); + renderOperators.append(renderOperator); + } + } + } + + child->layout(); + } + + // Skipping the embellished op does not work for nested structures like + // <munder><mover><mo>_</mo>...</mover> <mo>_</mo></munder>. + stretchWidth = std::max(stretchWidth, child->logicalWidth()); } + + // Set the sizes of (possibly embellished) stretchy operator children. + for (auto& renderOperator : renderOperators) + renderOperator->stretchTo(stretchWidth); } -RenderMathMLOperator* RenderMathMLUnderOver::unembellishedOperator() +bool RenderMathMLUnderOver::isValid() const { - RenderObject* base = firstChild(); - if (!base || !base->isRenderMathMLBlock()) - return 0; - return toRenderMathMLBlock(base)->unembellishedOperator(); + // Verify whether the list of children is valid: + // <munder> base under </munder> + // <mover> base over </mover> + // <munderover> base under over </munderover> + auto* child = firstChildBox(); + if (!child) + return false; + child = child->nextSiblingBox(); + if (!child) + return false; + child = child->nextSiblingBox(); + switch (m_scriptType) { + case Over: + case Under: + return !child; + case UnderOver: + return child && !child->nextSiblingBox(); + default: + ASSERT_NOT_REACHED(); + return false; + } } -int RenderMathMLUnderOver::firstLineBaseline() const +bool RenderMathMLUnderOver::shouldMoveLimits() { - RenderBox* base = firstChildBox(); - if (!base) - return -1; - LayoutUnit baseline = base->firstLineBaseline(); - if (baseline != -1) - baseline += base->logicalTop(); - return baseline; + if (auto* renderOperator = unembellishedOperator()) + return renderOperator->shouldMoveLimits(); + return false; +} + +RenderBox& RenderMathMLUnderOver::base() const +{ + ASSERT(isValid()); + return *firstChildBox(); +} + +RenderBox& RenderMathMLUnderOver::under() const +{ + ASSERT(isValid()); + ASSERT(m_scriptType == Under || m_scriptType == UnderOver); + return *firstChildBox()->nextSiblingBox(); +} + +RenderBox& RenderMathMLUnderOver::over() const +{ + ASSERT(isValid()); + ASSERT(m_scriptType == Over || m_scriptType == UnderOver); + auto* secondChild = firstChildBox()->nextSiblingBox(); + return m_scriptType == Over ? *secondChild : *secondChild->nextSiblingBox(); +} + + +void RenderMathMLUnderOver::computePreferredLogicalWidths() +{ + ASSERT(preferredLogicalWidthsDirty()); + + if (!isValid()) { + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = 0; + setPreferredLogicalWidthsDirty(false); + return; + } + + if (shouldMoveLimits()) { + RenderMathMLScripts::computePreferredLogicalWidths(); + return; + } + + LayoutUnit preferredWidth = base().maxPreferredLogicalWidth(); + + if (m_scriptType == Under || m_scriptType == UnderOver) + preferredWidth = std::max(preferredWidth, under().maxPreferredLogicalWidth()); + + if (m_scriptType == Over || m_scriptType == UnderOver) + preferredWidth = std::max(preferredWidth, over().maxPreferredLogicalWidth()); + + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = preferredWidth; + + setPreferredLogicalWidthsDirty(false); +} + +LayoutUnit RenderMathMLUnderOver::horizontalOffset(const RenderBox& child) const +{ + return (logicalWidth() - child.logicalWidth()) / 2; +} + +bool RenderMathMLUnderOver::hasAccent(bool accentUnder) const +{ + ASSERT(m_scriptType == UnderOver || (accentUnder && m_scriptType == Under) || (!accentUnder && m_scriptType == Over)); + + const MathMLElement::BooleanValue& attributeValue = accentUnder ? element().accentUnder() : element().accent(); + if (attributeValue == MathMLElement::BooleanValue::True) + return true; + if (attributeValue == MathMLElement::BooleanValue::False) + return false; + RenderBox& script = accentUnder ? under() : over(); + if (!is<RenderMathMLBlock>(script)) + return false; + auto* scriptOperator = downcast<RenderMathMLBlock>(script).unembellishedOperator(); + return scriptOperator && scriptOperator->hasOperatorFlag(MathMLOperatorDictionary::Accent); +} + +RenderMathMLUnderOver::VerticalParameters RenderMathMLUnderOver::verticalParameters() const +{ + VerticalParameters parameters; + + // By default, we set all values to zero. + parameters.underGapMin = 0; + parameters.overGapMin = 0; + parameters.underShiftMin = 0; + parameters.overShiftMin = 0; + parameters.underExtraDescender = 0; + parameters.overExtraAscender = 0; + parameters.accentBaseHeight = 0; + + const auto& primaryFont = style().fontCascade().primaryFont(); + auto* mathData = primaryFont.mathData(); + if (!mathData) { + // The MATH table specification does not really provide any suggestions, except for some underbar/overbar values and AccentBaseHeight. + LayoutUnit defaultLineThickness = ruleThicknessFallback(); + parameters.underGapMin = 3 * defaultLineThickness; + parameters.overGapMin = 3 * defaultLineThickness; + parameters.underExtraDescender = defaultLineThickness; + parameters.overExtraAscender = defaultLineThickness; + parameters.accentBaseHeight = style().fontMetrics().xHeight(); + parameters.useUnderOverBarFallBack = true; + return parameters; + } + + if (is<RenderMathMLBlock>(base())) { + if (auto* baseOperator = downcast<RenderMathMLBlock>(base()).unembellishedOperator()) { + if (baseOperator->hasOperatorFlag(MathMLOperatorDictionary::LargeOp)) { + // The base is a large operator so we read UpperLimit/LowerLimit constants from the MATH table. + parameters.underGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::LowerLimitGapMin); + parameters.overGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::UpperLimitGapMin); + parameters.underShiftMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::LowerLimitBaselineDropMin); + parameters.overShiftMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::UpperLimitBaselineRiseMin); + parameters.useUnderOverBarFallBack = false; + return parameters; + } + if (baseOperator->isStretchy() && !baseOperator->isVertical()) { + // The base is a horizontal stretchy operator, so we read StretchStack constants from the MATH table. + parameters.underGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::StretchStackGapBelowMin); + parameters.overGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::StretchStackGapAboveMin); + parameters.underShiftMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::StretchStackBottomShiftDown); + parameters.overShiftMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::StretchStackTopShiftUp); + parameters.useUnderOverBarFallBack = false; + return parameters; + } + } + } + + // By default, we just use the underbar/overbar constants. + parameters.underGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::UnderbarVerticalGap); + parameters.overGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::OverbarVerticalGap); + parameters.underExtraDescender = mathData->getMathConstant(primaryFont, OpenTypeMathData::UnderbarExtraDescender); + parameters.overExtraAscender = mathData->getMathConstant(primaryFont, OpenTypeMathData::OverbarExtraAscender); + parameters.accentBaseHeight = mathData->getMathConstant(primaryFont, OpenTypeMathData::AccentBaseHeight); + parameters.useUnderOverBarFallBack = true; + return parameters; +} + +void RenderMathMLUnderOver::layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight) +{ + ASSERT(needsLayout()); + + if (!relayoutChildren && simplifiedLayout()) + return; + + if (!isValid()) { + layoutInvalidMarkup(); + return; + } + + if (shouldMoveLimits()) { + RenderMathMLScripts::layoutBlock(relayoutChildren, pageLogicalHeight); + return; + } + + recomputeLogicalWidth(); + + computeOperatorsHorizontalStretch(); + + base().layoutIfNeeded(); + if (m_scriptType == Under || m_scriptType == UnderOver) + under().layoutIfNeeded(); + if (m_scriptType == Over || m_scriptType == UnderOver) + over().layoutIfNeeded(); + + LayoutUnit logicalWidth = base().logicalWidth(); + if (m_scriptType == Under || m_scriptType == UnderOver) + logicalWidth = std::max(logicalWidth, under().logicalWidth()); + if (m_scriptType == Over || m_scriptType == UnderOver) + logicalWidth = std::max(logicalWidth, over().logicalWidth()); + setLogicalWidth(logicalWidth); + + VerticalParameters parameters = verticalParameters(); + LayoutUnit verticalOffset = 0; + if (m_scriptType == Over || m_scriptType == UnderOver) { + verticalOffset += parameters.overExtraAscender; + over().setLocation(LayoutPoint(horizontalOffset(over()), verticalOffset)); + if (parameters.useUnderOverBarFallBack) { + verticalOffset += over().logicalHeight(); + if (hasAccent()) { + LayoutUnit baseAscent = ascentForChild(base()); + if (baseAscent < parameters.accentBaseHeight) + verticalOffset += parameters.accentBaseHeight - baseAscent; + } else + verticalOffset += parameters.overGapMin; + } else { + LayoutUnit overAscent = ascentForChild(over()); + verticalOffset += std::max(over().logicalHeight() + parameters.overGapMin, overAscent + parameters.overShiftMin); + } + } + base().setLocation(LayoutPoint(horizontalOffset(base()), verticalOffset)); + verticalOffset += base().logicalHeight(); + if (m_scriptType == Under || m_scriptType == UnderOver) { + if (parameters.useUnderOverBarFallBack) { + if (!hasAccentUnder()) + verticalOffset += parameters.underGapMin; + } else { + LayoutUnit underAscent = ascentForChild(under()); + verticalOffset += std::max(parameters.underGapMin, parameters.underShiftMin - underAscent); + } + under().setLocation(LayoutPoint(horizontalOffset(under()), verticalOffset)); + verticalOffset += under().logicalHeight(); + verticalOffset += parameters.underExtraDescender; + } + + setLogicalHeight(verticalOffset); + + clearNeedsLayout(); } } diff --git a/Source/WebCore/rendering/mathml/RenderMathMLUnderOver.h b/Source/WebCore/rendering/mathml/RenderMathMLUnderOver.h index 940979775..6c08d47b2 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLUnderOver.h +++ b/Source/WebCore/rendering/mathml/RenderMathMLUnderOver.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2009 Alex Milowski (alex@milowski.com). 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 @@ -23,33 +24,53 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef RenderMathMLUnderOver_h -#define RenderMathMLUnderOver_h +#pragma once #if ENABLE(MATHML) -#include "RenderMathMLBlock.h" +#include "RenderMathMLScripts.h" namespace WebCore { - -class RenderMathMLUnderOver : public RenderMathMLBlock { + +class MathMLUnderOverElement; + +class RenderMathMLUnderOver final : public RenderMathMLScripts { public: - RenderMathMLUnderOver(Element&, PassRef<RenderStyle>); - - virtual RenderMathMLOperator* unembellishedOperator(); + RenderMathMLUnderOver(MathMLUnderOverElement&, RenderStyle&&); - virtual int firstLineBaseline() const override; - private: - virtual bool isRenderMathMLUnderOver() const override { return true; } - virtual const char* renderName() const override { return "RenderMathMLUnderOver"; } + bool isRenderMathMLScripts() const final { return false; } + bool isRenderMathMLUnderOver() const final { return true; } + const char* renderName() const final { return "RenderMathMLUnderOver"; } + MathMLUnderOverElement& element() const; + + void computePreferredLogicalWidths() final; + void layoutBlock(bool relayoutChildren, LayoutUnit pageLogicalHeight = 0) final; - enum UnderOverType { Under, Over, UnderOver }; - UnderOverType m_kind; + void computeOperatorsHorizontalStretch(); + bool isValid() const; + bool shouldMoveLimits(); + RenderBox& base() const; + RenderBox& under() const; + RenderBox& over() const; + LayoutUnit horizontalOffset(const RenderBox&) const; + bool hasAccent(bool accentUnder = false) const; + bool hasAccentUnder() const { return hasAccent(true); }; + struct VerticalParameters { + bool useUnderOverBarFallBack; + LayoutUnit underGapMin; + LayoutUnit overGapMin; + LayoutUnit underShiftMin; + LayoutUnit overShiftMin; + LayoutUnit underExtraDescender; + LayoutUnit overExtraAscender; + LayoutUnit accentBaseHeight; + }; + VerticalParameters verticalParameters() const; }; - + } -#endif // ENABLE(MATHML) +SPECIALIZE_TYPE_TRAITS_RENDER_OBJECT(RenderMathMLUnderOver, isRenderMathMLUnderOver()) -#endif // RenderMathMLUnderOver_h +#endif // ENABLE(MATHML) |