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 "nsLayoutUtils.h"
13 #include "nsNameSpaceManager.h"
14 #include "nsMathMLChar.h"
15 #include "nsCSSPseudoElements.h"
16 #include "nsMathMLElement.h"
17 #include "gfxMathTable.h"
18 
19 // used to map attributes into CSS rules
20 #include "mozilla/StyleSetHandle.h"
21 #include "mozilla/StyleSetHandleInlines.h"
22 #include "nsDisplayList.h"
23 
24 using namespace mozilla;
25 using namespace mozilla::gfx;
26 
GetMathMLFrameType()27 eMathMLFrameType nsMathMLFrame::GetMathMLFrameType() {
28   // see if it is an embellished operator (mapped to 'Op' in TeX)
29   if (mEmbellishData.coreFrame)
30     return GetMathMLFrameTypeFor(mEmbellishData.coreFrame);
31 
32   // if it has a prescribed base, fetch the type from there
33   if (mPresentationData.baseFrame)
34     return GetMathMLFrameTypeFor(mPresentationData.baseFrame);
35 
36   // everything else is treated as ordinary (mapped to 'Ord' in TeX)
37   return eMathMLFrameType_Ordinary;
38 }
39 
40 NS_IMETHODIMP
InheritAutomaticData(nsIFrame * aParent)41 nsMathMLFrame::InheritAutomaticData(nsIFrame* aParent) {
42   mEmbellishData.flags = 0;
43   mEmbellishData.coreFrame = nullptr;
44   mEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED;
45   mEmbellishData.leadingSpace = 0;
46   mEmbellishData.trailingSpace = 0;
47 
48   mPresentationData.flags = 0;
49   mPresentationData.baseFrame = nullptr;
50 
51   // by default, just inherit the display of our parent
52   nsPresentationData parentData;
53   GetPresentationDataFrom(aParent, parentData);
54 
55 #if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
56   mPresentationData.flags |= NS_MATHML_SHOW_BOUNDING_METRICS;
57 #endif
58 
59   return NS_OK;
60 }
61 
62 NS_IMETHODIMP
UpdatePresentationData(uint32_t aFlagsValues,uint32_t aWhichFlags)63 nsMathMLFrame::UpdatePresentationData(uint32_t aFlagsValues,
64                                       uint32_t aWhichFlags) {
65   NS_ASSERTION(NS_MATHML_IS_COMPRESSED(aWhichFlags) ||
66                    NS_MATHML_IS_DTLS_SET(aWhichFlags),
67                "aWhichFlags should only be compression or dtls flag");
68 
69   if (NS_MATHML_IS_COMPRESSED(aWhichFlags)) {
70     // updating the compression flag is allowed
71     if (NS_MATHML_IS_COMPRESSED(aFlagsValues)) {
72       // 'compressed' means 'prime' style in App. G, TeXbook
73       mPresentationData.flags |= NS_MATHML_COMPRESSED;
74     }
75     // no else. the flag is sticky. it retains its value once it is set
76   }
77   // These flags determine whether the dtls font feature settings should
78   // be applied.
79   if (NS_MATHML_IS_DTLS_SET(aWhichFlags)) {
80     if (NS_MATHML_IS_DTLS_SET(aFlagsValues)) {
81       mPresentationData.flags |= NS_MATHML_DTLS;
82     } else {
83       mPresentationData.flags &= ~NS_MATHML_DTLS;
84     }
85   }
86   return NS_OK;
87 }
88 
89 // Helper to give a style context suitable for doing the stretching of
90 // a MathMLChar. Frame classes that use this should ensure that the
91 // extra leaf style contexts given to the MathMLChars are accessible to
92 // the Style System via the Get/Set AdditionalStyleContext() APIs.
ResolveMathMLCharStyle(nsPresContext * aPresContext,nsIContent * aContent,nsStyleContext * aParentStyleContext,nsMathMLChar * aMathMLChar)93 /* static */ void nsMathMLFrame::ResolveMathMLCharStyle(
94     nsPresContext* aPresContext, nsIContent* aContent,
95     nsStyleContext* aParentStyleContext, nsMathMLChar* aMathMLChar) {
96   CSSPseudoElementType pseudoType =
97       CSSPseudoElementType::mozMathAnonymous;  // savings
98   RefPtr<nsStyleContext> newStyleContext;
99   newStyleContext = aPresContext->StyleSet()->ResolvePseudoElementStyle(
100       aContent->AsElement(), pseudoType, aParentStyleContext, nullptr);
101 
102   aMathMLChar->SetStyleContext(newStyleContext);
103 }
104 
GetEmbellishDataFrom(nsIFrame * aFrame,nsEmbellishData & aEmbellishData)105 /* static */ void nsMathMLFrame::GetEmbellishDataFrom(
106     nsIFrame* aFrame, nsEmbellishData& aEmbellishData) {
107   // initialize OUT params
108   aEmbellishData.flags = 0;
109   aEmbellishData.coreFrame = nullptr;
110   aEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED;
111   aEmbellishData.leadingSpace = 0;
112   aEmbellishData.trailingSpace = 0;
113 
114   if (aFrame && aFrame->IsFrameOfType(nsIFrame::eMathML)) {
115     nsIMathMLFrame* mathMLFrame = do_QueryFrame(aFrame);
116     if (mathMLFrame) {
117       mathMLFrame->GetEmbellishData(aEmbellishData);
118     }
119   }
120 }
121 
122 // helper to get the presentation data of a frame, by possibly walking up
123 // the frame hierarchy if we happen to be surrounded by non-MathML frames.
GetPresentationDataFrom(nsIFrame * aFrame,nsPresentationData & aPresentationData,bool aClimbTree)124 /* static */ void nsMathMLFrame::GetPresentationDataFrom(
125     nsIFrame* aFrame, nsPresentationData& aPresentationData, bool aClimbTree) {
126   // initialize OUT params
127   aPresentationData.flags = 0;
128   aPresentationData.baseFrame = nullptr;
129 
130   nsIFrame* frame = aFrame;
131   while (frame) {
132     if (frame->IsFrameOfType(nsIFrame::eMathML)) {
133       nsIMathMLFrame* mathMLFrame = do_QueryFrame(frame);
134       if (mathMLFrame) {
135         mathMLFrame->GetPresentationData(aPresentationData);
136         break;
137       }
138     }
139     // stop if the caller doesn't want to lookup beyond the frame
140     if (!aClimbTree) {
141       break;
142     }
143     // stop if we reach the root <math> tag
144     nsIContent* content = frame->GetContent();
145     NS_ASSERTION(content || !frame->GetParent(),  // no assert for the root
146                  "dangling frame without a content node");
147     if (!content) break;
148 
149     if (content->IsMathMLElement(nsGkAtoms::math)) {
150       break;
151     }
152     frame = frame->GetParent();
153   }
154   NS_WARNING_ASSERTION(
155       frame && frame->GetContent(),
156       "bad MathML markup - could not find the top <math> element");
157 }
158 
GetRuleThickness(DrawTarget * aDrawTarget,nsFontMetrics * aFontMetrics,nscoord & aRuleThickness)159 /* static */ void nsMathMLFrame::GetRuleThickness(DrawTarget* aDrawTarget,
160                                                   nsFontMetrics* aFontMetrics,
161                                                   nscoord& aRuleThickness) {
162   nscoord xHeight = aFontMetrics->XHeight();
163   char16_t overBar = 0x00AF;
164   nsBoundingMetrics bm = nsLayoutUtils::AppUnitBoundsOfString(
165       &overBar, 1, *aFontMetrics, aDrawTarget);
166   aRuleThickness = bm.ascent + bm.descent;
167   if (aRuleThickness <= 0 || aRuleThickness >= xHeight) {
168     // fall-back to the other version
169     GetRuleThickness(aFontMetrics, aRuleThickness);
170   }
171 }
172 
GetAxisHeight(DrawTarget * aDrawTarget,nsFontMetrics * aFontMetrics,nscoord & aAxisHeight)173 /* static */ void nsMathMLFrame::GetAxisHeight(DrawTarget* aDrawTarget,
174                                                nsFontMetrics* aFontMetrics,
175                                                nscoord& aAxisHeight) {
176   gfxFont* mathFont = aFontMetrics->GetThebesFontGroup()->GetFirstMathFont();
177   if (mathFont) {
178     aAxisHeight = mathFont->MathTable()->Constant(
179         gfxMathTable::AxisHeight, aFontMetrics->AppUnitsPerDevPixel());
180     return;
181   }
182 
183   nscoord xHeight = aFontMetrics->XHeight();
184   char16_t minus = 0x2212;  // not '-', but official Unicode minus sign
185   nsBoundingMetrics bm = nsLayoutUtils::AppUnitBoundsOfString(
186       &minus, 1, *aFontMetrics, aDrawTarget);
187   aAxisHeight = bm.ascent - (bm.ascent + bm.descent) / 2;
188   if (aAxisHeight <= 0 || aAxisHeight >= xHeight) {
189     // fall-back to the other version
190     GetAxisHeight(aFontMetrics, aAxisHeight);
191   }
192 }
193 
CalcLength(nsPresContext * aPresContext,nsStyleContext * aStyleContext,const nsCSSValue & aCSSValue,float aFontSizeInflation)194 /* static */ nscoord nsMathMLFrame::CalcLength(nsPresContext* aPresContext,
195                                                nsStyleContext* aStyleContext,
196                                                const nsCSSValue& aCSSValue,
197                                                float aFontSizeInflation) {
198   NS_ASSERTION(aCSSValue.IsLengthUnit(), "not a length unit");
199 
200   if (aCSSValue.IsPixelLengthUnit()) {
201     return aCSSValue.GetPixelLength();
202   }
203 
204   nsCSSUnit unit = aCSSValue.GetUnit();
205 
206   if (eCSSUnit_EM == unit) {
207     const nsStyleFont* font = aStyleContext->StyleFont();
208     return NSToCoordRound(aCSSValue.GetFloatValue() * (float)font->mFont.size);
209   } else if (eCSSUnit_XHeight == unit) {
210     aPresContext->SetUsesExChUnits(true);
211     RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsForStyleContext(
212         aStyleContext, aFontSizeInflation);
213     nscoord xHeight = fm->XHeight();
214     return NSToCoordRound(aCSSValue.GetFloatValue() * (float)xHeight);
215   }
216 
217   // MathML doesn't specify other CSS units such as rem or ch
218   NS_ERROR("Unsupported unit");
219   return 0;
220 }
221 
ParseNumericValue(const nsString & aString,nscoord * aLengthValue,uint32_t aFlags,nsPresContext * aPresContext,nsStyleContext * aStyleContext,float aFontSizeInflation)222 /* static */ void nsMathMLFrame::ParseNumericValue(
223     const nsString& aString, nscoord* aLengthValue, uint32_t aFlags,
224     nsPresContext* aPresContext, nsStyleContext* aStyleContext,
225     float aFontSizeInflation) {
226   nsCSSValue cssValue;
227 
228   if (!nsMathMLElement::ParseNumericValue(aString, cssValue, aFlags,
229                                           aPresContext->Document())) {
230     // Invalid attribute value. aLengthValue remains unchanged, so the default
231     // length value is used.
232     return;
233   }
234 
235   nsCSSUnit unit = cssValue.GetUnit();
236 
237   if (unit == eCSSUnit_Percent || unit == eCSSUnit_Number) {
238     // Relative units. A multiple of the default length value is used.
239     *aLengthValue = NSToCoordRound(
240         *aLengthValue * (unit == eCSSUnit_Percent ? cssValue.GetPercentValue()
241                                                   : cssValue.GetFloatValue()));
242     return;
243   }
244 
245   // Absolute units.
246   *aLengthValue =
247       CalcLength(aPresContext, aStyleContext, cssValue, aFontSizeInflation);
248 }
249 
250 #if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
251 class nsDisplayMathMLBoundingMetrics : public nsDisplayItem {
252  public:
nsDisplayMathMLBoundingMetrics(nsDisplayListBuilder * aBuilder,nsIFrame * aFrame,const nsRect & aRect)253   nsDisplayMathMLBoundingMetrics(nsDisplayListBuilder* aBuilder,
254                                  nsIFrame* aFrame, const nsRect& aRect)
255       : nsDisplayItem(aBuilder, aFrame), mRect(aRect) {
256     MOZ_COUNT_CTOR(nsDisplayMathMLBoundingMetrics);
257   }
258 #ifdef NS_BUILD_REFCNT_LOGGING
~nsDisplayMathMLBoundingMetrics()259   virtual ~nsDisplayMathMLBoundingMetrics() {
260     MOZ_COUNT_DTOR(nsDisplayMathMLBoundingMetrics);
261   }
262 #endif
263 
264   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
265   NS_DISPLAY_DECL_NAME("MathMLBoundingMetrics", TYPE_MATHML_BOUNDING_METRICS)
266  private:
267   nsRect mRect;
268 };
269 
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)270 void nsDisplayMathMLBoundingMetrics::Paint(nsDisplayListBuilder* aBuilder,
271                                            gfxContext* aCtx) {
272   DrawTarget* drawTarget = aCtx->GetDrawTarget();
273   Rect r = NSRectToRect(mRect + ToReferenceFrame(),
274                         mFrame->PresContext()->AppUnitsPerDevPixel());
275   ColorPattern blue(ToDeviceColor(Color(0.f, 0.f, 1.f, 1.f)));
276   drawTarget->StrokeRect(r, blue);
277 }
278 
DisplayBoundingMetrics(nsDisplayListBuilder * aBuilder,nsIFrame * aFrame,const nsPoint & aPt,const nsBoundingMetrics & aMetrics,const nsDisplayListSet & aLists)279 void nsMathMLFrame::DisplayBoundingMetrics(nsDisplayListBuilder* aBuilder,
280                                            nsIFrame* aFrame, const nsPoint& aPt,
281                                            const nsBoundingMetrics& aMetrics,
282                                            const nsDisplayListSet& aLists) {
283   if (!NS_MATHML_PAINT_BOUNDING_METRICS(mPresentationData.flags)) return;
284 
285   nscoord x = aPt.x + aMetrics.leftBearing;
286   nscoord y = aPt.y - aMetrics.ascent;
287   nscoord w = aMetrics.rightBearing - aMetrics.leftBearing;
288   nscoord h = aMetrics.ascent + aMetrics.descent;
289 
290   aLists.Content()->AppendToTop(MakeDisplayItem<nsDisplayMathMLBoundingMetrics>(
291       aBuilder, aFrame, nsRect(x, y, w, h)));
292 }
293 #endif
294 
295 class nsDisplayMathMLBar : public nsDisplayItem {
296  public:
nsDisplayMathMLBar(nsDisplayListBuilder * aBuilder,nsIFrame * aFrame,const nsRect & aRect,uint32_t aIndex)297   nsDisplayMathMLBar(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
298                      const nsRect& aRect, uint32_t aIndex)
299       : nsDisplayItem(aBuilder, aFrame), mRect(aRect), mIndex(aIndex) {
300     MOZ_COUNT_CTOR(nsDisplayMathMLBar);
301   }
302 #ifdef NS_BUILD_REFCNT_LOGGING
~nsDisplayMathMLBar()303   virtual ~nsDisplayMathMLBar() { MOZ_COUNT_DTOR(nsDisplayMathMLBar); }
304 #endif
305 
GetPerFrameKey() const306   virtual uint32_t GetPerFrameKey() const override {
307     return (mIndex << TYPE_BITS) | nsDisplayItem::GetPerFrameKey();
308   }
309 
310   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
311   NS_DISPLAY_DECL_NAME("MathMLBar", TYPE_MATHML_BAR)
312  private:
313   nsRect mRect;
314   uint32_t mIndex;
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()->AppendToTop(
336       MakeDisplayItem<nsDisplayMathMLBar>(aBuilder, aFrame, aRect, aIndex));
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