1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsMathMLmfracFrame.h"
10 #include "mozilla/gfx/2D.h"
11 #include "mozilla/PresShell.h"
12 #include "mozilla/RefPtr.h"
13 #include "mozilla/StaticPrefs_mathml.h"
14 #include "nsLayoutUtils.h"
15 #include "nsPresContext.h"
16 #include "nsDisplayList.h"
17 #include "gfxContext.h"
18 #include "mozilla/dom/Document.h"
19 #include "mozilla/dom/MathMLElement.h"
21 #include "gfxMathTable.h"
22 #include "gfxTextRun.h"
24 using namespace mozilla
;
25 using namespace mozilla::gfx
;
28 // <mfrac> -- form a fraction from two subexpressions - implementation
31 nsIFrame
* NS_NewMathMLmfracFrame(PresShell
* aPresShell
, ComputedStyle
* aStyle
) {
32 return new (aPresShell
)
33 nsMathMLmfracFrame(aStyle
, aPresShell
->GetPresContext());
36 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmfracFrame
)
38 nsMathMLmfracFrame::~nsMathMLmfracFrame() = default;
40 eMathMLFrameType
nsMathMLmfracFrame::GetMathMLFrameType() {
41 // frac is "inner" in TeXBook, Appendix G, rule 15e. See also page 170.
42 return eMathMLFrameType_Inner
;
45 uint8_t nsMathMLmfracFrame::ScriptIncrement(nsIFrame
* aFrame
) {
46 if (StyleFont()->mMathStyle
== StyleMathStyle::Compact
&& aFrame
&&
47 (mFrames
.FirstChild() == aFrame
|| mFrames
.LastChild() == aFrame
)) {
54 nsMathMLmfracFrame::TransmitAutomaticData() {
55 // The TeXbook (Ch 17. p.141) says the numerator inherits the compression
56 // while the denominator is compressed
57 UpdatePresentationDataFromChildAt(1, 1, NS_MATHML_COMPRESSED
,
58 NS_MATHML_COMPRESSED
);
60 // If displaystyle is false, then scriptlevel is incremented, so notify the
62 if (StyleFont()->mMathStyle
== StyleMathStyle::Compact
) {
63 PropagateFrameFlagFor(mFrames
.FirstChild(),
64 NS_FRAME_MATHML_SCRIPT_DESCENDANT
);
65 PropagateFrameFlagFor(mFrames
.LastChild(),
66 NS_FRAME_MATHML_SCRIPT_DESCENDANT
);
69 // if our numerator is an embellished operator, let its state bubble to us
70 GetEmbellishDataFrom(mFrames
.FirstChild(), mEmbellishData
);
71 if (NS_MATHML_IS_EMBELLISH_OPERATOR(mEmbellishData
.flags
)) {
72 // even when embellished, we need to record that <mfrac> won't fire
73 // Stretch() on its embellished child
74 mEmbellishData
.direction
= NS_STRETCH_DIRECTION_UNSUPPORTED
;
80 nscoord
nsMathMLmfracFrame::CalcLineThickness(nsPresContext
* aPresContext
,
81 ComputedStyle
* aComputedStyle
,
82 nsString
& aThicknessAttribute
,
84 nscoord aDefaultRuleThickness
,
85 float aFontSizeInflation
) {
86 nscoord defaultThickness
= aDefaultRuleThickness
;
87 nscoord lineThickness
= aDefaultRuleThickness
;
88 nscoord minimumThickness
= onePixel
;
91 // https://w3c.github.io/mathml-core/#dfn-linethickness
92 if (!aThicknessAttribute
.IsEmpty()) {
93 lineThickness
= defaultThickness
;
94 ParseNumericValue(aThicknessAttribute
, &lineThickness
,
95 dom::MathMLElement::PARSE_ALLOW_NEGATIVE
, aPresContext
,
96 aComputedStyle
, aFontSizeInflation
);
97 // MathML Core says a negative value is interpreted as 0.
98 if (lineThickness
< 0) {
102 // use minimum if the lineThickness is a non-zero value less than minimun
103 if (lineThickness
&& lineThickness
< minimumThickness
)
104 lineThickness
= minimumThickness
;
106 return lineThickness
;
109 void nsMathMLmfracFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
110 const nsDisplayListSet
& aLists
) {
112 // paint the numerator and denominator
113 nsMathMLContainerFrame::BuildDisplayList(aBuilder
, aLists
);
116 // paint the fraction line
117 DisplayBar(aBuilder
, this, mLineRect
, aLists
);
120 nsresult
nsMathMLmfracFrame::AttributeChanged(int32_t aNameSpaceID
,
123 if (nsGkAtoms::linethickness_
== aAttribute
) {
126 return nsMathMLContainerFrame::AttributeChanged(aNameSpaceID
, aAttribute
,
131 nsresult
nsMathMLmfracFrame::MeasureForWidth(DrawTarget
* aDrawTarget
,
132 ReflowOutput
& aDesiredSize
) {
133 return PlaceInternal(aDrawTarget
, false, aDesiredSize
, true);
136 nscoord
nsMathMLmfracFrame::FixInterFrameSpacing(ReflowOutput
& aDesiredSize
) {
137 nscoord gap
= nsMathMLContainerFrame::FixInterFrameSpacing(aDesiredSize
);
140 mLineRect
.MoveBy(gap
, 0);
145 nsresult
nsMathMLmfracFrame::Place(DrawTarget
* aDrawTarget
, bool aPlaceOrigin
,
146 ReflowOutput
& aDesiredSize
) {
147 return PlaceInternal(aDrawTarget
, aPlaceOrigin
, aDesiredSize
, false);
150 nsresult
nsMathMLmfracFrame::PlaceInternal(DrawTarget
* aDrawTarget
,
152 ReflowOutput
& aDesiredSize
,
154 ////////////////////////////////////
155 // Get the children's desired sizes
156 nsBoundingMetrics bmNum
, bmDen
;
157 ReflowOutput
sizeNum(aDesiredSize
.GetWritingMode());
158 ReflowOutput
sizeDen(aDesiredSize
.GetWritingMode());
159 nsIFrame
* frameDen
= nullptr;
160 nsIFrame
* frameNum
= mFrames
.FirstChild();
161 if (frameNum
) frameDen
= frameNum
->GetNextSibling();
162 if (!frameNum
|| !frameDen
|| frameDen
->GetNextSibling()) {
163 // report an error, encourage people to get their markups in order
165 ReportChildCountError();
167 return PlaceAsMrow(aDrawTarget
, aPlaceOrigin
, aDesiredSize
);
169 GetReflowAndBoundingMetricsFor(frameNum
, sizeNum
, bmNum
);
170 GetReflowAndBoundingMetricsFor(frameDen
, sizeDen
, bmDen
);
172 nsPresContext
* presContext
= PresContext();
173 nscoord onePixel
= nsPresContext::CSSPixelsToAppUnits(1);
175 float fontSizeInflation
= nsLayoutUtils::FontSizeInflationFor(this);
176 RefPtr
<nsFontMetrics
> fm
=
177 nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation
);
179 nscoord defaultRuleThickness
, axisHeight
;
180 nscoord oneDevPixel
= fm
->AppUnitsPerDevPixel();
181 RefPtr
<gfxFont
> mathFont
= fm
->GetThebesFontGroup()->GetFirstMathFont();
183 defaultRuleThickness
= mathFont
->MathTable()->Constant(
184 gfxMathTable::FractionRuleThickness
, oneDevPixel
);
186 GetRuleThickness(aDrawTarget
, fm
, defaultRuleThickness
);
188 GetAxisHeight(aDrawTarget
, fm
, axisHeight
);
190 bool outermostEmbellished
= false;
191 if (mEmbellishData
.coreFrame
) {
192 nsEmbellishData parentData
;
193 GetEmbellishDataFrom(GetParent(), parentData
);
194 outermostEmbellished
= parentData
.coreFrame
!= mEmbellishData
.coreFrame
;
197 // see if the linethickness attribute is there
199 mContent
->AsElement()->GetAttr(nsGkAtoms::linethickness_
, value
);
201 CalcLineThickness(presContext
, mComputedStyle
, value
, onePixel
,
202 defaultRuleThickness
, fontSizeInflation
);
204 bool displayStyle
= StyleFont()->mMathStyle
== StyleMathStyle::Normal
;
206 mLineRect
.height
= mLineThickness
;
208 // by default, leave at least one-pixel padding at either end, and add
209 // lspace & rspace that may come from <mo> if we are an outermost
210 // embellished container (we fetch values from the core since they may use
211 // units that depend on style data, and style changes could have occurred
212 // in the core since our last visit there)
213 nscoord leftSpace
= onePixel
;
214 nscoord rightSpace
= onePixel
;
215 if (outermostEmbellished
) {
216 const bool isRTL
= StyleVisibility()->mDirection
== StyleDirection::Rtl
;
217 nsEmbellishData coreData
;
218 GetEmbellishDataFrom(mEmbellishData
.coreFrame
, coreData
);
219 leftSpace
+= isRTL
? coreData
.trailingSpace
: coreData
.leadingSpace
;
220 rightSpace
+= isRTL
? coreData
.leadingSpace
: coreData
.trailingSpace
;
223 nscoord actualRuleThickness
= mLineThickness
;
227 nscoord numShift
= 0;
228 nscoord denShift
= 0;
230 // Rule 15b, App. G, TeXbook
231 nscoord numShift1
, numShift2
, numShift3
;
232 nscoord denShift1
, denShift2
;
234 GetNumeratorShifts(fm
, numShift1
, numShift2
, numShift3
);
235 GetDenominatorShifts(fm
, denShift1
, denShift2
);
237 if (0 == actualRuleThickness
) {
238 numShift
= displayStyle
? numShift1
: numShift3
;
239 denShift
= displayStyle
? denShift1
: denShift2
;
241 numShift
= mathFont
->MathTable()->Constant(
242 displayStyle
? gfxMathTable::StackTopDisplayStyleShiftUp
243 : gfxMathTable::StackTopShiftUp
,
245 denShift
= mathFont
->MathTable()->Constant(
246 displayStyle
? gfxMathTable::StackBottomDisplayStyleShiftDown
247 : gfxMathTable::StackBottomShiftDown
,
251 numShift
= displayStyle
? numShift1
: numShift2
;
252 denShift
= displayStyle
? denShift1
: denShift2
;
254 numShift
= mathFont
->MathTable()->Constant(
255 displayStyle
? gfxMathTable::FractionNumeratorDisplayStyleShiftUp
256 : gfxMathTable::FractionNumeratorShiftUp
,
258 denShift
= mathFont
->MathTable()->Constant(
259 displayStyle
? gfxMathTable::FractionDenominatorDisplayStyleShiftDown
260 : gfxMathTable::FractionDenominatorShiftDown
,
265 if (0 == actualRuleThickness
) {
266 // Rule 15c, App. G, TeXbook
268 // min clearance between numerator and denominator
269 nscoord minClearance
=
270 displayStyle
? 7 * defaultRuleThickness
: 3 * defaultRuleThickness
;
272 minClearance
= mathFont
->MathTable()->Constant(
273 displayStyle
? gfxMathTable::StackDisplayStyleGapMin
274 : gfxMathTable::StackGapMin
,
278 nscoord actualClearance
=
279 (numShift
- bmNum
.descent
) - (bmDen
.ascent
- denShift
);
280 // actualClearance should be >= minClearance
281 if (actualClearance
< minClearance
) {
282 nscoord halfGap
= (minClearance
- actualClearance
) / 2;
287 // Rule 15d, App. G, TeXbook
289 // min clearance between numerator or denominator and middle of bar
291 // TeX has a different interpretation of the thickness.
292 // Try $a \above10pt b$ to see. Here is what TeX does:
293 // minClearance = displayStyle ?
294 // 3 * actualRuleThickness : actualRuleThickness;
296 // we slightly depart from TeX here. We use the defaultRuleThickness
297 // instead of the value coming from the linethickness attribute, i.e., we
298 // recover what TeX does if the user hasn't set linethickness. But when
299 // the linethickness is set, we avoid the wide gap problem.
300 nscoord minClearanceNum
= displayStyle
? 3 * defaultRuleThickness
301 : defaultRuleThickness
+ onePixel
;
302 nscoord minClearanceDen
= minClearanceNum
;
304 minClearanceNum
= mathFont
->MathTable()->Constant(
305 displayStyle
? gfxMathTable::FractionNumDisplayStyleGapMin
306 : gfxMathTable::FractionNumeratorGapMin
,
308 minClearanceDen
= mathFont
->MathTable()->Constant(
309 displayStyle
? gfxMathTable::FractionDenomDisplayStyleGapMin
310 : gfxMathTable::FractionDenominatorGapMin
,
314 // adjust numShift to maintain minClearanceNum if needed
315 nscoord actualClearanceNum
=
316 (numShift
- bmNum
.descent
) - (axisHeight
+ actualRuleThickness
/ 2);
317 if (actualClearanceNum
< minClearanceNum
) {
318 numShift
+= (minClearanceNum
- actualClearanceNum
);
320 // adjust denShift to maintain minClearanceDen if needed
321 nscoord actualClearanceDen
=
322 (axisHeight
- actualRuleThickness
/ 2) - (bmDen
.ascent
- denShift
);
323 if (actualClearanceDen
< minClearanceDen
) {
324 denShift
+= (minClearanceDen
- actualClearanceDen
);
331 // XXX Need revisiting the width. TeX uses the exact width
332 // e.g. in $$\huge\frac{\displaystyle\int}{i}$$
333 nscoord width
= std::max(bmNum
.width
, bmDen
.width
);
334 nscoord dxNum
= leftSpace
+ (width
- sizeNum
.Width()) / 2;
335 nscoord dxDen
= leftSpace
+ (width
- sizeDen
.Width()) / 2;
336 width
+= leftSpace
+ rightSpace
;
338 mBoundingMetrics
.rightBearing
=
339 std::max(dxNum
+ bmNum
.rightBearing
, dxDen
+ bmDen
.rightBearing
);
340 if (mBoundingMetrics
.rightBearing
< width
- rightSpace
)
341 mBoundingMetrics
.rightBearing
= width
- rightSpace
;
342 mBoundingMetrics
.leftBearing
=
343 std::min(dxNum
+ bmNum
.leftBearing
, dxDen
+ bmDen
.leftBearing
);
344 if (mBoundingMetrics
.leftBearing
> leftSpace
)
345 mBoundingMetrics
.leftBearing
= leftSpace
;
346 mBoundingMetrics
.ascent
= bmNum
.ascent
+ numShift
;
347 mBoundingMetrics
.descent
= bmDen
.descent
+ denShift
;
348 mBoundingMetrics
.width
= width
;
350 aDesiredSize
.SetBlockStartAscent(sizeNum
.BlockStartAscent() + numShift
);
351 aDesiredSize
.Height() = aDesiredSize
.BlockStartAscent() + sizeDen
.Height() -
352 sizeDen
.BlockStartAscent() + denShift
;
353 aDesiredSize
.Width() = mBoundingMetrics
.width
;
354 aDesiredSize
.mBoundingMetrics
= mBoundingMetrics
;
357 mReference
.y
= aDesiredSize
.BlockStartAscent();
363 FinishReflowChild(frameNum
, presContext
, sizeNum
, nullptr, dxNum
, dy
,
364 ReflowChildFlags::Default
);
366 dy
= aDesiredSize
.Height() - sizeDen
.Height();
367 FinishReflowChild(frameDen
, presContext
, sizeDen
, nullptr, dxDen
, dy
,
368 ReflowChildFlags::Default
);
369 // place the fraction bar - dy is top of bar
370 dy
= aDesiredSize
.BlockStartAscent() -
371 (axisHeight
+ actualRuleThickness
/ 2);
372 mLineRect
.SetRect(leftSpace
, dy
, width
- (leftSpace
+ rightSpace
),
373 actualRuleThickness
);