diff options
Diffstat (limited to 'Source/WebCore/rendering/mathml/RenderMathMLRoot.cpp')
-rw-r--r-- | Source/WebCore/rendering/mathml/RenderMathMLRoot.cpp | 448 |
1 files changed, 216 insertions, 232 deletions
diff --git a/Source/WebCore/rendering/mathml/RenderMathMLRoot.cpp b/Source/WebCore/rendering/mathml/RenderMathMLRoot.cpp index 0e975c730..3d757a7a8 100644 --- a/Source/WebCore/rendering/mathml/RenderMathMLRoot.cpp +++ b/Source/WebCore/rendering/mathml/RenderMathMLRoot.cpp @@ -1,6 +1,7 @@ /* * Copyright (C) 2009 Alex Milowski (alex@milowski.com). All rights reserved. * Copyright (C) 2010 François Sausset (sausset@gmail.com). All rights reserved. + * Copyright (C) 2016 Igalia S.L. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -25,285 +26,268 @@ */ #include "config.h" +#include "RenderMathMLRoot.h" #if ENABLE(MATHML) -#include "RenderMathMLRoot.h" - +#include "FontCache.h" #include "GraphicsContext.h" +#include "MathMLNames.h" +#include "MathMLRowElement.h" #include "PaintInfo.h" #include "RenderIterator.h" -#include "RenderMathMLRow.h" +#include "RenderMathMLMenclose.h" +#include "RenderMathMLOperator.h" + +static const UChar gRadicalCharacter = 0x221A; namespace WebCore { - -// FIXME: This whole file should be changed to work with various writing modes. See https://bugs.webkit.org/show_bug.cgi?id=48951. - -// Threshold above which the radical shape is modified to look nice with big bases (em) -const float gThresholdBaseHeightEms = 1.5f; -// Normal width of the front of the radical sign, before the base & overbar (em) -const float gFrontWidthEms = 0.75f; -// Gap between the base and overbar (em) -const float gSpaceAboveEms = 0.2f; -// Horizontal position of the bottom point of the radical (* frontWidth) -const float gRadicalBottomPointXFront = 0.5f; -// Lower the radical sign's bottom point (px) -const int gRadicalBottomPointLower = 3; -// Horizontal position of the top left point of the radical "dip" (* frontWidth) -const float gRadicalDipLeftPointXFront = 0.8f; -// Vertical position of the top left point of a sqrt radical "dip" (* baseHeight) -const float gSqrtRadicalDipLeftPointYPos = 0.5f; -// Vertical position of the top left point of an nth root radical "dip" (* baseHeight) -const float gRootRadicalDipLeftPointYPos = 0.625f; -// Vertical shift of the left end point of the radical (em) -const float gRadicalLeftEndYShiftEms = 0.05f; -// Additional bottom root padding if baseHeight > threshold (em) -const float gBigRootBottomPaddingEms = 0.2f; - -// Radical line thickness (em) -const float gRadicalLineThicknessEms = 0.02f; -// Radical thick line thickness (em) -const float gRadicalThickLineThicknessEms = 0.1f; - -RenderMathMLRoot::RenderMathMLRoot(Element& element, PassRef<RenderStyle> style) - : RenderMathMLBlock(element, std::move(style)) - , m_intrinsicPaddingBefore(0) - , m_intrinsicPaddingAfter(0) - , m_intrinsicPaddingStart(0) - , m_intrinsicPaddingEnd(0) + +RenderMathMLRoot::RenderMathMLRoot(MathMLRowElement& element, RenderStyle&& style) + : RenderMathMLRow(element, WTFMove(style)) { + // Determine what kind of expression we have by element name + if (element.hasTagName(MathMLNames::msqrtTag)) + m_kind = SquareRoot; + else if (element.hasTagName(MathMLNames::mrootTag)) + m_kind = RootWithIndex; + + m_radicalOperator.setOperator(RenderMathMLRoot::style(), gRadicalCharacter, MathOperator::Type::VerticalOperator); } -RenderMathMLRoot::RenderMathMLRoot(Document& document, PassRef<RenderStyle> style) - : RenderMathMLBlock(document, std::move(style)) - , m_intrinsicPaddingBefore(0) - , m_intrinsicPaddingAfter(0) - , m_intrinsicPaddingStart(0) - , m_intrinsicPaddingEnd(0) +bool RenderMathMLRoot::isValid() const { + // Verify whether the list of children is valid: + // <msqrt> child1 child2 ... childN </msqrt> + // <mroot> base index </mroot> + if (m_kind == SquareRoot) + return true; + + ASSERT(m_kind == RootWithIndex); + auto* child = firstChildBox(); + if (!child) + return false; + child = child->nextSiblingBox(); + return child && !child->nextSiblingBox(); } -LayoutUnit RenderMathMLRoot::paddingTop() const + +RenderBox& RenderMathMLRoot::getBase() const { - LayoutUnit result = computedCSSPaddingTop(); - switch (style().writingMode()) { - case TopToBottomWritingMode: - return result + m_intrinsicPaddingBefore; - case BottomToTopWritingMode: - return result + m_intrinsicPaddingAfter; - case LeftToRightWritingMode: - case RightToLeftWritingMode: - return result + (style().isLeftToRightDirection() ? m_intrinsicPaddingStart : m_intrinsicPaddingEnd); - } - ASSERT_NOT_REACHED(); - return result; + ASSERT(isValid()); + ASSERT(m_kind == RootWithIndex); + return *firstChildBox(); } -LayoutUnit RenderMathMLRoot::paddingBottom() const +RenderBox& RenderMathMLRoot::getIndex() const { - LayoutUnit result = computedCSSPaddingBottom(); - switch (style().writingMode()) { - case TopToBottomWritingMode: - return result + m_intrinsicPaddingAfter; - case BottomToTopWritingMode: - return result + m_intrinsicPaddingBefore; - case LeftToRightWritingMode: - case RightToLeftWritingMode: - return result + (style().isLeftToRightDirection() ? m_intrinsicPaddingEnd : m_intrinsicPaddingStart); - } - ASSERT_NOT_REACHED(); - return result; + ASSERT(isValid()); + ASSERT(m_kind == RootWithIndex); + return *firstChildBox()->nextSiblingBox(); } -LayoutUnit RenderMathMLRoot::paddingLeft() const +void RenderMathMLRoot::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) { - LayoutUnit result = computedCSSPaddingLeft(); - switch (style().writingMode()) { - case LeftToRightWritingMode: - return result + m_intrinsicPaddingBefore; - case RightToLeftWritingMode: - return result + m_intrinsicPaddingAfter; - case TopToBottomWritingMode: - case BottomToTopWritingMode: - return result + (style().isLeftToRightDirection() ? m_intrinsicPaddingStart : m_intrinsicPaddingEnd); - } - ASSERT_NOT_REACHED(); - return result; + RenderMathMLRow::styleDidChange(diff, oldStyle); + m_radicalOperator.reset(style()); } -LayoutUnit RenderMathMLRoot::paddingRight() const +RenderMathMLRoot::HorizontalParameters RenderMathMLRoot::horizontalParameters() { - LayoutUnit result = computedCSSPaddingRight(); - switch (style().writingMode()) { - case RightToLeftWritingMode: - return result + m_intrinsicPaddingBefore; - case LeftToRightWritingMode: - return result + m_intrinsicPaddingAfter; - case TopToBottomWritingMode: - case BottomToTopWritingMode: - return result + (style().isLeftToRightDirection() ? m_intrinsicPaddingEnd : m_intrinsicPaddingStart); + HorizontalParameters parameters; + + // Square roots do not require horizontal parameters. + if (m_kind == SquareRoot) + return parameters; + + // We try and read constants to draw the radical from the OpenType MATH and use fallback values otherwise. + const auto& primaryFont = style().fontCascade().primaryFont(); + if (auto* mathData = style().fontCascade().primaryFont().mathData()) { + parameters.kernBeforeDegree = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalKernBeforeDegree); + parameters.kernAfterDegree = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalKernAfterDegree); + } else { + // RadicalKernBeforeDegree: No suggested value provided. OT Math Illuminated mentions 5/18 em, Gecko uses 0. + // RadicalKernAfterDegree: Suggested value is -10/18 of em. + parameters.kernBeforeDegree = 5 * style().fontCascade().size() / 18; + parameters.kernAfterDegree = -10 * style().fontCascade().size() / 18; } - ASSERT_NOT_REACHED(); - return result; + return parameters; } -LayoutUnit RenderMathMLRoot::paddingBefore() const +RenderMathMLRoot::VerticalParameters RenderMathMLRoot::verticalParameters() { - return computedCSSPaddingBefore() + m_intrinsicPaddingBefore; -} + VerticalParameters parameters; + // We try and read constants to draw the radical from the OpenType MATH and use fallback values otherwise. + const auto& primaryFont = style().fontCascade().primaryFont(); + if (auto* mathData = style().fontCascade().primaryFont().mathData()) { + parameters.ruleThickness = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalRuleThickness); + parameters.verticalGap = mathData->getMathConstant(primaryFont, mathMLStyle().displayStyle() ? OpenTypeMathData::RadicalDisplayStyleVerticalGap : OpenTypeMathData::RadicalVerticalGap); + parameters.extraAscender = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalExtraAscender); + if (m_kind == RootWithIndex) + parameters.degreeBottomRaisePercent = mathData->getMathConstant(primaryFont, OpenTypeMathData::RadicalDegreeBottomRaisePercent); + } else { + // RadicalVerticalGap: Suggested value is 5/4 default rule thickness. + // RadicalDisplayStyleVerticalGap: Suggested value is default rule thickness + 1/4 x-height. + // RadicalRuleThickness: Suggested value is default rule thickness. + // RadicalExtraAscender: Suggested value is RadicalRuleThickness. + // RadicalDegreeBottomRaisePercent: Suggested value is 60%. + parameters.ruleThickness = ruleThicknessFallback(); + if (mathMLStyle().displayStyle()) + parameters.verticalGap = parameters.ruleThickness + style().fontMetrics().xHeight() / 4; + else + parameters.verticalGap = 5 * parameters.ruleThickness / 4; -LayoutUnit RenderMathMLRoot::paddingAfter() const -{ - return computedCSSPaddingAfter() + m_intrinsicPaddingAfter; + if (m_kind == RootWithIndex) { + parameters.extraAscender = parameters.ruleThickness; + parameters.degreeBottomRaisePercent = 0.6f; + } + } + return parameters; } -LayoutUnit RenderMathMLRoot::paddingStart() const +void RenderMathMLRoot::computePreferredLogicalWidths() { - return computedCSSPaddingStart() + m_intrinsicPaddingStart; -} + ASSERT(preferredLogicalWidthsDirty()); -LayoutUnit RenderMathMLRoot::paddingEnd() const -{ - return computedCSSPaddingEnd() + m_intrinsicPaddingEnd; -} + if (!isValid()) { + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = 0; + setPreferredLogicalWidthsDirty(false); + return; + } -void RenderMathMLRoot::addChild(RenderObject* newChild, RenderObject* beforeChild) -{ - // Insert an implicit <mrow> for <mroot> as well as <msqrt>, to ensure firstChild() will have a box - // to measure and store a glyph-based height for preferredLogicalHeightAfterSizing. - if (!firstChild()) - RenderMathMLBlock::addChild(RenderMathMLRow::createAnonymousWithParentRenderer(*this).leakPtr()); - - // An <mroot>'s index has { position: absolute }. - if (newChild->style().position() == AbsolutePosition) - RenderMathMLBlock::addChild(newChild); - else - toRenderElement(firstChild())->addChild(newChild, beforeChild && beforeChild->parent() == firstChild() ? beforeChild : 0); -} + LayoutUnit preferredWidth = 0; + if (m_kind == SquareRoot) { + preferredWidth += m_radicalOperator.maxPreferredWidth(); + setPreferredLogicalWidthsDirty(true); + RenderMathMLRow::computePreferredLogicalWidths(); + preferredWidth += m_maxPreferredLogicalWidth; + } else { + ASSERT(m_kind == RootWithIndex); + auto horizontal = horizontalParameters(); + preferredWidth += horizontal.kernBeforeDegree; + preferredWidth += getIndex().maxPreferredLogicalWidth(); + preferredWidth += horizontal.kernAfterDegree; + preferredWidth += m_radicalOperator.maxPreferredWidth(); + preferredWidth += getBase().maxPreferredLogicalWidth(); + } -RenderBox* RenderMathMLRoot::index() const -{ - if (!firstChild()) - return 0; - RenderObject* index = firstChild()->nextSibling(); - if (!index || !index->isBox()) - return 0; - return toRenderBox(index); + m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = preferredWidth; + setPreferredLogicalWidthsDirty(false); } -void RenderMathMLRoot::layout() +void RenderMathMLRoot::layoutBlock(bool relayoutChildren, LayoutUnit) { - for (auto& box : childrenOfType<RenderBox>(*this)) - box.layoutIfNeeded(); - - int baseHeight = firstChild() && firstChild()->isBox() ? roundToInt(toRenderBox(firstChild())->logicalHeight()) : style().fontSize(); - int frontWidth = lroundf(gFrontWidthEms * style().fontSize()); - - // Base height above which the shape of the root changes - float thresholdHeight = gThresholdBaseHeightEms * style().fontSize(); - if (baseHeight > thresholdHeight && thresholdHeight) { - float shift = std::min<float>((baseHeight - thresholdHeight) / thresholdHeight, 1.0f); - m_overbarLeftPointShift = static_cast<int>(shift * gRadicalBottomPointXFront * frontWidth); - m_intrinsicPaddingAfter = lroundf(gBigRootBottomPaddingEms * style().fontSize()); + ASSERT(needsLayout()); + + if (!relayoutChildren && simplifiedLayout()) + return; + + m_radicalOperatorTop = 0; + m_baseWidth = 0; + + if (!isValid()) { + layoutInvalidMarkup(); + return; + } + + // We layout the children, determine the vertical metrics of the base and set the logical width. + // Note: Per the MathML specification, the children of <msqrt> are wrapped in an inferred <mrow>, which is the desired base. + LayoutUnit baseAscent, baseDescent; + recomputeLogicalWidth(); + if (m_kind == SquareRoot) { + baseAscent = baseDescent; + RenderMathMLRow::computeLineVerticalStretch(baseAscent, baseDescent); + RenderMathMLRow::layoutRowItems(baseAscent, baseDescent); + m_baseWidth = logicalWidth(); } else { - m_overbarLeftPointShift = 0; - m_intrinsicPaddingAfter = 0; + getBase().layoutIfNeeded(); + m_baseWidth = getBase().logicalWidth(); + baseAscent = ascentForChild(getBase()); + baseDescent = getBase().logicalHeight() - baseAscent; + getIndex().layoutIfNeeded(); } - - int rootPad = lroundf(gSpaceAboveEms * style().fontSize()); - m_intrinsicPaddingBefore = rootPad; - m_indexTop = 0; - if (RenderBox* index = this->index()) { - m_intrinsicPaddingStart = roundToInt(index->maxPreferredLogicalWidth()) + m_overbarLeftPointShift; - - int indexHeight = roundToInt(index->logicalHeight()); - int partDipHeight = lroundf((1 - gRootRadicalDipLeftPointYPos) * baseHeight); - int rootExtraTop = partDipHeight + indexHeight - (baseHeight + rootPad); - if (rootExtraTop > 0) - m_intrinsicPaddingBefore += rootExtraTop; - else - m_indexTop = - rootExtraTop; - } else - m_intrinsicPaddingStart = frontWidth; - // FIXME: We should rewrite RenderMathMLRoot to rewrite -webkit-flex-order to get rid of the need - // for intrinsic padding. See https://bugs.webkit.org/show_bug.cgi?id=107151#c2. - // FIXME: We should make it so that the preferred width of RenderMathMLRoots doesn't change during layout. - // Technically, we currently only need to set the dirty bit here if one of the member variables above changes. - setPreferredLogicalWidthsDirty(true); + auto horizontal = horizontalParameters(); + auto vertical = verticalParameters(); - RenderMathMLBlock::layout(); + // Stretch the radical operator to cover the base height. + // We can then determine the metrics of the radical operator + the base. + m_radicalOperator.stretchTo(style(), baseAscent + baseDescent); + LayoutUnit radicalOperatorHeight = m_radicalOperator.ascent() + m_radicalOperator.descent(); + LayoutUnit indexBottomRaise = vertical.degreeBottomRaisePercent * radicalOperatorHeight; + LayoutUnit radicalAscent = baseAscent + vertical.verticalGap + vertical.ruleThickness + vertical.extraAscender; + LayoutUnit radicalDescent = std::max<LayoutUnit>(baseDescent, radicalOperatorHeight + vertical.extraAscender - radicalAscent); + LayoutUnit descent = radicalDescent; + LayoutUnit ascent = radicalAscent; - if (RenderBox* index = this->index()) - index->setLogicalTop(m_indexTop); + // We set the logical width. + if (m_kind == SquareRoot) + setLogicalWidth(m_radicalOperator.width() + m_baseWidth); + else { + ASSERT(m_kind == RootWithIndex); + setLogicalWidth(horizontal.kernBeforeDegree + getIndex().logicalWidth() + horizontal.kernAfterDegree + m_radicalOperator.width() + m_baseWidth); + } + + // For <mroot>, we update the metrics to take into account the index. + LayoutUnit indexAscent, indexDescent; + if (m_kind == RootWithIndex) { + indexAscent = ascentForChild(getIndex()); + indexDescent = getIndex().logicalHeight() - indexAscent; + ascent = std::max<LayoutUnit>(radicalAscent, indexBottomRaise + indexDescent + indexAscent - descent); + } + + // We set the final position of children. + m_radicalOperatorTop = ascent - radicalAscent + vertical.extraAscender; + LayoutUnit horizontalOffset = m_radicalOperator.width(); + if (m_kind == RootWithIndex) + horizontalOffset += horizontal.kernBeforeDegree + getIndex().logicalWidth() + horizontal.kernAfterDegree; + LayoutPoint baseLocation(mirrorIfNeeded(horizontalOffset, m_baseWidth), ascent - baseAscent); + if (m_kind == SquareRoot) { + for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) + child->setLocation(child->location() + baseLocation); + } else { + ASSERT(m_kind == RootWithIndex); + getBase().setLocation(baseLocation); + LayoutPoint indexLocation(mirrorIfNeeded(horizontal.kernBeforeDegree, getIndex()), ascent + descent - indexBottomRaise - indexDescent - indexAscent); + getIndex().setLocation(indexLocation); + } + + setLogicalHeight(ascent + descent); + clearNeedsLayout(); } void RenderMathMLRoot::paint(PaintInfo& info, const LayoutPoint& paintOffset) { - RenderMathMLBlock::paint(info, paintOffset); - - if (info.context->paintingDisabled() || style().visibility() != VISIBLE) + RenderMathMLRow::paint(info, paintOffset); + + if (!firstChild() || info.context().paintingDisabled() || style().visibility() != VISIBLE || !isValid()) + return; + + // We draw the radical operator. + LayoutPoint radicalOperatorTopLeft = paintOffset + location(); + LayoutUnit horizontalOffset = 0; + if (m_kind == RootWithIndex) { + auto horizontal = horizontalParameters(); + horizontalOffset = horizontal.kernBeforeDegree + getIndex().logicalWidth() + horizontal.kernAfterDegree; + } + radicalOperatorTopLeft.move(mirrorIfNeeded(horizontalOffset, m_radicalOperator.width()), m_radicalOperatorTop); + m_radicalOperator.paint(style(), info, radicalOperatorTopLeft); + + // We draw the radical line. + LayoutUnit ruleThickness = verticalParameters().ruleThickness; + if (!ruleThickness) return; - - IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset + location() + contentBoxRect().location()); - - int startX = adjustedPaintOffset.x(); - int frontWidth = lroundf(gFrontWidthEms * style().fontSize()); - int overbarWidth = roundToInt(contentLogicalWidth()) + m_overbarLeftPointShift; - - int baseHeight = roundToInt(contentLogicalHeight()); - int rootPad = lroundf(gSpaceAboveEms * style().fontSize()); - adjustedPaintOffset.setY(adjustedPaintOffset.y() - rootPad); - - float radicalDipLeftPointYPos = (index() ? gRootRadicalDipLeftPointYPos : gSqrtRadicalDipLeftPointYPos) * baseHeight; - - FloatPoint overbarLeftPoint(startX - m_overbarLeftPointShift, adjustedPaintOffset.y()); - FloatPoint bottomPoint(startX - gRadicalBottomPointXFront * frontWidth, adjustedPaintOffset.y() + baseHeight + gRadicalBottomPointLower); - FloatPoint dipLeftPoint(startX - gRadicalDipLeftPointXFront * frontWidth, adjustedPaintOffset.y() + radicalDipLeftPointYPos); - FloatPoint leftEnd(startX - frontWidth, dipLeftPoint.y() + gRadicalLeftEndYShiftEms * style().fontSize()); - - GraphicsContextStateSaver stateSaver(*info.context); - - info.context->setStrokeThickness(gRadicalLineThicknessEms * style().fontSize()); - info.context->setStrokeStyle(SolidStroke); - info.context->setStrokeColor(style().visitedDependentColor(CSSPropertyColor), ColorSpaceDeviceRGB); - info.context->setLineJoin(MiterJoin); - info.context->setMiterLimit(style().fontSize()); - - Path root; - - root.moveTo(FloatPoint(overbarLeftPoint.x() + overbarWidth, adjustedPaintOffset.y())); - // draw top - root.addLineTo(overbarLeftPoint); - // draw from top left corner to bottom point of radical - root.addLineTo(bottomPoint); - // draw from bottom point to top of left part of radical base "dip" - root.addLineTo(dipLeftPoint); - // draw to end - root.addLineTo(leftEnd); - - info.context->strokePath(root); - - GraphicsContextStateSaver maskStateSaver(*info.context); - - // Build a mask to draw the thick part of the root. - Path mask; - - mask.moveTo(overbarLeftPoint); - mask.addLineTo(bottomPoint); - mask.addLineTo(dipLeftPoint); - mask.addLineTo(FloatPoint(2 * dipLeftPoint.x() - leftEnd.x(), 2 * dipLeftPoint.y() - leftEnd.y())); - - info.context->clip(mask); - - // Draw the thick part of the root. - info.context->setStrokeThickness(gRadicalThickLineThicknessEms * style().fontSize()); - info.context->setLineCap(SquareCap); - - Path line; - line.moveTo(bottomPoint); - line.addLineTo(dipLeftPoint); - - info.context->strokePath(line); + GraphicsContextStateSaver stateSaver(info.context()); + + info.context().setStrokeThickness(ruleThickness); + info.context().setStrokeStyle(SolidStroke); + info.context().setStrokeColor(style().visitedDependentColor(CSSPropertyColor)); + LayoutPoint ruleOffsetFrom = paintOffset + location() + LayoutPoint(0, m_radicalOperatorTop + ruleThickness / 2); + LayoutPoint ruleOffsetTo = ruleOffsetFrom; + horizontalOffset += m_radicalOperator.width(); + ruleOffsetFrom.move(mirrorIfNeeded(horizontalOffset), 0); + horizontalOffset += m_baseWidth; + ruleOffsetTo.move(mirrorIfNeeded(horizontalOffset), 0); + info.context().drawLine(ruleOffsetFrom, ruleOffsetTo); } } |