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