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 "nsMathMLFrame.h"
8 
9 #include "gfxContext.h"
10 #include "gfxUtils.h"
11 #include "mozilla/gfx/2D.h"
12 #include "nsCSSValue.h"
13 #include "nsLayoutUtils.h"
14 #include "nsNameSpaceManager.h"
15 #include "nsMathMLChar.h"
16 #include "nsCSSPseudoElements.h"
17 #include "mozilla/dom/MathMLElement.h"
18 #include "gfxMathTable.h"
19 #include "nsPresContextInlines.h"
20 
21 // used to map attributes into CSS rules
22 #include "mozilla/ServoStyleSet.h"
23 #include "nsDisplayList.h"
24 
25 using namespace mozilla;
26 using namespace mozilla::gfx;
27 
GetMathMLFrameType()28 eMathMLFrameType nsMathMLFrame::GetMathMLFrameType() {
29   // see if it is an embellished operator (mapped to 'Op' in TeX)
30   if (mEmbellishData.coreFrame)
31     return GetMathMLFrameTypeFor(mEmbellishData.coreFrame);
32 
33   // if it has a prescribed base, fetch the type from there
34   if (mPresentationData.baseFrame)
35     return GetMathMLFrameTypeFor(mPresentationData.baseFrame);
36 
37   // everything else is treated as ordinary (mapped to 'Ord' in TeX)
38   return eMathMLFrameType_Ordinary;
39 }
40 
41 NS_IMETHODIMP
InheritAutomaticData(nsIFrame * aParent)42 nsMathMLFrame::InheritAutomaticData(nsIFrame* aParent) {
43   mEmbellishData.flags = 0;
44   mEmbellishData.coreFrame = nullptr;
45   mEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED;
46   mEmbellishData.leadingSpace = 0;
47   mEmbellishData.trailingSpace = 0;
48 
49   mPresentationData.flags = 0;
50   mPresentationData.baseFrame = nullptr;
51 
52   // by default, just inherit the display of our parent
53   nsPresentationData parentData;
54   GetPresentationDataFrom(aParent, parentData);
55 
56 #if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
57   mPresentationData.flags |= NS_MATHML_SHOW_BOUNDING_METRICS;
58 #endif
59 
60   return NS_OK;
61 }
62 
63 NS_IMETHODIMP
UpdatePresentationData(uint32_t aFlagsValues,uint32_t aWhichFlags)64 nsMathMLFrame::UpdatePresentationData(uint32_t aFlagsValues,
65                                       uint32_t aWhichFlags) {
66   NS_ASSERTION(NS_MATHML_IS_COMPRESSED(aWhichFlags) ||
67                    NS_MATHML_IS_DTLS_SET(aWhichFlags),
68                "aWhichFlags should only be compression or dtls flag");
69 
70   if (NS_MATHML_IS_COMPRESSED(aWhichFlags)) {
71     // updating the compression flag is allowed
72     if (NS_MATHML_IS_COMPRESSED(aFlagsValues)) {
73       // 'compressed' means 'prime' style in App. G, TeXbook
74       mPresentationData.flags |= NS_MATHML_COMPRESSED;
75     }
76     // no else. the flag is sticky. it retains its value once it is set
77   }
78   // These flags determine whether the dtls font feature settings should
79   // be applied.
80   if (NS_MATHML_IS_DTLS_SET(aWhichFlags)) {
81     if (NS_MATHML_IS_DTLS_SET(aFlagsValues)) {
82       mPresentationData.flags |= NS_MATHML_DTLS;
83     } else {
84       mPresentationData.flags &= ~NS_MATHML_DTLS;
85     }
86   }
87   return NS_OK;
88 }
89 
90 /* static */
GetEmbellishDataFrom(nsIFrame * aFrame,nsEmbellishData & aEmbellishData)91 void nsMathMLFrame::GetEmbellishDataFrom(nsIFrame* aFrame,
92                                          nsEmbellishData& aEmbellishData) {
93   // initialize OUT params
94   aEmbellishData.flags = 0;
95   aEmbellishData.coreFrame = nullptr;
96   aEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED;
97   aEmbellishData.leadingSpace = 0;
98   aEmbellishData.trailingSpace = 0;
99 
100   if (aFrame && aFrame->IsFrameOfType(nsIFrame::eMathML)) {
101     nsIMathMLFrame* mathMLFrame = do_QueryFrame(aFrame);
102     if (mathMLFrame) {
103       mathMLFrame->GetEmbellishData(aEmbellishData);
104     }
105   }
106 }
107 
108 // helper to get the presentation data of a frame, by possibly walking up
109 // the frame hierarchy if we happen to be surrounded by non-MathML frames.
110 /* static */
GetPresentationDataFrom(nsIFrame * aFrame,nsPresentationData & aPresentationData,bool aClimbTree)111 void nsMathMLFrame::GetPresentationDataFrom(
112     nsIFrame* aFrame, nsPresentationData& aPresentationData, bool aClimbTree) {
113   // initialize OUT params
114   aPresentationData.flags = 0;
115   aPresentationData.baseFrame = nullptr;
116 
117   nsIFrame* frame = aFrame;
118   while (frame) {
119     if (frame->IsFrameOfType(nsIFrame::eMathML)) {
120       nsIMathMLFrame* mathMLFrame = do_QueryFrame(frame);
121       if (mathMLFrame) {
122         mathMLFrame->GetPresentationData(aPresentationData);
123         break;
124       }
125     }
126     // stop if the caller doesn't want to lookup beyond the frame
127     if (!aClimbTree) {
128       break;
129     }
130     // stop if we reach the root <math> tag
131     nsIContent* content = frame->GetContent();
132     NS_ASSERTION(content || !frame->GetParent(),  // no assert for the root
133                  "dangling frame without a content node");
134     if (!content) break;
135 
136     if (content->IsMathMLElement(nsGkAtoms::math)) {
137       break;
138     }
139     frame = frame->GetParent();
140   }
141   NS_WARNING_ASSERTION(
142       frame && frame->GetContent(),
143       "bad MathML markup - could not find the top <math> element");
144 }
145 
146 /* static */
GetRuleThickness(DrawTarget * aDrawTarget,nsFontMetrics * aFontMetrics,nscoord & aRuleThickness)147 void nsMathMLFrame::GetRuleThickness(DrawTarget* aDrawTarget,
148                                      nsFontMetrics* aFontMetrics,
149                                      nscoord& aRuleThickness) {
150   nscoord xHeight = aFontMetrics->XHeight();
151   char16_t overBar = 0x00AF;
152   nsBoundingMetrics bm = nsLayoutUtils::AppUnitBoundsOfString(
153       &overBar, 1, *aFontMetrics, aDrawTarget);
154   aRuleThickness = bm.ascent + bm.descent;
155   if (aRuleThickness <= 0 || aRuleThickness >= xHeight) {
156     // fall-back to the other version
157     GetRuleThickness(aFontMetrics, aRuleThickness);
158   }
159 }
160 
161 /* static */
GetAxisHeight(DrawTarget * aDrawTarget,nsFontMetrics * aFontMetrics,nscoord & aAxisHeight)162 void nsMathMLFrame::GetAxisHeight(DrawTarget* aDrawTarget,
163                                   nsFontMetrics* aFontMetrics,
164                                   nscoord& aAxisHeight) {
165   gfxFont* mathFont = aFontMetrics->GetThebesFontGroup()->GetFirstMathFont();
166   if (mathFont) {
167     aAxisHeight = mathFont->MathTable()->Constant(
168         gfxMathTable::AxisHeight, aFontMetrics->AppUnitsPerDevPixel());
169     return;
170   }
171 
172   nscoord xHeight = aFontMetrics->XHeight();
173   char16_t minus = 0x2212;  // not '-', but official Unicode minus sign
174   nsBoundingMetrics bm = nsLayoutUtils::AppUnitBoundsOfString(
175       &minus, 1, *aFontMetrics, aDrawTarget);
176   aAxisHeight = bm.ascent - (bm.ascent + bm.descent) / 2;
177   if (aAxisHeight <= 0 || aAxisHeight >= xHeight) {
178     // fall-back to the other version
179     GetAxisHeight(aFontMetrics, aAxisHeight);
180   }
181 }
182 
183 /* static */
CalcLength(nsPresContext * aPresContext,ComputedStyle * aComputedStyle,const nsCSSValue & aCSSValue,float aFontSizeInflation)184 nscoord nsMathMLFrame::CalcLength(nsPresContext* aPresContext,
185                                   ComputedStyle* aComputedStyle,
186                                   const nsCSSValue& aCSSValue,
187                                   float aFontSizeInflation) {
188   NS_ASSERTION(aCSSValue.IsLengthUnit(), "not a length unit");
189 
190   if (aCSSValue.IsPixelLengthUnit()) {
191     return aCSSValue.GetPixelLength();
192   }
193 
194   nsCSSUnit unit = aCSSValue.GetUnit();
195 
196   if (eCSSUnit_EM == unit) {
197     const nsStyleFont* font = aComputedStyle->StyleFont();
198     return font->mFont.size.ScaledBy(aCSSValue.GetFloatValue()).ToAppUnits();
199   }
200 
201   if (eCSSUnit_XHeight == unit) {
202     aPresContext->SetUsesExChUnits(true);
203     RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsForComputedStyle(
204         aComputedStyle, aPresContext, aFontSizeInflation);
205     nscoord xHeight = fm->XHeight();
206     return NSToCoordRound(aCSSValue.GetFloatValue() * (float)xHeight);
207   }
208 
209   // MathML doesn't specify other CSS units such as rem or ch
210   NS_ERROR("Unsupported unit");
211   return 0;
212 }
213 
214 /* static */
GetSubDropFromChild(nsIFrame * aChild,nscoord & aSubDrop,float aFontSizeInflation)215 void nsMathMLFrame::GetSubDropFromChild(nsIFrame* aChild, nscoord& aSubDrop,
216                                         float aFontSizeInflation) {
217   RefPtr<nsFontMetrics> fm =
218       nsLayoutUtils::GetFontMetricsForFrame(aChild, aFontSizeInflation);
219   GetSubDrop(fm, aSubDrop);
220 }
221 
222 /* static */
GetSupDropFromChild(nsIFrame * aChild,nscoord & aSupDrop,float aFontSizeInflation)223 void nsMathMLFrame::GetSupDropFromChild(nsIFrame* aChild, nscoord& aSupDrop,
224                                         float aFontSizeInflation) {
225   RefPtr<nsFontMetrics> fm =
226       nsLayoutUtils::GetFontMetricsForFrame(aChild, aFontSizeInflation);
227   GetSupDrop(fm, aSupDrop);
228 }
229 
230 /* static */
ParseNumericValue(const nsString & aString,nscoord * aLengthValue,uint32_t aFlags,nsPresContext * aPresContext,ComputedStyle * aComputedStyle,float aFontSizeInflation)231 void nsMathMLFrame::ParseNumericValue(const nsString& aString,
232                                       nscoord* aLengthValue, uint32_t aFlags,
233                                       nsPresContext* aPresContext,
234                                       ComputedStyle* aComputedStyle,
235                                       float aFontSizeInflation) {
236   nsCSSValue cssValue;
237 
238   if (!dom::MathMLElement::ParseNumericValue(aString, cssValue, aFlags,
239                                              aPresContext->Document())) {
240     // Invalid attribute value. aLengthValue remains unchanged, so the default
241     // length value is used.
242     return;
243   }
244 
245   nsCSSUnit unit = cssValue.GetUnit();
246 
247   if (unit == eCSSUnit_Percent || unit == eCSSUnit_Number) {
248     // Relative units. A multiple of the default length value is used.
249     *aLengthValue = NSToCoordRound(
250         *aLengthValue * (unit == eCSSUnit_Percent ? cssValue.GetPercentValue()
251                                                   : cssValue.GetFloatValue()));
252     return;
253   }
254 
255   // Absolute units.
256   *aLengthValue =
257       CalcLength(aPresContext, aComputedStyle, cssValue, aFontSizeInflation);
258 }
259 
260 #if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
261 class nsDisplayMathMLBoundingMetrics final : public nsDisplayItem {
262  public:
nsDisplayMathMLBoundingMetrics(nsDisplayListBuilder * aBuilder,nsIFrame * aFrame,const nsRect & aRect)263   nsDisplayMathMLBoundingMetrics(nsDisplayListBuilder* aBuilder,
264                                  nsIFrame* aFrame, const nsRect& aRect)
265       : nsDisplayItem(aBuilder, aFrame), mRect(aRect) {
266     MOZ_COUNT_CTOR(nsDisplayMathMLBoundingMetrics);
267   }
268   MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayMathMLBoundingMetrics)
269 
270   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
271   NS_DISPLAY_DECL_NAME("MathMLBoundingMetrics", TYPE_MATHML_BOUNDING_METRICS)
272  private:
273   nsRect mRect;
274 };
275 
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)276 void nsDisplayMathMLBoundingMetrics::Paint(nsDisplayListBuilder* aBuilder,
277                                            gfxContext* aCtx) {
278   DrawTarget* drawTarget = aCtx->GetDrawTarget();
279   Rect r = NSRectToRect(mRect + ToReferenceFrame(),
280                         mFrame->PresContext()->AppUnitsPerDevPixel());
281   ColorPattern blue(ToDeviceColor(Color(0.f, 0.f, 1.f, 1.f)));
282   drawTarget->StrokeRect(r, blue);
283 }
284 
DisplayBoundingMetrics(nsDisplayListBuilder * aBuilder,nsIFrame * aFrame,const nsPoint & aPt,const nsBoundingMetrics & aMetrics,const nsDisplayListSet & aLists)285 void nsMathMLFrame::DisplayBoundingMetrics(nsDisplayListBuilder* aBuilder,
286                                            nsIFrame* aFrame, const nsPoint& aPt,
287                                            const nsBoundingMetrics& aMetrics,
288                                            const nsDisplayListSet& aLists) {
289   if (!NS_MATHML_PAINT_BOUNDING_METRICS(mPresentationData.flags)) return;
290 
291   nscoord x = aPt.x + aMetrics.leftBearing;
292   nscoord y = aPt.y - aMetrics.ascent;
293   nscoord w = aMetrics.rightBearing - aMetrics.leftBearing;
294   nscoord h = aMetrics.ascent + aMetrics.descent;
295 
296   aLists.Content()->AppendNewToTop<nsDisplayMathMLBoundingMetrics>(
297       aBuilder, aFrame, nsRect(x, y, w, h));
298 }
299 #endif
300 
301 class nsDisplayMathMLBar final : public nsPaintedDisplayItem {
302  public:
nsDisplayMathMLBar(nsDisplayListBuilder * aBuilder,nsIFrame * aFrame,const nsRect & aRect)303   nsDisplayMathMLBar(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
304                      const nsRect& aRect)
305       : nsPaintedDisplayItem(aBuilder, aFrame), mRect(aRect) {
306     MOZ_COUNT_CTOR(nsDisplayMathMLBar);
307   }
308   MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayMathMLBar)
309 
310   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
311   NS_DISPLAY_DECL_NAME("MathMLBar", TYPE_MATHML_BAR)
312 
313  private:
314   nsRect mRect;
315 };
316 
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)317 void nsDisplayMathMLBar::Paint(nsDisplayListBuilder* aBuilder,
318                                gfxContext* aCtx) {
319   // paint the bar with the current text color
320   DrawTarget* drawTarget = aCtx->GetDrawTarget();
321   Rect rect = NSRectToNonEmptySnappedRect(
322       mRect + ToReferenceFrame(), mFrame->PresContext()->AppUnitsPerDevPixel(),
323       *drawTarget);
324   ColorPattern color(ToDeviceColor(
325       mFrame->GetVisitedDependentColor(&nsStyleText::mWebkitTextFillColor)));
326   drawTarget->FillRect(rect, color);
327 }
328 
DisplayBar(nsDisplayListBuilder * aBuilder,nsIFrame * aFrame,const nsRect & aRect,const nsDisplayListSet & aLists,uint32_t aIndex)329 void nsMathMLFrame::DisplayBar(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
330                                const nsRect& aRect,
331                                const nsDisplayListSet& aLists,
332                                uint32_t aIndex) {
333   if (!aFrame->StyleVisibility()->IsVisible() || aRect.IsEmpty()) return;
334 
335   aLists.Content()->AppendNewToTopWithIndex<nsDisplayMathMLBar>(
336       aBuilder, aFrame, aIndex, aRect);
337 }
338 
GetRadicalParameters(nsFontMetrics * aFontMetrics,bool aDisplayStyle,nscoord & aRadicalRuleThickness,nscoord & aRadicalExtraAscender,nscoord & aRadicalVerticalGap)339 void nsMathMLFrame::GetRadicalParameters(nsFontMetrics* aFontMetrics,
340                                          bool aDisplayStyle,
341                                          nscoord& aRadicalRuleThickness,
342                                          nscoord& aRadicalExtraAscender,
343                                          nscoord& aRadicalVerticalGap) {
344   nscoord oneDevPixel = aFontMetrics->AppUnitsPerDevPixel();
345   gfxFont* mathFont = aFontMetrics->GetThebesFontGroup()->GetFirstMathFont();
346 
347   // get the radical rulethickness
348   if (mathFont) {
349     aRadicalRuleThickness = mathFont->MathTable()->Constant(
350         gfxMathTable::RadicalRuleThickness, oneDevPixel);
351   } else {
352     GetRuleThickness(aFontMetrics, aRadicalRuleThickness);
353   }
354 
355   // get the leading to be left at the top of the resulting frame
356   if (mathFont) {
357     aRadicalExtraAscender = mathFont->MathTable()->Constant(
358         gfxMathTable::RadicalExtraAscender, oneDevPixel);
359   } else {
360     // This seems more reliable than using aFontMetrics->GetLeading() on
361     // suspicious fonts.
362     nscoord em;
363     GetEmHeight(aFontMetrics, em);
364     aRadicalExtraAscender = nscoord(0.2f * em);
365   }
366 
367   // get the clearance between rule and content
368   if (mathFont) {
369     aRadicalVerticalGap = mathFont->MathTable()->Constant(
370         aDisplayStyle ? gfxMathTable::RadicalDisplayStyleVerticalGap
371                       : gfxMathTable::RadicalVerticalGap,
372         oneDevPixel);
373   } else {
374     // Rule 11, App. G, TeXbook
375     aRadicalVerticalGap =
376         aRadicalRuleThickness +
377         (aDisplayStyle ? aFontMetrics->XHeight() : aRadicalRuleThickness) / 4;
378   }
379 }
380