/src/mozilla-central/layout/mathml/nsMathMLmrootFrame.cpp
Line | Count | Source (jump to first uncovered line) |
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/. */ |
6 | | |
7 | | #include "nsMathMLmrootFrame.h" |
8 | | #include "nsPresContext.h" |
9 | | #include <algorithm> |
10 | | #include "gfxContext.h" |
11 | | #include "gfxMathTable.h" |
12 | | |
13 | | using namespace mozilla; |
14 | | |
15 | | // |
16 | | // <mroot> -- form a radical - implementation |
17 | | // |
18 | | |
19 | | // additional ComputedStyle to be used by our MathMLChar. |
20 | 0 | #define NS_SQR_CHAR_STYLE_CONTEXT_INDEX 0 |
21 | | |
22 | | static const char16_t kSqrChar = char16_t(0x221A); |
23 | | |
24 | | nsIFrame* |
25 | | NS_NewMathMLmrootFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle) |
26 | 0 | { |
27 | 0 | return new (aPresShell) nsMathMLmrootFrame(aStyle); |
28 | 0 | } |
29 | | |
30 | | NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmrootFrame) |
31 | | |
32 | | nsMathMLmrootFrame::nsMathMLmrootFrame(ComputedStyle* aStyle) : |
33 | | nsMathMLContainerFrame(aStyle, kClassID), |
34 | | mSqrChar(), |
35 | | mBarRect() |
36 | 0 | { |
37 | 0 | } |
38 | | |
39 | | nsMathMLmrootFrame::~nsMathMLmrootFrame() |
40 | 0 | { |
41 | 0 | } |
42 | | |
43 | | void |
44 | | nsMathMLmrootFrame::Init(nsIContent* aContent, |
45 | | nsContainerFrame* aParent, |
46 | | nsIFrame* aPrevInFlow) |
47 | 0 | { |
48 | 0 | nsMathMLContainerFrame::Init(aContent, aParent, aPrevInFlow); |
49 | 0 |
|
50 | 0 | nsPresContext *presContext = PresContext(); |
51 | 0 |
|
52 | 0 | // No need to track the ComputedStyle given to our MathML char. |
53 | 0 | // The Style System will use Get/SetAdditionalComputedStyle() to keep it |
54 | 0 | // up-to-date if dynamic changes arise. |
55 | 0 | nsAutoString sqrChar; sqrChar.Assign(kSqrChar); |
56 | 0 | mSqrChar.SetData(sqrChar); |
57 | 0 | ResolveMathMLCharStyle(presContext, mContent, mComputedStyle, &mSqrChar); |
58 | 0 | } |
59 | | |
60 | | NS_IMETHODIMP |
61 | | nsMathMLmrootFrame::TransmitAutomaticData() |
62 | 0 | { |
63 | 0 | // 1. The REC says: |
64 | 0 | // The <mroot> element increments scriptlevel by 2, and sets displaystyle to |
65 | 0 | // "false", within index, but leaves both attributes unchanged within base. |
66 | 0 | // 2. The TeXbook (Ch 17. p.141) says \sqrt is compressed |
67 | 0 | UpdatePresentationDataFromChildAt(1, 1, |
68 | 0 | NS_MATHML_COMPRESSED, |
69 | 0 | NS_MATHML_COMPRESSED); |
70 | 0 | UpdatePresentationDataFromChildAt(0, 0, |
71 | 0 | NS_MATHML_COMPRESSED, NS_MATHML_COMPRESSED); |
72 | 0 |
|
73 | 0 | PropagateFrameFlagFor(mFrames.LastChild(), |
74 | 0 | NS_FRAME_MATHML_SCRIPT_DESCENDANT); |
75 | 0 |
|
76 | 0 | return NS_OK; |
77 | 0 | } |
78 | | |
79 | | void |
80 | | nsMathMLmrootFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, |
81 | | const nsDisplayListSet& aLists) |
82 | 0 | { |
83 | 0 | ///////////// |
84 | 0 | // paint the content we are square-rooting |
85 | 0 | nsMathMLContainerFrame::BuildDisplayList(aBuilder, aLists); |
86 | 0 |
|
87 | 0 | ///////////// |
88 | 0 | // paint the sqrt symbol |
89 | 0 | if (!NS_MATHML_HAS_ERROR(mPresentationData.flags)) { |
90 | 0 | mSqrChar.Display(aBuilder, this, aLists, 0); |
91 | 0 |
|
92 | 0 | DisplayBar(aBuilder, this, mBarRect, aLists); |
93 | 0 |
|
94 | | #if defined(DEBUG) && defined(SHOW_BOUNDING_BOX) |
95 | | // for visual debug |
96 | | nsRect rect; |
97 | | mSqrChar.GetRect(rect); |
98 | | nsBoundingMetrics bm; |
99 | | mSqrChar.GetBoundingMetrics(bm); |
100 | | DisplayBoundingMetrics(aBuilder, this, rect.TopLeft(), bm, aLists); |
101 | | #endif |
102 | | } |
103 | 0 | } |
104 | | |
105 | | void |
106 | | nsMathMLmrootFrame::GetRadicalXOffsets(nscoord aIndexWidth, nscoord aSqrWidth, |
107 | | nsFontMetrics* aFontMetrics, |
108 | | nscoord* aIndexOffset, |
109 | | nscoord* aSqrOffset) |
110 | 0 | { |
111 | 0 | // The index is tucked in closer to the radical while making sure |
112 | 0 | // that the kern does not make the index and radical collide |
113 | 0 | nscoord dxIndex, dxSqr; |
114 | 0 | nscoord xHeight = aFontMetrics->XHeight(); |
115 | 0 | nscoord indexRadicalKern = NSToCoordRound(1.35f * xHeight); |
116 | 0 | nscoord oneDevPixel = aFontMetrics->AppUnitsPerDevPixel(); |
117 | 0 | gfxFont* mathFont = aFontMetrics->GetThebesFontGroup()->GetFirstMathFont(); |
118 | 0 | if (mathFont) { |
119 | 0 | indexRadicalKern = |
120 | 0 | mathFont->MathTable()->Constant(gfxMathTable::RadicalKernAfterDegree, |
121 | 0 | oneDevPixel); |
122 | 0 | indexRadicalKern = -indexRadicalKern; |
123 | 0 | } |
124 | 0 | if (indexRadicalKern > aIndexWidth) { |
125 | 0 | dxIndex = indexRadicalKern - aIndexWidth; |
126 | 0 | dxSqr = 0; |
127 | 0 | } |
128 | 0 | else { |
129 | 0 | dxIndex = 0; |
130 | 0 | dxSqr = aIndexWidth - indexRadicalKern; |
131 | 0 | } |
132 | 0 |
|
133 | 0 | if (mathFont) { |
134 | 0 | // add some kern before the radical index |
135 | 0 | nscoord indexRadicalKernBefore = 0; |
136 | 0 | indexRadicalKernBefore = |
137 | 0 | mathFont->MathTable()->Constant(gfxMathTable::RadicalKernBeforeDegree, |
138 | 0 | oneDevPixel); |
139 | 0 | dxIndex += indexRadicalKernBefore; |
140 | 0 | dxSqr += indexRadicalKernBefore; |
141 | 0 | } else { |
142 | 0 | // avoid collision by leaving a minimum space between index and radical |
143 | 0 | nscoord minimumClearance = aSqrWidth / 2; |
144 | 0 | if (dxIndex + aIndexWidth + minimumClearance > dxSqr + aSqrWidth) { |
145 | 0 | if (aIndexWidth + minimumClearance < aSqrWidth) { |
146 | 0 | dxIndex = aSqrWidth - (aIndexWidth + minimumClearance); |
147 | 0 | dxSqr = 0; |
148 | 0 | } |
149 | 0 | else { |
150 | 0 | dxIndex = 0; |
151 | 0 | dxSqr = (aIndexWidth + minimumClearance) - aSqrWidth; |
152 | 0 | } |
153 | 0 | } |
154 | 0 | } |
155 | 0 |
|
156 | 0 | if (aIndexOffset) |
157 | 0 | *aIndexOffset = dxIndex; |
158 | 0 | if (aSqrOffset) |
159 | 0 | *aSqrOffset = dxSqr; |
160 | 0 | } |
161 | | |
162 | | void |
163 | | nsMathMLmrootFrame::Reflow(nsPresContext* aPresContext, |
164 | | ReflowOutput& aDesiredSize, |
165 | | const ReflowInput& aReflowInput, |
166 | | nsReflowStatus& aStatus) |
167 | 0 | { |
168 | 0 | MarkInReflow(); |
169 | 0 | MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); |
170 | 0 |
|
171 | 0 | nsReflowStatus childStatus; |
172 | 0 | mPresentationData.flags &= ~NS_MATHML_ERROR; |
173 | 0 | aDesiredSize.ClearSize(); |
174 | 0 | aDesiredSize.SetBlockStartAscent(0); |
175 | 0 |
|
176 | 0 | nsBoundingMetrics bmSqr, bmBase, bmIndex; |
177 | 0 | DrawTarget* drawTarget = aReflowInput.mRenderingContext->GetDrawTarget(); |
178 | 0 |
|
179 | 0 | ////////////////// |
180 | 0 | // Reflow Children |
181 | 0 |
|
182 | 0 | int32_t count = 0; |
183 | 0 | nsIFrame* baseFrame = nullptr; |
184 | 0 | nsIFrame* indexFrame = nullptr; |
185 | 0 | ReflowOutput baseSize(aReflowInput); |
186 | 0 | ReflowOutput indexSize(aReflowInput); |
187 | 0 | nsIFrame* childFrame = mFrames.FirstChild(); |
188 | 0 | while (childFrame) { |
189 | 0 | // ask our children to compute their bounding metrics |
190 | 0 | ReflowOutput childDesiredSize(aReflowInput); |
191 | 0 | WritingMode wm = childFrame->GetWritingMode(); |
192 | 0 | LogicalSize availSize = aReflowInput.ComputedSize(wm); |
193 | 0 | availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; |
194 | 0 | ReflowInput childReflowInput(aPresContext, aReflowInput, |
195 | 0 | childFrame, availSize); |
196 | 0 | ReflowChild(childFrame, aPresContext, |
197 | 0 | childDesiredSize, childReflowInput, childStatus); |
198 | 0 | //NS_ASSERTION(childStatus.IsComplete(), "bad status"); |
199 | 0 | if (0 == count) { |
200 | 0 | // base |
201 | 0 | baseFrame = childFrame; |
202 | 0 | baseSize = childDesiredSize; |
203 | 0 | bmBase = childDesiredSize.mBoundingMetrics; |
204 | 0 | } |
205 | 0 | else if (1 == count) { |
206 | 0 | // index |
207 | 0 | indexFrame = childFrame; |
208 | 0 | indexSize = childDesiredSize; |
209 | 0 | bmIndex = childDesiredSize.mBoundingMetrics; |
210 | 0 | } |
211 | 0 | count++; |
212 | 0 | childFrame = childFrame->GetNextSibling(); |
213 | 0 | } |
214 | 0 | if (2 != count) { |
215 | 0 | // report an error, encourage people to get their markups in order |
216 | 0 | ReportChildCountError(); |
217 | 0 | ReflowError(drawTarget, aDesiredSize); |
218 | 0 | NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); |
219 | 0 | // Call DidReflow() for the child frames we successfully did reflow. |
220 | 0 | DidReflowChildren(mFrames.FirstChild(), childFrame); |
221 | 0 | return; |
222 | 0 | } |
223 | 0 |
|
224 | 0 | //////////// |
225 | 0 | // Prepare the radical symbol and the overline bar |
226 | 0 |
|
227 | 0 | float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this); |
228 | 0 | RefPtr<nsFontMetrics> fm = |
229 | 0 | nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation); |
230 | 0 |
|
231 | 0 | nscoord ruleThickness, leading, psi; |
232 | 0 | GetRadicalParameters(fm, StyleFont()->mMathDisplay == |
233 | 0 | NS_MATHML_DISPLAYSTYLE_BLOCK, |
234 | 0 | ruleThickness, leading, psi); |
235 | 0 |
|
236 | 0 | // built-in: adjust clearance psi to emulate \mathstrut using '1' (TexBook, p.131) |
237 | 0 | char16_t one = '1'; |
238 | 0 | nsBoundingMetrics bmOne = |
239 | 0 | nsLayoutUtils::AppUnitBoundsOfString(&one, 1, *fm, drawTarget); |
240 | 0 | if (bmOne.ascent > bmBase.ascent) |
241 | 0 | psi += bmOne.ascent - bmBase.ascent; |
242 | 0 |
|
243 | 0 | // make sure that the rule appears on on screen |
244 | 0 | nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1); |
245 | 0 | if (ruleThickness < onePixel) { |
246 | 0 | ruleThickness = onePixel; |
247 | 0 | } |
248 | 0 |
|
249 | 0 | // adjust clearance psi to get an exact number of pixels -- this |
250 | 0 | // gives a nicer & uniform look on stacked radicals (bug 130282) |
251 | 0 | nscoord delta = psi % onePixel; |
252 | 0 | if (delta) |
253 | 0 | psi += onePixel - delta; // round up |
254 | 0 |
|
255 | 0 | // Stretch the radical symbol to the appropriate height if it is not big enough. |
256 | 0 | nsBoundingMetrics contSize = bmBase; |
257 | 0 | contSize.descent = bmBase.ascent + bmBase.descent + psi; |
258 | 0 | contSize.ascent = ruleThickness; |
259 | 0 |
|
260 | 0 | // height(radical) should be >= height(base) + psi + ruleThickness |
261 | 0 | nsBoundingMetrics radicalSize; |
262 | 0 | mSqrChar.Stretch(this, drawTarget, |
263 | 0 | fontSizeInflation, |
264 | 0 | NS_STRETCH_DIRECTION_VERTICAL, |
265 | 0 | contSize, radicalSize, |
266 | 0 | NS_STRETCH_LARGER, |
267 | 0 | StyleVisibility()->mDirection); |
268 | 0 | // radicalSize have changed at this point, and should match with |
269 | 0 | // the bounding metrics of the char |
270 | 0 | mSqrChar.GetBoundingMetrics(bmSqr); |
271 | 0 |
|
272 | 0 | // Update the desired size for the container (like msqrt, index is not yet included) |
273 | 0 | // the baseline will be that of the base. |
274 | 0 | mBoundingMetrics.ascent = bmBase.ascent + psi + ruleThickness; |
275 | 0 | mBoundingMetrics.descent = |
276 | 0 | std::max(bmBase.descent, |
277 | 0 | (bmSqr.ascent + bmSqr.descent - mBoundingMetrics.ascent)); |
278 | 0 | mBoundingMetrics.width = bmSqr.width + bmBase.width; |
279 | 0 | mBoundingMetrics.leftBearing = bmSqr.leftBearing; |
280 | 0 | mBoundingMetrics.rightBearing = bmSqr.width + |
281 | 0 | std::max(bmBase.width, bmBase.rightBearing); // take also care of the rule |
282 | 0 |
|
283 | 0 | aDesiredSize.SetBlockStartAscent(mBoundingMetrics.ascent + leading); |
284 | 0 | aDesiredSize.Height() = aDesiredSize.BlockStartAscent() + |
285 | 0 | std::max(baseSize.Height() - baseSize.BlockStartAscent(), |
286 | 0 | mBoundingMetrics.descent + ruleThickness); |
287 | 0 | aDesiredSize.Width() = mBoundingMetrics.width; |
288 | 0 |
|
289 | 0 | ///////////// |
290 | 0 | // Re-adjust the desired size to include the index. |
291 | 0 |
|
292 | 0 | // the index is raised by some fraction of the height |
293 | 0 | // of the radical, see \mroot macro in App. B, TexBook |
294 | 0 | float raiseIndexPercent = 0.6f; |
295 | 0 | gfxFont* mathFont = fm->GetThebesFontGroup()->GetFirstMathFont(); |
296 | 0 | if (mathFont) { |
297 | 0 | raiseIndexPercent = mathFont->MathTable()-> |
298 | 0 | Constant(gfxMathTable::RadicalDegreeBottomRaisePercent); |
299 | 0 | } |
300 | 0 | nscoord raiseIndexDelta = NSToCoordRound(raiseIndexPercent * |
301 | 0 | (bmSqr.ascent + bmSqr.descent)); |
302 | 0 | nscoord indexRaisedAscent = mBoundingMetrics.ascent // top of radical |
303 | 0 | - (bmSqr.ascent + bmSqr.descent) // to bottom of radical |
304 | 0 | + raiseIndexDelta + bmIndex.ascent + bmIndex.descent; // to top of raised index |
305 | 0 |
|
306 | 0 | nscoord indexClearance = 0; |
307 | 0 | if (mBoundingMetrics.ascent < indexRaisedAscent) { |
308 | 0 | indexClearance = |
309 | 0 | indexRaisedAscent - mBoundingMetrics.ascent; // excess gap introduced by a tall index |
310 | 0 | mBoundingMetrics.ascent = indexRaisedAscent; |
311 | 0 | nscoord descent = aDesiredSize.Height() - aDesiredSize.BlockStartAscent(); |
312 | 0 | aDesiredSize.SetBlockStartAscent(mBoundingMetrics.ascent + leading); |
313 | 0 | aDesiredSize.Height() = aDesiredSize.BlockStartAscent() + descent; |
314 | 0 | } |
315 | 0 |
|
316 | 0 | nscoord dxIndex, dxSqr; |
317 | 0 | GetRadicalXOffsets(bmIndex.width, bmSqr.width, fm, &dxIndex, &dxSqr); |
318 | 0 |
|
319 | 0 | mBoundingMetrics.width = dxSqr + bmSqr.width + bmBase.width; |
320 | 0 | mBoundingMetrics.leftBearing = |
321 | 0 | std::min(dxIndex + bmIndex.leftBearing, dxSqr + bmSqr.leftBearing); |
322 | 0 | mBoundingMetrics.rightBearing = dxSqr + bmSqr.width + |
323 | 0 | std::max(bmBase.width, bmBase.rightBearing); |
324 | 0 |
|
325 | 0 | aDesiredSize.Width() = mBoundingMetrics.width; |
326 | 0 | aDesiredSize.mBoundingMetrics = mBoundingMetrics; |
327 | 0 | GatherAndStoreOverflow(&aDesiredSize); |
328 | 0 |
|
329 | 0 | // place the index |
330 | 0 | nscoord dx = dxIndex; |
331 | 0 | nscoord dy = aDesiredSize.BlockStartAscent() - |
332 | 0 | (indexRaisedAscent + indexSize.BlockStartAscent() - bmIndex.ascent); |
333 | 0 | FinishReflowChild(indexFrame, aPresContext, indexSize, nullptr, |
334 | 0 | MirrorIfRTL(aDesiredSize.Width(), indexSize.Width(), dx), |
335 | 0 | dy, 0); |
336 | 0 |
|
337 | 0 | // place the radical symbol and the radical bar |
338 | 0 | dx = dxSqr; |
339 | 0 | dy = indexClearance + leading; // leave a leading at the top |
340 | 0 | mSqrChar.SetRect(nsRect(MirrorIfRTL(aDesiredSize.Width(), bmSqr.width, dx), |
341 | 0 | dy, bmSqr.width, bmSqr.ascent + bmSqr.descent)); |
342 | 0 | dx += bmSqr.width; |
343 | 0 | mBarRect.SetRect(MirrorIfRTL(aDesiredSize.Width(), bmBase.width, dx), |
344 | 0 | dy, bmBase.width, ruleThickness); |
345 | 0 |
|
346 | 0 | // place the base |
347 | 0 | dy = aDesiredSize.BlockStartAscent() - baseSize.BlockStartAscent(); |
348 | 0 | FinishReflowChild(baseFrame, aPresContext, baseSize, nullptr, |
349 | 0 | MirrorIfRTL(aDesiredSize.Width(), baseSize.Width(), dx), |
350 | 0 | dy, 0); |
351 | 0 |
|
352 | 0 | mReference.x = 0; |
353 | 0 | mReference.y = aDesiredSize.BlockStartAscent(); |
354 | 0 |
|
355 | 0 | NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); |
356 | 0 | } |
357 | | |
358 | | /* virtual */ void |
359 | | nsMathMLmrootFrame::GetIntrinsicISizeMetrics(gfxContext* aRenderingContext, ReflowOutput& aDesiredSize) |
360 | 0 | { |
361 | 0 | nsIFrame* baseFrame = mFrames.FirstChild(); |
362 | 0 | nsIFrame* indexFrame = nullptr; |
363 | 0 | if (baseFrame) |
364 | 0 | indexFrame = baseFrame->GetNextSibling(); |
365 | 0 | if (!indexFrame || indexFrame->GetNextSibling()) { |
366 | 0 | ReflowError(aRenderingContext->GetDrawTarget(), aDesiredSize); |
367 | 0 | return; |
368 | 0 | } |
369 | 0 | |
370 | 0 | float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this); |
371 | 0 | nscoord baseWidth = |
372 | 0 | nsLayoutUtils::IntrinsicForContainer(aRenderingContext, baseFrame, |
373 | 0 | nsLayoutUtils::PREF_ISIZE); |
374 | 0 | nscoord indexWidth = |
375 | 0 | nsLayoutUtils::IntrinsicForContainer(aRenderingContext, indexFrame, |
376 | 0 | nsLayoutUtils::PREF_ISIZE); |
377 | 0 | nscoord sqrWidth = mSqrChar.GetMaxWidth(this, |
378 | 0 | aRenderingContext->GetDrawTarget(), |
379 | 0 | fontSizeInflation); |
380 | 0 |
|
381 | 0 | nscoord dxSqr; |
382 | 0 | RefPtr<nsFontMetrics> fm = |
383 | 0 | nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation); |
384 | 0 | GetRadicalXOffsets(indexWidth, sqrWidth, fm, nullptr, &dxSqr); |
385 | 0 |
|
386 | 0 | nscoord width = dxSqr + sqrWidth + baseWidth; |
387 | 0 |
|
388 | 0 | aDesiredSize.Width() = width; |
389 | 0 | aDesiredSize.mBoundingMetrics.width = width; |
390 | 0 | aDesiredSize.mBoundingMetrics.leftBearing = 0; |
391 | 0 | aDesiredSize.mBoundingMetrics.rightBearing = width; |
392 | 0 | } |
393 | | |
394 | | // ---------------------- |
395 | | // the Style System will use these to pass the proper ComputedStyle to our |
396 | | // MathMLChar |
397 | | ComputedStyle* |
398 | | nsMathMLmrootFrame::GetAdditionalComputedStyle(int32_t aIndex) const |
399 | 0 | { |
400 | 0 | switch (aIndex) { |
401 | 0 | case NS_SQR_CHAR_STYLE_CONTEXT_INDEX: |
402 | 0 | return mSqrChar.GetComputedStyle(); |
403 | 0 | default: |
404 | 0 | return nullptr; |
405 | 0 | } |
406 | 0 | } |
407 | | |
408 | | void |
409 | | nsMathMLmrootFrame::SetAdditionalComputedStyle(int32_t aIndex, |
410 | | ComputedStyle* aComputedStyle) |
411 | 0 | { |
412 | 0 | switch (aIndex) { |
413 | 0 | case NS_SQR_CHAR_STYLE_CONTEXT_INDEX: |
414 | 0 | mSqrChar.SetComputedStyle(aComputedStyle); |
415 | 0 | break; |
416 | 0 | } |
417 | 0 | } |