/* * 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(m_width, advanceWidthForGlyph(topOrRight)); m_width = std::max(m_width, advanceWidthForGlyph(extension)); m_width = std::max(m_width, advanceWidthForGlyph(bottomOrLeft)); m_width = std::max(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& sizeVariants, Vector& 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 sizeVariants; Vector 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& 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 sizeVariants; Vector 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(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(m_maxPreferredWidth, advanceWidthForGlyph(topOrRight)); m_maxPreferredWidth = std::max(m_maxPreferredWidth, advanceWidthForGlyph(extension)); m_maxPreferredWidth = std::max(m_maxPreferredWidth, advanceWidthForGlyph(middle)); m_maxPreferredWidth = std::max(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