|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "nsMathMLmrootFrame.h" |
|
7 #include "nsPresContext.h" |
|
8 #include "nsRenderingContext.h" |
|
9 #include <algorithm> |
|
10 |
|
11 // |
|
12 // <msqrt> and <mroot> -- form a radical - implementation |
|
13 // |
|
14 |
|
15 //NOTE: |
|
16 // The code assumes that TeX fonts are picked. |
|
17 // There is no fall-back to draw the branches of the sqrt explicitly |
|
18 // in the case where TeX fonts are not there. In general, there are no |
|
19 // fall-back(s) in MathML when some (freely-downloadable) fonts are missing. |
|
20 // Otherwise, this will add much work and unnecessary complexity to the core |
|
21 // MathML engine. Assuming that authors have the free fonts is part of the |
|
22 // deal. We are not responsible for cases of misconfigurations out there. |
|
23 |
|
24 // additional style context to be used by our MathMLChar. |
|
25 #define NS_SQR_CHAR_STYLE_CONTEXT_INDEX 0 |
|
26 |
|
27 static const char16_t kSqrChar = char16_t(0x221A); |
|
28 |
|
29 nsIFrame* |
|
30 NS_NewMathMLmrootFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) |
|
31 { |
|
32 return new (aPresShell) nsMathMLmrootFrame(aContext); |
|
33 } |
|
34 |
|
35 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmrootFrame) |
|
36 |
|
37 nsMathMLmrootFrame::nsMathMLmrootFrame(nsStyleContext* aContext) : |
|
38 nsMathMLContainerFrame(aContext), |
|
39 mSqrChar(), |
|
40 mBarRect() |
|
41 { |
|
42 } |
|
43 |
|
44 nsMathMLmrootFrame::~nsMathMLmrootFrame() |
|
45 { |
|
46 } |
|
47 |
|
48 void |
|
49 nsMathMLmrootFrame::Init(nsIContent* aContent, |
|
50 nsIFrame* aParent, |
|
51 nsIFrame* aPrevInFlow) |
|
52 { |
|
53 nsMathMLContainerFrame::Init(aContent, aParent, aPrevInFlow); |
|
54 |
|
55 nsPresContext *presContext = PresContext(); |
|
56 |
|
57 // No need to track the style context given to our MathML char. |
|
58 // The Style System will use Get/SetAdditionalStyleContext() to keep it |
|
59 // up-to-date if dynamic changes arise. |
|
60 nsAutoString sqrChar; sqrChar.Assign(kSqrChar); |
|
61 mSqrChar.SetData(presContext, sqrChar); |
|
62 ResolveMathMLCharStyle(presContext, mContent, mStyleContext, &mSqrChar); |
|
63 } |
|
64 |
|
65 NS_IMETHODIMP |
|
66 nsMathMLmrootFrame::TransmitAutomaticData() |
|
67 { |
|
68 // 1. The REC says: |
|
69 // The <mroot> element increments scriptlevel by 2, and sets displaystyle to |
|
70 // "false", within index, but leaves both attributes unchanged within base. |
|
71 // 2. The TeXbook (Ch 17. p.141) says \sqrt is compressed |
|
72 UpdatePresentationDataFromChildAt(1, 1, |
|
73 NS_MATHML_COMPRESSED, |
|
74 NS_MATHML_COMPRESSED); |
|
75 UpdatePresentationDataFromChildAt(0, 0, |
|
76 NS_MATHML_COMPRESSED, NS_MATHML_COMPRESSED); |
|
77 |
|
78 PropagateFrameFlagFor(mFrames.LastChild(), |
|
79 NS_FRAME_MATHML_SCRIPT_DESCENDANT); |
|
80 |
|
81 return NS_OK; |
|
82 } |
|
83 |
|
84 void |
|
85 nsMathMLmrootFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, |
|
86 const nsRect& aDirtyRect, |
|
87 const nsDisplayListSet& aLists) |
|
88 { |
|
89 ///////////// |
|
90 // paint the content we are square-rooting |
|
91 nsMathMLContainerFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists); |
|
92 |
|
93 ///////////// |
|
94 // paint the sqrt symbol |
|
95 if (!NS_MATHML_HAS_ERROR(mPresentationData.flags)) { |
|
96 mSqrChar.Display(aBuilder, this, aLists, 0); |
|
97 |
|
98 DisplayBar(aBuilder, this, mBarRect, aLists); |
|
99 |
|
100 #if defined(DEBUG) && defined(SHOW_BOUNDING_BOX) |
|
101 // for visual debug |
|
102 nsRect rect; |
|
103 mSqrChar.GetRect(rect); |
|
104 nsBoundingMetrics bm; |
|
105 mSqrChar.GetBoundingMetrics(bm); |
|
106 DisplayBoundingMetrics(aBuilder, this, rect.TopLeft(), bm, aLists); |
|
107 #endif |
|
108 } |
|
109 } |
|
110 |
|
111 static void |
|
112 GetRadicalXOffsets(nscoord aIndexWidth, nscoord aSqrWidth, |
|
113 nsFontMetrics* aFontMetrics, |
|
114 nscoord* aIndexOffset, nscoord* aSqrOffset) |
|
115 { |
|
116 // The index is tucked in closer to the radical while making sure |
|
117 // that the kern does not make the index and radical collide |
|
118 nscoord dxIndex, dxSqr; |
|
119 nscoord xHeight = aFontMetrics->XHeight(); |
|
120 nscoord indexRadicalKern = NSToCoordRound(1.35f * xHeight); |
|
121 if (indexRadicalKern > aIndexWidth) { |
|
122 dxIndex = indexRadicalKern - aIndexWidth; |
|
123 dxSqr = 0; |
|
124 } |
|
125 else { |
|
126 dxIndex = 0; |
|
127 dxSqr = aIndexWidth - indexRadicalKern; |
|
128 } |
|
129 // avoid collision by leaving a minimum space between index and radical |
|
130 nscoord minimumClearance = aSqrWidth/2; |
|
131 if (dxIndex + aIndexWidth + minimumClearance > dxSqr + aSqrWidth) { |
|
132 if (aIndexWidth + minimumClearance < aSqrWidth) { |
|
133 dxIndex = aSqrWidth - (aIndexWidth + minimumClearance); |
|
134 dxSqr = 0; |
|
135 } |
|
136 else { |
|
137 dxIndex = 0; |
|
138 dxSqr = (aIndexWidth + minimumClearance) - aSqrWidth; |
|
139 } |
|
140 } |
|
141 |
|
142 if (aIndexOffset) |
|
143 *aIndexOffset = dxIndex; |
|
144 if (aSqrOffset) |
|
145 *aSqrOffset = dxSqr; |
|
146 } |
|
147 |
|
148 nsresult |
|
149 nsMathMLmrootFrame::Reflow(nsPresContext* aPresContext, |
|
150 nsHTMLReflowMetrics& aDesiredSize, |
|
151 const nsHTMLReflowState& aReflowState, |
|
152 nsReflowStatus& aStatus) |
|
153 { |
|
154 nsresult rv = NS_OK; |
|
155 nsSize availSize(aReflowState.ComputedWidth(), NS_UNCONSTRAINEDSIZE); |
|
156 nsReflowStatus childStatus; |
|
157 |
|
158 aDesiredSize.Width() = aDesiredSize.Height() = 0; |
|
159 aDesiredSize.SetTopAscent(0); |
|
160 |
|
161 nsBoundingMetrics bmSqr, bmBase, bmIndex; |
|
162 nsRenderingContext& renderingContext = *aReflowState.rendContext; |
|
163 |
|
164 ////////////////// |
|
165 // Reflow Children |
|
166 |
|
167 int32_t count = 0; |
|
168 nsIFrame* baseFrame = nullptr; |
|
169 nsIFrame* indexFrame = nullptr; |
|
170 nsHTMLReflowMetrics baseSize(aReflowState); |
|
171 nsHTMLReflowMetrics indexSize(aReflowState); |
|
172 nsIFrame* childFrame = mFrames.FirstChild(); |
|
173 while (childFrame) { |
|
174 // ask our children to compute their bounding metrics |
|
175 nsHTMLReflowMetrics childDesiredSize(aReflowState, |
|
176 aDesiredSize.mFlags |
|
177 | NS_REFLOW_CALC_BOUNDING_METRICS); |
|
178 nsHTMLReflowState childReflowState(aPresContext, aReflowState, |
|
179 childFrame, availSize); |
|
180 rv = ReflowChild(childFrame, aPresContext, |
|
181 childDesiredSize, childReflowState, childStatus); |
|
182 //NS_ASSERTION(NS_FRAME_IS_COMPLETE(childStatus), "bad status"); |
|
183 if (NS_FAILED(rv)) { |
|
184 // Call DidReflow() for the child frames we successfully did reflow. |
|
185 DidReflowChildren(mFrames.FirstChild(), childFrame); |
|
186 return rv; |
|
187 } |
|
188 if (0 == count) { |
|
189 // base |
|
190 baseFrame = childFrame; |
|
191 baseSize = childDesiredSize; |
|
192 bmBase = childDesiredSize.mBoundingMetrics; |
|
193 } |
|
194 else if (1 == count) { |
|
195 // index |
|
196 indexFrame = childFrame; |
|
197 indexSize = childDesiredSize; |
|
198 bmIndex = childDesiredSize.mBoundingMetrics; |
|
199 } |
|
200 count++; |
|
201 childFrame = childFrame->GetNextSibling(); |
|
202 } |
|
203 if (2 != count) { |
|
204 // report an error, encourage people to get their markups in order |
|
205 ReportChildCountError(); |
|
206 rv = ReflowError(renderingContext, aDesiredSize); |
|
207 aStatus = NS_FRAME_COMPLETE; |
|
208 NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); |
|
209 // Call DidReflow() for the child frames we successfully did reflow. |
|
210 DidReflowChildren(mFrames.FirstChild(), childFrame); |
|
211 return rv; |
|
212 } |
|
213 |
|
214 //////////// |
|
215 // Prepare the radical symbol and the overline bar |
|
216 |
|
217 nsRefPtr<nsFontMetrics> fm; |
|
218 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm)); |
|
219 renderingContext.SetFont(fm); |
|
220 |
|
221 // For radical glyphs from TeX fonts and some of the radical glyphs from |
|
222 // Mathematica fonts, the thickness of the overline can be obtained from the |
|
223 // ascent of the glyph. Most fonts however have radical glyphs above the |
|
224 // baseline so no assumption can be made about the meaning of the ascent. |
|
225 nscoord ruleThickness, leading, em; |
|
226 GetRuleThickness(renderingContext, fm, ruleThickness); |
|
227 |
|
228 char16_t one = '1'; |
|
229 nsBoundingMetrics bmOne = renderingContext.GetBoundingMetrics(&one, 1); |
|
230 |
|
231 // get the leading to be left at the top of the resulting frame |
|
232 // this seems more reliable than using fm->GetLeading() on suspicious fonts |
|
233 GetEmHeight(fm, em); |
|
234 leading = nscoord(0.2f * em); |
|
235 |
|
236 // Rule 11, App. G, TeXbook |
|
237 // psi = clearance between rule and content |
|
238 nscoord phi = 0, psi = 0; |
|
239 if (StyleFont()->mMathDisplay == NS_MATHML_DISPLAYSTYLE_BLOCK) |
|
240 phi = fm->XHeight(); |
|
241 else |
|
242 phi = ruleThickness; |
|
243 psi = ruleThickness + phi/4; |
|
244 |
|
245 // built-in: adjust clearance psi to emulate \mathstrut using '1' (TexBook, p.131) |
|
246 if (bmOne.ascent > bmBase.ascent) |
|
247 psi += bmOne.ascent - bmBase.ascent; |
|
248 |
|
249 // make sure that the rule appears on on screen |
|
250 nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1); |
|
251 if (ruleThickness < onePixel) { |
|
252 ruleThickness = onePixel; |
|
253 } |
|
254 |
|
255 // adjust clearance psi to get an exact number of pixels -- this |
|
256 // gives a nicer & uniform look on stacked radicals (bug 130282) |
|
257 nscoord delta = psi % onePixel; |
|
258 if (delta) |
|
259 psi += onePixel - delta; // round up |
|
260 |
|
261 // Stretch the radical symbol to the appropriate height if it is not big enough. |
|
262 nsBoundingMetrics contSize = bmBase; |
|
263 contSize.descent = bmBase.ascent + bmBase.descent + psi; |
|
264 contSize.ascent = ruleThickness; |
|
265 |
|
266 // height(radical) should be >= height(base) + psi + ruleThickness |
|
267 nsBoundingMetrics radicalSize; |
|
268 mSqrChar.Stretch(aPresContext, renderingContext, |
|
269 NS_STRETCH_DIRECTION_VERTICAL, |
|
270 contSize, radicalSize, |
|
271 NS_STRETCH_LARGER, |
|
272 StyleVisibility()->mDirection); |
|
273 // radicalSize have changed at this point, and should match with |
|
274 // the bounding metrics of the char |
|
275 mSqrChar.GetBoundingMetrics(bmSqr); |
|
276 |
|
277 // Update the desired size for the container (like msqrt, index is not yet included) |
|
278 // the baseline will be that of the base. |
|
279 mBoundingMetrics.ascent = bmBase.ascent + psi + ruleThickness; |
|
280 mBoundingMetrics.descent = |
|
281 std::max(bmBase.descent, |
|
282 (bmSqr.ascent + bmSqr.descent - mBoundingMetrics.ascent)); |
|
283 mBoundingMetrics.width = bmSqr.width + bmBase.width; |
|
284 mBoundingMetrics.leftBearing = bmSqr.leftBearing; |
|
285 mBoundingMetrics.rightBearing = bmSqr.width + |
|
286 std::max(bmBase.width, bmBase.rightBearing); // take also care of the rule |
|
287 |
|
288 aDesiredSize.SetTopAscent(mBoundingMetrics.ascent + leading); |
|
289 aDesiredSize.Height() = aDesiredSize.TopAscent() + |
|
290 std::max(baseSize.Height() - baseSize.TopAscent(), |
|
291 mBoundingMetrics.descent + ruleThickness); |
|
292 aDesiredSize.Width() = mBoundingMetrics.width; |
|
293 |
|
294 ///////////// |
|
295 // Re-adjust the desired size to include the index. |
|
296 |
|
297 // the index is raised by some fraction of the height |
|
298 // of the radical, see \mroot macro in App. B, TexBook |
|
299 nscoord raiseIndexDelta = NSToCoordRound(0.6f * (bmSqr.ascent + bmSqr.descent)); |
|
300 nscoord indexRaisedAscent = mBoundingMetrics.ascent // top of radical |
|
301 - (bmSqr.ascent + bmSqr.descent) // to bottom of radical |
|
302 + raiseIndexDelta + bmIndex.ascent + bmIndex.descent; // to top of raised index |
|
303 |
|
304 nscoord indexClearance = 0; |
|
305 if (mBoundingMetrics.ascent < indexRaisedAscent) { |
|
306 indexClearance = |
|
307 indexRaisedAscent - mBoundingMetrics.ascent; // excess gap introduced by a tall index |
|
308 mBoundingMetrics.ascent = indexRaisedAscent; |
|
309 nscoord descent = aDesiredSize.Height() - aDesiredSize.TopAscent(); |
|
310 aDesiredSize.SetTopAscent(mBoundingMetrics.ascent + leading); |
|
311 aDesiredSize.Height() = aDesiredSize.TopAscent() + descent; |
|
312 } |
|
313 |
|
314 nscoord dxIndex, dxSqr; |
|
315 GetRadicalXOffsets(bmIndex.width, bmSqr.width, fm, &dxIndex, &dxSqr); |
|
316 |
|
317 mBoundingMetrics.width = dxSqr + bmSqr.width + bmBase.width; |
|
318 mBoundingMetrics.leftBearing = |
|
319 std::min(dxIndex + bmIndex.leftBearing, dxSqr + bmSqr.leftBearing); |
|
320 mBoundingMetrics.rightBearing = dxSqr + bmSqr.width + |
|
321 std::max(bmBase.width, bmBase.rightBearing); |
|
322 |
|
323 aDesiredSize.Width() = mBoundingMetrics.width; |
|
324 aDesiredSize.mBoundingMetrics = mBoundingMetrics; |
|
325 GatherAndStoreOverflow(&aDesiredSize); |
|
326 |
|
327 // place the index |
|
328 nscoord dx = dxIndex; |
|
329 nscoord dy = aDesiredSize.TopAscent() - (indexRaisedAscent + indexSize.TopAscent() - bmIndex.ascent); |
|
330 FinishReflowChild(indexFrame, aPresContext, indexSize, nullptr, |
|
331 MirrorIfRTL(aDesiredSize.Width(), indexSize.Width(), dx), |
|
332 dy, 0); |
|
333 |
|
334 // place the radical symbol and the radical bar |
|
335 dx = dxSqr; |
|
336 dy = indexClearance + leading; // leave a leading at the top |
|
337 mSqrChar.SetRect(nsRect(MirrorIfRTL(aDesiredSize.Width(), bmSqr.width, dx), |
|
338 dy, bmSqr.width, bmSqr.ascent + bmSqr.descent)); |
|
339 dx += bmSqr.width; |
|
340 mBarRect.SetRect(MirrorIfRTL(aDesiredSize.Width(), bmBase.width, dx), |
|
341 dy, bmBase.width, ruleThickness); |
|
342 |
|
343 // place the base |
|
344 dy = aDesiredSize.TopAscent() - baseSize.TopAscent(); |
|
345 FinishReflowChild(baseFrame, aPresContext, baseSize, nullptr, |
|
346 MirrorIfRTL(aDesiredSize.Width(), baseSize.Width(), dx), |
|
347 dy, 0); |
|
348 |
|
349 mReference.x = 0; |
|
350 mReference.y = aDesiredSize.TopAscent(); |
|
351 |
|
352 aStatus = NS_FRAME_COMPLETE; |
|
353 NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); |
|
354 return NS_OK; |
|
355 } |
|
356 |
|
357 /* virtual */ void |
|
358 nsMathMLmrootFrame::GetIntrinsicWidthMetrics(nsRenderingContext* aRenderingContext, nsHTMLReflowMetrics& aDesiredSize) |
|
359 { |
|
360 nsIFrame* baseFrame = mFrames.FirstChild(); |
|
361 nsIFrame* indexFrame = nullptr; |
|
362 if (baseFrame) |
|
363 indexFrame = baseFrame->GetNextSibling(); |
|
364 if (!indexFrame || indexFrame->GetNextSibling()) { |
|
365 ReflowError(*aRenderingContext, aDesiredSize); |
|
366 return; |
|
367 } |
|
368 |
|
369 nscoord baseWidth = |
|
370 nsLayoutUtils::IntrinsicForContainer(aRenderingContext, baseFrame, |
|
371 nsLayoutUtils::PREF_WIDTH); |
|
372 nscoord indexWidth = |
|
373 nsLayoutUtils::IntrinsicForContainer(aRenderingContext, indexFrame, |
|
374 nsLayoutUtils::PREF_WIDTH); |
|
375 nscoord sqrWidth = mSqrChar.GetMaxWidth(PresContext(), *aRenderingContext); |
|
376 |
|
377 nscoord dxSqr; |
|
378 nsRefPtr<nsFontMetrics> fm; |
|
379 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm)); |
|
380 GetRadicalXOffsets(indexWidth, sqrWidth, fm, nullptr, &dxSqr); |
|
381 |
|
382 nscoord width = dxSqr + sqrWidth + baseWidth; |
|
383 |
|
384 aDesiredSize.Width() = width; |
|
385 aDesiredSize.mBoundingMetrics.width = width; |
|
386 aDesiredSize.mBoundingMetrics.leftBearing = 0; |
|
387 aDesiredSize.mBoundingMetrics.rightBearing = width; |
|
388 } |
|
389 |
|
390 // ---------------------- |
|
391 // the Style System will use these to pass the proper style context to our MathMLChar |
|
392 nsStyleContext* |
|
393 nsMathMLmrootFrame::GetAdditionalStyleContext(int32_t aIndex) const |
|
394 { |
|
395 switch (aIndex) { |
|
396 case NS_SQR_CHAR_STYLE_CONTEXT_INDEX: |
|
397 return mSqrChar.GetStyleContext(); |
|
398 break; |
|
399 default: |
|
400 return nullptr; |
|
401 } |
|
402 } |
|
403 |
|
404 void |
|
405 nsMathMLmrootFrame::SetAdditionalStyleContext(int32_t aIndex, |
|
406 nsStyleContext* aStyleContext) |
|
407 { |
|
408 switch (aIndex) { |
|
409 case NS_SQR_CHAR_STYLE_CONTEXT_INDEX: |
|
410 mSqrChar.SetStyleContext(aStyleContext); |
|
411 break; |
|
412 } |
|
413 } |