blob: 2ee272e3f701bd3978077a4eb47ed6bb6410f3c5 [file] [log] [blame]
/*
* 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);
if (!topOrRight.font || !bottomOrLeft.font) {
LOG_ERROR("MathML: no font can be found for Unicode code point.");
return;
}
// 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);
if (!topOrRight.font || !bottomOrLeft.font) {
LOG_ERROR("MathML: no font can be found for Unicode code point.");
return;
}
// 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.visitedDependentColorWithColorFilter(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