/* * 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 and elements. For valid MathML elements, the DOM is // // Base Index // Child1 Child2 ... ChildN // // and the structure of the render tree will be // // IndexWrapper RadicalWrapper BaseWrapper // // where RadicalWrapper contains an . // For , the IndexWrapper and BaseWrapper should contain exactly one child (Index and Base respectively). // For , 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 and consistently, we will allow any number of children in the BaseWrapper of too. // We will allow the IndexWrapper to be empty and it will always contain the last child of the if there are at least 2 elements. RenderMathMLRoot::RenderMathMLRoot(Element& element, Ref&& style) : RenderMathMLBlock(element, WTFMove(style)) { } RenderMathMLRoot::RenderMathMLRoot(Document& document, Ref&& style) : RenderMathMLBlock(document, WTFMove(style)) { } RenderMathMLRootWrapper* RenderMathMLRoot::baseWrapper() const { ASSERT(!isEmpty()); return downcast(lastChild()); } RenderMathMLBlock* RenderMathMLRoot::radicalWrapper() const { ASSERT(!isEmpty()); return downcast(lastChild()->previousSibling()); } RenderMathMLRootWrapper* RenderMathMLRoot::indexWrapper() const { ASSERT(!isEmpty()); return is(*this) ? nullptr : downcast(firstChild()); } RenderMathMLRadicalOperator* RenderMathMLRoot::radicalOperator() const { ASSERT(!isEmpty()); return downcast(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 radicalOperator = createRenderer(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(*this)) { // For square root, we always insert the child into the base wrapper. actualParent = base; if (beforeChild && beforeChild->parent() == base) actualBeforeChild = downcast(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(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.get().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.get().setMarginTop(Length(0, Fixed)); // This will be updated in RenderMathMLRoot::layout(). baseStyle.get().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.get().setMarginTop(Length(0, Fixed)); // This will be updated in RenderMathMLRoot::layout(). indexStyle.get().setMarginStart(Length(kernBeforeDegree, Fixed)); indexStyle.get().setMarginEnd(Length(kernAfterDegree, Fixed)); indexStyle.get().setAlignItemsPosition(ItemPositionBaseline); index->setStyle(WTFMove(indexStyle)); index->setNeedsLayoutAndPrefWidthsRecalc(); } } Optional RenderMathMLRoot::firstLineBaseline() const { if (!isEmpty()) { auto base = baseWrapper(); return static_cast(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(*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(*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::createAnonymousWrapper(RenderMathMLRoot* renderObject) { RenderPtr newBlock = createRenderer(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(*parent()).restructureWrappers(); } } #endif // ENABLE(MATHML)