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