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