Bug 1869043 allow a device to be specified with MediaTrackGraph::NotifyWhenDeviceStar...
[gecko.git] / layout / mathml / nsMathMLmfracFrame.cpp
blobfc43d2dff179c0f05f07deca2a06b03c78187d3b
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"
9 #include "gfxUtils.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"
20 #include <algorithm>
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)) {
48 return 1;
50 return 0;
53 NS_IMETHODIMP
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
61 // children of this.
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;
77 return NS_OK;
80 nscoord nsMathMLmfracFrame::CalcLineThickness(nsPresContext* aPresContext,
81 ComputedStyle* aComputedStyle,
82 nsString& aThicknessAttribute,
83 nscoord onePixel,
84 nscoord aDefaultRuleThickness,
85 float aFontSizeInflation) {
86 nscoord defaultThickness = aDefaultRuleThickness;
87 nscoord lineThickness = aDefaultRuleThickness;
88 nscoord minimumThickness = onePixel;
90 // linethickness
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) {
99 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) {
111 /////////////
112 // paint the numerator and denominator
113 nsMathMLContainerFrame::BuildDisplayList(aBuilder, aLists);
115 /////////////
116 // paint the fraction line
117 DisplayBar(aBuilder, this, mLineRect, aLists);
120 nsresult nsMathMLmfracFrame::AttributeChanged(int32_t aNameSpaceID,
121 nsAtom* aAttribute,
122 int32_t aModType) {
123 if (nsGkAtoms::linethickness_ == aAttribute) {
124 InvalidateFrame();
126 return nsMathMLContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
127 aModType);
130 /* virtual */
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);
138 if (!gap) return 0;
140 mLineRect.MoveBy(gap, 0);
141 return gap;
144 /* virtual */
145 nsresult nsMathMLmfracFrame::Place(DrawTarget* aDrawTarget, bool aPlaceOrigin,
146 ReflowOutput& aDesiredSize) {
147 return PlaceInternal(aDrawTarget, aPlaceOrigin, aDesiredSize, false);
150 nsresult nsMathMLmfracFrame::PlaceInternal(DrawTarget* aDrawTarget,
151 bool aPlaceOrigin,
152 ReflowOutput& aDesiredSize,
153 bool aWidthOnly) {
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
164 if (aPlaceOrigin) {
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();
182 if (mathFont) {
183 defaultRuleThickness = mathFont->MathTable()->Constant(
184 gfxMathTable::FractionRuleThickness, oneDevPixel);
185 } else {
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
198 nsAutoString value;
199 mContent->AsElement()->GetAttr(nsGkAtoms::linethickness_, value);
200 mLineThickness =
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;
225 //////////////////
226 // Get shifts
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;
240 if (mathFont) {
241 numShift = mathFont->MathTable()->Constant(
242 displayStyle ? gfxMathTable::StackTopDisplayStyleShiftUp
243 : gfxMathTable::StackTopShiftUp,
244 oneDevPixel);
245 denShift = mathFont->MathTable()->Constant(
246 displayStyle ? gfxMathTable::StackBottomDisplayStyleShiftDown
247 : gfxMathTable::StackBottomShiftDown,
248 oneDevPixel);
250 } else {
251 numShift = displayStyle ? numShift1 : numShift2;
252 denShift = displayStyle ? denShift1 : denShift2;
253 if (mathFont) {
254 numShift = mathFont->MathTable()->Constant(
255 displayStyle ? gfxMathTable::FractionNumeratorDisplayStyleShiftUp
256 : gfxMathTable::FractionNumeratorShiftUp,
257 oneDevPixel);
258 denShift = mathFont->MathTable()->Constant(
259 displayStyle ? gfxMathTable::FractionDenominatorDisplayStyleShiftDown
260 : gfxMathTable::FractionDenominatorShiftDown,
261 oneDevPixel);
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;
271 if (mathFont) {
272 minClearance = mathFont->MathTable()->Constant(
273 displayStyle ? gfxMathTable::StackDisplayStyleGapMin
274 : gfxMathTable::StackGapMin,
275 oneDevPixel);
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;
283 numShift += halfGap;
284 denShift += halfGap;
286 } else {
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;
303 if (mathFont) {
304 minClearanceNum = mathFont->MathTable()->Constant(
305 displayStyle ? gfxMathTable::FractionNumDisplayStyleGapMin
306 : gfxMathTable::FractionNumeratorGapMin,
307 oneDevPixel);
308 minClearanceDen = mathFont->MathTable()->Constant(
309 displayStyle ? gfxMathTable::FractionDenomDisplayStyleGapMin
310 : gfxMathTable::FractionDenominatorGapMin,
311 oneDevPixel);
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);
328 //////////////////
329 // Place Children
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;
356 mReference.x = 0;
357 mReference.y = aDesiredSize.BlockStartAscent();
359 if (aPlaceOrigin) {
360 nscoord dy;
361 // place numerator
362 dy = 0;
363 FinishReflowChild(frameNum, presContext, sizeNum, nullptr, dxNum, dy,
364 ReflowChildFlags::Default);
365 // place denominator
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);
376 return NS_OK;