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/. */
7 #include "SVGPathData.h"
10 #include "gfxPlatform.h"
11 #include "mozilla/gfx/2D.h"
12 #include "mozilla/gfx/Types.h"
13 #include "mozilla/gfx/Point.h"
14 #include "mozilla/RefPtr.h"
17 #include "SVGPathDataParser.h"
19 #include "nsStyleConsts.h"
20 #include "SVGContentUtils.h"
21 #include "SVGGeometryElement.h"
22 #include "SVGPathSegUtils.h"
25 using namespace mozilla::dom::SVGPathSeg_Binding
;
26 using namespace mozilla::gfx
;
30 static inline bool IsMoveto(uint16_t aSegType
) {
31 return aSegType
== PATHSEG_MOVETO_ABS
|| aSegType
== PATHSEG_MOVETO_REL
;
34 static inline bool IsMoveto(StylePathCommand::Tag aSegType
) {
35 return aSegType
== StylePathCommand::Tag::MoveTo
;
38 static inline bool IsValidType(uint16_t aSegType
) {
39 return SVGPathSegUtils::IsValidType(aSegType
);
42 static inline bool IsValidType(StylePathCommand::Tag aSegType
) {
43 return aSegType
!= StylePathCommand::Tag::Unknown
;
46 static inline bool IsClosePath(uint16_t aSegType
) {
47 return aSegType
== PATHSEG_CLOSEPATH
;
50 static inline bool IsClosePath(StylePathCommand::Tag aSegType
) {
51 return aSegType
== StylePathCommand::Tag::ClosePath
;
54 static inline bool IsCubicType(StylePathCommand::Tag aType
) {
55 return aType
== StylePathCommand::Tag::CurveTo
||
56 aType
== StylePathCommand::Tag::SmoothCurveTo
;
59 static inline bool IsQuadraticType(StylePathCommand::Tag aType
) {
60 return aType
== StylePathCommand::Tag::QuadBezierCurveTo
||
61 aType
== StylePathCommand::Tag::SmoothQuadBezierCurveTo
;
64 nsresult
SVGPathData::CopyFrom(const SVGPathData
& rhs
) {
65 if (!mData
.Assign(rhs
.mData
, fallible
)) {
66 return NS_ERROR_OUT_OF_MEMORY
;
71 void SVGPathData::GetValueAsString(nsAString
& aValue
) const {
72 // we need this function in DidChangePathSegList
79 nsAutoString segAsString
;
80 SVGPathSegUtils::GetValueAsString(&mData
[i
], segAsString
);
81 // We ignore OOM, since it's not useful for us to return an error.
82 aValue
.Append(segAsString
);
83 i
+= 1 + SVGPathSegUtils::ArgCountForType(mData
[i
]);
84 if (i
>= mData
.Length()) {
85 MOZ_ASSERT(i
== mData
.Length(), "Very, very bad - mData corrupt");
92 nsresult
SVGPathData::SetValueFromString(const nsAString
& aValue
) {
93 // We don't use a temp variable since the spec says to parse everything up to
94 // the first error. We still return any error though so that callers know if
97 SVGPathDataParser
pathParser(aValue
, this);
98 return pathParser
.Parse() ? NS_OK
: NS_ERROR_DOM_SYNTAX_ERR
;
101 nsresult
SVGPathData::AppendSeg(uint32_t aType
, ...) {
102 uint32_t oldLength
= mData
.Length();
103 uint32_t newLength
= oldLength
+ 1 + SVGPathSegUtils::ArgCountForType(aType
);
104 if (!mData
.SetLength(newLength
, fallible
)) {
105 return NS_ERROR_OUT_OF_MEMORY
;
108 mData
[oldLength
] = SVGPathSegUtils::EncodeType(aType
);
110 va_start(args
, aType
);
111 for (uint32_t i
= oldLength
+ 1; i
< newLength
; ++i
) {
112 // NOTE! 'float' is promoted to 'double' when passed through '...'!
113 mData
[i
] = float(va_arg(args
, double));
119 float SVGPathData::GetPathLength() const {
120 SVGPathTraversalState state
;
123 while (i
< mData
.Length()) {
124 SVGPathSegUtils::TraversePathSegment(&mData
[i
], state
);
125 i
+= 1 + SVGPathSegUtils::ArgCountForType(mData
[i
]);
128 MOZ_ASSERT(i
== mData
.Length(), "Very, very bad - mData corrupt");
134 uint32_t SVGPathData::CountItems() const {
135 uint32_t i
= 0, count
= 0;
137 while (i
< mData
.Length()) {
138 i
+= 1 + SVGPathSegUtils::ArgCountForType(mData
[i
]);
142 MOZ_ASSERT(i
== mData
.Length(), "Very, very bad - mData corrupt");
148 bool SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(
149 FallibleTArray
<double>* aOutput
) const {
150 SVGPathTraversalState state
;
155 while (i
< mData
.Length()) {
156 uint32_t segType
= SVGPathSegUtils::DecodeType(mData
[i
]);
157 SVGPathSegUtils::TraversePathSegment(&mData
[i
], state
);
159 // With degenerately large point coordinates, TraversePathSegment can fail
160 // and end up producing NaNs.
161 if (!std::isfinite(state
.length
)) {
165 // We skip all moveto commands except an initial moveto. See the text 'A
166 // "move to" command does not count as an additional point when dividing up
169 // http://www.w3.org/TR/SVG11/animate.html#AnimateMotionElement
171 // This is important in the non-default case of calcMode="linear". In
172 // this case an equal amount of time is spent on each path segment,
173 // except on moveto segments which are jumped over immediately.
175 if (i
== 0 || !IsMoveto(segType
)) {
176 if (!aOutput
->AppendElement(state
.length
, fallible
)) {
180 i
+= 1 + SVGPathSegUtils::ArgCountForType(segType
);
183 MOZ_ASSERT(i
== mData
.Length(), "Very, very bad - mData corrupt?");
189 bool SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(
190 Span
<const StylePathCommand
> aPath
, FallibleTArray
<double>* aOutput
) {
191 SVGPathTraversalState state
;
195 bool firstMoveToIsChecked
= false;
196 for (const auto& cmd
: aPath
) {
197 SVGPathSegUtils::TraversePathSegment(cmd
, state
);
198 if (!std::isfinite(state
.length
)) {
202 // We skip all moveto commands except for the initial moveto.
203 if (!cmd
.IsMoveTo() || !firstMoveToIsChecked
) {
204 if (!aOutput
->AppendElement(state
.length
, fallible
)) {
209 if (cmd
.IsMoveTo() && !firstMoveToIsChecked
) {
210 firstMoveToIsChecked
= true;
217 uint32_t SVGPathData::GetPathSegAtLength(float aDistance
) const {
218 // TODO [SVGWG issue] get specified what happen if 'aDistance' < 0, or
219 // 'aDistance' > the length of the path, or the seg list is empty.
220 // Return -1? Throwing would better help authors avoid tricky bugs (DOM
221 // could do that if we return -1).
223 uint32_t i
= 0, segIndex
= 0;
224 SVGPathTraversalState state
;
226 while (i
< mData
.Length()) {
227 SVGPathSegUtils::TraversePathSegment(&mData
[i
], state
);
228 if (state
.length
>= aDistance
) {
231 i
+= 1 + SVGPathSegUtils::ArgCountForType(mData
[i
]);
235 MOZ_ASSERT(i
== mData
.Length(), "Very, very bad - mData corrupt");
237 return std::max(1U, segIndex
) -
238 1; // -1 because while loop takes us 1 too far
242 uint32_t SVGPathData::GetPathSegAtLength(Span
<const StylePathCommand
> aPath
,
244 uint32_t segIndex
= 0;
245 SVGPathTraversalState state
;
247 for (const auto& cmd
: aPath
) {
248 SVGPathSegUtils::TraversePathSegment(cmd
, state
);
249 if (state
.length
>= aDistance
) {
255 return std::max(1U, segIndex
) - 1;
259 * The SVG spec says we have to paint stroke caps for zero length subpaths:
261 * http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes
263 * Cairo only does this for |stroke-linecap: round| and not for
264 * |stroke-linecap: square| (since that's what Adobe Acrobat has always done).
265 * Most likely the other backends that DrawTarget uses have the same behavior.
267 * To help us conform to the SVG spec we have this helper function to draw an
268 * approximation of square caps for zero length subpaths. It does this by
269 * inserting a subpath containing a single user space axis aligned straight
270 * line that is as small as it can be while minimizing the risk of it being
271 * thrown away by the DrawTarget's backend for being too small to affect
272 * rendering. The idea is that we'll then get stroke caps drawn for this axis
273 * aligned line, creating an axis aligned rectangle that approximates the
274 * square that would ideally be drawn.
276 * Since we don't have any information about transforms from user space to
277 * device space, we choose the length of the small line that we insert by
278 * making it a small percentage of the stroke width of the path. This should
279 * hopefully allow us to make the line as long as possible (to avoid rounding
280 * issues in the backend resulting in the backend seeing it as having zero
281 * length) while still avoiding the small rectangle being noticeably different
284 * Note that this function inserts a subpath into the current gfx path that
285 * will be present during both fill and stroke operations.
287 static void ApproximateZeroLengthSubpathSquareCaps(PathBuilder
* aPB
,
289 Float aStrokeWidth
) {
290 // Note that caps are proportional to stroke width, so if stroke width is
291 // zero it's actually fine for |tinyLength| below to end up being zero.
292 // However, it would be a waste to inserting a LineTo in that case, so better
294 MOZ_ASSERT(aStrokeWidth
> 0.0f
,
295 "Make the caller check for this, or check it here");
297 // The fraction of the stroke width that we choose for the length of the
298 // line is rather arbitrary, other than being chosen to meet the requirements
299 // described in the comment above.
301 Float tinyLength
= aStrokeWidth
/ SVG_ZERO_LENGTH_PATH_FIX_FACTOR
;
303 aPB
->LineTo(aPoint
+ Point(tinyLength
, 0));
307 #define MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT \
309 if (!subpathHasLength && hasLineCaps && aStrokeWidth > 0 && \
310 subpathContainsNonMoveTo && IsValidType(prevSegType) && \
311 (!IsMoveto(prevSegType) || IsClosePath(segType))) { \
312 ApproximateZeroLengthSubpathSquareCaps(aBuilder, segStart, \
317 already_AddRefed
<Path
> SVGPathData::BuildPath(PathBuilder
* aBuilder
,
318 StyleStrokeLinecap aStrokeLineCap
,
319 Float aStrokeWidth
) const {
320 if (mData
.IsEmpty() || !IsMoveto(SVGPathSegUtils::DecodeType(mData
[0]))) {
321 return nullptr; // paths without an initial moveto are invalid
324 bool hasLineCaps
= aStrokeLineCap
!= StyleStrokeLinecap::Butt
;
325 bool subpathHasLength
= false; // visual length
326 bool subpathContainsNonMoveTo
= false;
328 uint32_t segType
= PATHSEG_UNKNOWN
;
329 uint32_t prevSegType
= PATHSEG_UNKNOWN
;
330 Point
pathStart(0.0, 0.0); // start point of [sub]path
331 Point
segStart(0.0, 0.0);
333 Point cp1
, cp2
; // previous bezier's control points
334 Point tcp1
, tcp2
; // temporaries
336 // Regarding cp1 and cp2: If the previous segment was a cubic bezier curve,
337 // then cp2 is its second control point. If the previous segment was a
338 // quadratic curve, then cp1 is its (only) control point.
341 while (i
< mData
.Length()) {
342 segType
= SVGPathSegUtils::DecodeType(mData
[i
++]);
343 uint32_t argCount
= SVGPathSegUtils::ArgCountForType(segType
);
346 case PATHSEG_CLOSEPATH
:
347 // set this early to allow drawing of square caps for "M{x},{y} Z":
348 subpathContainsNonMoveTo
= true;
349 MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT
;
354 case PATHSEG_MOVETO_ABS
:
355 MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT
;
356 pathStart
= segEnd
= Point(mData
[i
], mData
[i
+ 1]);
357 aBuilder
->MoveTo(segEnd
);
358 subpathHasLength
= false;
361 case PATHSEG_MOVETO_REL
:
362 MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT
;
363 pathStart
= segEnd
= segStart
+ Point(mData
[i
], mData
[i
+ 1]);
364 aBuilder
->MoveTo(segEnd
);
365 subpathHasLength
= false;
368 case PATHSEG_LINETO_ABS
:
369 segEnd
= Point(mData
[i
], mData
[i
+ 1]);
370 if (segEnd
!= segStart
) {
371 subpathHasLength
= true;
372 aBuilder
->LineTo(segEnd
);
376 case PATHSEG_LINETO_REL
:
377 segEnd
= segStart
+ Point(mData
[i
], mData
[i
+ 1]);
378 if (segEnd
!= segStart
) {
379 subpathHasLength
= true;
380 aBuilder
->LineTo(segEnd
);
384 case PATHSEG_CURVETO_CUBIC_ABS
:
385 cp1
= Point(mData
[i
], mData
[i
+ 1]);
386 cp2
= Point(mData
[i
+ 2], mData
[i
+ 3]);
387 segEnd
= Point(mData
[i
+ 4], mData
[i
+ 5]);
388 if (segEnd
!= segStart
|| segEnd
!= cp1
|| segEnd
!= cp2
) {
389 subpathHasLength
= true;
390 aBuilder
->BezierTo(cp1
, cp2
, segEnd
);
394 case PATHSEG_CURVETO_CUBIC_REL
:
395 cp1
= segStart
+ Point(mData
[i
], mData
[i
+ 1]);
396 cp2
= segStart
+ Point(mData
[i
+ 2], mData
[i
+ 3]);
397 segEnd
= segStart
+ Point(mData
[i
+ 4], mData
[i
+ 5]);
398 if (segEnd
!= segStart
|| segEnd
!= cp1
|| segEnd
!= cp2
) {
399 subpathHasLength
= true;
400 aBuilder
->BezierTo(cp1
, cp2
, segEnd
);
404 case PATHSEG_CURVETO_QUADRATIC_ABS
:
405 cp1
= Point(mData
[i
], mData
[i
+ 1]);
406 // Convert quadratic curve to cubic curve:
407 tcp1
= segStart
+ (cp1
- segStart
) * 2 / 3;
408 segEnd
= Point(mData
[i
+ 2], mData
[i
+ 3]); // set before setting tcp2!
409 tcp2
= cp1
+ (segEnd
- cp1
) / 3;
410 if (segEnd
!= segStart
|| segEnd
!= cp1
) {
411 subpathHasLength
= true;
412 aBuilder
->BezierTo(tcp1
, tcp2
, segEnd
);
416 case PATHSEG_CURVETO_QUADRATIC_REL
:
417 cp1
= segStart
+ Point(mData
[i
], mData
[i
+ 1]);
418 // Convert quadratic curve to cubic curve:
419 tcp1
= segStart
+ (cp1
- segStart
) * 2 / 3;
421 Point(mData
[i
+ 2], mData
[i
+ 3]); // set before setting tcp2!
422 tcp2
= cp1
+ (segEnd
- cp1
) / 3;
423 if (segEnd
!= segStart
|| segEnd
!= cp1
) {
424 subpathHasLength
= true;
425 aBuilder
->BezierTo(tcp1
, tcp2
, segEnd
);
429 case PATHSEG_ARC_ABS
:
430 case PATHSEG_ARC_REL
: {
431 Point
radii(mData
[i
], mData
[i
+ 1]);
432 segEnd
= Point(mData
[i
+ 5], mData
[i
+ 6]);
433 if (segType
== PATHSEG_ARC_REL
) {
436 if (segEnd
!= segStart
) {
437 subpathHasLength
= true;
438 if (radii
.x
== 0.0f
|| radii
.y
== 0.0f
) {
439 aBuilder
->LineTo(segEnd
);
441 SVGArcConverter
converter(segStart
, segEnd
, radii
, mData
[i
+ 2],
442 mData
[i
+ 3] != 0, mData
[i
+ 4] != 0);
443 while (converter
.GetNextSegment(&cp1
, &cp2
, &segEnd
)) {
444 aBuilder
->BezierTo(cp1
, cp2
, segEnd
);
451 case PATHSEG_LINETO_HORIZONTAL_ABS
:
452 segEnd
= Point(mData
[i
], segStart
.y
);
453 if (segEnd
!= segStart
) {
454 subpathHasLength
= true;
455 aBuilder
->LineTo(segEnd
);
459 case PATHSEG_LINETO_HORIZONTAL_REL
:
460 segEnd
= segStart
+ Point(mData
[i
], 0.0f
);
461 if (segEnd
!= segStart
) {
462 subpathHasLength
= true;
463 aBuilder
->LineTo(segEnd
);
467 case PATHSEG_LINETO_VERTICAL_ABS
:
468 segEnd
= Point(segStart
.x
, mData
[i
]);
469 if (segEnd
!= segStart
) {
470 subpathHasLength
= true;
471 aBuilder
->LineTo(segEnd
);
475 case PATHSEG_LINETO_VERTICAL_REL
:
476 segEnd
= segStart
+ Point(0.0f
, mData
[i
]);
477 if (segEnd
!= segStart
) {
478 subpathHasLength
= true;
479 aBuilder
->LineTo(segEnd
);
483 case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS
:
484 cp1
= SVGPathSegUtils::IsCubicType(prevSegType
) ? segStart
* 2 - cp2
486 cp2
= Point(mData
[i
], mData
[i
+ 1]);
487 segEnd
= Point(mData
[i
+ 2], mData
[i
+ 3]);
488 if (segEnd
!= segStart
|| segEnd
!= cp1
|| segEnd
!= cp2
) {
489 subpathHasLength
= true;
490 aBuilder
->BezierTo(cp1
, cp2
, segEnd
);
494 case PATHSEG_CURVETO_CUBIC_SMOOTH_REL
:
495 cp1
= SVGPathSegUtils::IsCubicType(prevSegType
) ? segStart
* 2 - cp2
497 cp2
= segStart
+ Point(mData
[i
], mData
[i
+ 1]);
498 segEnd
= segStart
+ Point(mData
[i
+ 2], mData
[i
+ 3]);
499 if (segEnd
!= segStart
|| segEnd
!= cp1
|| segEnd
!= cp2
) {
500 subpathHasLength
= true;
501 aBuilder
->BezierTo(cp1
, cp2
, segEnd
);
505 case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS
:
506 cp1
= SVGPathSegUtils::IsQuadraticType(prevSegType
) ? segStart
* 2 - cp1
508 // Convert quadratic curve to cubic curve:
509 tcp1
= segStart
+ (cp1
- segStart
) * 2 / 3;
510 segEnd
= Point(mData
[i
], mData
[i
+ 1]); // set before setting tcp2!
511 tcp2
= cp1
+ (segEnd
- cp1
) / 3;
512 if (segEnd
!= segStart
|| segEnd
!= cp1
) {
513 subpathHasLength
= true;
514 aBuilder
->BezierTo(tcp1
, tcp2
, segEnd
);
518 case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL
:
519 cp1
= SVGPathSegUtils::IsQuadraticType(prevSegType
) ? segStart
* 2 - cp1
521 // Convert quadratic curve to cubic curve:
522 tcp1
= segStart
+ (cp1
- segStart
) * 2 / 3;
524 Point(mData
[i
], mData
[i
+ 1]); // changed before setting tcp2!
525 tcp2
= cp1
+ (segEnd
- cp1
) / 3;
526 if (segEnd
!= segStart
|| segEnd
!= cp1
) {
527 subpathHasLength
= true;
528 aBuilder
->BezierTo(tcp1
, tcp2
, segEnd
);
533 MOZ_ASSERT_UNREACHABLE("Bad path segment type");
534 return nullptr; // according to spec we'd use everything up to the bad
538 subpathContainsNonMoveTo
= !IsMoveto(segType
);
540 prevSegType
= segType
;
544 MOZ_ASSERT(i
== mData
.Length(), "Very, very bad - mData corrupt");
545 MOZ_ASSERT(prevSegType
== segType
,
546 "prevSegType should be left at the final segType");
548 MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT
;
550 return aBuilder
->Finish();
553 already_AddRefed
<Path
> SVGPathData::BuildPathForMeasuring() const {
554 // Since the path that we return will not be used for painting it doesn't
555 // matter what we pass to CreatePathBuilder as aFillRule. Hawever, we do want
556 // to pass something other than NS_STYLE_STROKE_LINECAP_SQUARE as
557 // aStrokeLineCap to avoid the insertion of extra little lines (by
558 // ApproximateZeroLengthSubpathSquareCaps), in which case the value that we
559 // pass as aStrokeWidth doesn't matter (since it's only used to determine the
560 // length of those extra little lines).
562 RefPtr
<DrawTarget
> drawTarget
=
563 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
564 RefPtr
<PathBuilder
> builder
=
565 drawTarget
->CreatePathBuilder(FillRule::FILL_WINDING
);
566 return BuildPath(builder
, StyleStrokeLinecap::Butt
, 0);
570 already_AddRefed
<Path
> SVGPathData::BuildPathForMeasuring(
571 Span
<const StylePathCommand
> aPath
) {
572 RefPtr
<DrawTarget
> drawTarget
=
573 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
574 RefPtr
<PathBuilder
> builder
=
575 drawTarget
->CreatePathBuilder(FillRule::FILL_WINDING
);
576 return BuildPath(aPath
, builder
, StyleStrokeLinecap::Butt
, 0);
579 // We could simplify this function because this is only used by CSS motion path
580 // and clip-path, which don't render the SVG Path. i.e. The returned path is
581 // used as a reference.
583 already_AddRefed
<Path
> SVGPathData::BuildPath(
584 Span
<const StylePathCommand
> aPath
, PathBuilder
* aBuilder
,
585 StyleStrokeLinecap aStrokeLineCap
, Float aStrokeWidth
, float aZoomFactor
) {
586 if (aPath
.IsEmpty() || !aPath
[0].IsMoveTo()) {
587 return nullptr; // paths without an initial moveto are invalid
590 bool hasLineCaps
= aStrokeLineCap
!= StyleStrokeLinecap::Butt
;
591 bool subpathHasLength
= false; // visual length
592 bool subpathContainsNonMoveTo
= false;
594 StylePathCommand::Tag segType
= StylePathCommand::Tag::Unknown
;
595 StylePathCommand::Tag prevSegType
= StylePathCommand::Tag::Unknown
;
596 Point
pathStart(0.0, 0.0); // start point of [sub]path
597 Point
segStart(0.0, 0.0);
599 Point cp1
, cp2
; // previous bezier's control points
600 Point tcp1
, tcp2
; // temporaries
602 auto scale
= [aZoomFactor
](const Point
& p
) {
603 return Point(p
.x
* aZoomFactor
, p
.y
* aZoomFactor
);
606 // Regarding cp1 and cp2: If the previous segment was a cubic bezier curve,
607 // then cp2 is its second control point. If the previous segment was a
608 // quadratic curve, then cp1 is its (only) control point.
610 for (const StylePathCommand
& cmd
: aPath
) {
613 case StylePathCommand::Tag::ClosePath
:
614 // set this early to allow drawing of square caps for "M{x},{y} Z":
615 subpathContainsNonMoveTo
= true;
616 MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT
;
620 case StylePathCommand::Tag::MoveTo
: {
621 MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT
;
622 const Point
& p
= cmd
.move_to
.point
.ConvertsToGfxPoint();
624 cmd
.move_to
.absolute
== StyleIsAbsolute::Yes
? p
: segStart
+ p
;
625 aBuilder
->MoveTo(scale(segEnd
));
626 subpathHasLength
= false;
629 case StylePathCommand::Tag::LineTo
: {
630 const Point
& p
= cmd
.line_to
.point
.ConvertsToGfxPoint();
632 cmd
.line_to
.absolute
== StyleIsAbsolute::Yes
? p
: segStart
+ p
;
633 if (segEnd
!= segStart
) {
634 subpathHasLength
= true;
635 aBuilder
->LineTo(scale(segEnd
));
639 case StylePathCommand::Tag::CurveTo
:
640 cp1
= cmd
.curve_to
.control1
.ConvertsToGfxPoint();
641 cp2
= cmd
.curve_to
.control2
.ConvertsToGfxPoint();
642 segEnd
= cmd
.curve_to
.point
.ConvertsToGfxPoint();
644 if (cmd
.curve_to
.absolute
== StyleIsAbsolute::No
) {
650 if (segEnd
!= segStart
|| segEnd
!= cp1
|| segEnd
!= cp2
) {
651 subpathHasLength
= true;
652 aBuilder
->BezierTo(scale(cp1
), scale(cp2
), scale(segEnd
));
656 case StylePathCommand::Tag::QuadBezierCurveTo
:
657 cp1
= cmd
.quad_bezier_curve_to
.control1
.ConvertsToGfxPoint();
658 segEnd
= cmd
.quad_bezier_curve_to
.point
.ConvertsToGfxPoint();
660 if (cmd
.quad_bezier_curve_to
.absolute
== StyleIsAbsolute::No
) {
662 segEnd
+= segStart
; // set before setting tcp2!
665 // Convert quadratic curve to cubic curve:
666 tcp1
= segStart
+ (cp1
- segStart
) * 2 / 3;
667 tcp2
= cp1
+ (segEnd
- cp1
) / 3;
669 if (segEnd
!= segStart
|| segEnd
!= cp1
) {
670 subpathHasLength
= true;
671 aBuilder
->BezierTo(scale(tcp1
), scale(tcp2
), scale(segEnd
));
675 case StylePathCommand::Tag::EllipticalArc
: {
676 const auto& arc
= cmd
.elliptical_arc
;
677 Point
radii(arc
.rx
, arc
.ry
);
678 segEnd
= arc
.point
.ConvertsToGfxPoint();
679 if (arc
.absolute
== StyleIsAbsolute::No
) {
682 if (segEnd
!= segStart
) {
683 subpathHasLength
= true;
684 if (radii
.x
== 0.0f
|| radii
.y
== 0.0f
) {
685 aBuilder
->LineTo(scale(segEnd
));
687 SVGArcConverter
converter(segStart
, segEnd
, radii
, arc
.angle
,
688 arc
.large_arc_flag
._0
, arc
.sweep_flag
._0
);
689 while (converter
.GetNextSegment(&cp1
, &cp2
, &segEnd
)) {
690 aBuilder
->BezierTo(scale(cp1
), scale(cp2
), scale(segEnd
));
696 case StylePathCommand::Tag::HorizontalLineTo
:
697 if (cmd
.horizontal_line_to
.absolute
== StyleIsAbsolute::Yes
) {
698 segEnd
= Point(cmd
.horizontal_line_to
.x
, segStart
.y
);
700 segEnd
= segStart
+ Point(cmd
.horizontal_line_to
.x
, 0.0f
);
703 if (segEnd
!= segStart
) {
704 subpathHasLength
= true;
705 aBuilder
->LineTo(scale(segEnd
));
709 case StylePathCommand::Tag::VerticalLineTo
:
710 if (cmd
.vertical_line_to
.absolute
== StyleIsAbsolute::Yes
) {
711 segEnd
= Point(segStart
.x
, cmd
.vertical_line_to
.y
);
713 segEnd
= segStart
+ Point(0.0f
, cmd
.vertical_line_to
.y
);
716 if (segEnd
!= segStart
) {
717 subpathHasLength
= true;
718 aBuilder
->LineTo(scale(segEnd
));
722 case StylePathCommand::Tag::SmoothCurveTo
:
723 cp1
= IsCubicType(prevSegType
) ? segStart
* 2 - cp2
: segStart
;
724 cp2
= cmd
.smooth_curve_to
.control2
.ConvertsToGfxPoint();
725 segEnd
= cmd
.smooth_curve_to
.point
.ConvertsToGfxPoint();
727 if (cmd
.smooth_curve_to
.absolute
== StyleIsAbsolute::No
) {
732 if (segEnd
!= segStart
|| segEnd
!= cp1
|| segEnd
!= cp2
) {
733 subpathHasLength
= true;
734 aBuilder
->BezierTo(scale(cp1
), scale(cp2
), scale(segEnd
));
738 case StylePathCommand::Tag::SmoothQuadBezierCurveTo
: {
739 cp1
= IsQuadraticType(prevSegType
) ? segStart
* 2 - cp1
: segStart
;
740 // Convert quadratic curve to cubic curve:
741 tcp1
= segStart
+ (cp1
- segStart
) * 2 / 3;
744 cmd
.smooth_quad_bezier_curve_to
.point
.ConvertsToGfxPoint();
745 // set before setting tcp2!
747 cmd
.smooth_quad_bezier_curve_to
.absolute
== StyleIsAbsolute::Yes
750 tcp2
= cp1
+ (segEnd
- cp1
) / 3;
752 if (segEnd
!= segStart
|| segEnd
!= cp1
) {
753 subpathHasLength
= true;
754 aBuilder
->BezierTo(scale(tcp1
), scale(tcp2
), scale(segEnd
));
758 case StylePathCommand::Tag::Unknown
:
759 MOZ_ASSERT_UNREACHABLE("Unacceptable path segment type");
763 subpathContainsNonMoveTo
= !IsMoveto(segType
);
764 prevSegType
= segType
;
768 MOZ_ASSERT(prevSegType
== segType
,
769 "prevSegType should be left at the final segType");
771 MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT
;
773 return aBuilder
->Finish();
776 static double AngleOfVector(const Point
& aVector
) {
777 // C99 says about atan2 "A domain error may occur if both arguments are
778 // zero" and "On a domain error, the function returns an implementation-
779 // defined value". In the case of atan2 the implementation-defined value
780 // seems to commonly be zero, but it could just as easily be a NaN value.
781 // We specifically want zero in this case, hence the check:
783 return (aVector
!= Point(0.0, 0.0)) ? atan2(aVector
.y
, aVector
.x
) : 0.0;
786 static float AngleOfVector(const Point
& cp1
, const Point
& cp2
) {
787 return static_cast<float>(AngleOfVector(cp1
- cp2
));
790 // This implements F.6.5 and F.6.6 of
791 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
792 static std::tuple
<float, float, float, float>
793 /* rx, ry, segStartAngle, segEndAngle */
794 ComputeSegAnglesAndCorrectRadii(const Point
& aSegStart
, const Point
& aSegEnd
,
795 const float aAngle
, const bool aLargeArcFlag
,
796 const bool aSweepFlag
, const float aRx
,
798 float rx
= fabs(aRx
); // F.6.6.1
799 float ry
= fabs(aRy
);
802 const float angle
= static_cast<float>(aAngle
* M_PI
/ 180.0);
803 double x1p
= cos(angle
) * (aSegStart
.x
- aSegEnd
.x
) / 2.0 +
804 sin(angle
) * (aSegStart
.y
- aSegEnd
.y
) / 2.0;
805 double y1p
= -sin(angle
) * (aSegStart
.x
- aSegEnd
.x
) / 2.0 +
806 cos(angle
) * (aSegStart
.y
- aSegEnd
.y
) / 2.0;
808 // This is the root in F.6.5.2 and the numerator under that root:
811 rx
* rx
* ry
* ry
- rx
* rx
* y1p
* y1p
- ry
* ry
* x1p
* x1p
;
813 if (numerator
>= 0.0) {
814 root
= sqrt(numerator
/ (rx
* rx
* y1p
* y1p
+ ry
* ry
* x1p
* x1p
));
815 if (aLargeArcFlag
== aSweepFlag
) root
= -root
;
817 // F.6.6 step 3 - |numerator < 0.0|. This is equivalent to the result
818 // of F.6.6.2 (lamedh) being greater than one. What we have here is
819 // ellipse radii that are too small for the ellipse to reach between
820 // segStart and segEnd. We scale the radii up uniformly so that the
821 // ellipse is just big enough to fit (i.e. to the point where there is
822 // exactly one solution).
825 1.0 - numerator
/ (rx
* rx
* ry
* ry
); // equiv to eqn F.6.6.2
826 double s
= sqrt(lamedh
);
827 rx
= static_cast<float>((double)rx
* s
); // F.6.6.3
828 ry
= static_cast<float>((double)ry
* s
);
832 double cxp
= root
* rx
* y1p
/ ry
; // F.6.5.2
833 double cyp
= -root
* ry
* x1p
/ rx
;
836 AngleOfVector(Point(static_cast<float>((x1p
- cxp
) / rx
),
837 static_cast<float>((y1p
- cyp
) / ry
))); // F.6.5.5
839 AngleOfVector(Point(static_cast<float>((-x1p
- cxp
) / rx
),
840 static_cast<float>((-y1p
- cyp
) / ry
))) - // F.6.5.6
842 if (!aSweepFlag
&& delta
> 0) {
844 } else if (aSweepFlag
&& delta
< 0) {
848 double tx1
, ty1
, tx2
, ty2
;
849 tx1
= -cos(angle
) * rx
* sin(theta
) - sin(angle
) * ry
* cos(theta
);
850 ty1
= -sin(angle
) * rx
* sin(theta
) + cos(angle
) * ry
* cos(theta
);
851 tx2
= -cos(angle
) * rx
* sin(theta
+ delta
) -
852 sin(angle
) * ry
* cos(theta
+ delta
);
853 ty2
= -sin(angle
) * rx
* sin(theta
+ delta
) +
854 cos(angle
) * ry
* cos(theta
+ delta
);
863 return {rx
, ry
, static_cast<float>(atan2(ty1
, tx1
)),
864 static_cast<float>(atan2(ty2
, tx2
))};
867 void SVGPathData::GetMarkerPositioningData(nsTArray
<SVGMark
>* aMarks
) const {
868 // This code should assume that ANY type of segment can appear at ANY index.
869 // It should also assume that segments such as M and Z can appear in weird
870 // places, and repeat multiple times consecutively.
872 // info on current [sub]path (reset every M command):
873 Point
pathStart(0.0, 0.0);
874 float pathStartAngle
= 0.0f
;
875 uint32_t pathStartIndex
= 0;
877 // info on previous segment:
878 uint16_t prevSegType
= PATHSEG_UNKNOWN
;
879 Point
prevSegEnd(0.0, 0.0);
880 float prevSegEndAngle
= 0.0f
;
881 Point prevCP
; // if prev seg was a bezier, this was its last control point
884 while (i
< mData
.Length()) {
885 // info on current segment:
887 SVGPathSegUtils::DecodeType(mData
[i
++]); // advances i to args
888 Point
& segStart
= prevSegEnd
;
890 float segStartAngle
, segEndAngle
;
892 switch (segType
) // to find segStartAngle, segEnd and segEndAngle
894 case PATHSEG_CLOSEPATH
:
896 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
899 case PATHSEG_MOVETO_ABS
:
900 case PATHSEG_MOVETO_REL
:
901 if (segType
== PATHSEG_MOVETO_ABS
) {
902 segEnd
= Point(mData
[i
], mData
[i
+ 1]);
904 segEnd
= segStart
+ Point(mData
[i
], mData
[i
+ 1]);
907 pathStartIndex
= aMarks
->Length();
908 // If authors are going to specify multiple consecutive moveto commands
909 // with markers, me might as well make the angle do something useful:
910 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
914 case PATHSEG_LINETO_ABS
:
915 case PATHSEG_LINETO_REL
:
916 if (segType
== PATHSEG_LINETO_ABS
) {
917 segEnd
= Point(mData
[i
], mData
[i
+ 1]);
919 segEnd
= segStart
+ Point(mData
[i
], mData
[i
+ 1]);
921 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
925 case PATHSEG_CURVETO_CUBIC_ABS
:
926 case PATHSEG_CURVETO_CUBIC_REL
: {
927 Point cp1
, cp2
; // control points
928 if (segType
== PATHSEG_CURVETO_CUBIC_ABS
) {
929 cp1
= Point(mData
[i
], mData
[i
+ 1]);
930 cp2
= Point(mData
[i
+ 2], mData
[i
+ 3]);
931 segEnd
= Point(mData
[i
+ 4], mData
[i
+ 5]);
933 cp1
= segStart
+ Point(mData
[i
], mData
[i
+ 1]);
934 cp2
= segStart
+ Point(mData
[i
+ 2], mData
[i
+ 3]);
935 segEnd
= segStart
+ Point(mData
[i
+ 4], mData
[i
+ 5]);
938 segStartAngle
= AngleOfVector(
939 cp1
== segStart
? (cp1
== cp2
? segEnd
: cp2
) : cp1
, segStart
);
940 segEndAngle
= AngleOfVector(
941 segEnd
, cp2
== segEnd
? (cp1
== cp2
? segStart
: cp1
) : cp2
);
946 case PATHSEG_CURVETO_QUADRATIC_ABS
:
947 case PATHSEG_CURVETO_QUADRATIC_REL
: {
948 Point cp1
; // control point
949 if (segType
== PATHSEG_CURVETO_QUADRATIC_ABS
) {
950 cp1
= Point(mData
[i
], mData
[i
+ 1]);
951 segEnd
= Point(mData
[i
+ 2], mData
[i
+ 3]);
953 cp1
= segStart
+ Point(mData
[i
], mData
[i
+ 1]);
954 segEnd
= segStart
+ Point(mData
[i
+ 2], mData
[i
+ 3]);
957 segStartAngle
= AngleOfVector(cp1
== segStart
? segEnd
: cp1
, segStart
);
958 segEndAngle
= AngleOfVector(segEnd
, cp1
== segEnd
? segStart
: cp1
);
963 case PATHSEG_ARC_ABS
:
964 case PATHSEG_ARC_REL
: {
966 float ry
= mData
[i
+ 1];
967 float angle
= mData
[i
+ 2];
968 bool largeArcFlag
= mData
[i
+ 3] != 0.0f
;
969 bool sweepFlag
= mData
[i
+ 4] != 0.0f
;
970 if (segType
== PATHSEG_ARC_ABS
) {
971 segEnd
= Point(mData
[i
+ 5], mData
[i
+ 6]);
973 segEnd
= segStart
+ Point(mData
[i
+ 5], mData
[i
+ 6]);
976 // See section F.6 of SVG 1.1 for details on what we're doing here:
977 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
979 if (segStart
== segEnd
) {
980 // F.6.2 says "If the endpoints (x1, y1) and (x2, y2) are identical,
981 // then this is equivalent to omitting the elliptical arc segment
982 // entirely." We take that very literally here, not adding a mark, and
983 // not even setting any of the 'prev' variables so that it's as if
984 // this arc had never existed; note the difference this will make e.g.
985 // if the arc is proceeded by a bezier curve and followed by a
986 // "smooth" bezier curve of the same degree!
991 // Below we have funny interleaving of F.6.6 (Correction of out-of-range
992 // radii) and F.6.5 (Conversion from endpoint to center
993 // parameterization) which is designed to avoid some unnecessary
996 if (rx
== 0.0 || ry
== 0.0) {
997 // F.6.6 step 1 - straight line or coincidental points
998 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
1003 std::tie(rx
, ry
, segStartAngle
, segEndAngle
) =
1004 ComputeSegAnglesAndCorrectRadii(segStart
, segEnd
, angle
,
1005 largeArcFlag
, sweepFlag
, rx
, ry
);
1010 case PATHSEG_LINETO_HORIZONTAL_ABS
:
1011 case PATHSEG_LINETO_HORIZONTAL_REL
:
1012 if (segType
== PATHSEG_LINETO_HORIZONTAL_ABS
) {
1013 segEnd
= Point(mData
[i
++], segStart
.y
);
1015 segEnd
= segStart
+ Point(mData
[i
++], 0.0f
);
1017 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
1020 case PATHSEG_LINETO_VERTICAL_ABS
:
1021 case PATHSEG_LINETO_VERTICAL_REL
:
1022 if (segType
== PATHSEG_LINETO_VERTICAL_ABS
) {
1023 segEnd
= Point(segStart
.x
, mData
[i
++]);
1025 segEnd
= segStart
+ Point(0.0f
, mData
[i
++]);
1027 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
1030 case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS
:
1031 case PATHSEG_CURVETO_CUBIC_SMOOTH_REL
: {
1032 Point cp1
= SVGPathSegUtils::IsCubicType(prevSegType
)
1033 ? segStart
* 2 - prevCP
1036 if (segType
== PATHSEG_CURVETO_CUBIC_SMOOTH_ABS
) {
1037 cp2
= Point(mData
[i
], mData
[i
+ 1]);
1038 segEnd
= Point(mData
[i
+ 2], mData
[i
+ 3]);
1040 cp2
= segStart
+ Point(mData
[i
], mData
[i
+ 1]);
1041 segEnd
= segStart
+ Point(mData
[i
+ 2], mData
[i
+ 3]);
1044 segStartAngle
= AngleOfVector(
1045 cp1
== segStart
? (cp1
== cp2
? segEnd
: cp2
) : cp1
, segStart
);
1046 segEndAngle
= AngleOfVector(
1047 segEnd
, cp2
== segEnd
? (cp1
== cp2
? segStart
: cp1
) : cp2
);
1052 case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS
:
1053 case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL
: {
1054 Point cp1
= SVGPathSegUtils::IsQuadraticType(prevSegType
)
1055 ? segStart
* 2 - prevCP
1057 if (segType
== PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS
) {
1058 segEnd
= Point(mData
[i
], mData
[i
+ 1]);
1060 segEnd
= segStart
+ Point(mData
[i
], mData
[i
+ 1]);
1063 segStartAngle
= AngleOfVector(cp1
== segStart
? segEnd
: cp1
, segStart
);
1064 segEndAngle
= AngleOfVector(segEnd
, cp1
== segEnd
? segStart
: cp1
);
1070 // Leave any existing marks in aMarks so we have a visual indication of
1071 // when things went wrong.
1072 MOZ_ASSERT(false, "Unknown segment type - path corruption?");
1076 // Set the angle of the mark at the start of this segment:
1077 if (aMarks
->Length()) {
1078 SVGMark
& mark
= aMarks
->LastElement();
1079 if (!IsMoveto(segType
) && IsMoveto(prevSegType
)) {
1080 // start of new subpath
1081 pathStartAngle
= mark
.angle
= segStartAngle
;
1082 } else if (IsMoveto(segType
) && !IsMoveto(prevSegType
)) {
1084 if (prevSegType
!= PATHSEG_CLOSEPATH
) mark
.angle
= prevSegEndAngle
;
1086 if (!(segType
== PATHSEG_CLOSEPATH
&& prevSegType
== PATHSEG_CLOSEPATH
))
1088 SVGContentUtils::AngleBisect(prevSegEndAngle
, segStartAngle
);
1092 // Add the mark at the end of this segment, and set its position:
1093 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1094 // pretended earlier.
1095 aMarks
->AppendElement(SVGMark(static_cast<float>(segEnd
.x
),
1096 static_cast<float>(segEnd
.y
), 0.0f
,
1099 if (segType
== PATHSEG_CLOSEPATH
&& prevSegType
!= PATHSEG_CLOSEPATH
) {
1100 aMarks
->LastElement().angle
= aMarks
->ElementAt(pathStartIndex
).angle
=
1101 SVGContentUtils::AngleBisect(segEndAngle
, pathStartAngle
);
1104 prevSegType
= segType
;
1105 prevSegEnd
= segEnd
;
1106 prevSegEndAngle
= segEndAngle
;
1109 MOZ_ASSERT(i
== mData
.Length(), "Very, very bad - mData corrupt");
1111 if (aMarks
->Length()) {
1112 if (prevSegType
!= PATHSEG_CLOSEPATH
) {
1113 aMarks
->LastElement().angle
= prevSegEndAngle
;
1115 aMarks
->LastElement().type
= SVGMark::eEnd
;
1116 aMarks
->ElementAt(0).type
= SVGMark::eStart
;
1120 // Basically, this is identical to the above function, but replace |mData| with
1121 // |aPath|. We probably can factor out some identical calculation, but I believe
1122 // the above one will be removed because we will use any kind of array of
1123 // StylePathCommand for SVG d attribute in the future.
1125 void SVGPathData::GetMarkerPositioningData(Span
<const StylePathCommand
> aPath
,
1126 nsTArray
<SVGMark
>* aMarks
) {
1127 if (aPath
.IsEmpty()) {
1131 // info on current [sub]path (reset every M command):
1132 Point
pathStart(0.0, 0.0);
1133 float pathStartAngle
= 0.0f
;
1134 uint32_t pathStartIndex
= 0;
1136 // info on previous segment:
1137 StylePathCommand::Tag prevSegType
= StylePathCommand::Tag::Unknown
;
1138 Point
prevSegEnd(0.0, 0.0);
1139 float prevSegEndAngle
= 0.0f
;
1140 Point prevCP
; // if prev seg was a bezier, this was its last control point
1142 StylePathCommand::Tag segType
= StylePathCommand::Tag::Unknown
;
1143 for (const StylePathCommand
& cmd
: aPath
) {
1145 Point
& segStart
= prevSegEnd
;
1147 float segStartAngle
, segEndAngle
;
1149 switch (segType
) // to find segStartAngle, segEnd and segEndAngle
1151 case StylePathCommand::Tag::ClosePath
:
1153 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
1156 case StylePathCommand::Tag::MoveTo
: {
1157 const Point
& p
= cmd
.move_to
.point
.ConvertsToGfxPoint();
1158 pathStart
= segEnd
=
1159 cmd
.move_to
.absolute
== StyleIsAbsolute::Yes
? p
: segStart
+ p
;
1160 pathStartIndex
= aMarks
->Length();
1161 // If authors are going to specify multiple consecutive moveto commands
1162 // with markers, me might as well make the angle do something useful:
1163 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
1166 case StylePathCommand::Tag::LineTo
: {
1167 const Point
& p
= cmd
.line_to
.point
.ConvertsToGfxPoint();
1169 cmd
.line_to
.absolute
== StyleIsAbsolute::Yes
? p
: segStart
+ p
;
1170 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
1173 case StylePathCommand::Tag::CurveTo
: {
1174 Point cp1
= cmd
.curve_to
.control1
.ConvertsToGfxPoint();
1175 Point cp2
= cmd
.curve_to
.control2
.ConvertsToGfxPoint();
1176 segEnd
= cmd
.curve_to
.point
.ConvertsToGfxPoint();
1178 if (cmd
.curve_to
.absolute
== StyleIsAbsolute::No
) {
1185 segStartAngle
= AngleOfVector(
1186 cp1
== segStart
? (cp1
== cp2
? segEnd
: cp2
) : cp1
, segStart
);
1187 segEndAngle
= AngleOfVector(
1188 segEnd
, cp2
== segEnd
? (cp1
== cp2
? segStart
: cp1
) : cp2
);
1191 case StylePathCommand::Tag::QuadBezierCurveTo
: {
1192 Point cp1
= cmd
.quad_bezier_curve_to
.control1
.ConvertsToGfxPoint();
1193 segEnd
= cmd
.quad_bezier_curve_to
.point
.ConvertsToGfxPoint();
1195 if (cmd
.quad_bezier_curve_to
.absolute
== StyleIsAbsolute::No
) {
1197 segEnd
+= segStart
; // set before setting tcp2!
1201 segStartAngle
= AngleOfVector(cp1
== segStart
? segEnd
: cp1
, segStart
);
1202 segEndAngle
= AngleOfVector(segEnd
, cp1
== segEnd
? segStart
: cp1
);
1205 case StylePathCommand::Tag::EllipticalArc
: {
1206 const auto& arc
= cmd
.elliptical_arc
;
1209 float angle
= arc
.angle
;
1210 bool largeArcFlag
= arc
.large_arc_flag
._0
;
1211 bool sweepFlag
= arc
.sweep_flag
._0
;
1212 Point
radii(arc
.rx
, arc
.ry
);
1213 segEnd
= arc
.point
.ConvertsToGfxPoint();
1214 if (arc
.absolute
== StyleIsAbsolute::No
) {
1218 // See section F.6 of SVG 1.1 for details on what we're doing here:
1219 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
1221 if (segStart
== segEnd
) {
1222 // F.6.2 says "If the endpoints (x1, y1) and (x2, y2) are identical,
1223 // then this is equivalent to omitting the elliptical arc segment
1224 // entirely." We take that very literally here, not adding a mark, and
1225 // not even setting any of the 'prev' variables so that it's as if
1226 // this arc had never existed; note the difference this will make e.g.
1227 // if the arc is proceeded by a bezier curve and followed by a
1228 // "smooth" bezier curve of the same degree!
1232 // Below we have funny interleaving of F.6.6 (Correction of out-of-range
1233 // radii) and F.6.5 (Conversion from endpoint to center
1234 // parameterization) which is designed to avoid some unnecessary
1237 if (rx
== 0.0 || ry
== 0.0) {
1238 // F.6.6 step 1 - straight line or coincidental points
1239 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
1243 std::tie(rx
, ry
, segStartAngle
, segEndAngle
) =
1244 ComputeSegAnglesAndCorrectRadii(segStart
, segEnd
, angle
,
1245 largeArcFlag
, sweepFlag
, rx
, ry
);
1248 case StylePathCommand::Tag::HorizontalLineTo
: {
1249 if (cmd
.horizontal_line_to
.absolute
== StyleIsAbsolute::Yes
) {
1250 segEnd
= Point(cmd
.horizontal_line_to
.x
, segStart
.y
);
1252 segEnd
= segStart
+ Point(cmd
.horizontal_line_to
.x
, 0.0f
);
1254 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
1257 case StylePathCommand::Tag::VerticalLineTo
: {
1258 if (cmd
.vertical_line_to
.absolute
== StyleIsAbsolute::Yes
) {
1259 segEnd
= Point(segStart
.x
, cmd
.vertical_line_to
.y
);
1261 segEnd
= segStart
+ Point(0.0f
, cmd
.vertical_line_to
.y
);
1263 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
1266 case StylePathCommand::Tag::SmoothCurveTo
: {
1267 Point cp1
= IsCubicType(prevSegType
) ? segStart
* 2 - prevCP
: segStart
;
1268 Point cp2
= cmd
.smooth_curve_to
.control2
.ConvertsToGfxPoint();
1269 segEnd
= cmd
.smooth_curve_to
.point
.ConvertsToGfxPoint();
1271 if (cmd
.smooth_curve_to
.absolute
== StyleIsAbsolute::No
) {
1277 segStartAngle
= AngleOfVector(
1278 cp1
== segStart
? (cp1
== cp2
? segEnd
: cp2
) : cp1
, segStart
);
1279 segEndAngle
= AngleOfVector(
1280 segEnd
, cp2
== segEnd
? (cp1
== cp2
? segStart
: cp1
) : cp2
);
1283 case StylePathCommand::Tag::SmoothQuadBezierCurveTo
: {
1285 IsQuadraticType(prevSegType
) ? segStart
* 2 - prevCP
: segStart
;
1287 cmd
.smooth_quad_bezier_curve_to
.absolute
== StyleIsAbsolute::Yes
1288 ? cmd
.smooth_quad_bezier_curve_to
.point
.ConvertsToGfxPoint()
1289 : segStart
+ cmd
.smooth_quad_bezier_curve_to
.point
1290 .ConvertsToGfxPoint();
1293 segStartAngle
= AngleOfVector(cp1
== segStart
? segEnd
: cp1
, segStart
);
1294 segEndAngle
= AngleOfVector(segEnd
, cp1
== segEnd
? segStart
: cp1
);
1297 case StylePathCommand::Tag::Unknown
:
1298 // Leave any existing marks in aMarks so we have a visual indication of
1299 // when things went wrong.
1300 MOZ_ASSERT_UNREACHABLE("Unknown segment type - path corruption?");
1304 // Set the angle of the mark at the start of this segment:
1305 if (aMarks
->Length()) {
1306 SVGMark
& mark
= aMarks
->LastElement();
1307 if (!IsMoveto(segType
) && IsMoveto(prevSegType
)) {
1308 // start of new subpath
1309 pathStartAngle
= mark
.angle
= segStartAngle
;
1310 } else if (IsMoveto(segType
) && !IsMoveto(prevSegType
)) {
1312 if (prevSegType
!= StylePathCommand::Tag::ClosePath
) {
1313 mark
.angle
= prevSegEndAngle
;
1315 } else if (!(segType
== StylePathCommand::Tag::ClosePath
&&
1316 prevSegType
== StylePathCommand::Tag::ClosePath
)) {
1318 SVGContentUtils::AngleBisect(prevSegEndAngle
, segStartAngle
);
1322 // Add the mark at the end of this segment, and set its position:
1323 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1324 // pretended earlier.
1325 aMarks
->AppendElement(SVGMark(static_cast<float>(segEnd
.x
),
1326 static_cast<float>(segEnd
.y
), 0.0f
,
1329 if (segType
== StylePathCommand::Tag::ClosePath
&&
1330 prevSegType
!= StylePathCommand::Tag::ClosePath
) {
1331 aMarks
->LastElement().angle
= aMarks
->ElementAt(pathStartIndex
).angle
=
1332 SVGContentUtils::AngleBisect(segEndAngle
, pathStartAngle
);
1335 prevSegType
= segType
;
1336 prevSegEnd
= segEnd
;
1337 prevSegEndAngle
= segEndAngle
;
1340 if (aMarks
->Length()) {
1341 if (prevSegType
!= StylePathCommand::Tag::ClosePath
) {
1342 aMarks
->LastElement().angle
= prevSegEndAngle
;
1344 aMarks
->LastElement().type
= SVGMark::eEnd
;
1345 aMarks
->ElementAt(0).type
= SVGMark::eStart
;
1349 size_t SVGPathData::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf
) const {
1350 return mData
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
1353 size_t SVGPathData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
1354 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf
);
1357 } // namespace mozilla