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