summaryrefslogtreecommitdiff
path: root/Source/WebCore/rendering/mathml/MathOperator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/rendering/mathml/MathOperator.cpp')
-rw-r--r--Source/WebCore/rendering/mathml/MathOperator.cpp746
1 files changed, 746 insertions, 0 deletions
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