/* * Copyright (C) 2010 Alex Milowski (alex@milowski.com). All rights reserved. * Copyright (C) 2013 The MathJax Consortium. * * 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" #if ENABLE(MATHML) #include "RenderMathMLScripts.h" #include "MathMLElement.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 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(const RenderObject& renderObject) { return renderObject.node() && renderObject.node()->hasTagName(MathMLNames::mprescriptsTag); } RenderMathMLScripts::RenderMathMLScripts(Element& element, Ref&& style) : RenderMathMLBlock(element, WTFMove(style)) , m_baseWrapper(0) { // Determine what kind of sub/sup expression we have by element name if (element.hasTagName(MathMLNames::msubTag)) m_kind = Sub; else if (element.hasTagName(MathMLNames::msupTag)) m_kind = Super; else if (element.hasTagName(MathMLNames::msubsupTag)) m_kind = SubSup; else { ASSERT(element.hasTagName(MathMLNames::mmultiscriptsTag)); m_kind = Multiscripts; } } RenderBoxModelObject* RenderMathMLScripts::base() const { if (!m_baseWrapper) return nullptr; RenderObject* base = m_baseWrapper->firstChild(); if (!is(base)) return nullptr; return downcast(base); } void RenderMathMLScripts::fixAnonymousStyleForSubSupPair(RenderObject* subSupPair, bool isPostScript) { 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 and elements, the // subSupPair should actually have only one script. if (m_kind == Sub) scriptsStyle.setJustifyContentPosition(ContentPositionFlexStart); else if (m_kind == Super) scriptsStyle.setJustifyContentPosition(ContentPositionFlexEnd); else scriptsStyle.setJustifyContentDistribution(ContentDistributionSpaceBetween); // 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.setAlignItemsPosition(isPostScript ? ItemPositionFlexStart : ItemPositionFlexEnd); // 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(0.75 * style().fontSize()); scriptsStyle.setFontSize(scriptSize); } void RenderMathMLScripts::fixAnonymousStyles() { // 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().setAlignSelfPosition(ItemPositionFlexStart); // 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.setJustifyContentPosition(ContentPositionFlexStart); scriptsStyle.setAlignItemsPosition(ItemPositionCenter); scriptsStyle.setOrder(0); scriptsStyle.setFontSize(style().fontSize()); } } } void RenderMathMLScripts::addChildInternal(bool doNotRestructure, RenderObject* child, RenderObject* beforeChild) { if (doNotRestructure) { RenderMathMLBlock::addChild(child, beforeChild); return; } 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 = downcast(*parent); if (is(parentBlock)) { RenderMathMLScriptsWrapper& wrapper = downcast(parentBlock); wrapper.addChildInternal(false, child, beforeChild); return; } } } 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 separator. RenderMathMLBlock::addChild(child, beforeChild); return; } if (!beforeChild || isPrescript(*beforeChild)) { // We are at the end of a sequence of subSup pairs. RenderMathMLBlock* previousSibling = downcast(beforeChild ? beforeChild->previousSibling() : lastChild()); if (is(previousSibling)) { RenderMathMLScriptsWrapper& wrapper = downcast(*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 = downcast(*beforeChild); ASSERT(wrapper.m_kind == RenderMathMLScriptsWrapper::SubSupPair); ASSERT(!(m_baseWrapper->isEmpty() && m_baseWrapper->nextSibling() == beforeChild)); wrapper.addChildInternal(false, child, wrapper.firstChild()); } void RenderMathMLScripts::removeChildInternal(bool doNotRestructure, RenderObject& child) { 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 = downcast(*previousSibling); RenderMathMLScriptsWrapper& nextWrapper = downcast(*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); } void RenderMathMLScripts::addChild(RenderObject* child, RenderObject* beforeChild) { if (isEmpty()) { m_baseWrapper = RenderMathMLScriptsWrapper::createAnonymousWrapper(this, RenderMathMLScriptsWrapper::Base); RenderMathMLBlock::addChild(m_baseWrapper); } addChildInternal(false, child, beforeChild); fixAnonymousStyles(); } void RenderMathMLScripts::removeChild(RenderObject& child) { if (beingDestroyed() || documentBeingDestroyed()) { // The renderer is being destroyed so we remove the child normally. RenderMathMLBlock::removeChild(child); return; } removeChildInternal(false, child); fixAnonymousStyles(); } void RenderMathMLScripts::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) { RenderMathMLBlock::styleDidChange(diff, oldStyle); if (!isEmpty()) fixAnonymousStyles(); } RenderMathMLOperator* RenderMathMLScripts::unembellishedOperator() { RenderBoxModelObject* base = this->base(); if (!is(base)) return nullptr; return downcast(*base).unembellishedOperator(); } void RenderMathMLScripts::layout() { RenderMathMLBlock::layout(); if (!m_baseWrapper) return; RenderBox* base = m_baseWrapper->firstChildBox(); if (!base) 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().valueOr(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 = downcast(m_baseWrapper->nextSibling()); for (; subSupPair; subSupPair = downcast(subSupPair->nextSibling())) { // We skip the base and 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().valueOr(superscriptHeight); LayoutUnit minBaseline = std::max(fontSize / 3 + 1 + superscriptBaseline, superscriptHeight + axis + superscriptShiftValue); topPadding = std::max(topPadding, minBaseline - baseBaseline); } if (RenderBox* subscript = m_kind == Super ? 0 : subSupPair->firstChildBox()) { LayoutUnit subscriptHeight = subscript->logicalHeight(); LayoutUnit subscriptBaseline = subscript->firstLineBaseline().valueOr(subscriptHeight); LayoutUnit baseExtendUnderBaseline = baseHeight - baseBaseline; LayoutUnit subscriptUnderItsBaseline = subscriptHeight - subscriptBaseline; LayoutUnit minExtendUnderBaseline = std::max(fontSize / 5 + 1 + subscriptUnderItsBaseline, subscriptHeight + subscriptShiftValue - axis); bottomPadding = std::max(bottomPadding, minExtendUnderBaseline - baseExtendUnderBaseline); } } 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); RenderMathMLBlock::layout(); } Optional RenderMathMLScripts::firstLineBaseline() const { if (m_baseWrapper) { if (Optional baseline = m_baseWrapper->firstLineBaseline()) return baseline; } return RenderMathMLBlock::firstLineBaseline(); } RenderMathMLScriptsWrapper* RenderMathMLScriptsWrapper::createAnonymousWrapper(RenderMathMLScripts* renderObject, WrapperType type) { 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 = downcast(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; } // The old base (if any) becomes a script ; the new child becomes either the base or an 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 element. if (!beforeChild) parentNode->addChildInternal(true, child, nextSibling()); else if (beforeChild == firstChild()) parentNode->addChildInternal(true, child, this); else { // We insert the 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(); } 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 = downcast(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; } // We shift the successors in the current sequence of scripts. for (RenderObject* previousSibling = subSupPair->previousSibling(); subSupPair != this; previousSibling = previousSibling->previousSibling()) { RenderMathMLScriptsWrapper& previousSubSupPair = downcast(*previousSibling); RenderObject* script = previousSubSupPair.lastChild(); previousSubSupPair.removeChildInternal(true, *script); subSupPair->addChildInternal(true, script, subSupPair->firstChild()); subSupPair = downcast(previousSibling); } // 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 : nullptr); } void RenderMathMLScriptsWrapper::addChild(RenderObject* child, RenderObject* beforeChild) { RenderMathMLScripts* parentNode = downcast(parent()); addChildInternal(false, child, beforeChild); parentNode->fixAnonymousStyles(); } void RenderMathMLScriptsWrapper::removeChildInternal(bool doNotRestructure, RenderObject& child) { if (doNotRestructure) { RenderMathMLBlock::removeChild(child); return; } RenderMathMLScripts* parentNode = downcast(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 = downcast(*sibling); RenderObject* script = wrapper.firstChild(); wrapper.removeChildInternal(false, *script); RenderMathMLBlock::addChild(script); } 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 = downcast(*nextSibling); RenderObject* script = nextSubSupPair.firstChild(); nextSubSupPair.removeChildInternal(true, *script); subSupPair->addChildInternal(true, script); subSupPair = downcast(nextSibling); } // We remove the last subSup pair if it became empty. if (subSupPair->isEmpty()) { parentNode->removeChildInternal(true, *subSupPair); subSupPair->destroy(); } } void RenderMathMLScriptsWrapper::removeChild(RenderObject& child) { if (beingDestroyed() || documentBeingDestroyed()) { // The renderer is being destroyed so we remove the child normally. RenderMathMLBlock::removeChild(child); return; } RenderMathMLScripts* parentNode = downcast(parent()); removeChildInternal(false, child); parentNode->fixAnonymousStyles(); } } #endif // ENABLE(MATHML)