summaryrefslogtreecommitdiff
path: root/Source/WebCore/rendering/mathml/RenderMathMLScripts.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/rendering/mathml/RenderMathMLScripts.cpp')
-rw-r--r--Source/WebCore/rendering/mathml/RenderMathMLScripts.cpp791
1 files changed, 377 insertions, 414 deletions
diff --git a/Source/WebCore/rendering/mathml/RenderMathMLScripts.cpp b/Source/WebCore/rendering/mathml/RenderMathMLScripts.cpp
index 9f3bb8eff..05d50e0c9 100644
--- a/Source/WebCore/rendering/mathml/RenderMathMLScripts.cpp
+++ b/Source/WebCore/rendering/mathml/RenderMathMLScripts.cpp
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2010 Alex Milowski (alex@milowski.com). All rights reserved.
* Copyright (C) 2013 The MathJax Consortium.
+ * Copyright (C) 2016 Igalia S.L.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -25,498 +26,460 @@
*/
#include "config.h"
+#include "RenderMathMLScripts.h"
#if ENABLE(MATHML)
-#include "RenderMathMLScripts.h"
-
-#include "MathMLNames.h"
+#include "MathMLElement.h"
+#include "MathMLScriptsElement.h"
+#include "RenderMathMLOperator.h"
namespace WebCore {
-
-using namespace MathMLNames;
-
-// RenderMathMLScripts implements various MathML elements drawing scripts attached to a base. For valid MathML elements, the structure of the render tree should be:
-//
-// - msub, msup, msubsup: BaseWrapper SubSupPairWrapper
-// - mmultiscripts: BaseWrapper SubSupPairWrapper* (mprescripts SubSupPairWrapper*)*
-//
-// where BaseWrapper and SubSupPairWrapper do not contain any <mprescripts/> children. In addition, BaseWrapper must have one child and SubSupPairWrapper must have either one child (msub, msup) or two children (msubsup, mmultiscripts).
-//
-// In order to accept invalid markup and to handle the script elements consistently and uniformly, we will use a more general structure that encompasses both valid and invalid elements:
-//
-// BaseWrapper SubSupPairWrapper* (mprescripts SubSupPairWrapper*)*
-//
-// where BaseWrapper can now be empty and SubSupPairWrapper can now have one or two elements.
-//
-
-static bool isPrescript(RenderObject* renderObject)
+
+static bool isPrescriptDelimiter(const RenderObject& renderObject)
{
- ASSERT(renderObject);
- return renderObject->node() && renderObject->node()->hasTagName(MathMLNames::mprescriptsTag);
+ return renderObject.node() && renderObject.node()->hasTagName(MathMLNames::mprescriptsTag);
}
-RenderMathMLScripts::RenderMathMLScripts(Element& element, PassRef<RenderStyle> style)
- : RenderMathMLBlock(element, std::move(style))
- , m_baseWrapper(0)
+RenderMathMLScripts::RenderMathMLScripts(MathMLScriptsElement& element, RenderStyle&& style)
+ : RenderMathMLBlock(element, WTFMove(style))
{
// Determine what kind of sub/sup expression we have by element name
- if (element.hasLocalName(MathMLNames::msubTag))
- m_kind = Sub;
- else if (element.hasLocalName(MathMLNames::msupTag))
- m_kind = Super;
- else if (element.hasLocalName(MathMLNames::msubsupTag))
- m_kind = SubSup;
+ 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.hasLocalName(MathMLNames::mmultiscriptsTag));
- m_kind = Multiscripts;
+ ASSERT(element.hasTagName(MathMLNames::mmultiscriptsTag));
+ m_scriptType = Multiscripts;
}
}
-RenderBoxModelObject* RenderMathMLScripts::base() const
+MathMLScriptsElement& RenderMathMLScripts::element() const
{
- if (!m_baseWrapper)
- return 0;
- RenderObject* base = m_baseWrapper->firstChild();
- if (!base || !base->isBoxModelObject())
- return 0;
- return toRenderBoxModelObject(base);
+ return static_cast<MathMLScriptsElement&>(nodeForNonAnonymous());
}
-void RenderMathMLScripts::fixAnonymousStyleForSubSupPair(RenderObject* subSupPair, bool isPostScript)
+RenderMathMLOperator* RenderMathMLScripts::unembellishedOperator()
{
- ASSERT(subSupPair && subSupPair->style().refCount() == 1);
- RenderStyle& scriptsStyle = subSupPair->style();
-
- // subSup pairs are drawn in column from bottom (subscript) to top (superscript).
- scriptsStyle.setFlexDirection(FlowColumnReverse);
-
- // The MathML specification does not specify horizontal alignment of
- // scripts. We align the bottom (respectively top) edge of the subscript
- // (respectively superscript) with the bottom (respectively top) edge of
- // the flex container. Note that for valid <msub> and <msup> elements, the
- // subSupPair should actually have only one script.
- scriptsStyle.setJustifyContent(m_kind == Sub ? JustifyFlexStart : m_kind == Super ? JustifyFlexEnd : JustifySpaceBetween);
-
- // The MathML specification does not specify vertical alignment of scripts.
- // Let's right align prescripts and left align postscripts.
- // See http://lists.w3.org/Archives/Public/www-math/2012Aug/0006.html
- scriptsStyle.setAlignItems(isPostScript ? AlignFlexStart : AlignFlexEnd);
-
- // We set the order property so that the prescripts are drawn before the base.
- scriptsStyle.setOrder(isPostScript ? 0 : -1);
-
- // We set this wrapper's font-size for its line-height.
- LayoutUnit scriptSize = static_cast<int>(0.75 * style().fontSize());
- scriptsStyle.setFontSize(scriptSize);
+ auto base = firstChildBox();
+ if (!is<RenderMathMLBlock>(base))
+ return nullptr;
+ return downcast<RenderMathMLBlock>(base)->unembellishedOperator();
}
-void RenderMathMLScripts::fixAnonymousStyles()
+std::optional<RenderMathMLScripts::ReferenceChildren> RenderMathMLScripts::validateAndGetReferenceChildren()
{
- // We set the base wrapper's style so that baseHeight in layout() will be an unstretched height.
- ASSERT(m_baseWrapper && m_baseWrapper->style().hasOneRef());
- m_baseWrapper->style().setAlignSelf(AlignFlexStart);
-
- // This sets the style for postscript pairs.
- RenderObject* subSupPair = m_baseWrapper;
- for (subSupPair = subSupPair->nextSibling(); subSupPair && !isPrescript(subSupPair); subSupPair = subSupPair->nextSibling())
- fixAnonymousStyleForSubSupPair(subSupPair, true);
-
- if (subSupPair && m_kind == Multiscripts) {
- // This sets the style for prescript pairs.
- for (subSupPair = subSupPair->nextSibling(); subSupPair && !isPrescript(subSupPair); subSupPair = subSupPair->nextSibling())
- fixAnonymousStyleForSubSupPair(subSupPair, false);
- }
-
- // This resets style for extra subSup pairs.
- for (; subSupPair; subSupPair = subSupPair->nextSibling()) {
- if (!isPrescript(subSupPair)) {
- ASSERT(subSupPair && subSupPair->style().refCount() == 1);
- RenderStyle& scriptsStyle = subSupPair->style();
- scriptsStyle.setFlexDirection(FlowRow);
- scriptsStyle.setJustifyContent(JustifyFlexStart);
- scriptsStyle.setAlignItems(AlignCenter);
- scriptsStyle.setOrder(0);
- scriptsStyle.setFontSize(style().fontSize());
- }
+ // All scripted elements must have at least one child.
+ // The first child is the base.
+ auto base = firstChildBox();
+ if (!base)
+ return std::nullopt;
+
+ ReferenceChildren reference;
+ reference.base = base;
+ reference.firstPostScript = nullptr;
+ reference.firstPreScript = nullptr;
+ reference.prescriptDelimiter = nullptr;
+
+ switch (m_scriptType) {
+ case Sub:
+ case Super:
+ case Under:
+ case Over: {
+ // These elements must have exactly two children.
+ // The second child is a postscript and there are no prescripts.
+ // <msub> base subscript </msub>
+ // <msup> base superscript </msup>
+ // <munder> base underscript </munder>
+ // <mover> base overscript </mover>
+ auto script = base->nextSiblingBox();
+ if (!script || isPrescriptDelimiter(*script) || script->nextSiblingBox())
+ return std::nullopt;
+ reference.firstPostScript = script;
+ return reference;
}
-}
-
-void RenderMathMLScripts::addChildInternal(bool doNotRestructure, RenderObject* child, RenderObject* beforeChild)
-{
- if (doNotRestructure) {
- RenderMathMLBlock::addChild(child, beforeChild);
- return;
+ case SubSup:
+ case UnderOver: {
+ // These elements must have exactly three children.
+ // The second and third children are postscripts and there are no prescripts.
+ // <msubsup> base subscript superscript </msubsup>
+ // <munderover> base subscript superscript </munderover>
+ auto subScript = base->nextSiblingBox();
+ if (!subScript || isPrescriptDelimiter(*subScript))
+ return std::nullopt;
+ auto superScript = subScript->nextSiblingBox();
+ if (!superScript || isPrescriptDelimiter(*superScript) || superScript->nextSiblingBox())
+ return std::nullopt;
+ reference.firstPostScript = subScript;
+ return reference;
}
-
- if (beforeChild) {
- // beforeChild may be a grandchild, so we call the addChild function of the corresponding wrapper instead.
- RenderObject* parent = beforeChild->parent();
- if (parent != this) {
- RenderMathMLBlock* parentBlock = toRenderMathMLBlock(parent);
- if (parentBlock->isRenderMathMLScriptsWrapper()) {
- RenderMathMLScriptsWrapper* wrapper = toRenderMathMLScriptsWrapper(parentBlock);
- wrapper->addChildInternal(false, child, beforeChild);
- return;
+ case Multiscripts: {
+ // This element accepts the following syntax:
+ //
+ // <mmultiscripts>
+ // base
+ // (subscript superscript)*
+ // [ <mprescripts/> (presubscript presuperscript)* ]
+ // </mmultiscripts>
+ //
+ // https://www.w3.org/TR/MathML3/chapter3.html#presm.mmultiscripts
+ //
+ // We set the first postscript, unless (subscript superscript)* is empty.
+ if (base->nextSiblingBox() && !isPrescriptDelimiter(*base->nextSiblingBox()))
+ reference.firstPostScript = base->nextSiblingBox();
+
+ // We browse the children in order to
+ // 1) Set the first prescript, unless (presubscript presuperscript)* is empty.
+ // 2) Ensure the validity of the element i.e.
+ // a) That the list of postscripts can be grouped into pairs of subscript/superscript.
+ // b) That the list of prescripts can be grouped into pairs of subscript/superscript.
+ // c) That there is at most one <mprescripts/>.
+ bool numberOfScriptIsEven = true;
+ for (auto script = base->nextSiblingBox(); script; script = script->nextSiblingBox()) {
+ if (isPrescriptDelimiter(*script)) {
+ // This is a <mprescripts/>. Let's check 2a) and 2c).
+ if (!numberOfScriptIsEven || reference.firstPreScript)
+ return std::nullopt;
+ reference.firstPreScript = script->nextSiblingBox(); // We do 1).
+ reference.prescriptDelimiter = script;
+ continue;
}
+ numberOfScriptIsEven = !numberOfScriptIsEven;
}
+ return numberOfScriptIsEven ? std::optional<ReferenceChildren>(reference) : std::nullopt; // We verify 2b).
}
-
- if (beforeChild == m_baseWrapper) {
- // This is like inserting the child at the beginning of the base wrapper.
- m_baseWrapper->addChildInternal(false, child, m_baseWrapper->firstChild());
- return;
- }
-
- if (isPrescript(child)) {
- // The new child becomes an <mprescripts/> separator.
- RenderMathMLBlock::addChild(child, beforeChild);
- return;
- }
-
- if (!beforeChild || isPrescript(beforeChild)) {
- // We are at the end of a sequence of subSup pairs.
- RenderMathMLBlock* previousSibling = toRenderMathMLBlock(beforeChild ? beforeChild->previousSibling() : lastChild());
- if (previousSibling && previousSibling->isRenderMathMLScriptsWrapper()) {
- RenderMathMLScriptsWrapper* wrapper = toRenderMathMLScriptsWrapper(previousSibling);
- if ((wrapper->m_kind == RenderMathMLScriptsWrapper::Base && wrapper->isEmpty()) || (wrapper->m_kind == RenderMathMLScriptsWrapper::SubSupPair && !wrapper->firstChild()->nextSibling())) {
- // The previous sibling is either an empty base or a SubSup pair with a single child so we can insert the new child into that wrapper.
- wrapper->addChildInternal(true, child);
- return;
- }
- }
- // Otherwise we create a new subSupPair to store the new child.
- RenderMathMLScriptsWrapper* subSupPair = RenderMathMLScriptsWrapper::createAnonymousWrapper(this, RenderMathMLScriptsWrapper::SubSupPair);
- subSupPair->addChildInternal(true, child);
- RenderMathMLBlock::addChild(subSupPair, beforeChild);
- return;
}
- // beforeChild is a subSup pair. This is like inserting the new child at the beginning of the subSup wrapper.
- RenderMathMLScriptsWrapper* wrapper = toRenderMathMLScriptsWrapper(beforeChild);
- ASSERT(wrapper->m_kind == RenderMathMLScriptsWrapper::SubSupPair);
- ASSERT(!(m_baseWrapper->isEmpty() && m_baseWrapper->nextSibling() == beforeChild));
- wrapper->addChildInternal(false, child, wrapper->firstChild());
+ ASSERT_NOT_REACHED();
+ return std::nullopt;
}
-void RenderMathMLScripts::removeChildInternal(bool doNotRestructure, RenderObject& child)
+LayoutUnit RenderMathMLScripts::spaceAfterScript()
{
- if (doNotRestructure) {
- RenderMathMLBlock::removeChild(child);
- return;
- }
-
- ASSERT(isPrescript(&child));
-
- RenderObject* previousSibling = child.previousSibling();
- RenderObject* nextSibling = child.nextSibling();
- ASSERT(previousSibling);
-
- if (nextSibling && !isPrescript(previousSibling) && !isPrescript(nextSibling)) {
- RenderMathMLScriptsWrapper* previousWrapper = toRenderMathMLScriptsWrapper(previousSibling);
- RenderMathMLScriptsWrapper* nextWrapper = toRenderMathMLScriptsWrapper(nextSibling);
- ASSERT(nextWrapper->m_kind == RenderMathMLScriptsWrapper::SubSupPair && !nextWrapper->isEmpty());
- if ((previousWrapper->m_kind == RenderMathMLScriptsWrapper::Base && previousWrapper->isEmpty()) || (previousWrapper->m_kind == RenderMathMLScriptsWrapper::SubSupPair && !previousWrapper->firstChild()->nextSibling())) {
- RenderObject* script = nextWrapper->firstChild();
- nextWrapper->removeChildInternal(false, *script);
- previousWrapper->addChildInternal(true, script);
- }
- }
-
- RenderMathMLBlock::removeChild(child);
+ const auto& primaryFont = style().fontCascade().primaryFont();
+ if (auto* mathData = primaryFont.mathData())
+ return mathData->getMathConstant(primaryFont, OpenTypeMathData::SpaceAfterScript);
+ return style().fontCascade().size() / 5;
}
-void RenderMathMLScripts::addChild(RenderObject* child, RenderObject* beforeChild)
+LayoutUnit RenderMathMLScripts::italicCorrection(const ReferenceChildren& reference)
{
- if (isEmpty()) {
- m_baseWrapper = RenderMathMLScriptsWrapper::createAnonymousWrapper(this, RenderMathMLScriptsWrapper::Base);
- RenderMathMLBlock::addChild(m_baseWrapper);
+ if (is<RenderMathMLBlock>(*reference.base)) {
+ if (auto* renderOperator = downcast<RenderMathMLBlock>(*reference.base).unembellishedOperator())
+ return renderOperator->italicCorrection();
}
-
- addChildInternal(false, child, beforeChild);
-
- fixAnonymousStyles();
+ return 0;
}
-void RenderMathMLScripts::removeChild(RenderObject& child)
+void RenderMathMLScripts::computePreferredLogicalWidths()
{
- if (beingDestroyed() || documentBeingDestroyed()) {
- // The renderer is being destroyed so we remove the child normally.
- RenderMathMLBlock::removeChild(child);
- return;
- }
+ ASSERT(preferredLogicalWidthsDirty());
- removeChildInternal(false, child);
-
- fixAnonymousStyles();
-}
+ m_minPreferredLogicalWidth = 0;
+ m_maxPreferredLogicalWidth = 0;
-void RenderMathMLScripts::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
-{
- RenderMathMLBlock::styleDidChange(diff, oldStyle);
-
- if (!isEmpty())
- fixAnonymousStyles();
-}
-
-RenderMathMLOperator* RenderMathMLScripts::unembellishedOperator()
-{
- RenderBoxModelObject* base = this->base();
- if (!base || !base->isRenderMathMLBlock())
- return 0;
- return toRenderMathMLBlock(base)->unembellishedOperator();
-}
-
-void RenderMathMLScripts::layout()
-{
- RenderMathMLBlock::layout();
-
- if (!m_baseWrapper)
- return;
- RenderBox* base = m_baseWrapper->firstChildBox();
- if (!base)
+ auto possibleReference = validateAndGetReferenceChildren();
+ if (!possibleReference) {
+ setPreferredLogicalWidthsDirty(false);
return;
-
- // Our layout rules include: Don't let the superscript go below the "axis" (half x-height above the
- // baseline), or the subscript above the axis. Also, don't let the superscript's top edge be
- // below the base's top edge, or the subscript's bottom edge above the base's bottom edge.
-
- LayoutUnit baseHeight = base->logicalHeight();
- LayoutUnit baseBaseline = base->firstLineBaseline();
- if (baseBaseline == -1)
- baseBaseline = baseHeight;
- LayoutUnit axis = style().fontMetrics().xHeight() / 2;
- int fontSize = style().fontSize();
-
- ASSERT(m_baseWrapper->style().hasOneRef());
- bool needsSecondLayout = false;
-
- LayoutUnit topPadding = 0;
- LayoutUnit bottomPadding = 0;
-
- Element* scriptElement = element();
- LayoutUnit superscriptShiftValue = 0;
- LayoutUnit subscriptShiftValue = 0;
- if (m_kind == Sub || m_kind == SubSup || m_kind == Multiscripts)
- parseMathMLLength(scriptElement->fastGetAttribute(MathMLNames::subscriptshiftAttr), subscriptShiftValue, &style(), false);
- if (m_kind == Super || m_kind == SubSup || m_kind == Multiscripts)
- parseMathMLLength(scriptElement->fastGetAttribute(MathMLNames::superscriptshiftAttr), superscriptShiftValue, &style(), false);
-
- bool isPostScript = true;
- RenderMathMLBlock* subSupPair = toRenderMathMLBlock(m_baseWrapper->nextSibling());
- for (; subSupPair; subSupPair = toRenderMathMLBlock(subSupPair->nextSibling())) {
-
- // We skip the base and <mprescripts/> elements.
- if (isPrescript(subSupPair)) {
- if (!isPostScript)
- break;
- isPostScript = false;
- continue;
- }
-
- if (RenderBox* superscript = m_kind == Sub ? 0 : subSupPair->lastChildBox()) {
- LayoutUnit superscriptHeight = superscript->logicalHeight();
- LayoutUnit superscriptBaseline = superscript->firstLineBaseline();
- if (superscriptBaseline == -1)
- superscriptBaseline = superscriptHeight;
- LayoutUnit minBaseline = std::max<LayoutUnit>(fontSize / 3 + 1 + superscriptBaseline, superscriptHeight + axis + superscriptShiftValue);
-
- topPadding = std::max<LayoutUnit>(topPadding, minBaseline - baseBaseline);
+ }
+ auto& reference = possibleReference.value();
+
+ LayoutUnit baseItalicCorrection = std::min(reference.base->maxPreferredLogicalWidth(), italicCorrection(reference));
+ LayoutUnit space = spaceAfterScript();
+
+ switch (m_scriptType) {
+ case Sub:
+ case Under:
+ m_maxPreferredLogicalWidth += reference.base->maxPreferredLogicalWidth();
+ m_maxPreferredLogicalWidth += std::max(LayoutUnit(0), reference.firstPostScript->maxPreferredLogicalWidth() - baseItalicCorrection + space);
+ break;
+ case Super:
+ case Over:
+ m_maxPreferredLogicalWidth += reference.base->maxPreferredLogicalWidth();
+ m_maxPreferredLogicalWidth += std::max(LayoutUnit(0), reference.firstPostScript->maxPreferredLogicalWidth() + space);
+ break;
+ case SubSup:
+ case UnderOver:
+ case Multiscripts: {
+ auto subScript = reference.firstPreScript;
+ while (subScript) {
+ auto supScript = subScript->nextSiblingBox();
+ ASSERT(supScript);
+ LayoutUnit subSupPairWidth = std::max(subScript->maxPreferredLogicalWidth(), supScript->maxPreferredLogicalWidth());
+ m_maxPreferredLogicalWidth += subSupPairWidth + space;
+ subScript = supScript->nextSiblingBox();
}
-
- if (RenderBox* subscript = m_kind == Super ? 0 : subSupPair->firstChildBox()) {
- LayoutUnit subscriptHeight = subscript->logicalHeight();
- LayoutUnit subscriptBaseline = subscript->firstLineBaseline();
- if (subscriptBaseline == -1)
- subscriptBaseline = subscriptHeight;
- LayoutUnit baseExtendUnderBaseline = baseHeight - baseBaseline;
- LayoutUnit subscriptUnderItsBaseline = subscriptHeight - subscriptBaseline;
- LayoutUnit minExtendUnderBaseline = std::max<LayoutUnit>(fontSize / 5 + 1 + subscriptUnderItsBaseline, subscriptHeight + subscriptShiftValue - axis);
-
- bottomPadding = std::max<LayoutUnit>(bottomPadding, minExtendUnderBaseline - baseExtendUnderBaseline);
+ m_maxPreferredLogicalWidth += reference.base->maxPreferredLogicalWidth();
+ subScript = reference.firstPostScript;
+ while (subScript && subScript != reference.prescriptDelimiter) {
+ auto supScript = subScript->nextSiblingBox();
+ ASSERT(supScript);
+ LayoutUnit subSupPairWidth = std::max(std::max(LayoutUnit(0), subScript->maxPreferredLogicalWidth() - baseItalicCorrection), supScript->maxPreferredLogicalWidth());
+ m_maxPreferredLogicalWidth += subSupPairWidth + space;
+ subScript = supScript->nextSiblingBox();
}
}
-
- Length newPadding(topPadding, Fixed);
- if (newPadding != m_baseWrapper->style().paddingTop()) {
- m_baseWrapper->style().setPaddingTop(newPadding);
- needsSecondLayout = true;
- }
-
- newPadding = Length(bottomPadding, Fixed);
- if (newPadding != m_baseWrapper->style().paddingBottom()) {
- m_baseWrapper->style().setPaddingBottom(newPadding);
- needsSecondLayout = true;
}
- if (!needsSecondLayout)
- return;
-
- setNeedsLayout(MarkOnlyThis);
- m_baseWrapper->setChildNeedsLayout(MarkOnlyThis);
+ m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth;
- RenderMathMLBlock::layout();
+ setPreferredLogicalWidthsDirty(false);
}
-int RenderMathMLScripts::firstLineBaseline() const
+auto RenderMathMLScripts::verticalParameters() const -> VerticalParameters
{
- if (m_baseWrapper) {
- LayoutUnit baseline = m_baseWrapper->firstLineBaseline();
- if (baseline != -1)
- return baseline;
+ VerticalParameters parameters;
+ const auto& primaryFont = style().fontCascade().primaryFont();
+ if (auto* mathData = primaryFont.mathData()) {
+ parameters.subscriptShiftDown = mathData->getMathConstant(primaryFont, OpenTypeMathData::SubscriptShiftDown);
+ parameters.superscriptShiftUp = mathData->getMathConstant(primaryFont, OpenTypeMathData::SuperscriptShiftUp);
+ parameters.subscriptBaselineDropMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::SubscriptBaselineDropMin);
+ parameters.superScriptBaselineDropMax = mathData->getMathConstant(primaryFont, OpenTypeMathData::SuperscriptBaselineDropMax);
+ parameters.subSuperscriptGapMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::SubSuperscriptGapMin);
+ parameters.superscriptBottomMin = mathData->getMathConstant(primaryFont, OpenTypeMathData::SuperscriptBottomMin);
+ parameters.subscriptTopMax = mathData->getMathConstant(primaryFont, OpenTypeMathData::SubscriptTopMax);
+ parameters.superscriptBottomMaxWithSubscript = mathData->getMathConstant(primaryFont, OpenTypeMathData::SuperscriptBottomMaxWithSubscript);
+ } else {
+ // Default heuristic values when you do not have a font.
+ parameters.subscriptShiftDown = style().fontMetrics().xHeight() / 3;
+ parameters.superscriptShiftUp = style().fontMetrics().xHeight();
+ parameters.subscriptBaselineDropMin = style().fontMetrics().xHeight() / 2;
+ parameters.superScriptBaselineDropMax = style().fontMetrics().xHeight() / 2;
+ parameters.subSuperscriptGapMin = style().fontCascade().size() / 5;
+ parameters.superscriptBottomMin = style().fontMetrics().xHeight() / 4;
+ parameters.subscriptTopMax = 4 * style().fontMetrics().xHeight() / 5;
+ parameters.superscriptBottomMaxWithSubscript = 4 * style().fontMetrics().xHeight() / 5;
}
- return RenderMathMLBlock::firstLineBaseline();
+ return parameters;
}
-RenderMathMLScriptsWrapper* RenderMathMLScriptsWrapper::createAnonymousWrapper(RenderMathMLScripts* renderObject, WrapperType type)
+RenderMathMLScripts::VerticalMetrics RenderMathMLScripts::verticalMetrics(const ReferenceChildren& reference)
{
- RenderMathMLScriptsWrapper* newBlock = new RenderMathMLScriptsWrapper(renderObject->document(), RenderStyle::createAnonymousStyleWithDisplay(&renderObject->style(), FLEX), type);
- newBlock->initializeStyle();
- return newBlock;
-}
-
-void RenderMathMLScriptsWrapper::addChildInternal(bool doNotRestructure, RenderObject* child, RenderObject* beforeChild)
-{
- if (doNotRestructure) {
- RenderMathMLBlock::addChild(child, beforeChild);
- return;
- }
-
- RenderMathMLScripts* parentNode = toRenderMathMLScripts(parent());
-
- if (m_kind == Base) {
- RenderObject* sibling = nextSibling();
-
- if (!isEmpty() && !beforeChild) {
- // This is like inserting the child after the base wrapper.
- parentNode->addChildInternal(false, sibling);
- return;
+ VerticalParameters parameters = verticalParameters();
+ VerticalMetrics metrics = { 0, 0, 0, 0 };
+
+ LayoutUnit baseAscent = ascentForChild(*reference.base);
+ LayoutUnit baseDescent = reference.base->logicalHeight() - baseAscent;
+ if (m_scriptType == Sub || m_scriptType == SubSup || m_scriptType == Multiscripts || m_scriptType == Under || m_scriptType == UnderOver) {
+ metrics.subShift = std::max(parameters.subscriptShiftDown, baseDescent + parameters.subscriptBaselineDropMin);
+ if (!isRenderMathMLUnderOver()) {
+ // It is not clear how to interpret the default shift and it is not available yet anyway.
+ // Hence we just pass 0 as the default value used by toUserUnits.
+ LayoutUnit specifiedMinSubShift = toUserUnits(element().subscriptShift(), style(), 0);
+ metrics.subShift = std::max(metrics.subShift, specifiedMinSubShift);
}
-
- // The old base (if any) becomes a script ; the new child becomes either the base or an <mprescripts> separator.
- RenderObject* oldBase = firstChild();
- if (oldBase)
- RenderMathMLBlock::removeChild(*oldBase);
- if (isPrescript(child))
- parentNode->addChildInternal(true, child, sibling);
- else
- RenderMathMLBlock::addChild(child);
- if (oldBase)
- parentNode->addChildInternal(false, oldBase, sibling);
- return;
}
-
- if (isPrescript(child)) {
- // We insert an <mprescripts> element.
- if (!beforeChild)
- parentNode->addChildInternal(true, child, nextSibling());
- else if (beforeChild == firstChild())
- parentNode->addChildInternal(true, child, this);
- else {
- // We insert the <mprescripts> in the middle of a subSup pair so we must split that pair.
- RenderObject* sibling = nextSibling();
- parentNode->removeChildInternal(true, *this);
- parentNode->addChildInternal(true, child, sibling);
-
- RenderObject* script = firstChild();
- RenderMathMLBlock::removeChild(*script);
- parentNode->addChildInternal(false, script, child);
-
- script = beforeChild;
- RenderMathMLBlock::removeChild(*script);
- parentNode->addChildInternal(false, script, sibling);
- destroy();
+ if (m_scriptType == Super || m_scriptType == SubSup || m_scriptType == Multiscripts || m_scriptType == Over || m_scriptType == UnderOver) {
+ metrics.supShift = std::max(parameters.superscriptShiftUp, baseAscent - parameters.superScriptBaselineDropMax);
+ if (!isRenderMathMLUnderOver()) {
+ // It is not clear how to interpret the default shift and it is not available yet anyway.
+ // Hence we just pass 0 as the default value used by toUserUnits.
+ LayoutUnit specifiedMinSupShift = toUserUnits(element().superscriptShift(), style(), 0);
+ metrics.supShift = std::max(metrics.supShift, specifiedMinSupShift);
}
- return;
}
- // We first move to the last subSup pair in the curent sequence of scripts.
- RenderMathMLScriptsWrapper* subSupPair = this;
- while (subSupPair->nextSibling() && !isPrescript(subSupPair->nextSibling()))
- subSupPair = toRenderMathMLScriptsWrapper(subSupPair->nextSibling());
- if (subSupPair->firstChild()->nextSibling()) {
- // The last pair has two children so we need to create a new pair to leave room for the new child.
- RenderMathMLScriptsWrapper* newPair = createAnonymousWrapper(parentNode, RenderMathMLScriptsWrapper::SubSupPair);
- parentNode->addChildInternal(true, newPair, subSupPair->nextSibling());
- subSupPair = newPair;
+ switch (m_scriptType) {
+ case Sub:
+ case Under: {
+ LayoutUnit subAscent = ascentForChild(*reference.firstPostScript);
+ LayoutUnit subDescent = reference.firstPostScript->logicalHeight() - subAscent;
+ metrics.descent = subDescent;
+ metrics.subShift = std::max(metrics.subShift, subAscent - parameters.subscriptTopMax);
}
+ break;
+ case Super:
+ case Over: {
+ LayoutUnit supAscent = ascentForChild(*reference.firstPostScript);
+ LayoutUnit supDescent = reference.firstPostScript->logicalHeight() - supAscent;
+ metrics.ascent = supAscent;
+ metrics.supShift = std::max(metrics.supShift, parameters.superscriptBottomMin + supDescent);
+ }
+ break;
+ case SubSup:
+ case UnderOver:
+ case Multiscripts: {
+ // FIXME: We should move the code updating VerticalMetrics for each sub/sup pair in a helper
+ // function. That way, SubSup/UnderOver can just make one call and the loop for Multiscripts
+ // can be rewritten in a more readable.
+ auto subScript = reference.firstPostScript ? reference.firstPostScript : reference.firstPreScript;
+ while (subScript) {
+ auto supScript = subScript->nextSiblingBox();
+ ASSERT(supScript);
+ LayoutUnit subAscent = ascentForChild(*subScript);
+ LayoutUnit subDescent = subScript->logicalHeight() - subAscent;
+ LayoutUnit supAscent = ascentForChild(*supScript);
+ LayoutUnit supDescent = supScript->logicalHeight() - supAscent;
+ metrics.ascent = std::max(metrics.ascent, supAscent);
+ metrics.descent = std::max(metrics.descent, subDescent);
+ LayoutUnit subScriptShift = std::max(parameters.subscriptShiftDown, baseDescent + parameters.subscriptBaselineDropMin);
+ subScriptShift = std::max(subScriptShift, subAscent - parameters.subscriptTopMax);
+ LayoutUnit supScriptShift = std::max(parameters.superscriptShiftUp, baseAscent - parameters.superScriptBaselineDropMax);
+ supScriptShift = std::max(supScriptShift, parameters.superscriptBottomMin + supDescent);
+
+ LayoutUnit subSuperscriptGap = (subScriptShift - subAscent) + (supScriptShift - supDescent);
+ if (subSuperscriptGap < parameters.subSuperscriptGapMin) {
+ // First, we try and push the superscript up.
+ LayoutUnit delta = parameters.superscriptBottomMaxWithSubscript - (supScriptShift - supDescent);
+ if (delta > 0) {
+ delta = std::min(delta, parameters.subSuperscriptGapMin - subSuperscriptGap);
+ supScriptShift += delta;
+ subSuperscriptGap += delta;
+ }
+ // If that is not enough, we push the subscript down.
+ if (subSuperscriptGap < parameters.subSuperscriptGapMin)
+ subScriptShift += parameters.subSuperscriptGapMin - subSuperscriptGap;
+ }
+
+ metrics.subShift = std::max(metrics.subShift, subScriptShift);
+ metrics.supShift = std::max(metrics.supShift, supScriptShift);
- // We shift the successors in the current sequence of scripts.
- for (RenderObject* previousSibling = subSupPair->previousSibling(); subSupPair != this; previousSibling = previousSibling->previousSibling()) {
- RenderMathMLScriptsWrapper* previousSubSupPair = toRenderMathMLScriptsWrapper(previousSibling);
- RenderObject* script = previousSubSupPair->lastChild();
- previousSubSupPair->removeChildInternal(true, *script);
- subSupPair->addChildInternal(true, script, subSupPair->firstChild());
- subSupPair = toRenderMathMLScriptsWrapper(previousSibling);
+ subScript = supScript->nextSiblingBox();
+ if (subScript == reference.prescriptDelimiter)
+ subScript = reference.firstPreScript;
+ }
+ }
}
- // This subSup pair now contain one element which is either beforeChild or the script that was before. Hence we can insert the new child before of after that element.
- RenderMathMLBlock::addChild(child, firstChild() == beforeChild ? beforeChild : 0);
+ return metrics;
}
-void RenderMathMLScriptsWrapper::addChild(RenderObject* child, RenderObject* beforeChild)
+void RenderMathMLScripts::layoutBlock(bool relayoutChildren, LayoutUnit)
{
- RenderMathMLScripts* parentNode = toRenderMathMLScripts(parent());
-
- addChildInternal(false, child, beforeChild);
+ ASSERT(needsLayout());
- parentNode->fixAnonymousStyles();
-}
+ if (!relayoutChildren && simplifiedLayout())
+ return;
-void RenderMathMLScriptsWrapper::removeChildInternal(bool doNotRestructure, RenderObject& child)
-{
- if (doNotRestructure) {
- RenderMathMLBlock::removeChild(child);
+ auto possibleReference = validateAndGetReferenceChildren();
+ if (!possibleReference) {
+ layoutInvalidMarkup();
return;
}
-
- RenderMathMLScripts* parentNode = toRenderMathMLScripts(parent());
-
- if (m_kind == Base) {
- // We remove the child from the base wrapper.
- RenderObject* sibling = nextSibling();
- RenderMathMLBlock::removeChild(child);
- if (sibling && !isPrescript(sibling)) {
- // If there are postscripts, the first one becomes the base.
- RenderMathMLScriptsWrapper* wrapper = toRenderMathMLScriptsWrapper(sibling);
- RenderObject* script = wrapper->firstChild();
- wrapper->removeChildInternal(false, *script);
- RenderMathMLBlock::addChild(script);
+ auto& reference = possibleReference.value();
+
+ recomputeLogicalWidth();
+ for (auto child = firstChildBox(); child; child = child->nextSiblingBox())
+ child->layoutIfNeeded();
+
+ LayoutUnit space = spaceAfterScript();
+
+ // We determine the minimal shift/size of each script and take the maximum of the values.
+ VerticalMetrics metrics = verticalMetrics(reference);
+
+ LayoutUnit baseAscent = ascentForChild(*reference.base);
+ LayoutUnit baseDescent = reference.base->logicalHeight() - baseAscent;
+ LayoutUnit baseItalicCorrection = std::min(reference.base->logicalWidth(), italicCorrection(reference));
+ LayoutUnit horizontalOffset = 0;
+
+ LayoutUnit ascent = std::max(baseAscent, metrics.ascent + metrics.supShift);
+ LayoutUnit descent = std::max(baseDescent, metrics.descent + metrics.subShift);
+ setLogicalHeight(ascent + descent);
+
+ switch (m_scriptType) {
+ case Sub:
+ case Under: {
+ setLogicalWidth(reference.base->logicalWidth() + std::max(LayoutUnit(0), reference.firstPostScript->logicalWidth() - baseItalicCorrection + space));
+ LayoutPoint baseLocation(mirrorIfNeeded(horizontalOffset, *reference.base), ascent - baseAscent);
+ reference.base->setLocation(baseLocation);
+ horizontalOffset += reference.base->logicalWidth();
+ LayoutUnit scriptAscent = ascentForChild(*reference.firstPostScript);
+ LayoutPoint scriptLocation(mirrorIfNeeded(horizontalOffset - baseItalicCorrection, *reference.firstPostScript), ascent + metrics.subShift - scriptAscent);
+ reference.firstPostScript->setLocation(scriptLocation);
+ }
+ break;
+ case Super:
+ case Over: {
+ setLogicalWidth(reference.base->logicalWidth() + std::max(LayoutUnit(0), reference.firstPostScript->logicalWidth() + space));
+ LayoutPoint baseLocation(mirrorIfNeeded(horizontalOffset, *reference.base), ascent - baseAscent);
+ reference.base->setLocation(baseLocation);
+ horizontalOffset += reference.base->logicalWidth();
+ LayoutUnit scriptAscent = ascentForChild(*reference.firstPostScript);
+ LayoutPoint scriptLocation(mirrorIfNeeded(horizontalOffset, *reference.firstPostScript), ascent - metrics.supShift - scriptAscent);
+ reference.firstPostScript->setLocation(scriptLocation);
+ }
+ break;
+ case SubSup:
+ case UnderOver:
+ case Multiscripts: {
+ // Calculate the logical width.
+ LayoutUnit logicalWidth = 0;
+ auto subScript = reference.firstPreScript;
+ while (subScript) {
+ auto supScript = subScript->nextSiblingBox();
+ ASSERT(supScript);
+ LayoutUnit subSupPairWidth = std::max(subScript->logicalWidth(), supScript->logicalWidth());
+ logicalWidth += subSupPairWidth + space;
+ subScript = supScript->nextSiblingBox();
+ }
+ logicalWidth += reference.base->logicalWidth();
+ subScript = reference.firstPostScript;
+ while (subScript && subScript != reference.prescriptDelimiter) {
+ auto supScript = subScript->nextSiblingBox();
+ ASSERT(supScript);
+ LayoutUnit subSupPairWidth = std::max(std::max(LayoutUnit(0), subScript->logicalWidth() - baseItalicCorrection), supScript->logicalWidth());
+ logicalWidth += subSupPairWidth + space;
+ subScript = supScript->nextSiblingBox();
+ }
+ setLogicalWidth(logicalWidth);
+
+ subScript = reference.firstPreScript;
+ while (subScript) {
+ auto 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 + metrics.subShift - subAscent);
+ subScript->setLocation(subScriptLocation);
+ LayoutUnit supAscent = ascentForChild(*supScript);
+ LayoutPoint supScriptLocation(mirrorIfNeeded(horizontalOffset - supScript->logicalWidth(), *supScript), ascent - metrics.supShift - supAscent);
+ supScript->setLocation(supScriptLocation);
+ subScript = supScript->nextSiblingBox();
+ }
+ LayoutPoint baseLocation(mirrorIfNeeded(horizontalOffset, *reference.base), ascent - baseAscent);
+ reference.base->setLocation(baseLocation);
+ horizontalOffset += reference.base->logicalWidth();
+ subScript = reference.firstPostScript;
+ while (subScript && subScript != reference.prescriptDelimiter) {
+ auto supScript = subScript->nextSiblingBox();
+ ASSERT(supScript);
+ LayoutUnit subAscent = ascentForChild(*subScript);
+ LayoutPoint subScriptLocation(mirrorIfNeeded(horizontalOffset - baseItalicCorrection, *subScript), ascent + metrics.subShift - subAscent);
+ subScript->setLocation(subScriptLocation);
+ LayoutUnit supAscent = ascentForChild(*supScript);
+ LayoutPoint supScriptLocation(mirrorIfNeeded(horizontalOffset, *supScript), ascent - metrics.supShift - supAscent);
+ supScript->setLocation(supScriptLocation);
+
+ LayoutUnit subSupPairWidth = std::max(subScript->logicalWidth(), supScript->logicalWidth());
+ horizontalOffset += subSupPairWidth + space;
+ subScript = supScript->nextSiblingBox();
}
- return;
}
-
- // We remove the child and shift the successors in the current sequence of scripts.
- RenderMathMLBlock::removeChild(child);
- RenderMathMLScriptsWrapper* subSupPair = this;
- for (RenderObject* nextSibling = subSupPair->nextSibling(); nextSibling && !isPrescript(nextSibling); nextSibling = nextSibling->nextSibling()) {
- RenderMathMLScriptsWrapper* nextSubSupPair = toRenderMathMLScriptsWrapper(nextSibling);
- RenderObject* script = nextSubSupPair->firstChild();
- nextSubSupPair->removeChildInternal(true, *script);
- subSupPair->addChildInternal(true, script);
- subSupPair = toRenderMathMLScriptsWrapper(nextSibling);
}
- // We remove the last subSup pair if it became empty.
- if (subSupPair->isEmpty()) {
- parentNode->removeChildInternal(true, *subSupPair);
- subSupPair->destroy();
- }
+ clearNeedsLayout();
}
-void RenderMathMLScriptsWrapper::removeChild(RenderObject& child)
+std::optional<int> RenderMathMLScripts::firstLineBaseline() const
{
- if (beingDestroyed() || documentBeingDestroyed()) {
- // The renderer is being destroyed so we remove the child normally.
- RenderMathMLBlock::removeChild(child);
- return;
- }
-
- RenderMathMLScripts* parentNode = toRenderMathMLScripts(parent());
- removeChildInternal(false, child);
- parentNode->fixAnonymousStyles();
+ ASSERT(!needsLayout());
+ auto* base = firstChildBox();
+ if (!base)
+ return std::optional<int>();
+ return std::optional<int>(static_cast<int>(lroundf(ascentForChild(*base) + base->logicalTop())));
}
-}
+}
#endif // ENABLE(MATHML)