#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;
}
struct StretchyCharacter {
UChar32 character;
UChar topChar;
UChar extensionChar;
UChar bottomChar;
UChar middleChar;
};
static const short leftRightPairsCount = 5;
static const StretchyCharacter stretchyCharacters[14] = {
{ 0x28 , 0x239b, 0x239c, 0x239d, 0x0 }, { 0x29 , 0x239e, 0x239f, 0x23a0, 0x0 }, { 0x5b , 0x23a1, 0x23a2, 0x23a3, 0x0 }, { 0x5d , 0x23a4, 0x23a5, 0x23a6, 0x0 }, { 0x7b , 0x23a7, 0x23aa, 0x23a9, 0x23a8 }, { 0x7d , 0x23ab, 0x23aa, 0x23ad, 0x23ac }, { 0x2308, 0x23a1, 0x23a2, 0x23a2, 0x0 }, { 0x2309, 0x23a4, 0x23a5, 0x23a5, 0x0 }, { 0x230a, 0x23a2, 0x23a2, 0x23a3, 0x0 }, { 0x230b, 0x23a5, 0x23a5, 0x23a6, 0x0 }, { 0x7c , 0x7c, 0x7c, 0x7c, 0x0 }, { 0x2016, 0x2016, 0x2016, 0x2016, 0x0 }, { 0x2225, 0x2225, 0x2225, 0x2225, 0x0 }, { 0x222b, 0x2320, 0x23ae, 0x2321, 0x0 } };
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;
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); else if (m_operatorType == Type::DisplayOperator)
calculateDisplayStyleLargeOperator(style); }
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);
}
}
const unsigned maxFallbackPerCharacter = 3;
static const UChar32 characterFallback[][maxFallbackPerCharacter] = {
{ 0x005E, 0x0302, 0 }, { 0x005F, 0x0332, 0 }, { 0x007E, 0x0303, 0 }, { 0x00AF, 0x0304, 0x0305 }, { 0x02C6, 0x0302, 0 }, { 0x02C7, 0x030C, 0 } };
const unsigned characterFallbackSize = WTF_ARRAY_LENGTH(characterFallback);
void MathOperator::getMathVariantsWithFallback(const RenderStyle& style, bool isVertical, Vector<Glyph>& sizeVariants, Vector<OpenTypeMathData::AssemblyPart>& assemblyParts)
{
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;
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;
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);
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
{
int nonExtenderCount = 0;
for (auto& part : assemblyParts) {
if (!part.isExtender)
nonExtenderCount++;
}
if (nonExtenderCount > 3)
return false;
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 (expectedPartType == ExtenderBetweenStartAndMiddle)
expectedPartType = ExtenderBetweenMiddleAndEnd;
else if (expectedPartType == Middle)
expectedPartType = End;
}
if (part.isExtender) {
if (!assemblyData.extensionFallbackGlyph)
assemblyData.extensionFallbackGlyph = part.glyph; else if (assemblyData.extensionFallbackGlyph != part.glyph)
return false;
switch (expectedPartType) {
case Start:
expectedPartType = ExtenderBetweenStartAndMiddle;
continue;
case Middle:
expectedPartType = ExtenderBetweenMiddleAndEnd;
continue;
case End:
case None:
return false;
case ExtenderBetweenStartAndMiddle:
case ExtenderBetweenMiddleAndEnd:
continue;
}
}
switch (expectedPartType) {
case Start:
assemblyData.bottomOrLeftFallbackGlyph = part.glyph;
assemblyData.bottomOrLeftCodePoint = 0;
expectedPartType = ExtenderBetweenStartAndMiddle;
continue;
case ExtenderBetweenStartAndMiddle:
case Middle:
assemblyData.middleFallbackGlyph = part.glyph;
expectedPartType = ExtenderBetweenMiddleAndEnd;
continue;
case ExtenderBetweenMiddleAndEnd:
case End:
assemblyData.topOrRightFallbackGlyph = part.glyph;
assemblyData.topOrRightCodePoint = 0;
expectedPartType = None;
continue;
case None:
return false;
}
}
if (!assemblyData.hasExtension())
return false;
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) {
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);
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;
}
}
if (!calculateGlyphAssemblyFallback(assemblyParts, assemblyData))
return;
} else {
if (!isVertical)
return;
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) {
index += index % 2 ? -1 : 1;
}
break;
}
}
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 (!stretchyCharacter)
return;
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 (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;
}
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());
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());
}
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 (from.y() == to.y())
return;
GraphicsContextStateSaver stateSaver(info.context());
FloatRect glyphBounds = boundsForGlyph(extension);
LayoutRect clipBounds = info.rect;
clipBounds.shiftYEdgeTo(from.y());
clipBounds.shiftMaxYEdgeTo(to.y());
info.context().clip(clipBounds);
float offsetToGlyphTop = glyphBounds.y() + 2;
LayoutPoint glyphOrigin = LayoutPoint(from.x(), from.y() - offsetToGlyphTop);
FloatRect lastPaintedGlyphRect(from, FloatSize());
for (unsigned extensionCount = 0; lastPaintedGlyphRect.maxY() < to.y() && extensionCount < kMaximumExtensionCount; extensionCount++) {
lastPaintedGlyphRect = paintGlyph(style, info, extension, glyphOrigin, TrimTopAndBottom);
glyphOrigin.setY(glyphOrigin.y() + lastPaintedGlyphRect.height());
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 (from.x() == to.x())
return;
GraphicsContextStateSaver stateSaver(info.context());
LayoutRect clipBounds = info.rect;
clipBounds.shiftXEdgeTo(from.x());
clipBounds.shiftMaxXEdgeTo(to.x());
info.context().clip(clipBounds);
float offsetToGlyphLeft = -2;
LayoutPoint glyphOrigin = LayoutPoint(from.x() + offsetToGlyphLeft, from.y());
FloatRect lastPaintedGlyphRect(from, FloatSize());
for (unsigned extensionCount = 0; lastPaintedGlyphRect.maxX() < to.x() && extensionCount < kMaximumExtensionCount; extensionCount++) {
lastPaintedGlyphRect = paintGlyph(style, info, extension, glyphOrigin, TrimLeftAndRight);
glyphOrigin.setX(glyphOrigin.x() + lastPaintedGlyphRect.width());
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);
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);
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);
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);
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;
PaintInfo paintInfo(info);
GraphicsContextStateSaver stateSaver(paintInfo.context());
paintInfo.context().setFillColor(style.visitedDependentColor(CSSPropertyColor));
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