summaryrefslogtreecommitdiffstats
path: root/layout/mathml/nsMathMLmfracFrame.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/mathml/nsMathMLmfracFrame.cpp')
-rw-r--r--layout/mathml/nsMathMLmfracFrame.cpp377
1 files changed, 377 insertions, 0 deletions
diff --git a/layout/mathml/nsMathMLmfracFrame.cpp b/layout/mathml/nsMathMLmfracFrame.cpp
new file mode 100644
index 0000000000..fc43d2dff1
--- /dev/null
+++ b/layout/mathml/nsMathMLmfracFrame.cpp
@@ -0,0 +1,377 @@
+/* -*- 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 "nsMathMLmfracFrame.h"
+
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/StaticPrefs_mathml.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsDisplayList.h"
+#include "gfxContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/MathMLElement.h"
+#include <algorithm>
+#include "gfxMathTable.h"
+#include "gfxTextRun.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+//
+// <mfrac> -- form a fraction from two subexpressions - implementation
+//
+
+nsIFrame* NS_NewMathMLmfracFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsMathMLmfracFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmfracFrame)
+
+nsMathMLmfracFrame::~nsMathMLmfracFrame() = default;
+
+eMathMLFrameType nsMathMLmfracFrame::GetMathMLFrameType() {
+ // frac is "inner" in TeXBook, Appendix G, rule 15e. See also page 170.
+ return eMathMLFrameType_Inner;
+}
+
+uint8_t nsMathMLmfracFrame::ScriptIncrement(nsIFrame* aFrame) {
+ if (StyleFont()->mMathStyle == StyleMathStyle::Compact && aFrame &&
+ (mFrames.FirstChild() == aFrame || mFrames.LastChild() == aFrame)) {
+ return 1;
+ }
+ return 0;
+}
+
+NS_IMETHODIMP
+nsMathMLmfracFrame::TransmitAutomaticData() {
+ // The TeXbook (Ch 17. p.141) says the numerator inherits the compression
+ // while the denominator is compressed
+ UpdatePresentationDataFromChildAt(1, 1, NS_MATHML_COMPRESSED,
+ NS_MATHML_COMPRESSED);
+
+ // If displaystyle is false, then scriptlevel is incremented, so notify the
+ // children of this.
+ if (StyleFont()->mMathStyle == StyleMathStyle::Compact) {
+ PropagateFrameFlagFor(mFrames.FirstChild(),
+ NS_FRAME_MATHML_SCRIPT_DESCENDANT);
+ PropagateFrameFlagFor(mFrames.LastChild(),
+ NS_FRAME_MATHML_SCRIPT_DESCENDANT);
+ }
+
+ // if our numerator is an embellished operator, let its state bubble to us
+ GetEmbellishDataFrom(mFrames.FirstChild(), mEmbellishData);
+ if (NS_MATHML_IS_EMBELLISH_OPERATOR(mEmbellishData.flags)) {
+ // even when embellished, we need to record that <mfrac> won't fire
+ // Stretch() on its embellished child
+ mEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED;
+ }
+
+ return NS_OK;
+}
+
+nscoord nsMathMLmfracFrame::CalcLineThickness(nsPresContext* aPresContext,
+ ComputedStyle* aComputedStyle,
+ nsString& aThicknessAttribute,
+ nscoord onePixel,
+ nscoord aDefaultRuleThickness,
+ float aFontSizeInflation) {
+ nscoord defaultThickness = aDefaultRuleThickness;
+ nscoord lineThickness = aDefaultRuleThickness;
+ nscoord minimumThickness = onePixel;
+
+ // linethickness
+ // https://w3c.github.io/mathml-core/#dfn-linethickness
+ if (!aThicknessAttribute.IsEmpty()) {
+ lineThickness = defaultThickness;
+ ParseNumericValue(aThicknessAttribute, &lineThickness,
+ dom::MathMLElement::PARSE_ALLOW_NEGATIVE, aPresContext,
+ aComputedStyle, aFontSizeInflation);
+ // MathML Core says a negative value is interpreted as 0.
+ if (lineThickness < 0) {
+ lineThickness = 0;
+ }
+ }
+ // use minimum if the lineThickness is a non-zero value less than minimun
+ if (lineThickness && lineThickness < minimumThickness)
+ lineThickness = minimumThickness;
+
+ return lineThickness;
+}
+
+void nsMathMLmfracFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ /////////////
+ // paint the numerator and denominator
+ nsMathMLContainerFrame::BuildDisplayList(aBuilder, aLists);
+
+ /////////////
+ // paint the fraction line
+ DisplayBar(aBuilder, this, mLineRect, aLists);
+}
+
+nsresult nsMathMLmfracFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ if (nsGkAtoms::linethickness_ == aAttribute) {
+ InvalidateFrame();
+ }
+ return nsMathMLContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+}
+
+/* virtual */
+nsresult nsMathMLmfracFrame::MeasureForWidth(DrawTarget* aDrawTarget,
+ ReflowOutput& aDesiredSize) {
+ return PlaceInternal(aDrawTarget, false, aDesiredSize, true);
+}
+
+nscoord nsMathMLmfracFrame::FixInterFrameSpacing(ReflowOutput& aDesiredSize) {
+ nscoord gap = nsMathMLContainerFrame::FixInterFrameSpacing(aDesiredSize);
+ if (!gap) return 0;
+
+ mLineRect.MoveBy(gap, 0);
+ return gap;
+}
+
+/* virtual */
+nsresult nsMathMLmfracFrame::Place(DrawTarget* aDrawTarget, bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize) {
+ return PlaceInternal(aDrawTarget, aPlaceOrigin, aDesiredSize, false);
+}
+
+nsresult nsMathMLmfracFrame::PlaceInternal(DrawTarget* aDrawTarget,
+ bool aPlaceOrigin,
+ ReflowOutput& aDesiredSize,
+ bool aWidthOnly) {
+ ////////////////////////////////////
+ // Get the children's desired sizes
+ nsBoundingMetrics bmNum, bmDen;
+ ReflowOutput sizeNum(aDesiredSize.GetWritingMode());
+ ReflowOutput sizeDen(aDesiredSize.GetWritingMode());
+ nsIFrame* frameDen = nullptr;
+ nsIFrame* frameNum = mFrames.FirstChild();
+ if (frameNum) frameDen = frameNum->GetNextSibling();
+ if (!frameNum || !frameDen || frameDen->GetNextSibling()) {
+ // report an error, encourage people to get their markups in order
+ if (aPlaceOrigin) {
+ ReportChildCountError();
+ }
+ return PlaceAsMrow(aDrawTarget, aPlaceOrigin, aDesiredSize);
+ }
+ GetReflowAndBoundingMetricsFor(frameNum, sizeNum, bmNum);
+ GetReflowAndBoundingMetricsFor(frameDen, sizeDen, bmDen);
+
+ nsPresContext* presContext = PresContext();
+ nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
+
+ float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation);
+
+ nscoord defaultRuleThickness, axisHeight;
+ nscoord oneDevPixel = fm->AppUnitsPerDevPixel();
+ RefPtr<gfxFont> mathFont = fm->GetThebesFontGroup()->GetFirstMathFont();
+ if (mathFont) {
+ defaultRuleThickness = mathFont->MathTable()->Constant(
+ gfxMathTable::FractionRuleThickness, oneDevPixel);
+ } else {
+ GetRuleThickness(aDrawTarget, fm, defaultRuleThickness);
+ }
+ GetAxisHeight(aDrawTarget, fm, axisHeight);
+
+ bool outermostEmbellished = false;
+ if (mEmbellishData.coreFrame) {
+ nsEmbellishData parentData;
+ GetEmbellishDataFrom(GetParent(), parentData);
+ outermostEmbellished = parentData.coreFrame != mEmbellishData.coreFrame;
+ }
+
+ // see if the linethickness attribute is there
+ nsAutoString value;
+ mContent->AsElement()->GetAttr(nsGkAtoms::linethickness_, value);
+ mLineThickness =
+ CalcLineThickness(presContext, mComputedStyle, value, onePixel,
+ defaultRuleThickness, fontSizeInflation);
+
+ bool displayStyle = StyleFont()->mMathStyle == StyleMathStyle::Normal;
+
+ mLineRect.height = mLineThickness;
+
+ // by default, leave at least one-pixel padding at either end, and add
+ // lspace & rspace that may come from <mo> if we are an outermost
+ // embellished container (we fetch values from the core since they may use
+ // units that depend on style data, and style changes could have occurred
+ // in the core since our last visit there)
+ nscoord leftSpace = onePixel;
+ nscoord rightSpace = onePixel;
+ if (outermostEmbellished) {
+ const bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
+ nsEmbellishData coreData;
+ GetEmbellishDataFrom(mEmbellishData.coreFrame, coreData);
+ leftSpace += isRTL ? coreData.trailingSpace : coreData.leadingSpace;
+ rightSpace += isRTL ? coreData.leadingSpace : coreData.trailingSpace;
+ }
+
+ nscoord actualRuleThickness = mLineThickness;
+
+ //////////////////
+ // Get shifts
+ nscoord numShift = 0;
+ nscoord denShift = 0;
+
+ // Rule 15b, App. G, TeXbook
+ nscoord numShift1, numShift2, numShift3;
+ nscoord denShift1, denShift2;
+
+ GetNumeratorShifts(fm, numShift1, numShift2, numShift3);
+ GetDenominatorShifts(fm, denShift1, denShift2);
+
+ if (0 == actualRuleThickness) {
+ numShift = displayStyle ? numShift1 : numShift3;
+ denShift = displayStyle ? denShift1 : denShift2;
+ if (mathFont) {
+ numShift = mathFont->MathTable()->Constant(
+ displayStyle ? gfxMathTable::StackTopDisplayStyleShiftUp
+ : gfxMathTable::StackTopShiftUp,
+ oneDevPixel);
+ denShift = mathFont->MathTable()->Constant(
+ displayStyle ? gfxMathTable::StackBottomDisplayStyleShiftDown
+ : gfxMathTable::StackBottomShiftDown,
+ oneDevPixel);
+ }
+ } else {
+ numShift = displayStyle ? numShift1 : numShift2;
+ denShift = displayStyle ? denShift1 : denShift2;
+ if (mathFont) {
+ numShift = mathFont->MathTable()->Constant(
+ displayStyle ? gfxMathTable::FractionNumeratorDisplayStyleShiftUp
+ : gfxMathTable::FractionNumeratorShiftUp,
+ oneDevPixel);
+ denShift = mathFont->MathTable()->Constant(
+ displayStyle ? gfxMathTable::FractionDenominatorDisplayStyleShiftDown
+ : gfxMathTable::FractionDenominatorShiftDown,
+ oneDevPixel);
+ }
+ }
+
+ if (0 == actualRuleThickness) {
+ // Rule 15c, App. G, TeXbook
+
+ // min clearance between numerator and denominator
+ nscoord minClearance =
+ displayStyle ? 7 * defaultRuleThickness : 3 * defaultRuleThickness;
+ if (mathFont) {
+ minClearance = mathFont->MathTable()->Constant(
+ displayStyle ? gfxMathTable::StackDisplayStyleGapMin
+ : gfxMathTable::StackGapMin,
+ oneDevPixel);
+ }
+
+ nscoord actualClearance =
+ (numShift - bmNum.descent) - (bmDen.ascent - denShift);
+ // actualClearance should be >= minClearance
+ if (actualClearance < minClearance) {
+ nscoord halfGap = (minClearance - actualClearance) / 2;
+ numShift += halfGap;
+ denShift += halfGap;
+ }
+ } else {
+ // Rule 15d, App. G, TeXbook
+
+ // min clearance between numerator or denominator and middle of bar
+
+ // TeX has a different interpretation of the thickness.
+ // Try $a \above10pt b$ to see. Here is what TeX does:
+ // minClearance = displayStyle ?
+ // 3 * actualRuleThickness : actualRuleThickness;
+
+ // we slightly depart from TeX here. We use the defaultRuleThickness
+ // instead of the value coming from the linethickness attribute, i.e., we
+ // recover what TeX does if the user hasn't set linethickness. But when
+ // the linethickness is set, we avoid the wide gap problem.
+ nscoord minClearanceNum = displayStyle ? 3 * defaultRuleThickness
+ : defaultRuleThickness + onePixel;
+ nscoord minClearanceDen = minClearanceNum;
+ if (mathFont) {
+ minClearanceNum = mathFont->MathTable()->Constant(
+ displayStyle ? gfxMathTable::FractionNumDisplayStyleGapMin
+ : gfxMathTable::FractionNumeratorGapMin,
+ oneDevPixel);
+ minClearanceDen = mathFont->MathTable()->Constant(
+ displayStyle ? gfxMathTable::FractionDenomDisplayStyleGapMin
+ : gfxMathTable::FractionDenominatorGapMin,
+ oneDevPixel);
+ }
+
+ // adjust numShift to maintain minClearanceNum if needed
+ nscoord actualClearanceNum =
+ (numShift - bmNum.descent) - (axisHeight + actualRuleThickness / 2);
+ if (actualClearanceNum < minClearanceNum) {
+ numShift += (minClearanceNum - actualClearanceNum);
+ }
+ // adjust denShift to maintain minClearanceDen if needed
+ nscoord actualClearanceDen =
+ (axisHeight - actualRuleThickness / 2) - (bmDen.ascent - denShift);
+ if (actualClearanceDen < minClearanceDen) {
+ denShift += (minClearanceDen - actualClearanceDen);
+ }
+ }
+
+ //////////////////
+ // Place Children
+
+ // XXX Need revisiting the width. TeX uses the exact width
+ // e.g. in $$\huge\frac{\displaystyle\int}{i}$$
+ nscoord width = std::max(bmNum.width, bmDen.width);
+ nscoord dxNum = leftSpace + (width - sizeNum.Width()) / 2;
+ nscoord dxDen = leftSpace + (width - sizeDen.Width()) / 2;
+ width += leftSpace + rightSpace;
+
+ mBoundingMetrics.rightBearing =
+ std::max(dxNum + bmNum.rightBearing, dxDen + bmDen.rightBearing);
+ if (mBoundingMetrics.rightBearing < width - rightSpace)
+ mBoundingMetrics.rightBearing = width - rightSpace;
+ mBoundingMetrics.leftBearing =
+ std::min(dxNum + bmNum.leftBearing, dxDen + bmDen.leftBearing);
+ if (mBoundingMetrics.leftBearing > leftSpace)
+ mBoundingMetrics.leftBearing = leftSpace;
+ mBoundingMetrics.ascent = bmNum.ascent + numShift;
+ mBoundingMetrics.descent = bmDen.descent + denShift;
+ mBoundingMetrics.width = width;
+
+ aDesiredSize.SetBlockStartAscent(sizeNum.BlockStartAscent() + numShift);
+ aDesiredSize.Height() = aDesiredSize.BlockStartAscent() + sizeDen.Height() -
+ sizeDen.BlockStartAscent() + denShift;
+ aDesiredSize.Width() = mBoundingMetrics.width;
+ aDesiredSize.mBoundingMetrics = mBoundingMetrics;
+
+ mReference.x = 0;
+ mReference.y = aDesiredSize.BlockStartAscent();
+
+ if (aPlaceOrigin) {
+ nscoord dy;
+ // place numerator
+ dy = 0;
+ FinishReflowChild(frameNum, presContext, sizeNum, nullptr, dxNum, dy,
+ ReflowChildFlags::Default);
+ // place denominator
+ dy = aDesiredSize.Height() - sizeDen.Height();
+ FinishReflowChild(frameDen, presContext, sizeDen, nullptr, dxDen, dy,
+ ReflowChildFlags::Default);
+ // place the fraction bar - dy is top of bar
+ dy = aDesiredSize.BlockStartAscent() -
+ (axisHeight + actualRuleThickness / 2);
+ mLineRect.SetRect(leftSpace, dy, width - (leftSpace + rightSpace),
+ actualRuleThickness);
+ }
+
+ return NS_OK;
+}