/src/mozilla-central/dom/svg/SVGContentUtils.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 | | // Main header first: |
8 | | // This is also necessary to ensure our definition of M_SQRT1_2 is picked up |
9 | | #include "SVGContentUtils.h" |
10 | | |
11 | | // Keep others in (case-insensitive) order: |
12 | | #include "gfx2DGlue.h" |
13 | | #include "gfxMatrix.h" |
14 | | #include "gfxPlatform.h" |
15 | | #include "mozilla/gfx/2D.h" |
16 | | #include "mozilla/dom/SVGSVGElement.h" |
17 | | #include "mozilla/RefPtr.h" |
18 | | #include "mozilla/SVGContextPaint.h" |
19 | | #include "mozilla/TextUtils.h" |
20 | | #include "nsComputedDOMStyle.h" |
21 | | #include "nsFontMetrics.h" |
22 | | #include "nsIFrame.h" |
23 | | #include "nsIScriptError.h" |
24 | | #include "nsLayoutUtils.h" |
25 | | #include "nsMathUtils.h" |
26 | | #include "SVGAnimationElement.h" |
27 | | #include "SVGAnimatedPreserveAspectRatio.h" |
28 | | #include "nsContentUtils.h" |
29 | | #include "mozilla/gfx/2D.h" |
30 | | #include "mozilla/gfx/Types.h" |
31 | | #include "mozilla/FloatingPoint.h" |
32 | | #include "mozilla/ComputedStyle.h" |
33 | | #include "nsSVGPathDataParser.h" |
34 | | #include "SVGPathData.h" |
35 | | #include "SVGPathElement.h" |
36 | | |
37 | | using namespace mozilla; |
38 | | using namespace mozilla::dom; |
39 | | using namespace mozilla::dom::SVGPreserveAspectRatio_Binding; |
40 | | using namespace mozilla::gfx; |
41 | | |
42 | | SVGSVGElement* |
43 | | SVGContentUtils::GetOuterSVGElement(nsSVGElement *aSVGElement) |
44 | 0 | { |
45 | 0 | Element* element = nullptr; |
46 | 0 | Element* ancestor = aSVGElement->GetParentElementCrossingShadowRoot(); |
47 | 0 |
|
48 | 0 | while (ancestor && ancestor->IsSVGElement() && |
49 | 0 | !ancestor->IsSVGElement(nsGkAtoms::foreignObject)) { |
50 | 0 | element = ancestor; |
51 | 0 | ancestor = element->GetParentElementCrossingShadowRoot(); |
52 | 0 | } |
53 | 0 |
|
54 | 0 | if (element && element->IsSVGElement(nsGkAtoms::svg)) { |
55 | 0 | return static_cast<SVGSVGElement*>(element); |
56 | 0 | } |
57 | 0 | return nullptr; |
58 | 0 | } |
59 | | |
60 | | void |
61 | | SVGContentUtils::ActivateByHyperlink(nsIContent *aContent) |
62 | 0 | { |
63 | 0 | MOZ_ASSERT(aContent->IsNodeOfType(nsINode::eANIMATION), |
64 | 0 | "Expecting an animation element"); |
65 | 0 |
|
66 | 0 | static_cast<SVGAnimationElement*>(aContent)->ActivateByHyperlink(); |
67 | 0 | } |
68 | | |
69 | | enum DashState { |
70 | | eDashedStroke, |
71 | | eContinuousStroke, //< all dashes, no gaps |
72 | | eNoStroke //< all gaps, no dashes |
73 | | }; |
74 | | |
75 | | static DashState |
76 | | GetStrokeDashData(SVGContentUtils::AutoStrokeOptions* aStrokeOptions, |
77 | | nsSVGElement* aElement, |
78 | | const nsStyleSVG* aStyleSVG, |
79 | | SVGContextPaint* aContextPaint) |
80 | 0 | { |
81 | 0 | size_t dashArrayLength; |
82 | 0 | Float totalLengthOfDashes = 0.0, totalLengthOfGaps = 0.0; |
83 | 0 | Float pathScale = 1.0; |
84 | 0 |
|
85 | 0 | if (aContextPaint && aStyleSVG->StrokeDasharrayFromObject()) { |
86 | 0 | const FallibleTArray<Float>& dashSrc = aContextPaint->GetStrokeDashArray(); |
87 | 0 | dashArrayLength = dashSrc.Length(); |
88 | 0 | if (dashArrayLength <= 0) { |
89 | 0 | return eContinuousStroke; |
90 | 0 | } |
91 | 0 | Float* dashPattern = aStrokeOptions->InitDashPattern(dashArrayLength); |
92 | 0 | if (!dashPattern) { |
93 | 0 | return eContinuousStroke; |
94 | 0 | } |
95 | 0 | for (size_t i = 0; i < dashArrayLength; i++) { |
96 | 0 | if (dashSrc[i] < 0.0) { |
97 | 0 | return eContinuousStroke; // invalid |
98 | 0 | } |
99 | 0 | dashPattern[i] = Float(dashSrc[i]); |
100 | 0 | (i % 2 ? totalLengthOfGaps : totalLengthOfDashes) += dashSrc[i]; |
101 | 0 | } |
102 | 0 | } else { |
103 | 0 | const nsTArray<nsStyleCoord>& dasharray = aStyleSVG->mStrokeDasharray; |
104 | 0 | dashArrayLength = aStyleSVG->mStrokeDasharray.Length(); |
105 | 0 | if (dashArrayLength <= 0) { |
106 | 0 | return eContinuousStroke; |
107 | 0 | } |
108 | 0 | if (aElement->IsNodeOfType(nsINode::eSHAPE)) { |
109 | 0 | pathScale = static_cast<SVGGeometryElement*>(aElement)-> |
110 | 0 | GetPathLengthScale(SVGGeometryElement::eForStroking); |
111 | 0 | if (pathScale <= 0) { |
112 | 0 | return eContinuousStroke; |
113 | 0 | } |
114 | 0 | } |
115 | 0 | Float* dashPattern = aStrokeOptions->InitDashPattern(dashArrayLength); |
116 | 0 | if (!dashPattern) { |
117 | 0 | return eContinuousStroke; |
118 | 0 | } |
119 | 0 | for (uint32_t i = 0; i < dashArrayLength; i++) { |
120 | 0 | Float dashLength = |
121 | 0 | SVGContentUtils::CoordToFloat(aElement, dasharray[i]) * pathScale; |
122 | 0 | if (dashLength < 0.0) { |
123 | 0 | return eContinuousStroke; // invalid |
124 | 0 | } |
125 | 0 | dashPattern[i] = dashLength; |
126 | 0 | (i % 2 ? totalLengthOfGaps : totalLengthOfDashes) += dashLength; |
127 | 0 | } |
128 | 0 | } |
129 | 0 |
|
130 | 0 | // Now that aStrokeOptions.mDashPattern is fully initialized (we didn't |
131 | 0 | // return early above) we can safely set mDashLength: |
132 | 0 | aStrokeOptions->mDashLength = dashArrayLength; |
133 | 0 |
|
134 | 0 | if ((dashArrayLength % 2) == 1) { |
135 | 0 | // If we have a dash pattern with an odd number of lengths the pattern |
136 | 0 | // repeats a second time, per the SVG spec., and as implemented by Moz2D. |
137 | 0 | // When deciding whether to return eNoStroke or eContinuousStroke below we |
138 | 0 | // need to take into account that in the repeat pattern the dashes become |
139 | 0 | // gaps, and the gaps become dashes. |
140 | 0 | Float origTotalLengthOfDashes = totalLengthOfDashes; |
141 | 0 | totalLengthOfDashes += totalLengthOfGaps; |
142 | 0 | totalLengthOfGaps += origTotalLengthOfDashes; |
143 | 0 | } |
144 | 0 |
|
145 | 0 | // Stroking using dashes is much slower than stroking a continuous line |
146 | 0 | // (see bug 609361 comment 40), and much, much slower than not stroking the |
147 | 0 | // line at all. Here we check for cases when the dash pattern causes the |
148 | 0 | // stroke to essentially be continuous or to be nonexistent in which case |
149 | 0 | // we can avoid expensive stroking operations (the underlying platform |
150 | 0 | // graphics libraries don't seem to optimize for this). |
151 | 0 | if (totalLengthOfGaps <= 0) { |
152 | 0 | return eContinuousStroke; |
153 | 0 | } |
154 | 0 | // We can only return eNoStroke if the value of stroke-linecap isn't |
155 | 0 | // adding caps to zero length dashes. |
156 | 0 | if (totalLengthOfDashes <= 0 && |
157 | 0 | aStyleSVG->mStrokeLinecap == NS_STYLE_STROKE_LINECAP_BUTT) { |
158 | 0 | return eNoStroke; |
159 | 0 | } |
160 | 0 | |
161 | 0 | if (aContextPaint && aStyleSVG->StrokeDashoffsetFromObject()) { |
162 | 0 | aStrokeOptions->mDashOffset = Float(aContextPaint->GetStrokeDashOffset()); |
163 | 0 | } else { |
164 | 0 | aStrokeOptions->mDashOffset = |
165 | 0 | SVGContentUtils::CoordToFloat(aElement, aStyleSVG->mStrokeDashoffset) * |
166 | 0 | pathScale; |
167 | 0 | } |
168 | 0 |
|
169 | 0 | return eDashedStroke; |
170 | 0 | } |
171 | | |
172 | | void |
173 | | SVGContentUtils::GetStrokeOptions(AutoStrokeOptions* aStrokeOptions, |
174 | | nsSVGElement* aElement, |
175 | | ComputedStyle* aComputedStyle, |
176 | | SVGContextPaint* aContextPaint, |
177 | | StrokeOptionFlags aFlags) |
178 | 0 | { |
179 | 0 | RefPtr<ComputedStyle> computedStyle; |
180 | 0 | if (aComputedStyle) { |
181 | 0 | computedStyle = aComputedStyle; |
182 | 0 | } else { |
183 | 0 | computedStyle = |
184 | 0 | nsComputedDOMStyle::GetComputedStyleNoFlush(aElement, nullptr); |
185 | 0 | } |
186 | 0 |
|
187 | 0 | if (!computedStyle) { |
188 | 0 | return; |
189 | 0 | } |
190 | 0 | |
191 | 0 | const nsStyleSVG* styleSVG = computedStyle->StyleSVG(); |
192 | 0 |
|
193 | 0 | bool checkedDashAndStrokeIsDashed = false; |
194 | 0 | if (aFlags != eIgnoreStrokeDashing) { |
195 | 0 | DashState dashState = |
196 | 0 | GetStrokeDashData(aStrokeOptions, aElement, styleSVG, aContextPaint); |
197 | 0 |
|
198 | 0 | if (dashState == eNoStroke) { |
199 | 0 | // Hopefully this will shortcircuit any stroke operations: |
200 | 0 | aStrokeOptions->mLineWidth = 0; |
201 | 0 | return; |
202 | 0 | } |
203 | 0 | if (dashState == eContinuousStroke && aStrokeOptions->mDashPattern) { |
204 | 0 | // Prevent our caller from wasting time looking at a pattern without gaps: |
205 | 0 | aStrokeOptions->DiscardDashPattern(); |
206 | 0 | } |
207 | 0 | checkedDashAndStrokeIsDashed = (dashState == eDashedStroke); |
208 | 0 | } |
209 | 0 |
|
210 | 0 | aStrokeOptions->mLineWidth = |
211 | 0 | GetStrokeWidth(aElement, computedStyle, aContextPaint); |
212 | 0 |
|
213 | 0 | aStrokeOptions->mMiterLimit = Float(styleSVG->mStrokeMiterlimit); |
214 | 0 |
|
215 | 0 | switch (styleSVG->mStrokeLinejoin) { |
216 | 0 | case NS_STYLE_STROKE_LINEJOIN_MITER: |
217 | 0 | aStrokeOptions->mLineJoin = JoinStyle::MITER_OR_BEVEL; |
218 | 0 | break; |
219 | 0 | case NS_STYLE_STROKE_LINEJOIN_ROUND: |
220 | 0 | aStrokeOptions->mLineJoin = JoinStyle::ROUND; |
221 | 0 | break; |
222 | 0 | case NS_STYLE_STROKE_LINEJOIN_BEVEL: |
223 | 0 | aStrokeOptions->mLineJoin = JoinStyle::BEVEL; |
224 | 0 | break; |
225 | 0 | } |
226 | 0 |
|
227 | 0 | if (ShapeTypeHasNoCorners(aElement) && !checkedDashAndStrokeIsDashed) { |
228 | 0 | // Note: if aFlags == eIgnoreStrokeDashing then we may be returning the |
229 | 0 | // wrong linecap value here, since the actual linecap used on render in this |
230 | 0 | // case depends on whether the stroke is dashed or not. |
231 | 0 | aStrokeOptions->mLineCap = CapStyle::BUTT; |
232 | 0 | } else { |
233 | 0 | switch (styleSVG->mStrokeLinecap) { |
234 | 0 | case NS_STYLE_STROKE_LINECAP_BUTT: |
235 | 0 | aStrokeOptions->mLineCap = CapStyle::BUTT; |
236 | 0 | break; |
237 | 0 | case NS_STYLE_STROKE_LINECAP_ROUND: |
238 | 0 | aStrokeOptions->mLineCap = CapStyle::ROUND; |
239 | 0 | break; |
240 | 0 | case NS_STYLE_STROKE_LINECAP_SQUARE: |
241 | 0 | aStrokeOptions->mLineCap = CapStyle::SQUARE; |
242 | 0 | break; |
243 | 0 | } |
244 | 0 | } |
245 | 0 | } |
246 | | |
247 | | Float |
248 | | SVGContentUtils::GetStrokeWidth(nsSVGElement* aElement, |
249 | | ComputedStyle* aComputedStyle, |
250 | | SVGContextPaint* aContextPaint) |
251 | 0 | { |
252 | 0 | RefPtr<ComputedStyle> computedStyle; |
253 | 0 | if (aComputedStyle) { |
254 | 0 | computedStyle = aComputedStyle; |
255 | 0 | } else { |
256 | 0 | computedStyle = |
257 | 0 | nsComputedDOMStyle::GetComputedStyleNoFlush(aElement, nullptr); |
258 | 0 | } |
259 | 0 |
|
260 | 0 | if (!computedStyle) { |
261 | 0 | return 0.0f; |
262 | 0 | } |
263 | 0 | |
264 | 0 | const nsStyleSVG* styleSVG = computedStyle->StyleSVG(); |
265 | 0 |
|
266 | 0 | if (aContextPaint && styleSVG->StrokeWidthFromObject()) { |
267 | 0 | return aContextPaint->GetStrokeWidth(); |
268 | 0 | } |
269 | 0 | |
270 | 0 | return SVGContentUtils::CoordToFloat(aElement, styleSVG->mStrokeWidth); |
271 | 0 | } |
272 | | |
273 | | float |
274 | | SVGContentUtils::GetFontSize(Element* aElement) |
275 | 0 | { |
276 | 0 | if (!aElement) { |
277 | 0 | return 1.0f; |
278 | 0 | } |
279 | 0 | |
280 | 0 | nsPresContext* pc = nsContentUtils::GetContextForContent(aElement); |
281 | 0 | if (!pc) { |
282 | 0 | return 1.0f; |
283 | 0 | } |
284 | 0 | |
285 | 0 | RefPtr<ComputedStyle> computedStyle = |
286 | 0 | nsComputedDOMStyle::GetComputedStyleNoFlush(aElement, nullptr); |
287 | 0 | if (!computedStyle) { |
288 | 0 | // ReportToConsole |
289 | 0 | NS_WARNING("Couldn't get ComputedStyle for content in GetFontStyle"); |
290 | 0 | return 1.0f; |
291 | 0 | } |
292 | 0 |
|
293 | 0 | return GetFontSize(computedStyle, pc); |
294 | 0 | } |
295 | | |
296 | | float |
297 | | SVGContentUtils::GetFontSize(nsIFrame* aFrame) |
298 | 0 | { |
299 | 0 | MOZ_ASSERT(aFrame, "NULL frame in GetFontSize"); |
300 | 0 | return GetFontSize(aFrame->Style(), aFrame->PresContext()); |
301 | 0 | } |
302 | | |
303 | | float |
304 | | SVGContentUtils::GetFontSize(ComputedStyle* aComputedStyle, |
305 | | nsPresContext* aPresContext) |
306 | 0 | { |
307 | 0 | MOZ_ASSERT(aComputedStyle); |
308 | 0 | MOZ_ASSERT(aPresContext); |
309 | 0 |
|
310 | 0 | nscoord fontSize = aComputedStyle->StyleFont()->mSize; |
311 | 0 | return nsPresContext::AppUnitsToFloatCSSPixels(fontSize) / |
312 | 0 | aPresContext->EffectiveTextZoom(); |
313 | 0 | } |
314 | | |
315 | | float |
316 | | SVGContentUtils::GetFontXHeight(Element* aElement) |
317 | 0 | { |
318 | 0 | if (!aElement) { |
319 | 0 | return 1.0f; |
320 | 0 | } |
321 | 0 | |
322 | 0 | nsPresContext* pc = nsContentUtils::GetContextForContent(aElement); |
323 | 0 | if (!pc) { |
324 | 0 | return 1.0f; |
325 | 0 | } |
326 | 0 | |
327 | 0 | RefPtr<ComputedStyle> style = |
328 | 0 | nsComputedDOMStyle::GetComputedStyleNoFlush(aElement, nullptr); |
329 | 0 | if (!style) { |
330 | 0 | // ReportToConsole |
331 | 0 | NS_WARNING("Couldn't get ComputedStyle for content in GetFontStyle"); |
332 | 0 | return 1.0f; |
333 | 0 | } |
334 | 0 |
|
335 | 0 | return GetFontXHeight(style, pc); |
336 | 0 | } |
337 | | |
338 | | float |
339 | | SVGContentUtils::GetFontXHeight(nsIFrame *aFrame) |
340 | 0 | { |
341 | 0 | MOZ_ASSERT(aFrame, "NULL frame in GetFontXHeight"); |
342 | 0 | return GetFontXHeight(aFrame->Style(), aFrame->PresContext()); |
343 | 0 | } |
344 | | |
345 | | float |
346 | | SVGContentUtils::GetFontXHeight(ComputedStyle* aComputedStyle, |
347 | | nsPresContext* aPresContext) |
348 | 0 | { |
349 | 0 | MOZ_ASSERT(aComputedStyle && aPresContext); |
350 | 0 |
|
351 | 0 | RefPtr<nsFontMetrics> fontMetrics = |
352 | 0 | nsLayoutUtils::GetFontMetricsForComputedStyle(aComputedStyle, aPresContext); |
353 | 0 |
|
354 | 0 | if (!fontMetrics) { |
355 | 0 | // ReportToConsole |
356 | 0 | NS_WARNING("no FontMetrics in GetFontXHeight()"); |
357 | 0 | return 1.0f; |
358 | 0 | } |
359 | 0 |
|
360 | 0 | nscoord xHeight = fontMetrics->XHeight(); |
361 | 0 | return nsPresContext::AppUnitsToFloatCSSPixels(xHeight) / |
362 | 0 | aPresContext->EffectiveTextZoom(); |
363 | 0 | } |
364 | | nsresult |
365 | | SVGContentUtils::ReportToConsole(nsIDocument* doc, |
366 | | const char* aWarning, |
367 | | const char16_t **aParams, |
368 | | uint32_t aParamsLength) |
369 | 0 | { |
370 | 0 | return nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, |
371 | 0 | NS_LITERAL_CSTRING("SVG"), doc, |
372 | 0 | nsContentUtils::eSVG_PROPERTIES, |
373 | 0 | aWarning, |
374 | 0 | aParams, aParamsLength); |
375 | 0 | } |
376 | | |
377 | | bool |
378 | | SVGContentUtils::EstablishesViewport(nsIContent *aContent) |
379 | 0 | { |
380 | 0 | // Although SVG 1.1 states that <image> is an element that establishes a |
381 | 0 | // viewport, this is really only for the document it references, not |
382 | 0 | // for any child content, which is what this function is used for. |
383 | 0 | return aContent && aContent->IsAnyOfSVGElements(nsGkAtoms::svg, |
384 | 0 | nsGkAtoms::foreignObject, |
385 | 0 | nsGkAtoms::symbol); |
386 | 0 | } |
387 | | |
388 | | SVGViewportElement* |
389 | | SVGContentUtils::GetNearestViewportElement(const nsIContent *aContent) |
390 | 0 | { |
391 | 0 | nsIContent *element = aContent->GetFlattenedTreeParent(); |
392 | 0 |
|
393 | 0 | while (element && element->IsSVGElement()) { |
394 | 0 | if (EstablishesViewport(element)) { |
395 | 0 | if (element->IsSVGElement(nsGkAtoms::foreignObject)) { |
396 | 0 | return nullptr; |
397 | 0 | } |
398 | 0 | MOZ_ASSERT(element->IsAnyOfSVGElements(nsGkAtoms::svg, |
399 | 0 | nsGkAtoms::symbol), |
400 | 0 | "upcoming static_cast is only valid for " |
401 | 0 | "SVGViewportElement subclasses"); |
402 | 0 | return static_cast<SVGViewportElement*>(element); |
403 | 0 | } |
404 | 0 | element = element->GetFlattenedTreeParent(); |
405 | 0 | } |
406 | 0 | return nullptr; |
407 | 0 | } |
408 | | |
409 | | static gfx::Matrix |
410 | | GetCTMInternal(nsSVGElement *aElement, bool aScreenCTM, bool aHaveRecursed) |
411 | 0 | { |
412 | 0 | gfxMatrix matrix = aElement->PrependLocalTransformsTo(gfxMatrix(), |
413 | 0 | aHaveRecursed ? eAllTransforms : eUserSpaceToParent); |
414 | 0 | nsSVGElement *element = aElement; |
415 | 0 | nsIContent *ancestor = aElement->GetFlattenedTreeParent(); |
416 | 0 |
|
417 | 0 | while (ancestor && ancestor->IsSVGElement() && |
418 | 0 | !ancestor->IsSVGElement(nsGkAtoms::foreignObject)) { |
419 | 0 | element = static_cast<nsSVGElement*>(ancestor); |
420 | 0 | matrix *= element->PrependLocalTransformsTo(gfxMatrix()); // i.e. *A*ppend |
421 | 0 | if (!aScreenCTM && SVGContentUtils::EstablishesViewport(element)) { |
422 | 0 | if (!element->NodeInfo()->Equals(nsGkAtoms::svg, kNameSpaceID_SVG) && |
423 | 0 | !element->NodeInfo()->Equals(nsGkAtoms::symbol, kNameSpaceID_SVG)) { |
424 | 0 | NS_ERROR("New (SVG > 1.1) SVG viewport establishing element?"); |
425 | 0 | return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular |
426 | 0 | } |
427 | 0 | // XXX spec seems to say x,y translation should be undone for IsInnerSVG |
428 | 0 | return gfx::ToMatrix(matrix); |
429 | 0 | } |
430 | 0 | ancestor = ancestor->GetFlattenedTreeParent(); |
431 | 0 | } |
432 | 0 | if (!aScreenCTM) { |
433 | 0 | // didn't find a nearestViewportElement |
434 | 0 | return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular |
435 | 0 | } |
436 | 0 | if (!element->IsSVGElement(nsGkAtoms::svg)) { |
437 | 0 | // Not a valid SVG fragment |
438 | 0 | return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular |
439 | 0 | } |
440 | 0 | if (element == aElement && !aHaveRecursed) { |
441 | 0 | // We get here when getScreenCTM() is called on an outer-<svg>. |
442 | 0 | // Consistency with other elements would have us include only the |
443 | 0 | // eFromUserSpace transforms, but we include the eAllTransforms |
444 | 0 | // transforms in this case since that's what we've been doing for |
445 | 0 | // a while, and it keeps us consistent with WebKit and Opera (if not |
446 | 0 | // really with the ambiguous spec). |
447 | 0 | matrix = aElement->PrependLocalTransformsTo(gfxMatrix()); |
448 | 0 | } |
449 | 0 | if (!ancestor || !ancestor->IsElement()) { |
450 | 0 | return gfx::ToMatrix(matrix); |
451 | 0 | } |
452 | 0 | if (ancestor->IsSVGElement()) { |
453 | 0 | return |
454 | 0 | gfx::ToMatrix(matrix) * GetCTMInternal(static_cast<nsSVGElement*>(ancestor), true, true); |
455 | 0 | } |
456 | 0 | |
457 | 0 | // XXX this does not take into account CSS transform, or that the non-SVG |
458 | 0 | // content that we've hit may itself be inside an SVG foreignObject higher up |
459 | 0 | nsIDocument* currentDoc = aElement->GetComposedDoc(); |
460 | 0 | float x = 0.0f, y = 0.0f; |
461 | 0 | if (currentDoc && element->NodeInfo()->Equals(nsGkAtoms::svg, kNameSpaceID_SVG)) { |
462 | 0 | nsIPresShell *presShell = currentDoc->GetShell(); |
463 | 0 | if (presShell) { |
464 | 0 | nsIFrame* frame = element->GetPrimaryFrame(); |
465 | 0 | nsIFrame* ancestorFrame = presShell->GetRootFrame(); |
466 | 0 | if (frame && ancestorFrame) { |
467 | 0 | nsPoint point = frame->GetOffsetTo(ancestorFrame); |
468 | 0 | x = nsPresContext::AppUnitsToFloatCSSPixels(point.x); |
469 | 0 | y = nsPresContext::AppUnitsToFloatCSSPixels(point.y); |
470 | 0 | } |
471 | 0 | } |
472 | 0 | } |
473 | 0 | return ToMatrix(matrix).PostTranslate(x, y); |
474 | 0 | } |
475 | | |
476 | | gfx::Matrix |
477 | | SVGContentUtils::GetCTM(nsSVGElement *aElement, bool aScreenCTM) |
478 | 0 | { |
479 | 0 | return GetCTMInternal(aElement, aScreenCTM, false); |
480 | 0 | } |
481 | | |
482 | | void |
483 | | SVGContentUtils::RectilinearGetStrokeBounds(const Rect& aRect, |
484 | | const Matrix& aToBoundsSpace, |
485 | | const Matrix& aToNonScalingStrokeSpace, |
486 | | float aStrokeWidth, |
487 | | Rect* aBounds) |
488 | 0 | { |
489 | 0 | MOZ_ASSERT(aToBoundsSpace.IsRectilinear(), |
490 | 0 | "aToBoundsSpace must be rectilinear"); |
491 | 0 | MOZ_ASSERT(aToNonScalingStrokeSpace.IsRectilinear(), |
492 | 0 | "aToNonScalingStrokeSpace must be rectilinear"); |
493 | 0 |
|
494 | 0 | Matrix nonScalingToSource = aToNonScalingStrokeSpace.Inverse(); |
495 | 0 | Matrix nonScalingToBounds = nonScalingToSource * aToBoundsSpace; |
496 | 0 |
|
497 | 0 | *aBounds = aToBoundsSpace.TransformBounds(aRect); |
498 | 0 |
|
499 | 0 | // Compute the amounts dx and dy that nonScalingToBounds scales a half-width |
500 | 0 | // stroke in the x and y directions, and then inflate aBounds by those amounts |
501 | 0 | // so that when aBounds is transformed back to non-scaling-stroke space |
502 | 0 | // it will map onto the correct stroked bounds. |
503 | 0 |
|
504 | 0 | Float dx = 0.0f; |
505 | 0 | Float dy = 0.0f; |
506 | 0 | // nonScalingToBounds is rectilinear, so either _12 and _21 are zero or _11 |
507 | 0 | // and _22 are zero, and in each case the non-zero entries (from among _11, |
508 | 0 | // _12, _21, _22) simply scale the stroke width in the x and y directions. |
509 | 0 | if (FuzzyEqual(nonScalingToBounds._12, 0) && |
510 | 0 | FuzzyEqual(nonScalingToBounds._21, 0)) { |
511 | 0 | dx = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._11); |
512 | 0 | dy = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._22); |
513 | 0 | } else { |
514 | 0 | dx = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._21); |
515 | 0 | dy = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._12); |
516 | 0 | } |
517 | 0 |
|
518 | 0 | aBounds->Inflate(dx, dy); |
519 | 0 | } |
520 | | |
521 | | double |
522 | | SVGContentUtils::ComputeNormalizedHypotenuse(double aWidth, double aHeight) |
523 | 0 | { |
524 | 0 | return NS_hypot(aWidth, aHeight) / M_SQRT2; |
525 | 0 | } |
526 | | |
527 | | float |
528 | | SVGContentUtils::AngleBisect(float a1, float a2) |
529 | 0 | { |
530 | 0 | float delta = fmod(a2 - a1, static_cast<float>(2*M_PI)); |
531 | 0 | if (delta < 0) { |
532 | 0 | delta += static_cast<float>(2*M_PI); |
533 | 0 | } |
534 | 0 | /* delta is now the angle from a1 around to a2, in the range [0, 2*M_PI) */ |
535 | 0 | float r = a1 + delta/2; |
536 | 0 | if (delta >= M_PI) { |
537 | 0 | /* the arc from a2 to a1 is smaller, so use the ray on that side */ |
538 | 0 | r += static_cast<float>(M_PI); |
539 | 0 | } |
540 | 0 | return r; |
541 | 0 | } |
542 | | |
543 | | gfx::Matrix |
544 | | SVGContentUtils::GetViewBoxTransform(float aViewportWidth, float aViewportHeight, |
545 | | float aViewboxX, float aViewboxY, |
546 | | float aViewboxWidth, float aViewboxHeight, |
547 | | const SVGAnimatedPreserveAspectRatio &aPreserveAspectRatio) |
548 | 0 | { |
549 | 0 | return GetViewBoxTransform(aViewportWidth, aViewportHeight, |
550 | 0 | aViewboxX, aViewboxY, |
551 | 0 | aViewboxWidth, aViewboxHeight, |
552 | 0 | aPreserveAspectRatio.GetAnimValue()); |
553 | 0 | } |
554 | | |
555 | | gfx::Matrix |
556 | | SVGContentUtils::GetViewBoxTransform(float aViewportWidth, float aViewportHeight, |
557 | | float aViewboxX, float aViewboxY, |
558 | | float aViewboxWidth, float aViewboxHeight, |
559 | | const SVGPreserveAspectRatio &aPreserveAspectRatio) |
560 | 0 | { |
561 | 0 | NS_ASSERTION(aViewportWidth >= 0, "viewport width must be nonnegative!"); |
562 | 0 | NS_ASSERTION(aViewportHeight >= 0, "viewport height must be nonnegative!"); |
563 | 0 | NS_ASSERTION(aViewboxWidth > 0, "viewBox width must be greater than zero!"); |
564 | 0 | NS_ASSERTION(aViewboxHeight > 0, "viewBox height must be greater than zero!"); |
565 | 0 |
|
566 | 0 | uint16_t align = aPreserveAspectRatio.GetAlign(); |
567 | 0 | uint16_t meetOrSlice = aPreserveAspectRatio.GetMeetOrSlice(); |
568 | 0 |
|
569 | 0 | // default to the defaults |
570 | 0 | if (align == SVG_PRESERVEASPECTRATIO_UNKNOWN) |
571 | 0 | align = SVG_PRESERVEASPECTRATIO_XMIDYMID; |
572 | 0 | if (meetOrSlice == SVG_MEETORSLICE_UNKNOWN) |
573 | 0 | meetOrSlice = SVG_MEETORSLICE_MEET; |
574 | 0 |
|
575 | 0 | float a, d, e, f; |
576 | 0 | a = aViewportWidth / aViewboxWidth; |
577 | 0 | d = aViewportHeight / aViewboxHeight; |
578 | 0 | e = 0.0f; |
579 | 0 | f = 0.0f; |
580 | 0 |
|
581 | 0 | if (align != SVG_PRESERVEASPECTRATIO_NONE && |
582 | 0 | a != d) { |
583 | 0 | if ((meetOrSlice == SVG_MEETORSLICE_MEET && a < d) || |
584 | 0 | (meetOrSlice == SVG_MEETORSLICE_SLICE && d < a)) { |
585 | 0 | d = a; |
586 | 0 | switch (align) { |
587 | 0 | case SVG_PRESERVEASPECTRATIO_XMINYMIN: |
588 | 0 | case SVG_PRESERVEASPECTRATIO_XMIDYMIN: |
589 | 0 | case SVG_PRESERVEASPECTRATIO_XMAXYMIN: |
590 | 0 | break; |
591 | 0 | case SVG_PRESERVEASPECTRATIO_XMINYMID: |
592 | 0 | case SVG_PRESERVEASPECTRATIO_XMIDYMID: |
593 | 0 | case SVG_PRESERVEASPECTRATIO_XMAXYMID: |
594 | 0 | f = (aViewportHeight - a * aViewboxHeight) / 2.0f; |
595 | 0 | break; |
596 | 0 | case SVG_PRESERVEASPECTRATIO_XMINYMAX: |
597 | 0 | case SVG_PRESERVEASPECTRATIO_XMIDYMAX: |
598 | 0 | case SVG_PRESERVEASPECTRATIO_XMAXYMAX: |
599 | 0 | f = aViewportHeight - a * aViewboxHeight; |
600 | 0 | break; |
601 | 0 | default: |
602 | 0 | MOZ_ASSERT_UNREACHABLE("Unknown value for align"); |
603 | 0 | } |
604 | 0 | } |
605 | 0 | else if ( |
606 | 0 | (meetOrSlice == SVG_MEETORSLICE_MEET && |
607 | 0 | d < a) || |
608 | 0 | (meetOrSlice == SVG_MEETORSLICE_SLICE && |
609 | 0 | a < d)) { |
610 | 0 | a = d; |
611 | 0 | switch (align) { |
612 | 0 | case SVG_PRESERVEASPECTRATIO_XMINYMIN: |
613 | 0 | case SVG_PRESERVEASPECTRATIO_XMINYMID: |
614 | 0 | case SVG_PRESERVEASPECTRATIO_XMINYMAX: |
615 | 0 | break; |
616 | 0 | case SVG_PRESERVEASPECTRATIO_XMIDYMIN: |
617 | 0 | case SVG_PRESERVEASPECTRATIO_XMIDYMID: |
618 | 0 | case SVG_PRESERVEASPECTRATIO_XMIDYMAX: |
619 | 0 | e = (aViewportWidth - a * aViewboxWidth) / 2.0f; |
620 | 0 | break; |
621 | 0 | case SVG_PRESERVEASPECTRATIO_XMAXYMIN: |
622 | 0 | case SVG_PRESERVEASPECTRATIO_XMAXYMID: |
623 | 0 | case SVG_PRESERVEASPECTRATIO_XMAXYMAX: |
624 | 0 | e = aViewportWidth - a * aViewboxWidth; |
625 | 0 | break; |
626 | 0 | default: |
627 | 0 | MOZ_ASSERT_UNREACHABLE("Unknown value for align"); |
628 | 0 | } |
629 | 0 | } |
630 | 0 | else MOZ_ASSERT_UNREACHABLE("Unknown value for meetOrSlice"); |
631 | 0 | } |
632 | 0 |
|
633 | 0 | if (aViewboxX) e += -a * aViewboxX; |
634 | 0 | if (aViewboxY) f += -d * aViewboxY; |
635 | 0 |
|
636 | 0 | return gfx::Matrix(a, 0.0f, 0.0f, d, e, f); |
637 | 0 | } |
638 | | |
639 | | static bool |
640 | | ParseNumber(RangedPtr<const char16_t>& aIter, |
641 | | const RangedPtr<const char16_t>& aEnd, |
642 | | double& aValue) |
643 | 0 | { |
644 | 0 | int32_t sign; |
645 | 0 | if (!SVGContentUtils::ParseOptionalSign(aIter, aEnd, sign)) { |
646 | 0 | return false; |
647 | 0 | } |
648 | 0 | |
649 | 0 | // Absolute value of the integer part of the mantissa. |
650 | 0 | double intPart = 0.0; |
651 | 0 |
|
652 | 0 | bool gotDot = *aIter == '.'; |
653 | 0 |
|
654 | 0 | if (!gotDot) { |
655 | 0 | if (!mozilla::IsAsciiDigit(*aIter)) { |
656 | 0 | return false; |
657 | 0 | } |
658 | 0 | do { |
659 | 0 | intPart = 10.0 * intPart + mozilla::AsciiAlphanumericToNumber(*aIter); |
660 | 0 | ++aIter; |
661 | 0 | } while (aIter != aEnd && mozilla::IsAsciiDigit(*aIter)); |
662 | 0 |
|
663 | 0 | if (aIter != aEnd) { |
664 | 0 | gotDot = *aIter == '.'; |
665 | 0 | } |
666 | 0 | } |
667 | 0 |
|
668 | 0 | // Fractional part of the mantissa. |
669 | 0 | double fracPart = 0.0; |
670 | 0 |
|
671 | 0 | if (gotDot) { |
672 | 0 | ++aIter; |
673 | 0 | if (aIter == aEnd || !mozilla::IsAsciiDigit(*aIter)) { |
674 | 0 | return false; |
675 | 0 | } |
676 | 0 | |
677 | 0 | // Power of ten by which we need to divide the fraction |
678 | 0 | double divisor = 1.0; |
679 | 0 |
|
680 | 0 | do { |
681 | 0 | fracPart = 10.0 * fracPart + mozilla::AsciiAlphanumericToNumber(*aIter); |
682 | 0 | divisor *= 10.0; |
683 | 0 | ++aIter; |
684 | 0 | } while (aIter != aEnd && mozilla::IsAsciiDigit(*aIter)); |
685 | 0 |
|
686 | 0 | fracPart /= divisor; |
687 | 0 | } |
688 | 0 |
|
689 | 0 | bool gotE = false; |
690 | 0 | int32_t exponent = 0; |
691 | 0 | int32_t expSign; |
692 | 0 |
|
693 | 0 | if (aIter != aEnd && (*aIter == 'e' || *aIter == 'E')) { |
694 | 0 |
|
695 | 0 | RangedPtr<const char16_t> expIter(aIter); |
696 | 0 |
|
697 | 0 | ++expIter; |
698 | 0 | if (expIter != aEnd) { |
699 | 0 | expSign = *expIter == '-' ? -1 : 1; |
700 | 0 | if (*expIter == '-' || *expIter == '+') { |
701 | 0 | ++expIter; |
702 | 0 | } |
703 | 0 | if (expIter != aEnd && mozilla::IsAsciiDigit(*expIter)) { |
704 | 0 | // At this point we're sure this is an exponent |
705 | 0 | // and not the start of a unit such as em or ex. |
706 | 0 | gotE = true; |
707 | 0 | } |
708 | 0 | } |
709 | 0 |
|
710 | 0 | if (gotE) { |
711 | 0 | aIter = expIter; |
712 | 0 | do { |
713 | 0 | exponent = 10.0 * exponent + mozilla::AsciiAlphanumericToNumber(*aIter); |
714 | 0 | ++aIter; |
715 | 0 | } while (aIter != aEnd && mozilla::IsAsciiDigit(*aIter)); |
716 | 0 | } |
717 | 0 | } |
718 | 0 |
|
719 | 0 | // Assemble the number |
720 | 0 | aValue = sign * (intPart + fracPart); |
721 | 0 | if (gotE) { |
722 | 0 | aValue *= pow(10.0, expSign * exponent); |
723 | 0 | } |
724 | 0 | return true; |
725 | 0 | } |
726 | | |
727 | | template<class floatType> |
728 | | bool |
729 | | SVGContentUtils::ParseNumber(RangedPtr<const char16_t>& aIter, |
730 | | const RangedPtr<const char16_t>& aEnd, |
731 | | floatType& aValue) |
732 | 0 | { |
733 | 0 | RangedPtr<const char16_t> iter(aIter); |
734 | 0 |
|
735 | 0 | double value; |
736 | 0 | if (!::ParseNumber(iter, aEnd, value)) { |
737 | 0 | return false; |
738 | 0 | } |
739 | 0 | floatType floatValue = floatType(value); |
740 | 0 | if (!IsFinite(floatValue)) { |
741 | 0 | return false; |
742 | 0 | } |
743 | 0 | aValue = floatValue; |
744 | 0 | aIter = iter; |
745 | 0 | return true; |
746 | 0 | } Unexecuted instantiation: bool SVGContentUtils::ParseNumber<float>(mozilla::RangedPtr<char16_t const>&, mozilla::RangedPtr<char16_t const> const&, float&) Unexecuted instantiation: bool SVGContentUtils::ParseNumber<double>(mozilla::RangedPtr<char16_t const>&, mozilla::RangedPtr<char16_t const> const&, double&) |
747 | | |
748 | | template bool |
749 | | SVGContentUtils::ParseNumber<float>(RangedPtr<const char16_t>& aIter, |
750 | | const RangedPtr<const char16_t>& aEnd, |
751 | | float& aValue); |
752 | | |
753 | | template bool |
754 | | SVGContentUtils::ParseNumber<double>(RangedPtr<const char16_t>& aIter, |
755 | | const RangedPtr<const char16_t>& aEnd, |
756 | | double& aValue); |
757 | | |
758 | | RangedPtr<const char16_t> |
759 | | SVGContentUtils::GetStartRangedPtr(const nsAString& aString) |
760 | 0 | { |
761 | 0 | return RangedPtr<const char16_t>(aString.Data(), aString.Length()); |
762 | 0 | } |
763 | | |
764 | | RangedPtr<const char16_t> |
765 | | SVGContentUtils::GetEndRangedPtr(const nsAString& aString) |
766 | 0 | { |
767 | 0 | return RangedPtr<const char16_t>(aString.Data() + aString.Length(), |
768 | 0 | aString.Data(), aString.Length()); |
769 | 0 | } |
770 | | |
771 | | template<class floatType> |
772 | | bool |
773 | | SVGContentUtils::ParseNumber(const nsAString& aString, |
774 | | floatType& aValue) |
775 | 0 | { |
776 | 0 | RangedPtr<const char16_t> iter = GetStartRangedPtr(aString); |
777 | 0 | const RangedPtr<const char16_t> end = GetEndRangedPtr(aString); |
778 | 0 |
|
779 | 0 | return ParseNumber(iter, end, aValue) && iter == end; |
780 | 0 | } Unexecuted instantiation: bool SVGContentUtils::ParseNumber<float>(nsTSubstring<char16_t> const&, float&) Unexecuted instantiation: bool SVGContentUtils::ParseNumber<double>(nsTSubstring<char16_t> const&, double&) |
781 | | |
782 | | template bool |
783 | | SVGContentUtils::ParseNumber<float>(const nsAString& aString, |
784 | | float& aValue); |
785 | | template bool |
786 | | SVGContentUtils::ParseNumber<double>(const nsAString& aString, |
787 | | double& aValue); |
788 | | |
789 | | /* static */ |
790 | | bool |
791 | | SVGContentUtils::ParseInteger(RangedPtr<const char16_t>& aIter, |
792 | | const RangedPtr<const char16_t>& aEnd, |
793 | | int32_t& aValue) |
794 | 0 | { |
795 | 0 | RangedPtr<const char16_t> iter(aIter); |
796 | 0 |
|
797 | 0 | int32_t sign; |
798 | 0 | if (!ParseOptionalSign(iter, aEnd, sign)) { |
799 | 0 | return false; |
800 | 0 | } |
801 | 0 | |
802 | 0 | if (!mozilla::IsAsciiDigit(*iter)) { |
803 | 0 | return false; |
804 | 0 | } |
805 | 0 | |
806 | 0 | int64_t value = 0; |
807 | 0 |
|
808 | 0 | do { |
809 | 0 | if (value <= std::numeric_limits<int32_t>::max()) { |
810 | 0 | value = 10 * value + mozilla::AsciiAlphanumericToNumber(*iter); |
811 | 0 | } |
812 | 0 | ++iter; |
813 | 0 | } while (iter != aEnd && mozilla::IsAsciiDigit(*iter)); |
814 | 0 |
|
815 | 0 | aIter = iter; |
816 | 0 | aValue = int32_t(clamped(sign * value, |
817 | 0 | int64_t(std::numeric_limits<int32_t>::min()), |
818 | 0 | int64_t(std::numeric_limits<int32_t>::max()))); |
819 | 0 | return true; |
820 | 0 | } |
821 | | |
822 | | /* static */ |
823 | | bool |
824 | | SVGContentUtils::ParseInteger(const nsAString& aString, |
825 | | int32_t& aValue) |
826 | 0 | { |
827 | 0 | RangedPtr<const char16_t> iter = GetStartRangedPtr(aString); |
828 | 0 | const RangedPtr<const char16_t> end = GetEndRangedPtr(aString); |
829 | 0 |
|
830 | 0 | return ParseInteger(iter, end, aValue) && iter == end; |
831 | 0 | } |
832 | | |
833 | | float |
834 | | SVGContentUtils::CoordToFloat(nsSVGElement *aContent, |
835 | | const nsStyleCoord &aCoord) |
836 | 0 | { |
837 | 0 | switch (aCoord.GetUnit()) { |
838 | 0 | case eStyleUnit_Factor: |
839 | 0 | // user units |
840 | 0 | return aCoord.GetFactorValue(); |
841 | 0 |
|
842 | 0 | case eStyleUnit_Coord: |
843 | 0 | return nsPresContext::AppUnitsToFloatCSSPixels(aCoord.GetCoordValue()); |
844 | 0 |
|
845 | 0 | case eStyleUnit_Percent: { |
846 | 0 | SVGViewportElement* ctx = aContent->GetCtx(); |
847 | 0 | return ctx ? aCoord.GetPercentValue() * ctx->GetLength(SVGContentUtils::XY) : 0.0f; |
848 | 0 | } |
849 | 0 | default: |
850 | 0 | return 0.0f; |
851 | 0 | } |
852 | 0 | } |
853 | | |
854 | | already_AddRefed<gfx::Path> |
855 | | SVGContentUtils::GetPath(const nsAString& aPathString) |
856 | 0 | { |
857 | 0 | SVGPathData pathData; |
858 | 0 | nsSVGPathDataParser parser(aPathString, &pathData); |
859 | 0 | if (!parser.Parse()) { |
860 | 0 | return NULL; |
861 | 0 | } |
862 | 0 | |
863 | 0 | RefPtr<DrawTarget> drawTarget = |
864 | 0 | gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); |
865 | 0 | RefPtr<PathBuilder> builder = |
866 | 0 | drawTarget->CreatePathBuilder(FillRule::FILL_WINDING); |
867 | 0 |
|
868 | 0 | return pathData.BuildPath(builder, NS_STYLE_STROKE_LINECAP_BUTT, 1); |
869 | 0 | } |
870 | | |
871 | | bool |
872 | 0 | SVGContentUtils::ShapeTypeHasNoCorners(const nsIContent* aContent) { |
873 | 0 | return aContent && aContent->IsAnyOfSVGElements(nsGkAtoms::circle, |
874 | 0 | nsGkAtoms::ellipse); |
875 | 0 | } |