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