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 "nsMathMLmrootFrame.h"
9 #include "mozilla/PresShell.h"
10 #include "nsLayoutUtils.h"
11 #include "nsPresContext.h"
13 #include "gfxContext.h"
14 #include "gfxMathTable.h"
16 using namespace mozilla
;
19 // <mroot> -- form a radical - implementation
22 static const char16_t kSqrChar
= char16_t(0x221A);
24 nsIFrame
* NS_NewMathMLmrootFrame(PresShell
* aPresShell
, ComputedStyle
* aStyle
) {
25 return new (aPresShell
)
26 nsMathMLmrootFrame(aStyle
, aPresShell
->GetPresContext());
29 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmrootFrame
)
31 nsMathMLmrootFrame::nsMathMLmrootFrame(ComputedStyle
* aStyle
,
32 nsPresContext
* aPresContext
)
33 : nsMathMLContainerFrame(aStyle
, aPresContext
, kClassID
) {}
35 nsMathMLmrootFrame::~nsMathMLmrootFrame() = default;
37 void nsMathMLmrootFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
38 nsIFrame
* aPrevInFlow
) {
39 nsMathMLContainerFrame::Init(aContent
, aParent
, aPrevInFlow
);
42 sqrChar
.Assign(kSqrChar
);
43 mSqrChar
.SetData(sqrChar
);
44 mSqrChar
.SetComputedStyle(Style());
47 bool nsMathMLmrootFrame::ShouldUseRowFallback() {
48 nsIFrame
* baseFrame
= mFrames
.FirstChild();
52 nsIFrame
* indexFrame
= baseFrame
->GetNextSibling();
53 return !indexFrame
|| indexFrame
->GetNextSibling();
57 nsMathMLmrootFrame::TransmitAutomaticData() {
59 // The <mroot> element increments scriptlevel by 2, and sets displaystyle
60 // to "false", within index, but leaves both attributes unchanged within
62 // 2. The TeXbook (Ch 17. p.141) says \sqrt is compressed
63 UpdatePresentationDataFromChildAt(1, 1, NS_MATHML_COMPRESSED
,
64 NS_MATHML_COMPRESSED
);
65 UpdatePresentationDataFromChildAt(0, 0, NS_MATHML_COMPRESSED
,
66 NS_MATHML_COMPRESSED
);
68 PropagateFrameFlagFor(mFrames
.LastChild(), NS_FRAME_MATHML_SCRIPT_DESCENDANT
);
73 void nsMathMLmrootFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
74 const nsDisplayListSet
& aLists
) {
76 // paint the content we are square-rooting
77 nsMathMLContainerFrame::BuildDisplayList(aBuilder
, aLists
);
79 if (ShouldUseRowFallback()) return;
82 // paint the sqrt symbol
83 if (!NS_MATHML_HAS_ERROR(mPresentationData
.flags
)) {
84 mSqrChar
.Display(aBuilder
, this, aLists
, 0);
86 DisplayBar(aBuilder
, this, mBarRect
, aLists
);
88 #if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
91 mSqrChar
.GetRect(rect
);
93 mSqrChar
.GetBoundingMetrics(bm
);
94 DisplayBoundingMetrics(aBuilder
, this, rect
.TopLeft(), bm
, aLists
);
99 void nsMathMLmrootFrame::GetRadicalXOffsets(nscoord aIndexWidth
,
101 nsFontMetrics
* aFontMetrics
,
102 nscoord
* aIndexOffset
,
103 nscoord
* aSqrOffset
) {
104 // The index is tucked in closer to the radical while making sure
105 // that the kern does not make the index and radical collide
106 nscoord dxIndex
, dxSqr
;
107 nscoord xHeight
= aFontMetrics
->XHeight();
108 nscoord indexRadicalKern
= NSToCoordRound(1.35f
* xHeight
);
109 nscoord oneDevPixel
= aFontMetrics
->AppUnitsPerDevPixel();
110 RefPtr
<gfxFont
> mathFont
=
111 aFontMetrics
->GetThebesFontGroup()->GetFirstMathFont();
113 indexRadicalKern
= mathFont
->MathTable()->Constant(
114 gfxMathTable::RadicalKernAfterDegree
, oneDevPixel
);
115 indexRadicalKern
= -indexRadicalKern
;
117 if (indexRadicalKern
> aIndexWidth
) {
118 dxIndex
= indexRadicalKern
- aIndexWidth
;
122 dxSqr
= aIndexWidth
- indexRadicalKern
;
126 // add some kern before the radical index
127 nscoord indexRadicalKernBefore
= 0;
128 indexRadicalKernBefore
= mathFont
->MathTable()->Constant(
129 gfxMathTable::RadicalKernBeforeDegree
, oneDevPixel
);
130 dxIndex
+= indexRadicalKernBefore
;
131 dxSqr
+= indexRadicalKernBefore
;
133 // avoid collision by leaving a minimum space between index and radical
134 nscoord minimumClearance
= aSqrWidth
/ 2;
135 if (dxIndex
+ aIndexWidth
+ minimumClearance
> dxSqr
+ aSqrWidth
) {
136 if (aIndexWidth
+ minimumClearance
< aSqrWidth
) {
137 dxIndex
= aSqrWidth
- (aIndexWidth
+ minimumClearance
);
141 dxSqr
= (aIndexWidth
+ minimumClearance
) - aSqrWidth
;
146 if (aIndexOffset
) *aIndexOffset
= dxIndex
;
147 if (aSqrOffset
) *aSqrOffset
= dxSqr
;
150 void nsMathMLmrootFrame::Reflow(nsPresContext
* aPresContext
,
151 ReflowOutput
& aDesiredSize
,
152 const ReflowInput
& aReflowInput
,
153 nsReflowStatus
& aStatus
) {
154 if (ShouldUseRowFallback()) {
155 ReportChildCountError();
156 nsMathMLContainerFrame::Reflow(aPresContext
, aDesiredSize
, aReflowInput
,
162 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
164 nsReflowStatus childStatus
;
165 mPresentationData
.flags
&= ~NS_MATHML_ERROR
;
166 aDesiredSize
.ClearSize();
167 aDesiredSize
.SetBlockStartAscent(0);
169 nsBoundingMetrics bmSqr
, bmBase
, bmIndex
;
170 DrawTarget
* drawTarget
= aReflowInput
.mRenderingContext
->GetDrawTarget();
176 nsIFrame
* baseFrame
= nullptr;
177 nsIFrame
* indexFrame
= nullptr;
178 ReflowOutput
baseSize(aReflowInput
);
179 ReflowOutput
indexSize(aReflowInput
);
180 nsIFrame
* childFrame
= mFrames
.FirstChild();
182 // ask our children to compute their bounding metrics
183 ReflowOutput
childDesiredSize(aReflowInput
);
184 WritingMode wm
= childFrame
->GetWritingMode();
185 LogicalSize availSize
= aReflowInput
.ComputedSize(wm
);
186 availSize
.BSize(wm
) = NS_UNCONSTRAINEDSIZE
;
187 ReflowInput
childReflowInput(aPresContext
, aReflowInput
, childFrame
,
189 ReflowChild(childFrame
, aPresContext
, childDesiredSize
, childReflowInput
,
191 // NS_ASSERTION(childStatus.IsComplete(), "bad status");
194 baseFrame
= childFrame
;
195 baseSize
= childDesiredSize
;
196 bmBase
= childDesiredSize
.mBoundingMetrics
;
197 } else if (1 == count
) {
199 indexFrame
= childFrame
;
200 indexSize
= childDesiredSize
;
201 bmIndex
= childDesiredSize
.mBoundingMetrics
;
204 childFrame
= childFrame
->GetNextSibling();
208 // Prepare the radical symbol and the overline bar
210 float fontSizeInflation
= nsLayoutUtils::FontSizeInflationFor(this);
211 RefPtr
<nsFontMetrics
> fm
=
212 nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation
);
214 nscoord ruleThickness
, leading
, psi
;
215 GetRadicalParameters(fm
, StyleFont()->mMathStyle
== StyleMathStyle::Normal
,
216 ruleThickness
, leading
, psi
);
218 // built-in: adjust clearance psi to emulate \mathstrut using '1' (TexBook,
221 nsBoundingMetrics bmOne
=
222 nsLayoutUtils::AppUnitBoundsOfString(&one
, 1, *fm
, drawTarget
);
223 if (bmOne
.ascent
> bmBase
.ascent
) psi
+= bmOne
.ascent
- bmBase
.ascent
;
225 // make sure that the rule appears on on screen
226 nscoord onePixel
= nsPresContext::CSSPixelsToAppUnits(1);
227 if (ruleThickness
< onePixel
) {
228 ruleThickness
= onePixel
;
231 // adjust clearance psi to get an exact number of pixels -- this
232 // gives a nicer & uniform look on stacked radicals (bug 130282)
233 nscoord delta
= psi
% onePixel
;
234 if (delta
) psi
+= onePixel
- delta
; // round up
236 // Stretch the radical symbol to the appropriate height if it is not big
238 nsBoundingMetrics contSize
= bmBase
;
239 contSize
.descent
= bmBase
.ascent
+ bmBase
.descent
+ psi
;
240 contSize
.ascent
= ruleThickness
;
242 // height(radical) should be >= height(base) + psi + ruleThickness
243 nsBoundingMetrics radicalSize
;
244 mSqrChar
.Stretch(this, drawTarget
, fontSizeInflation
,
245 NS_STRETCH_DIRECTION_VERTICAL
, contSize
, radicalSize
,
247 StyleVisibility()->mDirection
== StyleDirection::Rtl
);
248 // radicalSize have changed at this point, and should match with
249 // the bounding metrics of the char
250 mSqrChar
.GetBoundingMetrics(bmSqr
);
252 // Update the desired size for the container (like msqrt, index is not yet
253 // included) the baseline will be that of the base.
254 mBoundingMetrics
.ascent
= bmBase
.ascent
+ psi
+ ruleThickness
;
255 mBoundingMetrics
.descent
= std::max(
256 bmBase
.descent
, (bmSqr
.ascent
+ bmSqr
.descent
- mBoundingMetrics
.ascent
));
257 mBoundingMetrics
.width
= bmSqr
.width
+ bmBase
.width
;
258 mBoundingMetrics
.leftBearing
= bmSqr
.leftBearing
;
259 mBoundingMetrics
.rightBearing
=
261 std::max(bmBase
.width
,
262 bmBase
.rightBearing
); // take also care of the rule
264 aDesiredSize
.SetBlockStartAscent(mBoundingMetrics
.ascent
+ leading
);
265 aDesiredSize
.Height() =
266 aDesiredSize
.BlockStartAscent() +
267 std::max(baseSize
.Height() - baseSize
.BlockStartAscent(),
268 mBoundingMetrics
.descent
+ ruleThickness
);
269 aDesiredSize
.Width() = mBoundingMetrics
.width
;
272 // Re-adjust the desired size to include the index.
274 // the index is raised by some fraction of the height
275 // of the radical, see \mroot macro in App. B, TexBook
276 float raiseIndexPercent
= 0.6f
;
277 RefPtr
<gfxFont
> mathFont
= fm
->GetThebesFontGroup()->GetFirstMathFont();
279 raiseIndexPercent
= mathFont
->MathTable()->Constant(
280 gfxMathTable::RadicalDegreeBottomRaisePercent
);
282 nscoord raiseIndexDelta
=
283 NSToCoordRound(raiseIndexPercent
* (bmSqr
.ascent
+ bmSqr
.descent
));
284 nscoord indexRaisedAscent
=
285 mBoundingMetrics
.ascent
// top of radical
286 - (bmSqr
.ascent
+ bmSqr
.descent
) // to bottom of radical
287 + raiseIndexDelta
+ bmIndex
.ascent
+
288 bmIndex
.descent
; // to top of raised index
290 nscoord indexClearance
= 0;
291 if (mBoundingMetrics
.ascent
< indexRaisedAscent
) {
294 mBoundingMetrics
.ascent
; // excess gap introduced by a tall index
295 mBoundingMetrics
.ascent
= indexRaisedAscent
;
296 nscoord descent
= aDesiredSize
.Height() - aDesiredSize
.BlockStartAscent();
297 aDesiredSize
.SetBlockStartAscent(mBoundingMetrics
.ascent
+ leading
);
298 aDesiredSize
.Height() = aDesiredSize
.BlockStartAscent() + descent
;
301 nscoord dxIndex
, dxSqr
;
302 GetRadicalXOffsets(bmIndex
.width
, bmSqr
.width
, fm
, &dxIndex
, &dxSqr
);
304 mBoundingMetrics
.width
= dxSqr
+ bmSqr
.width
+ bmBase
.width
;
305 mBoundingMetrics
.leftBearing
=
306 std::min(dxIndex
+ bmIndex
.leftBearing
, dxSqr
+ bmSqr
.leftBearing
);
307 mBoundingMetrics
.rightBearing
=
308 dxSqr
+ bmSqr
.width
+ std::max(bmBase
.width
, bmBase
.rightBearing
);
310 aDesiredSize
.Width() = mBoundingMetrics
.width
;
311 aDesiredSize
.mBoundingMetrics
= mBoundingMetrics
;
312 GatherAndStoreOverflow(&aDesiredSize
);
315 nscoord dx
= dxIndex
;
317 aDesiredSize
.BlockStartAscent() -
318 (indexRaisedAscent
+ indexSize
.BlockStartAscent() - bmIndex
.ascent
);
319 FinishReflowChild(indexFrame
, aPresContext
, indexSize
, nullptr,
320 MirrorIfRTL(aDesiredSize
.Width(), indexSize
.Width(), dx
),
321 dy
, ReflowChildFlags::Default
);
323 // place the radical symbol and the radical bar
325 dy
= indexClearance
+ leading
; // leave a leading at the top
326 mSqrChar
.SetRect(nsRect(MirrorIfRTL(aDesiredSize
.Width(), bmSqr
.width
, dx
),
327 dy
, bmSqr
.width
, bmSqr
.ascent
+ bmSqr
.descent
));
329 mBarRect
.SetRect(MirrorIfRTL(aDesiredSize
.Width(), bmBase
.width
, dx
), dy
,
330 bmBase
.width
, ruleThickness
);
333 dy
= aDesiredSize
.BlockStartAscent() - baseSize
.BlockStartAscent();
334 FinishReflowChild(baseFrame
, aPresContext
, baseSize
, nullptr,
335 MirrorIfRTL(aDesiredSize
.Width(), baseSize
.Width(), dx
), dy
,
336 ReflowChildFlags::Default
);
339 mReference
.y
= aDesiredSize
.BlockStartAscent();
343 void nsMathMLmrootFrame::GetIntrinsicISizeMetrics(gfxContext
* aRenderingContext
,
344 ReflowOutput
& aDesiredSize
) {
345 if (ShouldUseRowFallback()) {
346 nsMathMLContainerFrame::GetIntrinsicISizeMetrics(aRenderingContext
,
351 // ShouldUseRowFallback() returned false so there are exactly two children.
352 nsIFrame
* baseFrame
= mFrames
.FirstChild();
353 MOZ_ASSERT(baseFrame
);
354 nsIFrame
* indexFrame
= baseFrame
->GetNextSibling();
355 MOZ_ASSERT(indexFrame
);
356 MOZ_ASSERT(!indexFrame
->GetNextSibling());
358 float fontSizeInflation
= nsLayoutUtils::FontSizeInflationFor(this);
359 nscoord baseWidth
= nsLayoutUtils::IntrinsicForContainer(
360 aRenderingContext
, baseFrame
, IntrinsicISizeType::PrefISize
);
361 nscoord indexWidth
= nsLayoutUtils::IntrinsicForContainer(
362 aRenderingContext
, indexFrame
, IntrinsicISizeType::PrefISize
);
363 nscoord sqrWidth
= mSqrChar
.GetMaxWidth(
364 this, aRenderingContext
->GetDrawTarget(), fontSizeInflation
);
367 RefPtr
<nsFontMetrics
> fm
=
368 nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation
);
369 GetRadicalXOffsets(indexWidth
, sqrWidth
, fm
, nullptr, &dxSqr
);
371 nscoord width
= dxSqr
+ sqrWidth
+ baseWidth
;
373 aDesiredSize
.Width() = width
;
374 aDesiredSize
.mBoundingMetrics
.width
= width
;
375 aDesiredSize
.mBoundingMetrics
.leftBearing
= 0;
376 aDesiredSize
.mBoundingMetrics
.rightBearing
= width
;
379 void nsMathMLmrootFrame::DidSetComputedStyle(ComputedStyle
* aOldStyle
) {
380 nsMathMLContainerFrame::DidSetComputedStyle(aOldStyle
);
381 mSqrChar
.SetComputedStyle(Style());