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 "mozilla/dom/MathMLElement.h"
17 #include "gfxMathTable.h"
18 #include "nsPresContextInlines.h"
19 
20 // used to map attributes into CSS rules
21 #include "mozilla/ServoStyleSet.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 ComputedStyle suitable for doing the stretching of
90 // a MathMLChar. Frame classes that use this should ensure that the
91 // extra leaf ComputedStyle given to the MathMLChars are accessible to
92 // the Style System via the Get/Set AdditionalComputedStyle() APIs.
93 /* static */
ResolveMathMLCharStyle(nsPresContext * aPresContext,nsIContent * aContent,ComputedStyle * aParentComputedStyle,nsMathMLChar * aMathMLChar)94 void nsMathMLFrame::ResolveMathMLCharStyle(nsPresContext* aPresContext,
95                                            nsIContent* aContent,
96                                            ComputedStyle* aParentComputedStyle,
97                                            nsMathMLChar* aMathMLChar) {
98   PseudoStyleType pseudoType = PseudoStyleType::mozMathAnonymous;  // savings
99   RefPtr<ComputedStyle> newComputedStyle;
100   newComputedStyle = aPresContext->StyleSet()->ResolvePseudoElementStyle(
101       *aContent->AsElement(), pseudoType, aParentComputedStyle);
102 
103   aMathMLChar->SetComputedStyle(newComputedStyle);
104 }
105 
106 /* static */
GetEmbellishDataFrom(nsIFrame * aFrame,nsEmbellishData & aEmbellishData)107 void nsMathMLFrame::GetEmbellishDataFrom(nsIFrame* aFrame,
108                                          nsEmbellishData& aEmbellishData) {
109   // initialize OUT params
110   aEmbellishData.flags = 0;
111   aEmbellishData.coreFrame = nullptr;
112   aEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED;
113   aEmbellishData.leadingSpace = 0;
114   aEmbellishData.trailingSpace = 0;
115 
116   if (aFrame && aFrame->IsFrameOfType(nsIFrame::eMathML)) {
117     nsIMathMLFrame* mathMLFrame = do_QueryFrame(aFrame);
118     if (mathMLFrame) {
119       mathMLFrame->GetEmbellishData(aEmbellishData);
120     }
121   }
122 }
123 
124 // helper to get the presentation data of a frame, by possibly walking up
125 // the frame hierarchy if we happen to be surrounded by non-MathML frames.
126 /* static */
GetPresentationDataFrom(nsIFrame * aFrame,nsPresentationData & aPresentationData,bool aClimbTree)127 void nsMathMLFrame::GetPresentationDataFrom(
128     nsIFrame* aFrame, nsPresentationData& aPresentationData, bool aClimbTree) {
129   // initialize OUT params
130   aPresentationData.flags = 0;
131   aPresentationData.baseFrame = nullptr;
132 
133   nsIFrame* frame = aFrame;
134   while (frame) {
135     if (frame->IsFrameOfType(nsIFrame::eMathML)) {
136       nsIMathMLFrame* mathMLFrame = do_QueryFrame(frame);
137       if (mathMLFrame) {
138         mathMLFrame->GetPresentationData(aPresentationData);
139         break;
140       }
141     }
142     // stop if the caller doesn't want to lookup beyond the frame
143     if (!aClimbTree) {
144       break;
145     }
146     // stop if we reach the root <math> tag
147     nsIContent* content = frame->GetContent();
148     NS_ASSERTION(content || !frame->GetParent(),  // no assert for the root
149                  "dangling frame without a content node");
150     if (!content) break;
151 
152     if (content->IsMathMLElement(nsGkAtoms::math)) {
153       break;
154     }
155     frame = frame->GetParent();
156   }
157   NS_WARNING_ASSERTION(
158       frame && frame->GetContent(),
159       "bad MathML markup - could not find the top <math> element");
160 }
161 
162 /* static */
GetRuleThickness(DrawTarget * aDrawTarget,nsFontMetrics * aFontMetrics,nscoord & aRuleThickness)163 void nsMathMLFrame::GetRuleThickness(DrawTarget* aDrawTarget,
164                                      nsFontMetrics* aFontMetrics,
165                                      nscoord& aRuleThickness) {
166   nscoord xHeight = aFontMetrics->XHeight();
167   char16_t overBar = 0x00AF;
168   nsBoundingMetrics bm = nsLayoutUtils::AppUnitBoundsOfString(
169       &overBar, 1, *aFontMetrics, aDrawTarget);
170   aRuleThickness = bm.ascent + bm.descent;
171   if (aRuleThickness <= 0 || aRuleThickness >= xHeight) {
172     // fall-back to the other version
173     GetRuleThickness(aFontMetrics, aRuleThickness);
174   }
175 }
176 
177 /* static */
GetAxisHeight(DrawTarget * aDrawTarget,nsFontMetrics * aFontMetrics,nscoord & aAxisHeight)178 void nsMathMLFrame::GetAxisHeight(DrawTarget* aDrawTarget,
179                                   nsFontMetrics* aFontMetrics,
180                                   nscoord& aAxisHeight) {
181   gfxFont* mathFont = aFontMetrics->GetThebesFontGroup()->GetFirstMathFont();
182   if (mathFont) {
183     aAxisHeight = mathFont->MathTable()->Constant(
184         gfxMathTable::AxisHeight, aFontMetrics->AppUnitsPerDevPixel());
185     return;
186   }
187 
188   nscoord xHeight = aFontMetrics->XHeight();
189   char16_t minus = 0x2212;  // not '-', but official Unicode minus sign
190   nsBoundingMetrics bm = nsLayoutUtils::AppUnitBoundsOfString(
191       &minus, 1, *aFontMetrics, aDrawTarget);
192   aAxisHeight = bm.ascent - (bm.ascent + bm.descent) / 2;
193   if (aAxisHeight <= 0 || aAxisHeight >= xHeight) {
194     // fall-back to the other version
195     GetAxisHeight(aFontMetrics, aAxisHeight);
196   }
197 }
198 
199 /* static */
CalcLength(nsPresContext * aPresContext,ComputedStyle * aComputedStyle,const nsCSSValue & aCSSValue,float aFontSizeInflation)200 nscoord nsMathMLFrame::CalcLength(nsPresContext* aPresContext,
201                                   ComputedStyle* aComputedStyle,
202                                   const nsCSSValue& aCSSValue,
203                                   float aFontSizeInflation) {
204   NS_ASSERTION(aCSSValue.IsLengthUnit(), "not a length unit");
205 
206   if (aCSSValue.IsPixelLengthUnit()) {
207     return aCSSValue.GetPixelLength();
208   }
209 
210   nsCSSUnit unit = aCSSValue.GetUnit();
211 
212   if (eCSSUnit_EM == unit) {
213     const nsStyleFont* font = aComputedStyle->StyleFont();
214     return NSToCoordRound(aCSSValue.GetFloatValue() * (float)font->mFont.size);
215   } else if (eCSSUnit_XHeight == unit) {
216     aPresContext->SetUsesExChUnits(true);
217     RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsForComputedStyle(
218         aComputedStyle, aPresContext, aFontSizeInflation);
219     nscoord xHeight = fm->XHeight();
220     return NSToCoordRound(aCSSValue.GetFloatValue() * (float)xHeight);
221   }
222 
223   // MathML doesn't specify other CSS units such as rem or ch
224   NS_ERROR("Unsupported unit");
225   return 0;
226 }
227 
228 /* static */
ParseNumericValue(const nsString & aString,nscoord * aLengthValue,uint32_t aFlags,nsPresContext * aPresContext,ComputedStyle * aComputedStyle,float aFontSizeInflation)229 void nsMathMLFrame::ParseNumericValue(const nsString& aString,
230                                       nscoord* aLengthValue, uint32_t aFlags,
231                                       nsPresContext* aPresContext,
232                                       ComputedStyle* aComputedStyle,
233                                       float aFontSizeInflation) {
234   nsCSSValue cssValue;
235 
236   if (!dom::MathMLElement::ParseNumericValue(aString, cssValue, aFlags,
237                                              aPresContext->Document())) {
238     // Invalid attribute value. aLengthValue remains unchanged, so the default
239     // length value is used.
240     return;
241   }
242 
243   nsCSSUnit unit = cssValue.GetUnit();
244 
245   if (unit == eCSSUnit_Percent || unit == eCSSUnit_Number) {
246     // Relative units. A multiple of the default length value is used.
247     *aLengthValue = NSToCoordRound(
248         *aLengthValue * (unit == eCSSUnit_Percent ? cssValue.GetPercentValue()
249                                                   : cssValue.GetFloatValue()));
250     return;
251   }
252 
253   // Absolute units.
254   *aLengthValue =
255       CalcLength(aPresContext, aComputedStyle, cssValue, aFontSizeInflation);
256 }
257 
258 #if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
259 class nsDisplayMathMLBoundingMetrics final : public nsDisplayItem {
260  public:
nsDisplayMathMLBoundingMetrics(nsDisplayListBuilder * aBuilder,nsIFrame * aFrame,const nsRect & aRect)261   nsDisplayMathMLBoundingMetrics(nsDisplayListBuilder* aBuilder,
262                                  nsIFrame* aFrame, const nsRect& aRect)
263       : nsDisplayItem(aBuilder, aFrame), mRect(aRect) {
264     MOZ_COUNT_CTOR(nsDisplayMathMLBoundingMetrics);
265   }
266   MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayMathMLBoundingMetrics)
267 
268   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
269   NS_DISPLAY_DECL_NAME("MathMLBoundingMetrics", TYPE_MATHML_BOUNDING_METRICS)
270  private:
271   nsRect mRect;
272 };
273 
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)274 void nsDisplayMathMLBoundingMetrics::Paint(nsDisplayListBuilder* aBuilder,
275                                            gfxContext* aCtx) {
276   DrawTarget* drawTarget = aCtx->GetDrawTarget();
277   Rect r = NSRectToRect(mRect + ToReferenceFrame(),
278                         mFrame->PresContext()->AppUnitsPerDevPixel());
279   ColorPattern blue(ToDeviceColor(Color(0.f, 0.f, 1.f, 1.f)));
280   drawTarget->StrokeRect(r, blue);
281 }
282 
DisplayBoundingMetrics(nsDisplayListBuilder * aBuilder,nsIFrame * aFrame,const nsPoint & aPt,const nsBoundingMetrics & aMetrics,const nsDisplayListSet & aLists)283 void nsMathMLFrame::DisplayBoundingMetrics(nsDisplayListBuilder* aBuilder,
284                                            nsIFrame* aFrame, const nsPoint& aPt,
285                                            const nsBoundingMetrics& aMetrics,
286                                            const nsDisplayListSet& aLists) {
287   if (!NS_MATHML_PAINT_BOUNDING_METRICS(mPresentationData.flags)) return;
288 
289   nscoord x = aPt.x + aMetrics.leftBearing;
290   nscoord y = aPt.y - aMetrics.ascent;
291   nscoord w = aMetrics.rightBearing - aMetrics.leftBearing;
292   nscoord h = aMetrics.ascent + aMetrics.descent;
293 
294   aLists.Content()->AppendNewToTop<nsDisplayMathMLBoundingMetrics>(
295       aBuilder, aFrame, nsRect(x, y, w, h));
296 }
297 #endif
298 
299 class nsDisplayMathMLBar final : public nsPaintedDisplayItem {
300  public:
nsDisplayMathMLBar(nsDisplayListBuilder * aBuilder,nsIFrame * aFrame,const nsRect & aRect)301   nsDisplayMathMLBar(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
302                      const nsRect& aRect)
303       : nsPaintedDisplayItem(aBuilder, aFrame), mRect(aRect) {
304     MOZ_COUNT_CTOR(nsDisplayMathMLBar);
305   }
306   MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayMathMLBar)
307 
308   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
309   NS_DISPLAY_DECL_NAME("MathMLBar", TYPE_MATHML_BAR)
310 
311  private:
312   nsRect mRect;
313 };
314 
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)315 void nsDisplayMathMLBar::Paint(nsDisplayListBuilder* aBuilder,
316                                gfxContext* aCtx) {
317   // paint the bar with the current text color
318   DrawTarget* drawTarget = aCtx->GetDrawTarget();
319   Rect rect = NSRectToNonEmptySnappedRect(
320       mRect + ToReferenceFrame(), mFrame->PresContext()->AppUnitsPerDevPixel(),
321       *drawTarget);
322   ColorPattern color(ToDeviceColor(
323       mFrame->GetVisitedDependentColor(&nsStyleText::mWebkitTextFillColor)));
324   drawTarget->FillRect(rect, color);
325 }
326 
DisplayBar(nsDisplayListBuilder * aBuilder,nsIFrame * aFrame,const nsRect & aRect,const nsDisplayListSet & aLists,uint32_t aIndex)327 void nsMathMLFrame::DisplayBar(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
328                                const nsRect& aRect,
329                                const nsDisplayListSet& aLists,
330                                uint32_t aIndex) {
331   if (!aFrame->StyleVisibility()->IsVisible() || aRect.IsEmpty()) return;
332 
333   aLists.Content()->AppendNewToTopWithIndex<nsDisplayMathMLBar>(
334       aBuilder, aFrame, aIndex, aRect);
335 }
336 
GetRadicalParameters(nsFontMetrics * aFontMetrics,bool aDisplayStyle,nscoord & aRadicalRuleThickness,nscoord & aRadicalExtraAscender,nscoord & aRadicalVerticalGap)337 void nsMathMLFrame::GetRadicalParameters(nsFontMetrics* aFontMetrics,
338                                          bool aDisplayStyle,
339                                          nscoord& aRadicalRuleThickness,
340                                          nscoord& aRadicalExtraAscender,
341                                          nscoord& aRadicalVerticalGap) {
342   nscoord oneDevPixel = aFontMetrics->AppUnitsPerDevPixel();
343   gfxFont* mathFont = aFontMetrics->GetThebesFontGroup()->GetFirstMathFont();
344 
345   // get the radical rulethickness
346   if (mathFont) {
347     aRadicalRuleThickness = mathFont->MathTable()->Constant(
348         gfxMathTable::RadicalRuleThickness, oneDevPixel);
349   } else {
350     GetRuleThickness(aFontMetrics, aRadicalRuleThickness);
351   }
352 
353   // get the leading to be left at the top of the resulting frame
354   if (mathFont) {
355     aRadicalExtraAscender = mathFont->MathTable()->Constant(
356         gfxMathTable::RadicalExtraAscender, oneDevPixel);
357   } else {
358     // This seems more reliable than using aFontMetrics->GetLeading() on
359     // suspicious fonts.
360     nscoord em;
361     GetEmHeight(aFontMetrics, em);
362     aRadicalExtraAscender = nscoord(0.2f * em);
363   }
364 
365   // get the clearance between rule and content
366   if (mathFont) {
367     aRadicalVerticalGap = mathFont->MathTable()->Constant(
368         aDisplayStyle ? gfxMathTable::RadicalDisplayStyleVerticalGap
369                       : gfxMathTable::RadicalVerticalGap,
370         oneDevPixel);
371   } else {
372     // Rule 11, App. G, TeXbook
373     aRadicalVerticalGap =
374         aRadicalRuleThickness +
375         (aDisplayStyle ? aFontMetrics->XHeight() : aRadicalRuleThickness) / 4;
376   }
377 }
378