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 "nsMathMLmencloseFrame.h"
8 
9 #include "gfx2DGlue.h"
10 #include "gfxUtils.h"
11 #include "mozilla/gfx/2D.h"
12 #include "mozilla/gfx/PathHelpers.h"
13 #include "nsPresContext.h"
14 #include "nsWhitespaceTokenizer.h"
15 
16 #include "nsDisplayList.h"
17 #include "gfxContext.h"
18 #include "nsMathMLChar.h"
19 #include <algorithm>
20 
21 using namespace mozilla;
22 using namespace mozilla::gfx;
23 
24 //
25 // <menclose> -- enclose content with a stretching symbol such
26 // as a long division sign. - implementation
27 
28 // longdiv:
29 // Unicode 5.1 assigns U+27CC to LONG DIVISION, but a right parenthesis
30 // renders better with current font support.
31 static const char16_t kLongDivChar = ')';
32 
33 // radical: 'SQUARE ROOT'
34 static const char16_t kRadicalChar = 0x221A;
35 
36 // updiagonalstrike
37 static const uint8_t kArrowHeadSize = 10;
38 
39 // phasorangle
40 static const uint8_t kPhasorangleWidth = 8;
41 
NS_NewMathMLmencloseFrame(nsIPresShell * aPresShell,nsStyleContext * aContext)42 nsIFrame* NS_NewMathMLmencloseFrame(nsIPresShell* aPresShell,
43                                     nsStyleContext* aContext) {
44   return new (aPresShell) nsMathMLmencloseFrame(aContext);
45 }
46 
NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmencloseFrame)47 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmencloseFrame)
48 
49 nsMathMLmencloseFrame::nsMathMLmencloseFrame(nsStyleContext* aContext,
50                                              ClassID aID)
51     : nsMathMLContainerFrame(aContext, aID),
52       mRuleThickness(0),
53       mRadicalRuleThickness(0),
54       mLongDivCharIndex(-1),
55       mRadicalCharIndex(-1),
56       mContentWidth(0) {}
57 
~nsMathMLmencloseFrame()58 nsMathMLmencloseFrame::~nsMathMLmencloseFrame() {}
59 
AllocateMathMLChar(nsMencloseNotation mask)60 nsresult nsMathMLmencloseFrame::AllocateMathMLChar(nsMencloseNotation mask) {
61   // Is the char already allocated?
62   if ((mask == NOTATION_LONGDIV && mLongDivCharIndex >= 0) ||
63       (mask == NOTATION_RADICAL && mRadicalCharIndex >= 0))
64     return NS_OK;
65 
66   // No need to track the style context given to our MathML chars.
67   // The Style System will use Get/SetAdditionalStyleContext() to keep it
68   // up-to-date if dynamic changes arise.
69   uint32_t i = mMathMLChar.Length();
70   nsAutoString Char;
71 
72   if (!mMathMLChar.AppendElement()) return NS_ERROR_OUT_OF_MEMORY;
73 
74   if (mask == NOTATION_LONGDIV) {
75     Char.Assign(kLongDivChar);
76     mLongDivCharIndex = i;
77   } else if (mask == NOTATION_RADICAL) {
78     Char.Assign(kRadicalChar);
79     mRadicalCharIndex = i;
80   }
81 
82   nsPresContext* presContext = PresContext();
83   mMathMLChar[i].SetData(Char);
84   ResolveMathMLCharStyle(presContext, mContent, mStyleContext, &mMathMLChar[i]);
85 
86   return NS_OK;
87 }
88 
89 /*
90  * Add a notation to draw, if the argument is the name of a known notation.
91  * @param aNotation string name of a notation
92  */
AddNotation(const nsAString & aNotation)93 nsresult nsMathMLmencloseFrame::AddNotation(const nsAString& aNotation) {
94   nsresult rv;
95 
96   if (aNotation.EqualsLiteral("longdiv")) {
97     rv = AllocateMathMLChar(NOTATION_LONGDIV);
98     NS_ENSURE_SUCCESS(rv, rv);
99     mNotationsToDraw += NOTATION_LONGDIV;
100   } else if (aNotation.EqualsLiteral("actuarial")) {
101     mNotationsToDraw += NOTATION_RIGHT;
102     mNotationsToDraw += NOTATION_TOP;
103   } else if (aNotation.EqualsLiteral("radical")) {
104     rv = AllocateMathMLChar(NOTATION_RADICAL);
105     NS_ENSURE_SUCCESS(rv, rv);
106     mNotationsToDraw += NOTATION_RADICAL;
107   } else if (aNotation.EqualsLiteral("box")) {
108     mNotationsToDraw += NOTATION_LEFT;
109     mNotationsToDraw += NOTATION_RIGHT;
110     mNotationsToDraw += NOTATION_TOP;
111     mNotationsToDraw += NOTATION_BOTTOM;
112   } else if (aNotation.EqualsLiteral("roundedbox")) {
113     mNotationsToDraw += NOTATION_ROUNDEDBOX;
114   } else if (aNotation.EqualsLiteral("circle")) {
115     mNotationsToDraw += NOTATION_CIRCLE;
116   } else if (aNotation.EqualsLiteral("left")) {
117     mNotationsToDraw += NOTATION_LEFT;
118   } else if (aNotation.EqualsLiteral("right")) {
119     mNotationsToDraw += NOTATION_RIGHT;
120   } else if (aNotation.EqualsLiteral("top")) {
121     mNotationsToDraw += NOTATION_TOP;
122   } else if (aNotation.EqualsLiteral("bottom")) {
123     mNotationsToDraw += NOTATION_BOTTOM;
124   } else if (aNotation.EqualsLiteral("updiagonalstrike")) {
125     mNotationsToDraw += NOTATION_UPDIAGONALSTRIKE;
126   } else if (aNotation.EqualsLiteral("updiagonalarrow")) {
127     mNotationsToDraw += NOTATION_UPDIAGONALARROW;
128   } else if (aNotation.EqualsLiteral("downdiagonalstrike")) {
129     mNotationsToDraw += NOTATION_DOWNDIAGONALSTRIKE;
130   } else if (aNotation.EqualsLiteral("verticalstrike")) {
131     mNotationsToDraw += NOTATION_VERTICALSTRIKE;
132   } else if (aNotation.EqualsLiteral("horizontalstrike")) {
133     mNotationsToDraw += NOTATION_HORIZONTALSTRIKE;
134   } else if (aNotation.EqualsLiteral("madruwb")) {
135     mNotationsToDraw += NOTATION_RIGHT;
136     mNotationsToDraw += NOTATION_BOTTOM;
137   } else if (aNotation.EqualsLiteral("phasorangle")) {
138     mNotationsToDraw += NOTATION_BOTTOM;
139     mNotationsToDraw += NOTATION_PHASORANGLE;
140   }
141 
142   return NS_OK;
143 }
144 
145 /*
146  * Initialize the list of notations to draw
147  */
InitNotations()148 void nsMathMLmencloseFrame::InitNotations() {
149   MarkNeedsDisplayItemRebuild();
150   mNotationsToDraw.clear();
151   mLongDivCharIndex = mRadicalCharIndex = -1;
152   mMathMLChar.Clear();
153 
154   nsAutoString value;
155 
156   if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::notation_,
157                                      value)) {
158     // parse the notation attribute
159     nsWhitespaceTokenizer tokenizer(value);
160 
161     while (tokenizer.hasMoreTokens()) AddNotation(tokenizer.nextToken());
162 
163     if (IsToDraw(NOTATION_UPDIAGONALARROW)) {
164       // For <menclose notation="updiagonalstrike updiagonalarrow">, if
165       // the two notations are drawn then the strike line may cause the point of
166       // the arrow to be too wide. Hence we will only draw the updiagonalarrow
167       // and the arrow shaft may be thought to be the updiagonalstrike.
168       mNotationsToDraw -= NOTATION_UPDIAGONALSTRIKE;
169     }
170   } else {
171     // default: longdiv
172     if (NS_FAILED(AllocateMathMLChar(NOTATION_LONGDIV))) return;
173     mNotationsToDraw += NOTATION_LONGDIV;
174   }
175 }
176 
177 NS_IMETHODIMP
InheritAutomaticData(nsIFrame * aParent)178 nsMathMLmencloseFrame::InheritAutomaticData(nsIFrame* aParent) {
179   // let the base class get the default from our parent
180   nsMathMLContainerFrame::InheritAutomaticData(aParent);
181 
182   mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY;
183 
184   InitNotations();
185 
186   return NS_OK;
187 }
188 
189 NS_IMETHODIMP
TransmitAutomaticData()190 nsMathMLmencloseFrame::TransmitAutomaticData() {
191   if (IsToDraw(NOTATION_RADICAL)) {
192     // The TeXBook (Ch 17. p.141) says that \sqrt is cramped
193     UpdatePresentationDataFromChildAt(0, -1, NS_MATHML_COMPRESSED,
194                                       NS_MATHML_COMPRESSED);
195   }
196 
197   return NS_OK;
198 }
199 
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)200 void nsMathMLmencloseFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
201                                              const nsDisplayListSet& aLists) {
202   /////////////
203   // paint the menclosed content
204   nsMathMLContainerFrame::BuildDisplayList(aBuilder, aLists);
205 
206   if (NS_MATHML_HAS_ERROR(mPresentationData.flags)) return;
207 
208   nsRect mencloseRect = nsIFrame::GetRect();
209   mencloseRect.x = mencloseRect.y = 0;
210 
211   if (IsToDraw(NOTATION_RADICAL)) {
212     mMathMLChar[mRadicalCharIndex].Display(aBuilder, this, aLists, 0);
213 
214     nsRect rect;
215     mMathMLChar[mRadicalCharIndex].GetRect(rect);
216     rect.MoveBy(StyleVisibility()->mDirection ? -mContentWidth : rect.width, 0);
217     rect.SizeTo(mContentWidth, mRadicalRuleThickness);
218     DisplayBar(aBuilder, this, rect, aLists, NOTATION_RADICAL);
219   }
220 
221   if (IsToDraw(NOTATION_PHASORANGLE)) {
222     DisplayNotation(aBuilder, this, mencloseRect, aLists, mRuleThickness,
223                     NOTATION_PHASORANGLE);
224   }
225 
226   if (IsToDraw(NOTATION_LONGDIV)) {
227     mMathMLChar[mLongDivCharIndex].Display(aBuilder, this, aLists, 1);
228 
229     nsRect rect;
230     mMathMLChar[mLongDivCharIndex].GetRect(rect);
231     rect.SizeTo(rect.width + mContentWidth, mRuleThickness);
232     DisplayBar(aBuilder, this, rect, aLists, NOTATION_LONGDIV);
233   }
234 
235   if (IsToDraw(NOTATION_TOP)) {
236     nsRect rect(0, 0, mencloseRect.width, mRuleThickness);
237     DisplayBar(aBuilder, this, rect, aLists, NOTATION_TOP);
238   }
239 
240   if (IsToDraw(NOTATION_BOTTOM)) {
241     nsRect rect(0, mencloseRect.height - mRuleThickness, mencloseRect.width,
242                 mRuleThickness);
243     DisplayBar(aBuilder, this, rect, aLists, NOTATION_BOTTOM);
244   }
245 
246   if (IsToDraw(NOTATION_LEFT)) {
247     nsRect rect(0, 0, mRuleThickness, mencloseRect.height);
248     DisplayBar(aBuilder, this, rect, aLists, NOTATION_LEFT);
249   }
250 
251   if (IsToDraw(NOTATION_RIGHT)) {
252     nsRect rect(mencloseRect.width - mRuleThickness, 0, mRuleThickness,
253                 mencloseRect.height);
254     DisplayBar(aBuilder, this, rect, aLists, NOTATION_RIGHT);
255   }
256 
257   if (IsToDraw(NOTATION_ROUNDEDBOX)) {
258     DisplayNotation(aBuilder, this, mencloseRect, aLists, mRuleThickness,
259                     NOTATION_ROUNDEDBOX);
260   }
261 
262   if (IsToDraw(NOTATION_CIRCLE)) {
263     DisplayNotation(aBuilder, this, mencloseRect, aLists, mRuleThickness,
264                     NOTATION_CIRCLE);
265   }
266 
267   if (IsToDraw(NOTATION_UPDIAGONALSTRIKE)) {
268     DisplayNotation(aBuilder, this, mencloseRect, aLists, mRuleThickness,
269                     NOTATION_UPDIAGONALSTRIKE);
270   }
271 
272   if (IsToDraw(NOTATION_UPDIAGONALARROW)) {
273     DisplayNotation(aBuilder, this, mencloseRect, aLists, mRuleThickness,
274                     NOTATION_UPDIAGONALARROW);
275   }
276 
277   if (IsToDraw(NOTATION_DOWNDIAGONALSTRIKE)) {
278     DisplayNotation(aBuilder, this, mencloseRect, aLists, mRuleThickness,
279                     NOTATION_DOWNDIAGONALSTRIKE);
280   }
281 
282   if (IsToDraw(NOTATION_HORIZONTALSTRIKE)) {
283     nsRect rect(0, mencloseRect.height / 2 - mRuleThickness / 2,
284                 mencloseRect.width, mRuleThickness);
285     DisplayBar(aBuilder, this, rect, aLists, NOTATION_HORIZONTALSTRIKE);
286   }
287 
288   if (IsToDraw(NOTATION_VERTICALSTRIKE)) {
289     nsRect rect(mencloseRect.width / 2 - mRuleThickness / 2, 0, mRuleThickness,
290                 mencloseRect.height);
291     DisplayBar(aBuilder, this, rect, aLists, NOTATION_VERTICALSTRIKE);
292   }
293 }
294 
MeasureForWidth(DrawTarget * aDrawTarget,ReflowOutput & aDesiredSize)295 /* virtual */ nsresult nsMathMLmencloseFrame::MeasureForWidth(
296     DrawTarget* aDrawTarget, ReflowOutput& aDesiredSize) {
297   return PlaceInternal(aDrawTarget, false, aDesiredSize, true);
298 }
299 
Place(DrawTarget * aDrawTarget,bool aPlaceOrigin,ReflowOutput & aDesiredSize)300 /* virtual */ nsresult nsMathMLmencloseFrame::Place(
301     DrawTarget* aDrawTarget, bool aPlaceOrigin, ReflowOutput& aDesiredSize) {
302   return PlaceInternal(aDrawTarget, aPlaceOrigin, aDesiredSize, false);
303 }
304 
PlaceInternal(DrawTarget * aDrawTarget,bool aPlaceOrigin,ReflowOutput & aDesiredSize,bool aWidthOnly)305 /* virtual */ nsresult nsMathMLmencloseFrame::PlaceInternal(
306     DrawTarget* aDrawTarget, bool aPlaceOrigin, ReflowOutput& aDesiredSize,
307     bool aWidthOnly) {
308   ///////////////
309   // Measure the size of our content using the base class to format like an
310   // inferred mrow.
311   ReflowOutput baseSize(aDesiredSize.GetWritingMode());
312   nsresult rv = nsMathMLContainerFrame::Place(aDrawTarget, false, baseSize);
313 
314   if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || NS_FAILED(rv)) {
315     DidReflowChildren(PrincipalChildList().FirstChild());
316     return rv;
317   }
318 
319   nsBoundingMetrics bmBase = baseSize.mBoundingMetrics;
320   nscoord dx_left = 0, dx_right = 0;
321   nsBoundingMetrics bmLongdivChar, bmRadicalChar;
322   nscoord radicalAscent = 0, radicalDescent = 0;
323   nscoord longdivAscent = 0, longdivDescent = 0;
324   nscoord psi = 0;
325   nscoord leading = 0;
326 
327   ///////////////
328   // Thickness of bars and font metrics
329   nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
330 
331   float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
332   RefPtr<nsFontMetrics> fm =
333       nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation);
334   GetRuleThickness(aDrawTarget, fm, mRuleThickness);
335   if (mRuleThickness < onePixel) {
336     mRuleThickness = onePixel;
337   }
338 
339   char16_t one = '1';
340   nsBoundingMetrics bmOne =
341       nsLayoutUtils::AppUnitBoundsOfString(&one, 1, *fm, aDrawTarget);
342 
343   ///////////////
344   // General rules: the menclose element takes the size of the enclosed content.
345   // We add a padding when needed.
346 
347   // determine padding & psi
348   nscoord padding = 3 * mRuleThickness;
349   nscoord delta = padding % onePixel;
350   if (delta) padding += onePixel - delta;  // round up
351 
352   if (IsToDraw(NOTATION_LONGDIV) || IsToDraw(NOTATION_RADICAL)) {
353     GetRadicalParameters(
354         fm, StyleFont()->mMathDisplay == NS_MATHML_DISPLAYSTYLE_BLOCK,
355         mRadicalRuleThickness, leading, psi);
356 
357     // make sure that the rule appears on on screen
358     if (mRadicalRuleThickness < onePixel) {
359       mRadicalRuleThickness = onePixel;
360     }
361 
362     // adjust clearance psi to get an exact number of pixels -- this
363     // gives a nicer & uniform look on stacked radicals (bug 130282)
364     delta = psi % onePixel;
365     if (delta) {
366       psi += onePixel - delta;  // round up
367     }
368   }
369 
370   // Set horizontal parameters
371   if (IsToDraw(NOTATION_ROUNDEDBOX) || IsToDraw(NOTATION_TOP) ||
372       IsToDraw(NOTATION_LEFT) || IsToDraw(NOTATION_BOTTOM) ||
373       IsToDraw(NOTATION_CIRCLE))
374     dx_left = padding;
375 
376   if (IsToDraw(NOTATION_ROUNDEDBOX) || IsToDraw(NOTATION_TOP) ||
377       IsToDraw(NOTATION_RIGHT) || IsToDraw(NOTATION_BOTTOM) ||
378       IsToDraw(NOTATION_CIRCLE))
379     dx_right = padding;
380 
381   // Set vertical parameters
382   if (IsToDraw(NOTATION_RIGHT) || IsToDraw(NOTATION_LEFT) ||
383       IsToDraw(NOTATION_UPDIAGONALSTRIKE) ||
384       IsToDraw(NOTATION_UPDIAGONALARROW) ||
385       IsToDraw(NOTATION_DOWNDIAGONALSTRIKE) ||
386       IsToDraw(NOTATION_VERTICALSTRIKE) || IsToDraw(NOTATION_CIRCLE) ||
387       IsToDraw(NOTATION_ROUNDEDBOX) || IsToDraw(NOTATION_RADICAL) ||
388       IsToDraw(NOTATION_LONGDIV) || IsToDraw(NOTATION_PHASORANGLE)) {
389     // set a minimal value for the base height
390     bmBase.ascent = std::max(bmOne.ascent, bmBase.ascent);
391     bmBase.descent = std::max(0, bmBase.descent);
392   }
393 
394   mBoundingMetrics.ascent = bmBase.ascent;
395   mBoundingMetrics.descent = bmBase.descent;
396 
397   if (IsToDraw(NOTATION_ROUNDEDBOX) || IsToDraw(NOTATION_TOP) ||
398       IsToDraw(NOTATION_LEFT) || IsToDraw(NOTATION_RIGHT) ||
399       IsToDraw(NOTATION_CIRCLE))
400     mBoundingMetrics.ascent += padding;
401 
402   if (IsToDraw(NOTATION_ROUNDEDBOX) || IsToDraw(NOTATION_LEFT) ||
403       IsToDraw(NOTATION_RIGHT) || IsToDraw(NOTATION_BOTTOM) ||
404       IsToDraw(NOTATION_CIRCLE))
405     mBoundingMetrics.descent += padding;
406 
407   ///////////////
408   // phasorangle notation
409   if (IsToDraw(NOTATION_PHASORANGLE)) {
410     nscoord phasorangleWidth = kPhasorangleWidth * mRuleThickness;
411     // Update horizontal parameters
412     dx_left = std::max(dx_left, phasorangleWidth);
413   }
414 
415   ///////////////
416   // updiagonal arrow notation. We need enough space at the top right corner to
417   // draw the arrow head.
418   if (IsToDraw(NOTATION_UPDIAGONALARROW)) {
419     // This is an estimate, see nsDisplayNotation::Paint for the exact head size
420     nscoord arrowHeadSize = kArrowHeadSize * mRuleThickness;
421 
422     // We want that the arrow shaft strikes the menclose content and that the
423     // arrow head does not overlap with that content. Hence we add some space
424     // on the right. We don't add space on the top but only ensure that the
425     // ascent is large enough.
426     dx_right = std::max(dx_right, arrowHeadSize);
427     mBoundingMetrics.ascent = std::max(mBoundingMetrics.ascent, arrowHeadSize);
428   }
429 
430   ///////////////
431   // circle notation: we don't want the ellipse to overlap the enclosed
432   // content. Hence, we need to increase the size of the bounding box by a
433   // factor of at least sqrt(2).
434   if (IsToDraw(NOTATION_CIRCLE)) {
435     double ratio = (sqrt(2.0) - 1.0) / 2.0;
436     nscoord padding2;
437 
438     // Update horizontal parameters
439     padding2 = ratio * bmBase.width;
440 
441     dx_left = std::max(dx_left, padding2);
442     dx_right = std::max(dx_right, padding2);
443 
444     // Update vertical parameters
445     padding2 = ratio * (bmBase.ascent + bmBase.descent);
446 
447     mBoundingMetrics.ascent =
448         std::max(mBoundingMetrics.ascent, bmBase.ascent + padding2);
449     mBoundingMetrics.descent =
450         std::max(mBoundingMetrics.descent, bmBase.descent + padding2);
451   }
452 
453   ///////////////
454   // longdiv notation:
455   if (IsToDraw(NOTATION_LONGDIV)) {
456     if (aWidthOnly) {
457       nscoord longdiv_width = mMathMLChar[mLongDivCharIndex].GetMaxWidth(
458           this, aDrawTarget, fontSizeInflation);
459 
460       // Update horizontal parameters
461       dx_left = std::max(dx_left, longdiv_width);
462     } else {
463       // Stretch the parenthesis to the appropriate height if it is not
464       // big enough.
465       nsBoundingMetrics contSize = bmBase;
466       contSize.ascent = mRuleThickness;
467       contSize.descent = bmBase.ascent + bmBase.descent + psi;
468 
469       // height(longdiv) should be >= height(base) + psi + mRuleThickness
470       mMathMLChar[mLongDivCharIndex].Stretch(
471           this, aDrawTarget, fontSizeInflation, NS_STRETCH_DIRECTION_VERTICAL,
472           contSize, bmLongdivChar, NS_STRETCH_LARGER, false);
473       mMathMLChar[mLongDivCharIndex].GetBoundingMetrics(bmLongdivChar);
474 
475       // Update horizontal parameters
476       dx_left = std::max(dx_left, bmLongdivChar.width);
477 
478       // Update vertical parameters
479       longdivAscent = bmBase.ascent + psi + mRuleThickness;
480       longdivDescent = std::max(
481           bmBase.descent,
482           (bmLongdivChar.ascent + bmLongdivChar.descent - longdivAscent));
483 
484       mBoundingMetrics.ascent =
485           std::max(mBoundingMetrics.ascent, longdivAscent);
486       mBoundingMetrics.descent =
487           std::max(mBoundingMetrics.descent, longdivDescent);
488     }
489   }
490 
491   ///////////////
492   // radical notation:
493   if (IsToDraw(NOTATION_RADICAL)) {
494     nscoord* dx_leading = StyleVisibility()->mDirection ? &dx_right : &dx_left;
495 
496     if (aWidthOnly) {
497       nscoord radical_width = mMathMLChar[mRadicalCharIndex].GetMaxWidth(
498           this, aDrawTarget, fontSizeInflation);
499 
500       // Update horizontal parameters
501       *dx_leading = std::max(*dx_leading, radical_width);
502     } else {
503       // Stretch the radical symbol to the appropriate height if it is not
504       // big enough.
505       nsBoundingMetrics contSize = bmBase;
506       contSize.ascent = mRadicalRuleThickness;
507       contSize.descent = bmBase.ascent + bmBase.descent + psi;
508 
509       // height(radical) should be >= height(base) + psi + mRadicalRuleThickness
510       mMathMLChar[mRadicalCharIndex].Stretch(
511           this, aDrawTarget, fontSizeInflation, NS_STRETCH_DIRECTION_VERTICAL,
512           contSize, bmRadicalChar, NS_STRETCH_LARGER,
513           StyleVisibility()->mDirection);
514       mMathMLChar[mRadicalCharIndex].GetBoundingMetrics(bmRadicalChar);
515 
516       // Update horizontal parameters
517       *dx_leading = std::max(*dx_leading, bmRadicalChar.width);
518 
519       // Update vertical parameters
520       radicalAscent = bmBase.ascent + psi + mRadicalRuleThickness;
521       radicalDescent = std::max(
522           bmBase.descent,
523           (bmRadicalChar.ascent + bmRadicalChar.descent - radicalAscent));
524 
525       mBoundingMetrics.ascent =
526           std::max(mBoundingMetrics.ascent, radicalAscent);
527       mBoundingMetrics.descent =
528           std::max(mBoundingMetrics.descent, radicalDescent);
529     }
530   }
531 
532   ///////////////
533   //
534   if (IsToDraw(NOTATION_CIRCLE) || IsToDraw(NOTATION_ROUNDEDBOX) ||
535       (IsToDraw(NOTATION_LEFT) && IsToDraw(NOTATION_RIGHT))) {
536     // center the menclose around the content (horizontally)
537     dx_left = dx_right = std::max(dx_left, dx_right);
538   }
539 
540   ///////////////
541   // The maximum size is now computed: set the remaining parameters
542   mBoundingMetrics.width = dx_left + bmBase.width + dx_right;
543 
544   mBoundingMetrics.leftBearing = std::min(0, dx_left + bmBase.leftBearing);
545   mBoundingMetrics.rightBearing =
546       std::max(mBoundingMetrics.width, dx_left + bmBase.rightBearing);
547 
548   aDesiredSize.Width() = mBoundingMetrics.width;
549 
550   aDesiredSize.SetBlockStartAscent(
551       std::max(mBoundingMetrics.ascent, baseSize.BlockStartAscent()));
552   aDesiredSize.Height() =
553       aDesiredSize.BlockStartAscent() +
554       std::max(mBoundingMetrics.descent,
555                baseSize.Height() - baseSize.BlockStartAscent());
556 
557   if (IsToDraw(NOTATION_LONGDIV) || IsToDraw(NOTATION_RADICAL)) {
558     nscoord desiredSizeAscent = aDesiredSize.BlockStartAscent();
559     nscoord desiredSizeDescent =
560         aDesiredSize.Height() - aDesiredSize.BlockStartAscent();
561 
562     if (IsToDraw(NOTATION_LONGDIV)) {
563       desiredSizeAscent = std::max(desiredSizeAscent, longdivAscent + leading);
564       desiredSizeDescent =
565           std::max(desiredSizeDescent, longdivDescent + mRuleThickness);
566     }
567 
568     if (IsToDraw(NOTATION_RADICAL)) {
569       desiredSizeAscent = std::max(desiredSizeAscent, radicalAscent + leading);
570       desiredSizeDescent =
571           std::max(desiredSizeDescent, radicalDescent + mRadicalRuleThickness);
572     }
573 
574     aDesiredSize.SetBlockStartAscent(desiredSizeAscent);
575     aDesiredSize.Height() = desiredSizeAscent + desiredSizeDescent;
576   }
577 
578   if (IsToDraw(NOTATION_CIRCLE) || IsToDraw(NOTATION_ROUNDEDBOX) ||
579       (IsToDraw(NOTATION_TOP) && IsToDraw(NOTATION_BOTTOM))) {
580     // center the menclose around the content (vertically)
581     nscoord dy = std::max(aDesiredSize.BlockStartAscent() - bmBase.ascent,
582                           aDesiredSize.Height() -
583                               aDesiredSize.BlockStartAscent() - bmBase.descent);
584 
585     aDesiredSize.SetBlockStartAscent(bmBase.ascent + dy);
586     aDesiredSize.Height() =
587         aDesiredSize.BlockStartAscent() + bmBase.descent + dy;
588   }
589 
590   // Update mBoundingMetrics ascent/descent
591   if (IsToDraw(NOTATION_TOP) || IsToDraw(NOTATION_RIGHT) ||
592       IsToDraw(NOTATION_LEFT) || IsToDraw(NOTATION_UPDIAGONALSTRIKE) ||
593       IsToDraw(NOTATION_UPDIAGONALARROW) ||
594       IsToDraw(NOTATION_DOWNDIAGONALSTRIKE) ||
595       IsToDraw(NOTATION_VERTICALSTRIKE) || IsToDraw(NOTATION_CIRCLE) ||
596       IsToDraw(NOTATION_ROUNDEDBOX))
597     mBoundingMetrics.ascent = aDesiredSize.BlockStartAscent();
598 
599   if (IsToDraw(NOTATION_BOTTOM) || IsToDraw(NOTATION_RIGHT) ||
600       IsToDraw(NOTATION_LEFT) || IsToDraw(NOTATION_UPDIAGONALSTRIKE) ||
601       IsToDraw(NOTATION_UPDIAGONALARROW) ||
602       IsToDraw(NOTATION_DOWNDIAGONALSTRIKE) ||
603       IsToDraw(NOTATION_VERTICALSTRIKE) || IsToDraw(NOTATION_CIRCLE) ||
604       IsToDraw(NOTATION_ROUNDEDBOX))
605     mBoundingMetrics.descent =
606         aDesiredSize.Height() - aDesiredSize.BlockStartAscent();
607 
608   // phasorangle notation:
609   // move up from the bottom by the angled line height
610   if (IsToDraw(NOTATION_PHASORANGLE))
611     mBoundingMetrics.ascent = std::max(
612         mBoundingMetrics.ascent,
613         2 * kPhasorangleWidth * mRuleThickness - mBoundingMetrics.descent);
614 
615   aDesiredSize.mBoundingMetrics = mBoundingMetrics;
616 
617   mReference.x = 0;
618   mReference.y = aDesiredSize.BlockStartAscent();
619 
620   if (aPlaceOrigin) {
621     //////////////////
622     // Set position and size of MathMLChars
623     if (IsToDraw(NOTATION_LONGDIV))
624       mMathMLChar[mLongDivCharIndex].SetRect(nsRect(
625           dx_left - bmLongdivChar.width,
626           aDesiredSize.BlockStartAscent() - longdivAscent, bmLongdivChar.width,
627           bmLongdivChar.ascent + bmLongdivChar.descent));
628 
629     if (IsToDraw(NOTATION_RADICAL)) {
630       nscoord dx =
631           (StyleVisibility()->mDirection ? dx_left + bmBase.width
632                                          : dx_left - bmRadicalChar.width);
633 
634       mMathMLChar[mRadicalCharIndex].SetRect(nsRect(
635           dx, aDesiredSize.BlockStartAscent() - radicalAscent,
636           bmRadicalChar.width, bmRadicalChar.ascent + bmRadicalChar.descent));
637     }
638 
639     mContentWidth = bmBase.width;
640 
641     //////////////////
642     // Finish reflowing child frames
643     PositionRowChildFrames(dx_left, aDesiredSize.BlockStartAscent());
644   }
645 
646   return NS_OK;
647 }
648 
FixInterFrameSpacing(ReflowOutput & aDesiredSize)649 nscoord nsMathMLmencloseFrame::FixInterFrameSpacing(
650     ReflowOutput& aDesiredSize) {
651   nscoord gap = nsMathMLContainerFrame::FixInterFrameSpacing(aDesiredSize);
652   if (!gap) return 0;
653 
654   // Move the MathML characters
655   nsRect rect;
656   for (uint32_t i = 0; i < mMathMLChar.Length(); i++) {
657     mMathMLChar[i].GetRect(rect);
658     rect.MoveBy(gap, 0);
659     mMathMLChar[i].SetRect(rect);
660   }
661 
662   return gap;
663 }
664 
AttributeChanged(int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType)665 nsresult nsMathMLmencloseFrame::AttributeChanged(int32_t aNameSpaceID,
666                                                  nsAtom* aAttribute,
667                                                  int32_t aModType) {
668   if (aAttribute == nsGkAtoms::notation_) {
669     InitNotations();
670   }
671 
672   return nsMathMLContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
673                                                   aModType);
674 }
675 
676 //////////////////
677 // the Style System will use these to pass the proper style context to our
678 // MathMLChar
GetAdditionalStyleContext(int32_t aIndex) const679 nsStyleContext* nsMathMLmencloseFrame::GetAdditionalStyleContext(
680     int32_t aIndex) const {
681   int32_t len = mMathMLChar.Length();
682   if (aIndex >= 0 && aIndex < len)
683     return mMathMLChar[aIndex].GetStyleContext();
684   else
685     return nullptr;
686 }
687 
SetAdditionalStyleContext(int32_t aIndex,nsStyleContext * aStyleContext)688 void nsMathMLmencloseFrame::SetAdditionalStyleContext(
689     int32_t aIndex, nsStyleContext* aStyleContext) {
690   int32_t len = mMathMLChar.Length();
691   if (aIndex >= 0 && aIndex < len)
692     mMathMLChar[aIndex].SetStyleContext(aStyleContext);
693 }
694 
695 class nsDisplayNotation : public nsDisplayItem {
696  public:
nsDisplayNotation(nsDisplayListBuilder * aBuilder,nsIFrame * aFrame,const nsRect & aRect,nscoord aThickness,nsMencloseNotation aType)697   nsDisplayNotation(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
698                     const nsRect& aRect, nscoord aThickness,
699                     nsMencloseNotation aType)
700       : nsDisplayItem(aBuilder, aFrame),
701         mRect(aRect),
702         mThickness(aThickness),
703         mType(aType) {
704     MOZ_COUNT_CTOR(nsDisplayNotation);
705   }
706 #ifdef NS_BUILD_REFCNT_LOGGING
~nsDisplayNotation()707   virtual ~nsDisplayNotation() { MOZ_COUNT_DTOR(nsDisplayNotation); }
708 #endif
709 
GetPerFrameKey() const710   virtual uint32_t GetPerFrameKey() const override {
711     return (mType << TYPE_BITS) | nsDisplayItem::GetPerFrameKey();
712   }
713 
714   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
715   NS_DISPLAY_DECL_NAME("MathMLMencloseNotation", TYPE_MATHML_MENCLOSE_NOTATION)
716 
717  private:
718   nsRect mRect;
719   nscoord mThickness;
720   nsMencloseNotation mType;
721 };
722 
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)723 void nsDisplayNotation::Paint(nsDisplayListBuilder* aBuilder,
724                               gfxContext* aCtx) {
725   DrawTarget& aDrawTarget = *aCtx->GetDrawTarget();
726   nsPresContext* presContext = mFrame->PresContext();
727 
728   Float strokeWidth = presContext->AppUnitsToGfxUnits(mThickness);
729 
730   Rect rect = NSRectToRect(mRect + ToReferenceFrame(),
731                            presContext->AppUnitsPerDevPixel());
732   rect.Deflate(strokeWidth / 2.f);
733 
734   ColorPattern color(ToDeviceColor(
735       mFrame->GetVisitedDependentColor(&nsStyleText::mWebkitTextFillColor)));
736 
737   StrokeOptions strokeOptions(strokeWidth);
738 
739   switch (mType) {
740     case NOTATION_CIRCLE: {
741       RefPtr<Path> ellipse =
742           MakePathForEllipse(aDrawTarget, rect.Center(), rect.Size());
743       aDrawTarget.Stroke(ellipse, color, strokeOptions);
744       return;
745     }
746     case NOTATION_ROUNDEDBOX: {
747       Float radius = 3 * strokeWidth;
748       RectCornerRadii radii(radius, radius);
749       RefPtr<Path> roundedRect =
750           MakePathForRoundedRect(aDrawTarget, rect, radii, true);
751       aDrawTarget.Stroke(roundedRect, color, strokeOptions);
752       return;
753     }
754     case NOTATION_UPDIAGONALSTRIKE: {
755       aDrawTarget.StrokeLine(rect.BottomLeft(), rect.TopRight(), color,
756                              strokeOptions);
757       return;
758     }
759     case NOTATION_DOWNDIAGONALSTRIKE: {
760       aDrawTarget.StrokeLine(rect.TopLeft(), rect.BottomRight(), color,
761                              strokeOptions);
762       return;
763     }
764     case NOTATION_UPDIAGONALARROW: {
765       // Compute some parameters to draw the updiagonalarrow. The values below
766       // are taken from MathJax's HTML-CSS output.
767       Float W = rect.Width();
768       gfxFloat H = rect.Height();
769       Float l = sqrt(W * W + H * H);
770       Float f = Float(kArrowHeadSize) * strokeWidth / l;
771       Float w = W * f;
772       gfxFloat h = H * f;
773 
774       // Draw the arrow shaft
775       aDrawTarget.StrokeLine(rect.BottomLeft(),
776                              rect.TopRight() + Point(-.7 * w, .7 * h), color,
777                              strokeOptions);
778 
779       // Draw the arrow head
780       RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
781       builder->MoveTo(rect.TopRight());
782       builder->LineTo(
783           rect.TopRight() +
784           Point(-w - .4 * h, std::max(-strokeWidth / 2.0, h - .4 * w)));
785       builder->LineTo(rect.TopRight() + Point(-.7 * w, .7 * h));
786       builder->LineTo(
787           rect.TopRight() +
788           Point(std::min(strokeWidth / 2.0, -w + .4 * h), h + .4 * w));
789       builder->Close();
790       RefPtr<Path> path = builder->Finish();
791       aDrawTarget.Fill(path, color);
792       return;
793     }
794     case NOTATION_PHASORANGLE: {
795       // Compute some parameters to draw the angled line,
796       // that uses a slope of 2 (angle = tan^-1(2)).
797       // H = w * tan(angle) = w * 2
798       Float w = Float(kPhasorangleWidth) * strokeWidth;
799       Float H = 2 * w;
800 
801       // Draw the angled line
802       aDrawTarget.StrokeLine(rect.BottomLeft(),
803                              rect.BottomLeft() + Point(w, -H), color,
804                              strokeOptions);
805       return;
806     }
807     default:
808       NS_NOTREACHED("This notation can not be drawn using nsDisplayNotation");
809   }
810 }
811 
DisplayNotation(nsDisplayListBuilder * aBuilder,nsIFrame * aFrame,const nsRect & aRect,const nsDisplayListSet & aLists,nscoord aThickness,nsMencloseNotation aType)812 void nsMathMLmencloseFrame::DisplayNotation(nsDisplayListBuilder* aBuilder,
813                                             nsIFrame* aFrame,
814                                             const nsRect& aRect,
815                                             const nsDisplayListSet& aLists,
816                                             nscoord aThickness,
817                                             nsMencloseNotation aType) {
818   if (!aFrame->StyleVisibility()->IsVisible() || aRect.IsEmpty() ||
819       aThickness <= 0)
820     return;
821 
822   aLists.Content()->AppendToTop(MakeDisplayItem<nsDisplayNotation>(
823       aBuilder, aFrame, aRect, aThickness, aType));
824 }
825