diff options
Diffstat (limited to 'layout/mathml/nsMathMLmrootFrame.cpp')
-rw-r--r-- | layout/mathml/nsMathMLmrootFrame.cpp | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/layout/mathml/nsMathMLmrootFrame.cpp b/layout/mathml/nsMathMLmrootFrame.cpp new file mode 100644 index 0000000000..77637ffe30 --- /dev/null +++ b/layout/mathml/nsMathMLmrootFrame.cpp @@ -0,0 +1,399 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMathMLmrootFrame.h" + +#include "mozilla/PresShell.h" +#include "mozilla/StaticPrefs_mathml.h" +#include "nsLayoutUtils.h" +#include "nsPresContext.h" +#include <algorithm> +#include "gfxContext.h" +#include "gfxMathTable.h" + +using namespace mozilla; + +// +// <mroot> -- form a radical - implementation +// + +static const char16_t kSqrChar = char16_t(0x221A); + +nsIFrame* NS_NewMathMLmrootFrame(PresShell* aPresShell, ComputedStyle* aStyle) { + return new (aPresShell) + nsMathMLmrootFrame(aStyle, aPresShell->GetPresContext()); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmrootFrame) + +nsMathMLmrootFrame::nsMathMLmrootFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext) + : nsMathMLContainerFrame(aStyle, aPresContext, kClassID), + mSqrChar(), + mBarRect() {} + +nsMathMLmrootFrame::~nsMathMLmrootFrame() = default; + +void nsMathMLmrootFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + nsMathMLContainerFrame::Init(aContent, aParent, aPrevInFlow); + + nsAutoString sqrChar; + sqrChar.Assign(kSqrChar); + mSqrChar.SetData(sqrChar); + mSqrChar.SetComputedStyle(Style()); +} + +bool nsMathMLmrootFrame::ShouldUseRowFallback() { + if (!StaticPrefs::mathml_error_message_layout_for_invalid_markup_disabled()) { + return false; + } + nsIFrame* baseFrame = mFrames.FirstChild(); + if (!baseFrame) { + return true; + } + nsIFrame* indexFrame = baseFrame->GetNextSibling(); + return !indexFrame || indexFrame->GetNextSibling(); +} + +NS_IMETHODIMP +nsMathMLmrootFrame::TransmitAutomaticData() { + // 1. The REC says: + // The <mroot> element increments scriptlevel by 2, and sets displaystyle + // to "false", within index, but leaves both attributes unchanged within + // base. + // 2. The TeXbook (Ch 17. p.141) says \sqrt is compressed + UpdatePresentationDataFromChildAt(1, 1, NS_MATHML_COMPRESSED, + NS_MATHML_COMPRESSED); + UpdatePresentationDataFromChildAt(0, 0, NS_MATHML_COMPRESSED, + NS_MATHML_COMPRESSED); + + PropagateFrameFlagFor(mFrames.LastChild(), NS_FRAME_MATHML_SCRIPT_DESCENDANT); + + return NS_OK; +} + +void nsMathMLmrootFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + ///////////// + // paint the content we are square-rooting + nsMathMLContainerFrame::BuildDisplayList(aBuilder, aLists); + + if (ShouldUseRowFallback()) return; + + ///////////// + // paint the sqrt symbol + if (!NS_MATHML_HAS_ERROR(mPresentationData.flags)) { + mSqrChar.Display(aBuilder, this, aLists, 0); + + DisplayBar(aBuilder, this, mBarRect, aLists); + +#if defined(DEBUG) && defined(SHOW_BOUNDING_BOX) + // for visual debug + nsRect rect; + mSqrChar.GetRect(rect); + nsBoundingMetrics bm; + mSqrChar.GetBoundingMetrics(bm); + DisplayBoundingMetrics(aBuilder, this, rect.TopLeft(), bm, aLists); +#endif + } +} + +void nsMathMLmrootFrame::GetRadicalXOffsets(nscoord aIndexWidth, + nscoord aSqrWidth, + nsFontMetrics* aFontMetrics, + nscoord* aIndexOffset, + nscoord* aSqrOffset) { + // The index is tucked in closer to the radical while making sure + // that the kern does not make the index and radical collide + nscoord dxIndex, dxSqr; + nscoord xHeight = aFontMetrics->XHeight(); + nscoord indexRadicalKern = NSToCoordRound(1.35f * xHeight); + nscoord oneDevPixel = aFontMetrics->AppUnitsPerDevPixel(); + RefPtr<gfxFont> mathFont = + aFontMetrics->GetThebesFontGroup()->GetFirstMathFont(); + if (mathFont) { + indexRadicalKern = mathFont->MathTable()->Constant( + gfxMathTable::RadicalKernAfterDegree, oneDevPixel); + indexRadicalKern = -indexRadicalKern; + } + if (indexRadicalKern > aIndexWidth) { + dxIndex = indexRadicalKern - aIndexWidth; + dxSqr = 0; + } else { + dxIndex = 0; + dxSqr = aIndexWidth - indexRadicalKern; + } + + if (mathFont) { + // add some kern before the radical index + nscoord indexRadicalKernBefore = 0; + indexRadicalKernBefore = mathFont->MathTable()->Constant( + gfxMathTable::RadicalKernBeforeDegree, oneDevPixel); + dxIndex += indexRadicalKernBefore; + dxSqr += indexRadicalKernBefore; + } else { + // avoid collision by leaving a minimum space between index and radical + nscoord minimumClearance = aSqrWidth / 2; + if (dxIndex + aIndexWidth + minimumClearance > dxSqr + aSqrWidth) { + if (aIndexWidth + minimumClearance < aSqrWidth) { + dxIndex = aSqrWidth - (aIndexWidth + minimumClearance); + dxSqr = 0; + } else { + dxIndex = 0; + dxSqr = (aIndexWidth + minimumClearance) - aSqrWidth; + } + } + } + + if (aIndexOffset) *aIndexOffset = dxIndex; + if (aSqrOffset) *aSqrOffset = dxSqr; +} + +void nsMathMLmrootFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) { + if (ShouldUseRowFallback()) { + ReportChildCountError(); + nsMathMLContainerFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, + aStatus); + return; + } + + MarkInReflow(); + MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); + + nsReflowStatus childStatus; + mPresentationData.flags &= ~NS_MATHML_ERROR; + aDesiredSize.ClearSize(); + aDesiredSize.SetBlockStartAscent(0); + + nsBoundingMetrics bmSqr, bmBase, bmIndex; + DrawTarget* drawTarget = aReflowInput.mRenderingContext->GetDrawTarget(); + + ////////////////// + // Reflow Children + + int32_t count = 0; + nsIFrame* baseFrame = nullptr; + nsIFrame* indexFrame = nullptr; + ReflowOutput baseSize(aReflowInput); + ReflowOutput indexSize(aReflowInput); + nsIFrame* childFrame = mFrames.FirstChild(); + while (childFrame) { + // ask our children to compute their bounding metrics + ReflowOutput childDesiredSize(aReflowInput); + WritingMode wm = childFrame->GetWritingMode(); + LogicalSize availSize = aReflowInput.ComputedSize(wm); + availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; + ReflowInput childReflowInput(aPresContext, aReflowInput, childFrame, + availSize); + ReflowChild(childFrame, aPresContext, childDesiredSize, childReflowInput, + childStatus); + // NS_ASSERTION(childStatus.IsComplete(), "bad status"); + if (0 == count) { + // base + baseFrame = childFrame; + baseSize = childDesiredSize; + bmBase = childDesiredSize.mBoundingMetrics; + } else if (1 == count) { + // index + indexFrame = childFrame; + indexSize = childDesiredSize; + bmIndex = childDesiredSize.mBoundingMetrics; + } + count++; + childFrame = childFrame->GetNextSibling(); + } + // FIXME: Bug 1788223: Remove this code when the preference + // mathml.error_message_layout_for_invalid_markup.disabled no longer needed. + if (2 != count) { + // report an error, encourage people to get their markups in order + ReportChildCountError(); + ReflowError(drawTarget, aDesiredSize); + // Call DidReflow() for the child frames we successfully did reflow. + DidReflowChildren(mFrames.FirstChild(), childFrame); + return; + } + + //////////// + // Prepare the radical symbol and the overline bar + + float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this); + RefPtr<nsFontMetrics> fm = + nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation); + + nscoord ruleThickness, leading, psi; + GetRadicalParameters(fm, StyleFont()->mMathStyle == StyleMathStyle::Normal, + ruleThickness, leading, psi); + + // built-in: adjust clearance psi to emulate \mathstrut using '1' (TexBook, + // p.131) + char16_t one = '1'; + nsBoundingMetrics bmOne = + nsLayoutUtils::AppUnitBoundsOfString(&one, 1, *fm, drawTarget); + if (bmOne.ascent > bmBase.ascent) psi += bmOne.ascent - bmBase.ascent; + + // make sure that the rule appears on on screen + nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1); + if (ruleThickness < onePixel) { + ruleThickness = onePixel; + } + + // adjust clearance psi to get an exact number of pixels -- this + // gives a nicer & uniform look on stacked radicals (bug 130282) + nscoord delta = psi % onePixel; + if (delta) psi += onePixel - delta; // round up + + // Stretch the radical symbol to the appropriate height if it is not big + // enough. + nsBoundingMetrics contSize = bmBase; + contSize.descent = bmBase.ascent + bmBase.descent + psi; + contSize.ascent = ruleThickness; + + // height(radical) should be >= height(base) + psi + ruleThickness + nsBoundingMetrics radicalSize; + mSqrChar.Stretch(this, drawTarget, fontSizeInflation, + NS_STRETCH_DIRECTION_VERTICAL, contSize, radicalSize, + NS_STRETCH_LARGER, + StyleVisibility()->mDirection == StyleDirection::Rtl); + // radicalSize have changed at this point, and should match with + // the bounding metrics of the char + mSqrChar.GetBoundingMetrics(bmSqr); + + // Update the desired size for the container (like msqrt, index is not yet + // included) the baseline will be that of the base. + mBoundingMetrics.ascent = bmBase.ascent + psi + ruleThickness; + mBoundingMetrics.descent = std::max( + bmBase.descent, (bmSqr.ascent + bmSqr.descent - mBoundingMetrics.ascent)); + mBoundingMetrics.width = bmSqr.width + bmBase.width; + mBoundingMetrics.leftBearing = bmSqr.leftBearing; + mBoundingMetrics.rightBearing = + bmSqr.width + + std::max(bmBase.width, + bmBase.rightBearing); // take also care of the rule + + aDesiredSize.SetBlockStartAscent(mBoundingMetrics.ascent + leading); + aDesiredSize.Height() = + aDesiredSize.BlockStartAscent() + + std::max(baseSize.Height() - baseSize.BlockStartAscent(), + mBoundingMetrics.descent + ruleThickness); + aDesiredSize.Width() = mBoundingMetrics.width; + + ///////////// + // Re-adjust the desired size to include the index. + + // the index is raised by some fraction of the height + // of the radical, see \mroot macro in App. B, TexBook + float raiseIndexPercent = 0.6f; + RefPtr<gfxFont> mathFont = fm->GetThebesFontGroup()->GetFirstMathFont(); + if (mathFont) { + raiseIndexPercent = mathFont->MathTable()->Constant( + gfxMathTable::RadicalDegreeBottomRaisePercent); + } + nscoord raiseIndexDelta = + NSToCoordRound(raiseIndexPercent * (bmSqr.ascent + bmSqr.descent)); + nscoord indexRaisedAscent = + mBoundingMetrics.ascent // top of radical + - (bmSqr.ascent + bmSqr.descent) // to bottom of radical + + raiseIndexDelta + bmIndex.ascent + + bmIndex.descent; // to top of raised index + + nscoord indexClearance = 0; + if (mBoundingMetrics.ascent < indexRaisedAscent) { + indexClearance = + indexRaisedAscent - + mBoundingMetrics.ascent; // excess gap introduced by a tall index + mBoundingMetrics.ascent = indexRaisedAscent; + nscoord descent = aDesiredSize.Height() - aDesiredSize.BlockStartAscent(); + aDesiredSize.SetBlockStartAscent(mBoundingMetrics.ascent + leading); + aDesiredSize.Height() = aDesiredSize.BlockStartAscent() + descent; + } + + nscoord dxIndex, dxSqr; + GetRadicalXOffsets(bmIndex.width, bmSqr.width, fm, &dxIndex, &dxSqr); + + mBoundingMetrics.width = dxSqr + bmSqr.width + bmBase.width; + mBoundingMetrics.leftBearing = + std::min(dxIndex + bmIndex.leftBearing, dxSqr + bmSqr.leftBearing); + mBoundingMetrics.rightBearing = + dxSqr + bmSqr.width + std::max(bmBase.width, bmBase.rightBearing); + + aDesiredSize.Width() = mBoundingMetrics.width; + aDesiredSize.mBoundingMetrics = mBoundingMetrics; + GatherAndStoreOverflow(&aDesiredSize); + + // place the index + nscoord dx = dxIndex; + nscoord dy = + aDesiredSize.BlockStartAscent() - + (indexRaisedAscent + indexSize.BlockStartAscent() - bmIndex.ascent); + FinishReflowChild(indexFrame, aPresContext, indexSize, nullptr, + MirrorIfRTL(aDesiredSize.Width(), indexSize.Width(), dx), + dy, ReflowChildFlags::Default); + + // place the radical symbol and the radical bar + dx = dxSqr; + dy = indexClearance + leading; // leave a leading at the top + mSqrChar.SetRect(nsRect(MirrorIfRTL(aDesiredSize.Width(), bmSqr.width, dx), + dy, bmSqr.width, bmSqr.ascent + bmSqr.descent)); + dx += bmSqr.width; + mBarRect.SetRect(MirrorIfRTL(aDesiredSize.Width(), bmBase.width, dx), dy, + bmBase.width, ruleThickness); + + // place the base + dy = aDesiredSize.BlockStartAscent() - baseSize.BlockStartAscent(); + FinishReflowChild(baseFrame, aPresContext, baseSize, nullptr, + MirrorIfRTL(aDesiredSize.Width(), baseSize.Width(), dx), dy, + ReflowChildFlags::Default); + + mReference.x = 0; + mReference.y = aDesiredSize.BlockStartAscent(); +} + +/* virtual */ +void nsMathMLmrootFrame::GetIntrinsicISizeMetrics(gfxContext* aRenderingContext, + ReflowOutput& aDesiredSize) { + if (ShouldUseRowFallback()) { + nsMathMLContainerFrame::GetIntrinsicISizeMetrics(aRenderingContext, + aDesiredSize); + return; + } + + nsIFrame* baseFrame = mFrames.FirstChild(); + nsIFrame* indexFrame = nullptr; + if (baseFrame) indexFrame = baseFrame->GetNextSibling(); + if (!indexFrame || indexFrame->GetNextSibling()) { + ReflowError(aRenderingContext->GetDrawTarget(), aDesiredSize); + return; + } + + float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this); + nscoord baseWidth = nsLayoutUtils::IntrinsicForContainer( + aRenderingContext, baseFrame, IntrinsicISizeType::PrefISize); + nscoord indexWidth = nsLayoutUtils::IntrinsicForContainer( + aRenderingContext, indexFrame, IntrinsicISizeType::PrefISize); + nscoord sqrWidth = mSqrChar.GetMaxWidth( + this, aRenderingContext->GetDrawTarget(), fontSizeInflation); + + nscoord dxSqr; + RefPtr<nsFontMetrics> fm = + nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation); + GetRadicalXOffsets(indexWidth, sqrWidth, fm, nullptr, &dxSqr); + + nscoord width = dxSqr + sqrWidth + baseWidth; + + aDesiredSize.Width() = width; + aDesiredSize.mBoundingMetrics.width = width; + aDesiredSize.mBoundingMetrics.leftBearing = 0; + aDesiredSize.mBoundingMetrics.rightBearing = width; +} + +void nsMathMLmrootFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) { + nsMathMLContainerFrame::DidSetComputedStyle(aOldStyle); + mSqrChar.SetComputedStyle(Style()); +} |