RenderMathMLOperator.cpp [plain text]
#include "config.h"
#if ENABLE(MATHML)
#include "RenderMathMLOperator.h"
#include "FontCache.h"
#include "FontSelector.h"
#include "MathMLNames.h"
#include "PaintInfo.h"
#include "RenderText.h"
#include "ScaleTransformOperation.h"
#include "TransformOperations.h"
#include <wtf/MathExtras.h>
namespace WebCore {
using namespace MathMLNames;
static RenderMathMLOperator::StretchyCharacter stretchyCharacters[13] = {
{ 0x28 , 0x239b, 0x239c, 0x239d, 0x0 }, { 0x29 , 0x239e, 0x239f, 0x23a0, 0x0 }, { 0x5b , 0x23a1, 0x23a2, 0x23a3, 0x0 }, { 0x2308, 0x23a1, 0x23a2, 0x23a2, 0x0 }, { 0x230a, 0x23a2, 0x23a2, 0x23a3, 0x0 }, { 0x5d , 0x23a4, 0x23a5, 0x23a6, 0x0 }, { 0x2309, 0x23a4, 0x23a5, 0x23a5, 0x0 }, { 0x230b, 0x23a5, 0x23a5, 0x23a6, 0x0 }, { 0x7b , 0x23a7, 0x23aa, 0x23a9, 0x23a8 }, { 0x7c , 0x23aa, 0x23aa, 0x23aa, 0x0 }, { 0x2016, 0x2016, 0x2016, 0x2016, 0x0 }, { 0x7d , 0x23ab, 0x23aa, 0x23ad, 0x23ac }, { 0x222b, 0x2320, 0x23ae, 0x2321, 0x0 } };
RenderMathMLOperator::RenderMathMLOperator(Element* element)
: RenderMathMLBlock(element)
, m_stretchHeight(0)
, m_operator(0)
, m_operatorType(Default)
, m_stretchyCharacter(0)
{
}
RenderMathMLOperator::RenderMathMLOperator(Element* element, UChar operatorChar)
: RenderMathMLBlock(element)
, m_stretchHeight(0)
, m_operator(convertHyphenMinusToMinusSign(operatorChar))
, m_operatorType(Default)
, m_stretchyCharacter(0)
{
}
bool RenderMathMLOperator::isChildAllowed(RenderObject*, RenderStyle*) const
{
return false;
}
static const float gOperatorExpansion = 1.2f;
float RenderMathMLOperator::expandedStretchHeight() const
{
return m_stretchHeight * gOperatorExpansion;
}
void RenderMathMLOperator::stretchToHeight(int height)
{
if (m_stretchHeight == height)
return;
m_stretchHeight = height;
updateStyle();
}
void RenderMathMLOperator::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
{
RenderMathMLBlock::styleDidChange(diff, oldStyle);
updateFromElement();
}
FloatRect RenderMathMLOperator::glyphBoundsForCharacter(UChar character)
{
GlyphData data = style()->font().glyphDataForCharacter(character, false);
return data.fontData->boundsForGlyph(data.glyph);
}
float RenderMathMLOperator::glyphHeightForCharacter(UChar character)
{
return glyphBoundsForCharacter(character).height();
}
float RenderMathMLOperator::advanceForCharacter(UChar character)
{
GlyphData data = style()->font().glyphDataForCharacter(convertHyphenMinusToMinusSign(character), false);
return data.fontData->widthForGlyph(data.glyph);
}
void RenderMathMLOperator::computePreferredLogicalWidths()
{
ASSERT(preferredLogicalWidthsDirty());
UChar stretchedCharacter;
bool allowStretching = shouldAllowStretching(stretchedCharacter);
if (!allowStretching) {
RenderMathMLBlock::computePreferredLogicalWidths();
return;
}
float maximumGlyphWidth = advanceForCharacter(stretchedCharacter);
for (unsigned index = 0; index < WTF_ARRAY_LENGTH(stretchyCharacters); ++index) {
if (stretchyCharacters[index].character != stretchedCharacter)
continue;
StretchyCharacter& character = stretchyCharacters[index];
if (character.topGlyph)
maximumGlyphWidth = std::max(maximumGlyphWidth, advanceForCharacter(character.topGlyph));
if (character.extensionGlyph)
maximumGlyphWidth = std::max(maximumGlyphWidth, advanceForCharacter(character.extensionGlyph));
if (character.bottomGlyph)
maximumGlyphWidth = std::max(maximumGlyphWidth, advanceForCharacter(character.bottomGlyph));
if (character.middleGlyph)
maximumGlyphWidth = std::max(maximumGlyphWidth, advanceForCharacter(character.middleGlyph));
m_maxPreferredLogicalWidth = m_minPreferredLogicalWidth = maximumGlyphWidth;
return;
}
m_maxPreferredLogicalWidth = m_minPreferredLogicalWidth = maximumGlyphWidth;
}
void RenderMathMLOperator::updateFromElement()
{
RenderObject* savedRenderer = node()->renderer();
children()->destroyLeftoverChildren();
node()->setRenderer(savedRenderer);
RefPtr<RenderStyle> newStyle = RenderStyle::create();
newStyle->inheritFrom(style());
newStyle->setDisplay(FLEX);
Element* mo = toElement(node());
RenderMathMLBlock* container = new (renderArena()) RenderMathMLBlock(mo);
container->setIgnoreInAccessibilityTree(true);
container->setStyle(newStyle.release());
addChild(container);
RenderText* text = 0;
if (m_operator)
text = new (renderArena()) RenderText(node(), StringImpl::create(&m_operator, 1));
else
text = new (renderArena()) RenderText(node(), mo->textContent().replace(hyphenMinus, minusSign).impl());
if (text) {
RefPtr<RenderStyle> newStyle = RenderStyle::create();
newStyle->inheritFrom(style());
newStyle->setDisplay(FLEX);
text->setStyle(newStyle.release());
container->addChild(text);
}
updateStyle();
setNeedsLayoutAndPrefWidthsRecalc();
}
bool RenderMathMLOperator::shouldAllowStretching(UChar& stretchedCharacter)
{
Element* mo = toElement(node());
if (equalIgnoringCase(mo->getAttribute(MathMLNames::stretchyAttr), "false"))
return false;
if (m_operator) {
stretchedCharacter = m_operator;
return true;
}
String opText = mo->textContent();
stretchedCharacter = 0;
for (unsigned i = 0; i < opText.length(); ++i) {
if (stretchedCharacter && !isSpaceOrNewline(opText[i]))
return false;
if (!isSpaceOrNewline(opText[i]))
stretchedCharacter = opText[i];
}
return stretchedCharacter;
}
RenderMathMLOperator::StretchyCharacter* RenderMathMLOperator::findAcceptableStretchyCharacter(UChar character)
{
StretchyCharacter* stretchyCharacter = 0;
const int maxIndex = WTF_ARRAY_LENGTH(stretchyCharacters);
for (int index = 0; index < maxIndex; ++index) {
if (stretchyCharacters[index].character == character) {
stretchyCharacter = &stretchyCharacters[index];
break;
}
}
if (!stretchyCharacter)
return 0;
float height = glyphHeightForCharacter(stretchyCharacter->topGlyph) + glyphHeightForCharacter(stretchyCharacter->bottomGlyph);
if (stretchyCharacter->middleGlyph)
height += glyphHeightForCharacter(stretchyCharacter->middleGlyph);
if (height > expandedStretchHeight())
return 0;
return stretchyCharacter;
}
void RenderMathMLOperator::updateStyle()
{
ASSERT(firstChild());
if (!firstChild())
return;
UChar stretchedCharacter;
bool allowStretching = shouldAllowStretching(stretchedCharacter);
float stretchedCharacterHeight = style()->fontMetrics().floatHeight();
m_isStretched = allowStretching && expandedStretchHeight() > stretchedCharacterHeight;
m_stretchyCharacter = m_isStretched ? findAcceptableStretchyCharacter(stretchedCharacter) : 0;
if (!m_stretchyCharacter)
m_isStretched = false;
}
int RenderMathMLOperator::firstLineBoxBaseline() const
{
if (m_isStretched)
return expandedStretchHeight() * 2 / 3 - (expandedStretchHeight() - m_stretchHeight) / 2;
return RenderMathMLBlock::firstLineBoxBaseline();
}
void RenderMathMLOperator::computeLogicalHeight(LayoutUnit logicalHeight, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const
{
if (m_isStretched)
logicalHeight = expandedStretchHeight();
RenderBox::computeLogicalHeight(logicalHeight, logicalTop, computedValues);
}
LayoutRect RenderMathMLOperator::paintCharacter(PaintInfo& info, UChar character, const LayoutPoint& origin, CharacterPaintTrimming trim)
{
GlyphData data = style()->font().glyphDataForCharacter(character, false);
FloatRect glyphBounds = data.fontData->boundsForGlyph(data.glyph);
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:
LayoutUnit temp = glyphPaintRect.y() + 1;
glyphPaintRect.shiftYEdgeTo(temp.ceil());
glyphPaintRect.shiftMaxYEdgeTo(glyphPaintRect.maxY().floor() - 1);
clipBounds.shiftYEdgeTo(glyphPaintRect.y());
clipBounds.shiftMaxYEdgeTo(glyphPaintRect.maxY());
break;
}
GraphicsContextStateSaver stateSaver(*info.context);
info.context->clip(clipBounds);
info.context->drawText(style()->font(), TextRun(&character, 1), origin);
return glyphPaintRect;
}
void RenderMathMLOperator::fillWithExtensionGlyph(PaintInfo& info, const LayoutPoint& from, const LayoutPoint& to)
{
ASSERT(m_stretchyCharacter);
ASSERT(m_stretchyCharacter->extensionGlyph);
ASSERT(from.y() < to.y());
GraphicsContextStateSaver stateSaver(*info.context);
FloatRect glyphBounds = glyphBoundsForCharacter(m_stretchyCharacter->extensionGlyph);
IntRect 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());
while (lastPaintedGlyphRect.maxY() < to.y()) {
lastPaintedGlyphRect = paintCharacter(info, m_stretchyCharacter->extensionGlyph, glyphOrigin, TrimTopAndBottom);
glyphOrigin.setY(glyphOrigin.y() + lastPaintedGlyphRect.height());
if (lastPaintedGlyphRect.isEmpty())
break;
}
}
void RenderMathMLOperator::paint(PaintInfo& info, const LayoutPoint& paintOffset)
{
RenderMathMLBlock::paint(info, paintOffset);
if (info.context->paintingDisabled() || info.phase != PaintPhaseForeground)
return;
if (!m_isStretched && !m_stretchyCharacter) {
RenderMathMLBlock::paint(info, paintOffset);
return;
}
GraphicsContextStateSaver stateSaver(*info.context);
info.context->setFillColor(style()->visitedDependentColor(CSSPropertyColor), style()->colorSpace());
ASSERT(m_stretchyCharacter->topGlyph);
ASSERT(m_stretchyCharacter->bottomGlyph);
LayoutPoint operatorTopLeft = ceiledIntPoint(paintOffset + location());
FloatRect topGlyphBounds = glyphBoundsForCharacter(m_stretchyCharacter->topGlyph);
LayoutPoint topGlyphOrigin(operatorTopLeft.x(), operatorTopLeft.y() - topGlyphBounds.y());
LayoutRect topGlyphPaintRect = paintCharacter(info, m_stretchyCharacter->topGlyph, topGlyphOrigin, TrimBottom);
FloatRect bottomGlyphBounds = glyphBoundsForCharacter(m_stretchyCharacter->bottomGlyph);
LayoutPoint bottomGlyphOrigin(operatorTopLeft.x(), operatorTopLeft.y() + offsetHeight() - (bottomGlyphBounds.height() + bottomGlyphBounds.y()));
LayoutRect bottomGlyphPaintRect = paintCharacter(info, m_stretchyCharacter->bottomGlyph, bottomGlyphOrigin, TrimTop);
if (m_stretchyCharacter->middleGlyph) {
FloatRect middleGlyphBounds = glyphBoundsForCharacter(m_stretchyCharacter->middleGlyph);
LayoutPoint middleGlyphOrigin(operatorTopLeft.x(), topGlyphOrigin.y() + y());
middleGlyphOrigin.moveBy(LayoutPoint(0, (bottomGlyphPaintRect.y() - topGlyphPaintRect.maxY()) / 2.0));
middleGlyphOrigin.moveBy(LayoutPoint(0, middleGlyphBounds.height() / 2.0));
LayoutRect middleGlyphPaintRect = paintCharacter(info, m_stretchyCharacter->middleGlyph, middleGlyphOrigin, TrimTopAndBottom);
fillWithExtensionGlyph(info, topGlyphPaintRect.minXMaxYCorner(), middleGlyphPaintRect.minXMinYCorner());
fillWithExtensionGlyph(info, middleGlyphPaintRect.minXMaxYCorner(), bottomGlyphPaintRect.minXMinYCorner());
} else
fillWithExtensionGlyph(info, topGlyphPaintRect.minXMaxYCorner(), bottomGlyphPaintRect.minXMinYCorner());
}
void RenderMathMLOperator::paintChildren(PaintInfo& paintInfo, const LayoutPoint& paintOffset, PaintInfo& paintInfoForChild, bool usePrintRect)
{
if (m_isStretched)
return;
RenderMathMLBlock::paintChildren(paintInfo, paintOffset, paintInfoForChild, usePrintRect);
}
}
#endif