/* -*- 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 #include "gfxMathTable.h" #include "gfxTextRun.h" using namespace mozilla; using namespace mozilla::gfx; // // -- 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 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 fm = nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation); nscoord defaultRuleThickness, axisHeight; nscoord oneDevPixel = fm->AppUnitsPerDevPixel(); RefPtr 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 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; }