| /* |
| * Copyright (C) 2009 Alex Milowski (alex@milowski.com). All rights reserved. |
| * Copyright (C) 2010 François Sausset (sausset@gmail.com). All rights reserved. |
| * |
| * 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 "RenderMathMLRoot.h" |
| |
| #include "FontCache.h" |
| #include "GraphicsContext.h" |
| #include "PaintInfo.h" |
| #include "RenderIterator.h" |
| #include "RenderMathMLRadicalOperator.h" |
| #include "RenderMathMLSquareRoot.h" |
| |
| namespace WebCore { |
| |
| // RenderMathMLRoot implements drawing of radicals via the <mroot> and <msqrt> elements. For valid MathML elements, the DOM is |
| // |
| // <mroot> Base Index </mroot> |
| // <msqrt> Child1 Child2 ... ChildN </msqrt> |
| // |
| // and the structure of the render tree will be |
| // |
| // IndexWrapper RadicalWrapper BaseWrapper |
| // |
| // where RadicalWrapper contains an <mo>√</mo>. |
| // For <mroot>, the IndexWrapper and BaseWrapper should contain exactly one child (Index and Base respectively). |
| // For <msqrt>, the IndexWrapper should be empty and the BaseWrapper can contain any number of children (Child1, ... ChildN). |
| // |
| // In order to accept invalid markup and to handle <mroot> and <msqrt> consistently, we will allow any number of children in the BaseWrapper of <mroot> too. |
| // We will allow the IndexWrapper to be empty and it will always contain the last child of the <mroot> if there are at least 2 elements. |
| |
| RenderMathMLRoot::RenderMathMLRoot(Element& element, std::unique_ptr<RenderStyle> style) |
| : RenderMathMLBlock(element, WTFMove(style)) |
| { |
| } |
| |
| RenderMathMLRoot::RenderMathMLRoot(Document& document, std::unique_ptr<RenderStyle> style) |
| : RenderMathMLBlock(document, WTFMove(style)) |
| { |
| } |
| |
| RenderMathMLRootWrapper* RenderMathMLRoot::baseWrapper() const |
| { |
| ASSERT(!isEmpty()); |
| return downcast<RenderMathMLRootWrapper>(lastChild()); |
| } |
| |
| RenderMathMLBlock* RenderMathMLRoot::radicalWrapper() const |
| { |
| ASSERT(!isEmpty()); |
| return downcast<RenderMathMLBlock>(lastChild()->previousSibling()); |
| } |
| |
| RenderMathMLRootWrapper* RenderMathMLRoot::indexWrapper() const |
| { |
| ASSERT(!isEmpty()); |
| return is<RenderMathMLSquareRoot>(*this) ? nullptr : downcast<RenderMathMLRootWrapper>(firstChild()); |
| } |
| |
| RenderMathMLRadicalOperator* RenderMathMLRoot::radicalOperator() const |
| { |
| ASSERT(!isEmpty()); |
| return downcast<RenderMathMLRadicalOperator>(radicalWrapper()->firstChild()); |
| } |
| |
| void RenderMathMLRoot::restructureWrappers() |
| { |
| ASSERT(!isEmpty()); |
| |
| auto base = baseWrapper(); |
| auto index = indexWrapper(); |
| auto radical = radicalWrapper(); |
| |
| // For visual consistency with the initial state, we remove the radical when the base/index wrappers become empty. |
| if (base->isEmpty() && (!index || index->isEmpty())) { |
| if (!radical->isEmpty()) { |
| auto child = radicalOperator(); |
| radical->removeChild(*child); |
| child->destroy(); |
| } |
| // FIXME: early return!!! |
| } |
| |
| if (radical->isEmpty()) { |
| // We create the radical operator. |
| RenderPtr<RenderMathMLRadicalOperator> radicalOperator = createRenderer<RenderMathMLRadicalOperator>(document(), RenderStyle::createAnonymousStyleWithDisplay(&style(), FLEX)); |
| radicalOperator->initializeStyle(); |
| radical->addChild(radicalOperator.leakPtr()); |
| } |
| |
| if (isRenderMathMLSquareRoot()) |
| return; |
| |
| if (auto childToMove = base->lastChild()) { |
| // We move the last child of the base wrapper into the index wrapper if the index wrapper is empty and the base wrapper has at least two children. |
| if (childToMove->previousSibling() && index->isEmpty()) { |
| base->removeChildWithoutRestructuring(*childToMove); |
| index->addChild(childToMove); |
| } |
| } |
| |
| if (auto childToMove = index->firstChild()) { |
| // We move the first child of the index wrapper into the base wrapper if: |
| // - either the index wrapper has at least two children. |
| // - or the base wrapper is empty but the index wrapper is not. |
| if (childToMove->nextSibling() || base->isEmpty()) { |
| index->removeChildWithoutRestructuring(*childToMove); |
| base->addChild(childToMove); |
| } |
| } |
| } |
| |
| void RenderMathMLRoot::addChild(RenderObject* newChild, RenderObject* beforeChild) |
| { |
| if (isEmpty()) { |
| if (!isRenderMathMLSquareRoot()) { |
| // We add the IndexWrapper. |
| RenderMathMLBlock::addChild(RenderMathMLRootWrapper::createAnonymousWrapper(this).leakPtr()); |
| } |
| |
| // We create the radicalWrapper |
| RenderMathMLBlock::addChild(RenderMathMLBlock::createAnonymousMathMLBlock().leakPtr()); |
| |
| // We create the BaseWrapper. |
| RenderMathMLBlock::addChild(RenderMathMLRootWrapper::createAnonymousWrapper(this).leakPtr()); |
| |
| updateStyle(); |
| } |
| |
| // We insert the child. |
| auto base = baseWrapper(); |
| auto index = indexWrapper(); |
| RenderElement* actualParent; |
| RenderElement* actualBeforeChild; |
| if (is<RenderMathMLSquareRoot>(*this)) { |
| // For square root, we always insert the child into the base wrapper. |
| actualParent = base; |
| if (beforeChild && beforeChild->parent() == base) |
| actualBeforeChild = downcast<RenderElement>(beforeChild); |
| else |
| actualBeforeChild = nullptr; |
| } else { |
| // For mroot, we insert the child into the parent of beforeChild, or at the end of the index. The wrapper structure is reorganize below. |
| actualParent = beforeChild ? beforeChild->parent() : nullptr; |
| if (actualParent == base || actualParent == index) |
| actualBeforeChild = downcast<RenderElement>(beforeChild); |
| else { |
| actualParent = index; |
| actualBeforeChild = nullptr; |
| } |
| } |
| actualParent->addChild(newChild, actualBeforeChild); |
| restructureWrappers(); |
| } |
| |
| void RenderMathMLRoot::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) |
| { |
| RenderMathMLBlock::styleDidChange(diff, oldStyle); |
| if (!isEmpty()) |
| updateStyle(); |
| } |
| |
| void RenderMathMLRoot::updateFromElement() |
| { |
| RenderMathMLBlock::updateFromElement(); |
| if (!isEmpty()) |
| updateStyle(); |
| } |
| |
| void RenderMathMLRoot::updateStyle() |
| { |
| ASSERT(!isEmpty()); |
| |
| // We set some constants to draw the radical, as defined in the OpenType MATH tables. |
| |
| m_ruleThickness = 0.05f * style().fontCascade().size(); |
| |
| // FIXME: The recommended default for m_verticalGap in displaystyle is rule thickness + 1/4 x-height (https://bugs.webkit.org/show_bug.cgi?id=118737). |
| m_verticalGap = 11 * m_ruleThickness / 4; |
| m_extraAscender = m_ruleThickness; |
| LayoutUnit kernBeforeDegree = 5 * style().fontCascade().size() / 18; |
| LayoutUnit kernAfterDegree = -10 * style().fontCascade().size() / 18; |
| m_degreeBottomRaisePercent = 0.6f; |
| |
| const auto& primaryFont = style().fontCascade().primaryFont(); |
| if (auto* mathData = style().fontCascade().primaryFont().mathData()) { |
| // FIXME: m_verticalGap should use RadicalDisplayStyleVertical in display mode (https://bugs.webkit.org/show_bug.cgi?id=118737). |
| m_verticalGap = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalVerticalGap); |
| m_ruleThickness = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalRuleThickness); |
| m_extraAscender = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalExtraAscender); |
| |
| if (!isRenderMathMLSquareRoot()) { |
| kernBeforeDegree = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalKernBeforeDegree); |
| kernAfterDegree = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalKernAfterDegree); |
| m_degreeBottomRaisePercent = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalDegreeBottomRaisePercent); |
| } |
| } |
| |
| // We set the style of the anonymous wrappers. |
| |
| auto radical = radicalWrapper(); |
| auto radicalStyle = RenderStyle::createAnonymousStyleWithDisplay(&style(), FLEX); |
| radicalStyle->setMarginTop(Length(0, Fixed)); // This will be updated in RenderMathMLRoot::layout(). |
| radical->setStyle(WTFMove(radicalStyle)); |
| radical->setNeedsLayoutAndPrefWidthsRecalc(); |
| |
| auto base = baseWrapper(); |
| auto baseStyle = RenderStyle::createAnonymousStyleWithDisplay(&style(), FLEX); |
| baseStyle->setMarginTop(Length(0, Fixed)); // This will be updated in RenderMathMLRoot::layout(). |
| baseStyle->setAlignItemsPosition(ItemPositionBaseline); |
| base->setStyle(WTFMove(baseStyle)); |
| base->setNeedsLayoutAndPrefWidthsRecalc(); |
| |
| if (!isRenderMathMLSquareRoot()) { |
| // For mroot, we also set the style of the index wrapper. |
| auto index = indexWrapper(); |
| auto indexStyle = RenderStyle::createAnonymousStyleWithDisplay(&style(), FLEX); |
| indexStyle->setMarginTop(Length(0, Fixed)); // This will be updated in RenderMathMLRoot::layout(). |
| indexStyle->setMarginStart(Length(kernBeforeDegree, Fixed)); |
| indexStyle->setMarginEnd(Length(kernAfterDegree, Fixed)); |
| indexStyle->setAlignItemsPosition(ItemPositionBaseline); |
| index->setStyle(WTFMove(indexStyle)); |
| index->setNeedsLayoutAndPrefWidthsRecalc(); |
| } |
| } |
| |
| Optional<int> RenderMathMLRoot::firstLineBaseline() const |
| { |
| if (!isEmpty()) { |
| auto base = baseWrapper(); |
| return static_cast<int>(lroundf(base->firstLineBaseline().valueOr(-1) + base->marginTop())); |
| } |
| |
| return RenderMathMLBlock::firstLineBaseline(); |
| } |
| |
| void RenderMathMLRoot::layout() |
| { |
| if (isEmpty()) { |
| RenderMathMLBlock::layout(); |
| return; |
| } |
| |
| // FIXME: It seems that changing the top margin of the base below modifies its logical height and leads to reftest failures. |
| // For now, we workaround that by avoiding to recompute the child margins if they were not reset in updateStyle(). |
| auto base = baseWrapper(); |
| if (base->marginTop() > 0) { |
| RenderMathMLBlock::layout(); |
| return; |
| } |
| |
| // We layout the children. |
| for (RenderObject* child = firstChild(); child; child = child->nextSibling()) { |
| if (child->needsLayout()) |
| downcast<RenderElement>(*child).layout(); |
| } |
| |
| auto radical = radicalOperator(); |
| if (radical) { |
| // We stretch the radical sign to cover the height of the base wrapper. |
| float baseHeight = base->logicalHeight(); |
| float baseHeightAboveBaseline = base->firstLineBaseline().valueOr(baseHeight); |
| float baseDepthBelowBaseline = baseHeight - baseHeightAboveBaseline; |
| baseHeightAboveBaseline += m_verticalGap; |
| radical->stretchTo(baseHeightAboveBaseline, baseDepthBelowBaseline); |
| |
| // We modify the top margins to adjust the vertical positions of wrappers. |
| float radicalTopMargin = m_extraAscender; |
| float baseTopMargin = m_verticalGap + m_ruleThickness + m_extraAscender; |
| if (!isRenderMathMLSquareRoot()) { |
| // For mroot, we try to place the index so the space below its baseline is m_degreeBottomRaisePercent times the height of the radical. |
| auto index = indexWrapper(); |
| float indexHeight = 0; |
| if (!index->isEmpty()) |
| indexHeight = downcast<RenderBlock>(*index->firstChild()).logicalHeight(); |
| float indexTopMargin = (1.0 - m_degreeBottomRaisePercent) * radical->stretchSize() + radicalTopMargin - indexHeight; |
| if (indexTopMargin < 0) { |
| // If the index is too tall, we must add space at the top of renderer. |
| radicalTopMargin -= indexTopMargin; |
| baseTopMargin -= indexTopMargin; |
| indexTopMargin = 0; |
| } |
| index->style().setMarginTop(Length(indexTopMargin, Fixed)); |
| } |
| radical->style().setMarginTop(Length(radicalTopMargin, Fixed)); |
| base->style().setMarginTop(Length(baseTopMargin, Fixed)); |
| } |
| |
| RenderMathMLBlock::layout(); |
| } |
| |
| void RenderMathMLRoot::paint(PaintInfo& info, const LayoutPoint& paintOffset) |
| { |
| RenderMathMLBlock::paint(info, paintOffset); |
| |
| if (isEmpty() || info.context().paintingDisabled() || style().visibility() != VISIBLE) |
| return; |
| |
| auto base = baseWrapper(); |
| auto radical = radicalOperator(); |
| if (!base || !radical || !m_ruleThickness) |
| return; |
| |
| // We draw the radical line. |
| GraphicsContextStateSaver stateSaver(info.context()); |
| |
| info.context().setStrokeThickness(m_ruleThickness); |
| info.context().setStrokeStyle(SolidStroke); |
| info.context().setStrokeColor(style().visitedDependentColor(CSSPropertyColor)); |
| |
| // The preferred width of the radical is sometimes incorrect, so we draw a slightly longer line to ensure it touches the radical symbol (https://bugs.webkit.org/show_bug.cgi?id=130326). |
| LayoutUnit sizeError = radical->trailingSpaceError(); |
| IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset + location() + base->location() + LayoutPoint(-sizeError, -(m_verticalGap + m_ruleThickness / 2))); |
| info.context().drawLine(adjustedPaintOffset, roundedIntPoint(LayoutPoint(adjustedPaintOffset.x() + base->offsetWidth() + sizeError, adjustedPaintOffset.y()))); |
| } |
| |
| RenderPtr<RenderMathMLRootWrapper> RenderMathMLRootWrapper::createAnonymousWrapper(RenderMathMLRoot* renderObject) |
| { |
| RenderPtr<RenderMathMLRootWrapper> newBlock = createRenderer<RenderMathMLRootWrapper>(renderObject->document(), RenderStyle::createAnonymousStyleWithDisplay(&renderObject->style(), FLEX)); |
| newBlock->initializeStyle(); |
| return newBlock; |
| } |
| |
| void RenderMathMLRootWrapper::removeChildWithoutRestructuring(RenderObject& child) |
| { |
| RenderMathMLBlock::removeChild(child); |
| } |
| |
| void RenderMathMLRootWrapper::removeChild(RenderObject& child) |
| { |
| RenderMathMLBlock::removeChild(child); |
| |
| if (!(beingDestroyed() || documentBeingDestroyed())) |
| downcast<RenderMathMLRoot>(*parent()).restructureWrappers(); |
| } |
| |
| } |
| |
| #endif // ENABLE(MATHML) |