Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
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/. */
6 #include "nsMathMLmrootFrame.h"
7 #include "nsPresContext.h"
8 #include "nsRenderingContext.h"
9 #include <algorithm>
11 //
12 // <msqrt> and <mroot> -- form a radical - implementation
13 //
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.
24 // additional style context to be used by our MathMLChar.
25 #define NS_SQR_CHAR_STYLE_CONTEXT_INDEX 0
27 static const char16_t kSqrChar = char16_t(0x221A);
29 nsIFrame*
30 NS_NewMathMLmrootFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
31 {
32 return new (aPresShell) nsMathMLmrootFrame(aContext);
33 }
35 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmrootFrame)
37 nsMathMLmrootFrame::nsMathMLmrootFrame(nsStyleContext* aContext) :
38 nsMathMLContainerFrame(aContext),
39 mSqrChar(),
40 mBarRect()
41 {
42 }
44 nsMathMLmrootFrame::~nsMathMLmrootFrame()
45 {
46 }
48 void
49 nsMathMLmrootFrame::Init(nsIContent* aContent,
50 nsIFrame* aParent,
51 nsIFrame* aPrevInFlow)
52 {
53 nsMathMLContainerFrame::Init(aContent, aParent, aPrevInFlow);
55 nsPresContext *presContext = PresContext();
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 }
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);
78 PropagateFrameFlagFor(mFrames.LastChild(),
79 NS_FRAME_MATHML_SCRIPT_DESCENDANT);
81 return NS_OK;
82 }
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);
93 /////////////
94 // paint the sqrt symbol
95 if (!NS_MATHML_HAS_ERROR(mPresentationData.flags)) {
96 mSqrChar.Display(aBuilder, this, aLists, 0);
98 DisplayBar(aBuilder, this, mBarRect, aLists);
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 }
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 }
142 if (aIndexOffset)
143 *aIndexOffset = dxIndex;
144 if (aSqrOffset)
145 *aSqrOffset = dxSqr;
146 }
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;
158 aDesiredSize.Width() = aDesiredSize.Height() = 0;
159 aDesiredSize.SetTopAscent(0);
161 nsBoundingMetrics bmSqr, bmBase, bmIndex;
162 nsRenderingContext& renderingContext = *aReflowState.rendContext;
164 //////////////////
165 // Reflow Children
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 }
214 ////////////
215 // Prepare the radical symbol and the overline bar
217 nsRefPtr<nsFontMetrics> fm;
218 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm));
219 renderingContext.SetFont(fm);
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);
228 char16_t one = '1';
229 nsBoundingMetrics bmOne = renderingContext.GetBoundingMetrics(&one, 1);
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);
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;
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;
249 // make sure that the rule appears on on screen
250 nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
251 if (ruleThickness < onePixel) {
252 ruleThickness = onePixel;
253 }
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
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;
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);
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
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;
294 /////////////
295 // Re-adjust the desired size to include the index.
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
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 }
314 nscoord dxIndex, dxSqr;
315 GetRadicalXOffsets(bmIndex.width, bmSqr.width, fm, &dxIndex, &dxSqr);
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);
323 aDesiredSize.Width() = mBoundingMetrics.width;
324 aDesiredSize.mBoundingMetrics = mBoundingMetrics;
325 GatherAndStoreOverflow(&aDesiredSize);
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);
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);
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);
349 mReference.x = 0;
350 mReference.y = aDesiredSize.TopAscent();
352 aStatus = NS_FRAME_COMPLETE;
353 NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
354 return NS_OK;
355 }
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 }
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);
377 nscoord dxSqr;
378 nsRefPtr<nsFontMetrics> fm;
379 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm));
380 GetRadicalXOffsets(indexWidth, sqrWidth, fm, nullptr, &dxSqr);
382 nscoord width = dxSqr + sqrWidth + baseWidth;
384 aDesiredSize.Width() = width;
385 aDesiredSize.mBoundingMetrics.width = width;
386 aDesiredSize.mBoundingMetrics.leftBearing = 0;
387 aDesiredSize.mBoundingMetrics.rightBearing = width;
388 }
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 }
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 }