RenderMathMLScripts.cpp [plain text]
#include "config.h"
#if ENABLE(MATHML)
#include "RenderMathMLScripts.h"
#include "MathMLElement.h"
#include "RenderMathMLOperator.h"
namespace WebCore {
using namespace MathMLNames;
static bool isPrescriptDelimiter(const RenderObject& renderObject)
{
return renderObject.node() && renderObject.node()->hasTagName(MathMLNames::mprescriptsTag);
}
RenderMathMLScripts::RenderMathMLScripts(Element& element, RenderStyle&& style)
: RenderMathMLBlock(element, WTFMove(style))
{
if (element.hasTagName(MathMLNames::msubTag))
m_scriptType = Sub;
else if (element.hasTagName(MathMLNames::msupTag))
m_scriptType = Super;
else if (element.hasTagName(MathMLNames::msubsupTag))
m_scriptType = SubSup;
else if (element.hasTagName(MathMLNames::munderTag))
m_scriptType = Under;
else if (element.hasTagName(MathMLNames::moverTag))
m_scriptType = Over;
else if (element.hasTagName(MathMLNames::munderoverTag))
m_scriptType = UnderOver;
else {
ASSERT(element.hasTagName(MathMLNames::mmultiscriptsTag));
m_scriptType = Multiscripts;
}
}
RenderMathMLOperator* RenderMathMLScripts::unembellishedOperator()
{
auto base = firstChildBox();
if (!is<RenderMathMLBlock>(base))
return nullptr;
return downcast<RenderMathMLBlock>(base)->unembellishedOperator();
}
bool RenderMathMLScripts::getBaseAndScripts(RenderBox*& base, RenderBox*& firstPostScript, RenderBox*& firstPreScript)
{
base = firstChildBox();
firstPostScript = nullptr;
firstPreScript = nullptr;
if (!base)
return false;
switch (m_scriptType) {
case Sub:
case Super:
case Under:
case Over:
firstPostScript = base->nextSiblingBox();
return firstPostScript && !isPrescriptDelimiter(*firstPostScript) && !firstPostScript->nextSiblingBox();
case SubSup:
case UnderOver: {
firstPostScript = base->nextSiblingBox();
if (!firstPostScript || isPrescriptDelimiter(*firstPostScript))
return false;
auto superScript = firstPostScript->nextSiblingBox();
return superScript && !isPrescriptDelimiter(*superScript) && !superScript->nextSiblingBox();
}
case Multiscripts: {
if (base->nextSiblingBox() && !isPrescriptDelimiter(*base->nextSiblingBox()))
firstPostScript = base->nextSiblingBox();
bool numberOfScriptIsEven = true;
for (auto script = base->nextSiblingBox(); script; script = script->nextSiblingBox()) {
if (isPrescriptDelimiter(*script)) {
if (!numberOfScriptIsEven || firstPreScript)
return false;
firstPreScript = script->nextSiblingBox(); continue;
}
numberOfScriptIsEven = !numberOfScriptIsEven;
}
return numberOfScriptIsEven; }
}
ASSERT_NOT_REACHED();
return false;
}
LayoutUnit RenderMathMLScripts::spaceAfterScript()
{
const auto& primaryFont = style().fontCascade().primaryFont();
if (auto* mathData = primaryFont.mathData())
return mathData->getMathConstant(primaryFont, OpenTypeMathData::SpaceAfterScript);
return style().fontCascade().size() / 5;
}
LayoutUnit RenderMathMLScripts::italicCorrection(RenderBox* base)
{
if (is<RenderMathMLBlock>(*base)) {
if (auto* renderOperator = downcast<RenderMathMLBlock>(*base).unembellishedOperator())
return renderOperator->italicCorrection();
}
return 0;
}
void RenderMathMLScripts::computePreferredLogicalWidths()
{
m_minPreferredLogicalWidth = 0;
m_maxPreferredLogicalWidth = 0;
RenderBox* base;
RenderBox* firstPostScript;
RenderBox* firstPreScript;
if (!getBaseAndScripts(base, firstPostScript, firstPreScript))
return;
LayoutUnit baseItalicCorrection = std::min(base->maxPreferredLogicalWidth(), italicCorrection(base));
LayoutUnit space = spaceAfterScript();
switch (m_scriptType) {
case Sub:
case Under:
m_maxPreferredLogicalWidth += base->maxPreferredLogicalWidth();
m_maxPreferredLogicalWidth += std::max(LayoutUnit(0), firstPostScript->maxPreferredLogicalWidth() - baseItalicCorrection + space);
break;
case Super:
case Over:
m_maxPreferredLogicalWidth += base->maxPreferredLogicalWidth();
m_maxPreferredLogicalWidth += std::max(LayoutUnit(0), firstPostScript->maxPreferredLogicalWidth() + space);
break;
case SubSup:
case UnderOver:
case Multiscripts: {
RenderBox* supScript;
for (auto* subScript = firstPreScript; subScript; subScript = supScript->nextSiblingBox()) {
supScript = subScript->nextSiblingBox();
ASSERT(supScript);
LayoutUnit subSupPairWidth = std::max(subScript->maxPreferredLogicalWidth(), supScript->maxPreferredLogicalWidth());
m_maxPreferredLogicalWidth += subSupPairWidth + space;
}
m_maxPreferredLogicalWidth += base->maxPreferredLogicalWidth();
for (auto* subScript = firstPostScript; subScript && !isPrescriptDelimiter(*subScript); subScript = supScript->nextSiblingBox()) {
supScript = subScript->nextSiblingBox();
ASSERT(supScript);
LayoutUnit subSupPairWidth = std::max(std::max(LayoutUnit(0), subScript->maxPreferredLogicalWidth() - baseItalicCorrection), supScript->maxPreferredLogicalWidth());
m_maxPreferredLogicalWidth += subSupPairWidth + space;
}
}
}
m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth;
}
void RenderMathMLScripts::getScriptMetricsAndLayoutIfNeeded(RenderBox* base, RenderBox* script, LayoutUnit& minSubScriptShift, LayoutUnit& minSupScriptShift, LayoutUnit& maxScriptDescent, LayoutUnit& maxScriptAscent)
{
LayoutUnit baseAscent = ascentForChild(*base);
LayoutUnit baseDescent = base->logicalHeight() - baseAscent;
LayoutUnit subscriptShiftDown;
LayoutUnit superscriptShiftUp;
LayoutUnit subscriptBaselineDropMin;
LayoutUnit superScriptBaselineDropMax;
LayoutUnit subSuperscriptGapMin;
LayoutUnit superscriptBottomMin;
LayoutUnit subscriptTopMax;
LayoutUnit superscriptBottomMaxWithSubscript;
const auto& primaryFont = style().fontCascade().primaryFont();
if (auto* mathData = primaryFont.mathData()) {
subscriptShiftDown = mathData->getMathConstant(primaryFont, OpenTypeMathData::SubscriptShiftDown);
superscriptShiftUp = mathData->getMathConstant(primaryFont, OpenTypeMathData::SuperscriptShiftUp);
subscriptBaselineDropMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::SubscriptBaselineDropMin);
superScriptBaselineDropMax = mathData->getMathConstant(primaryFont, OpenTypeMathData::SuperscriptBaselineDropMax);
subSuperscriptGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::SubSuperscriptGapMin);
superscriptBottomMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::SuperscriptBottomMin);
subscriptTopMax = mathData->getMathConstant(primaryFont, OpenTypeMathData::SubscriptTopMax);
superscriptBottomMaxWithSubscript = mathData->getMathConstant(primaryFont, OpenTypeMathData::SuperscriptBottomMaxWithSubscript);
} else {
subscriptShiftDown = style().fontMetrics().xHeight() / 3;
superscriptShiftUp = style().fontMetrics().xHeight();
subscriptBaselineDropMin = style().fontMetrics().xHeight() / 2;
superScriptBaselineDropMax = style().fontMetrics().xHeight() / 2;
subSuperscriptGapMin = style().fontCascade().size() / 5;
superscriptBottomMin = style().fontMetrics().xHeight() / 4;
subscriptTopMax = 4 * style().fontMetrics().xHeight() / 5;
superscriptBottomMaxWithSubscript = 4 * style().fontMetrics().xHeight() / 5;
}
if (m_scriptType == Sub || m_scriptType == SubSup || m_scriptType == Multiscripts || m_scriptType == Under || m_scriptType == UnderOver) {
minSubScriptShift = std::max(subscriptShiftDown, baseDescent + subscriptBaselineDropMin);
if (!isRenderMathMLUnderOver()) {
LayoutUnit specifiedMinSubShift = 0;
parseMathMLLength(element()->attributeWithoutSynchronization(MathMLNames::subscriptshiftAttr), specifiedMinSubShift, &style(), false);
minSubScriptShift = std::max(minSubScriptShift, specifiedMinSubShift);
}
}
if (m_scriptType == Super || m_scriptType == SubSup || m_scriptType == Multiscripts || m_scriptType == Over || m_scriptType == UnderOver) {
minSupScriptShift = std::max(superscriptShiftUp, baseAscent - superScriptBaselineDropMax);
if (!isRenderMathMLUnderOver()) {
LayoutUnit specifiedMinSupShift = 0;
parseMathMLLength(element()->attributeWithoutSynchronization(MathMLNames::superscriptshiftAttr), specifiedMinSupShift, &style(), false);
minSupScriptShift = std::max(minSupScriptShift, specifiedMinSupShift);
}
}
switch (m_scriptType) {
case Sub:
case Under: {
script->layoutIfNeeded();
LayoutUnit subAscent = ascentForChild(*script);
LayoutUnit subDescent = script->logicalHeight() - subAscent;
maxScriptDescent = subDescent;
minSubScriptShift = std::max(minSubScriptShift, subAscent - subscriptTopMax);
}
break;
case Super:
case Over: {
script->layoutIfNeeded();
LayoutUnit supAscent = ascentForChild(*script);
LayoutUnit supDescent = script->logicalHeight() - supAscent;
maxScriptAscent = supAscent;
minSupScriptShift = std::max(minSupScriptShift, superscriptBottomMin + supDescent);
}
break;
case SubSup:
case UnderOver:
case Multiscripts: {
RenderBox* supScript;
for (auto* subScript = script; subScript && !isPrescriptDelimiter(*subScript); subScript = supScript->nextSiblingBox()) {
supScript = subScript->nextSiblingBox();
ASSERT(supScript);
subScript->layoutIfNeeded();
supScript->layoutIfNeeded();
LayoutUnit subAscent = ascentForChild(*subScript);
LayoutUnit subDescent = subScript->logicalHeight() - subAscent;
LayoutUnit supAscent = ascentForChild(*supScript);
LayoutUnit supDescent = supScript->logicalHeight() - supAscent;
maxScriptAscent = std::max(maxScriptAscent, supAscent);
maxScriptDescent = std::max(maxScriptDescent, subDescent);
LayoutUnit subScriptShift = std::max(subscriptShiftDown, baseDescent + subscriptBaselineDropMin);
subScriptShift = std::max(subScriptShift, subAscent - subscriptTopMax);
LayoutUnit supScriptShift = std::max(superscriptShiftUp, baseAscent - superScriptBaselineDropMax);
supScriptShift = std::max(supScriptShift, superscriptBottomMin + supDescent);
LayoutUnit subSuperscriptGap = (subScriptShift - subAscent) + (supScriptShift - supDescent);
if (subSuperscriptGap < subSuperscriptGapMin) {
LayoutUnit delta = superscriptBottomMaxWithSubscript - (supScriptShift - supDescent);
if (delta > 0) {
delta = std::min(delta, subSuperscriptGapMin - subSuperscriptGap);
supScriptShift += delta;
subSuperscriptGap += delta;
}
if (subSuperscriptGap < subSuperscriptGapMin)
subScriptShift += subSuperscriptGapMin - subSuperscriptGap;
}
minSubScriptShift = std::max(minSubScriptShift, subScriptShift);
minSupScriptShift = std::max(minSupScriptShift, supScriptShift);
}
}
}
}
void RenderMathMLScripts::layoutBlock(bool relayoutChildren, LayoutUnit)
{
ASSERT(needsLayout());
if (!relayoutChildren && simplifiedLayout())
return;
RenderBox* base;
RenderBox* firstPostScript;
RenderBox* firstPreScript;
if (!getBaseAndScripts(base, firstPostScript, firstPreScript)) {
setLogicalWidth(0);
setLogicalHeight(0);
clearNeedsLayout();
return;
}
recomputeLogicalWidth();
base->layoutIfNeeded();
LayoutUnit supScriptShift = 0;
LayoutUnit subScriptShift = 0;
LayoutUnit scriptDescent = 0;
LayoutUnit scriptAscent = 0;
LayoutUnit space = spaceAfterScript();
if (m_scriptType == Multiscripts)
getScriptMetricsAndLayoutIfNeeded(base, firstPreScript, subScriptShift, supScriptShift, scriptDescent, scriptAscent);
getScriptMetricsAndLayoutIfNeeded(base, firstPostScript, subScriptShift, supScriptShift, scriptDescent, scriptAscent);
LayoutUnit baseAscent = ascentForChild(*base);
LayoutUnit baseDescent = base->logicalHeight() - baseAscent;
LayoutUnit baseItalicCorrection = std::min(base->logicalWidth(), italicCorrection(base));
LayoutUnit horizontalOffset = 0;
LayoutUnit ascent = std::max(baseAscent, scriptAscent + supScriptShift);
LayoutUnit descent = std::max(baseDescent, scriptDescent + subScriptShift);
setLogicalHeight(ascent + descent);
switch (m_scriptType) {
case Sub:
case Under: {
setLogicalWidth(base->logicalWidth() + std::max(LayoutUnit(0), firstPostScript->logicalWidth() - baseItalicCorrection + space));
LayoutPoint baseLocation(mirrorIfNeeded(horizontalOffset, *base), ascent - baseAscent);
base->setLocation(baseLocation);
horizontalOffset += base->logicalWidth();
LayoutUnit scriptAscent = ascentForChild(*firstPostScript);
LayoutPoint scriptLocation(mirrorIfNeeded(horizontalOffset - baseItalicCorrection, *firstPostScript), ascent + subScriptShift - scriptAscent);
firstPostScript->setLocation(scriptLocation);
}
break;
case Super:
case Over: {
setLogicalWidth(base->logicalWidth() + std::max(LayoutUnit(0), firstPostScript->logicalWidth() + space));
LayoutPoint baseLocation(mirrorIfNeeded(horizontalOffset, *base), ascent - baseAscent);
base->setLocation(baseLocation);
horizontalOffset += base->logicalWidth();
LayoutUnit scriptAscent = ascentForChild(*firstPostScript);
LayoutPoint scriptLocation(mirrorIfNeeded(horizontalOffset, *firstPostScript), ascent - supScriptShift - scriptAscent);
firstPostScript->setLocation(scriptLocation);
}
break;
case SubSup:
case UnderOver:
case Multiscripts: {
RenderBox* supScript;
LayoutUnit logicalWidth = 0;
for (auto* subScript = firstPreScript; subScript; subScript = supScript->nextSiblingBox()) {
supScript = subScript->nextSiblingBox();
ASSERT(supScript);
LayoutUnit subSupPairWidth = std::max(subScript->logicalWidth(), supScript->logicalWidth());
logicalWidth += subSupPairWidth + space;
}
logicalWidth += base->logicalWidth();
for (auto* subScript = firstPostScript; subScript && !isPrescriptDelimiter(*subScript); subScript = supScript->nextSiblingBox()) {
supScript = subScript->nextSiblingBox();
ASSERT(supScript);
LayoutUnit subSupPairWidth = std::max(std::max(LayoutUnit(0), subScript->logicalWidth() - baseItalicCorrection), supScript->logicalWidth());
logicalWidth += subSupPairWidth + space;
}
setLogicalWidth(logicalWidth);
for (auto* subScript = firstPreScript; subScript; subScript = supScript->nextSiblingBox()) {
supScript = subScript->nextSiblingBox();
ASSERT(supScript);
LayoutUnit subSupPairWidth = std::max(subScript->logicalWidth(), supScript->logicalWidth());
horizontalOffset += space + subSupPairWidth;
LayoutUnit subAscent = ascentForChild(*subScript);
LayoutPoint subScriptLocation(mirrorIfNeeded(horizontalOffset - subScript->logicalWidth(), *subScript), ascent + subScriptShift - subAscent);
subScript->setLocation(subScriptLocation);
LayoutUnit supAscent = ascentForChild(*supScript);
LayoutPoint supScriptLocation(mirrorIfNeeded(horizontalOffset - supScript->logicalWidth(), *supScript), ascent - supScriptShift - supAscent);
supScript->setLocation(supScriptLocation);
}
LayoutPoint baseLocation(mirrorIfNeeded(horizontalOffset, *base), ascent - baseAscent);
base->setLocation(baseLocation);
horizontalOffset += base->logicalWidth();
for (auto* subScript = firstPostScript; subScript && !isPrescriptDelimiter(*subScript); subScript = supScript->nextSiblingBox()) {
supScript = subScript->nextSiblingBox();
ASSERT(supScript);
LayoutUnit subAscent = ascentForChild(*subScript);
LayoutPoint subScriptLocation(mirrorIfNeeded(horizontalOffset - baseItalicCorrection, *subScript), ascent + subScriptShift - subAscent);
subScript->setLocation(subScriptLocation);
LayoutUnit supAscent = ascentForChild(*supScript);
LayoutPoint supScriptLocation(mirrorIfNeeded(horizontalOffset, *supScript), ascent - supScriptShift - supAscent);
supScript->setLocation(supScriptLocation);
LayoutUnit subSupPairWidth = std::max(subScript->logicalWidth(), supScript->logicalWidth());
horizontalOffset += subSupPairWidth + space;
}
}
}
clearNeedsLayout();
}
Optional<int> RenderMathMLScripts::firstLineBaseline() const
{
ASSERT(!needsLayout());
auto* base = firstChildBox();
if (!base)
return Optional<int>();
return Optional<int>(static_cast<int>(lroundf(ascentForChild(*base) + base->logicalTop())));
}
}
#endif // ENABLE(MATHML)