diff options
Diffstat (limited to 'Source/WebCore/rendering/mathml/MathOperator.cpp')
-rw-r--r-- | Source/WebCore/rendering/mathml/MathOperator.cpp | 746 |
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 |