blob: 5e8d96793245755fb921f9edff038db4732d880c [file] [log] [blame]
/*
* 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 "MathMLNames.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)
{
ASSERT(renderObject);
return renderObject->node() && renderObject->node()->hasTagName(MathMLNames::mprescriptsTag);
}
RenderMathMLScripts::RenderMathMLScripts(Element& element, PassRef<RenderStyle> style)
: RenderMathMLBlock(element, std::move(style))
, m_baseWrapper(0)
{
// 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;
else {
ASSERT(element.hasLocalName(MathMLNames::mmultiscriptsTag));
m_kind = Multiscripts;
}
}
RenderBoxModelObject* RenderMathMLScripts::base() const
{
if (!m_baseWrapper)
return 0;
RenderObject* base = m_baseWrapper->firstChild();
if (!base || !base->isBoxModelObject())
return 0;
return toRenderBoxModelObject(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.
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);
}
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()->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());
}
}
}
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) {
RenderMathMLScriptsWrapper* wrapper = toRenderMathMLScriptsWrapper(parent);
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 = 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());
}
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 = 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);
}
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 (!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)
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);
}
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);
}
}
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();
}
int RenderMathMLScripts::firstLineBaseline() const
{
if (m_baseWrapper) {
LayoutUnit baseline = m_baseWrapper->firstLineBaseline();
if (baseline != -1)
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 = 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;
}
// 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 = 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;
}
// 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);
}
// 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);
}
void RenderMathMLScriptsWrapper::addChild(RenderObject* child, RenderObject* beforeChild)
{
RenderMathMLScripts* parentNode = toRenderMathMLScripts(parent());
addChildInternal(false, child, beforeChild);
parentNode->fixAnonymousStyles();
}
void RenderMathMLScriptsWrapper::removeChildInternal(bool doNotRestructure, RenderObject& child)
{
if (doNotRestructure) {
RenderMathMLBlock::removeChild(child);
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);
}
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();
}
}
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 = toRenderMathMLScripts(parent());
removeChildInternal(false, child);
parentNode->fixAnonymousStyles();
}
}
#endif // ENABLE(MATHML)