diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2015-10-15 09:45:50 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2015-10-15 09:45:50 +0000 |
commit | e15dd966d523731101f70ccf768bba12435a0208 (patch) | |
tree | ae9cb828a24ded2585a41af3f21411523b47897d /Source/WebCore/rendering/mathml/RenderMathMLScripts.cpp | |
download | WebKitGtk-tarball-e15dd966d523731101f70ccf768bba12435a0208.tar.gz |
webkitgtk-2.10.2webkitgtk-2.10.2
Diffstat (limited to 'Source/WebCore/rendering/mathml/RenderMathMLScripts.cpp')
-rw-r--r-- | Source/WebCore/rendering/mathml/RenderMathMLScripts.cpp | 518 |
1 files changed, 518 insertions, 0 deletions
diff --git a/Source/WebCore/rendering/mathml/RenderMathMLScripts.cpp b/Source/WebCore/rendering/mathml/RenderMathMLScripts.cpp new file mode 100644 index 000000000..d17f9ad63 --- /dev/null +++ b/Source/WebCore/rendering/mathml/RenderMathMLScripts.cpp @@ -0,0 +1,518 @@ +/* + * 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 <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(const RenderObject& renderObject) +{ + return renderObject.node() && renderObject.node()->hasTagName(MathMLNames::mprescriptsTag); +} + +RenderMathMLScripts::RenderMathMLScripts(Element& element, Ref<RenderStyle>&& style) + : RenderMathMLBlock(element, WTF::move(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<RenderBoxModelObject>(base)) + return nullptr; + return downcast<RenderBoxModelObject>(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 <msub> and <msup> 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<int>(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<RenderMathMLBlock>(*parent); + if (is<RenderMathMLScriptsWrapper>(parentBlock)) { + RenderMathMLScriptsWrapper& wrapper = downcast<RenderMathMLScriptsWrapper>(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 <mprescripts/> separator. + RenderMathMLBlock::addChild(child, beforeChild); + return; + } + + if (!beforeChild || isPrescript(*beforeChild)) { + // We are at the end of a sequence of subSup pairs. + RenderMathMLBlock* previousSibling = downcast<RenderMathMLBlock>(beforeChild ? beforeChild->previousSibling() : lastChild()); + if (is<RenderMathMLScriptsWrapper>(previousSibling)) { + RenderMathMLScriptsWrapper& wrapper = downcast<RenderMathMLScriptsWrapper>(*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<RenderMathMLScriptsWrapper>(*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<RenderMathMLScriptsWrapper>(*previousSibling); + RenderMathMLScriptsWrapper& nextWrapper = downcast<RenderMathMLScriptsWrapper>(*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<RenderMathMLBlock>(base)) + return nullptr; + return downcast<RenderMathMLBlock>(*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<RenderMathMLBlock>(m_baseWrapper->nextSibling()); + for (; subSupPair; subSupPair = downcast<RenderMathMLBlock>(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().valueOr(superscriptHeight); + LayoutUnit minBaseline = std::max<LayoutUnit>(fontSize / 3 + 1 + superscriptBaseline, superscriptHeight + axis + superscriptShiftValue); + + topPadding = std::max<LayoutUnit>(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<LayoutUnit>(fontSize / 5 + 1 + subscriptUnderItsBaseline, subscriptHeight + subscriptShiftValue - axis); + + bottomPadding = std::max<LayoutUnit>(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<int> RenderMathMLScripts::firstLineBaseline() const +{ + if (m_baseWrapper) { + if (Optional<int> 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<RenderMathMLScripts>(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 <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(); + } + 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<RenderMathMLScriptsWrapper>(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<RenderMathMLScriptsWrapper>(*previousSibling); + RenderObject* script = previousSubSupPair.lastChild(); + previousSubSupPair.removeChildInternal(true, *script); + subSupPair->addChildInternal(true, script, subSupPair->firstChild()); + subSupPair = downcast<RenderMathMLScriptsWrapper>(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<RenderMathMLScripts>(parent()); + + addChildInternal(false, child, beforeChild); + + parentNode->fixAnonymousStyles(); +} + +void RenderMathMLScriptsWrapper::removeChildInternal(bool doNotRestructure, RenderObject& child) +{ + if (doNotRestructure) { + RenderMathMLBlock::removeChild(child); + return; + } + + RenderMathMLScripts* parentNode = downcast<RenderMathMLScripts>(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<RenderMathMLScriptsWrapper>(*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<RenderMathMLScriptsWrapper>(*nextSibling); + RenderObject* script = nextSubSupPair.firstChild(); + nextSubSupPair.removeChildInternal(true, *script); + subSupPair->addChildInternal(true, script); + subSupPair = downcast<RenderMathMLScriptsWrapper>(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<RenderMathMLScripts>(parent()); + removeChildInternal(false, child); + parentNode->fixAnonymousStyles(); +} + +} + +#endif // ENABLE(MATHML) |