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
, const Point
& aOffset
,
587 if (aPath
.IsEmpty() || !aPath
[0].IsMoveTo()) {
588 return nullptr; // paths without an initial moveto are invalid
591 bool hasLineCaps
= aStrokeLineCap
!= StyleStrokeLinecap::Butt
;
592 bool subpathHasLength
= false; // visual length
593 bool subpathContainsNonMoveTo
= false;
595 StylePathCommand::Tag segType
= StylePathCommand::Tag::Unknown
;
596 StylePathCommand::Tag prevSegType
= StylePathCommand::Tag::Unknown
;
597 Point
pathStart(0.0, 0.0); // start point of [sub]path
598 Point
segStart(0.0, 0.0);
600 Point cp1
, cp2
; // previous bezier's control points
601 Point tcp1
, tcp2
; // temporaries
603 auto scale
= [aOffset
, aZoomFactor
](const Point
& p
) {
604 return Point(p
.x
* aZoomFactor
, p
.y
* aZoomFactor
) + aOffset
;
607 // Regarding cp1 and cp2: If the previous segment was a cubic bezier curve,
608 // then cp2 is its second control point. If the previous segment was a
609 // quadratic curve, then cp1 is its (only) control point.
611 for (const StylePathCommand
& cmd
: aPath
) {
614 case StylePathCommand::Tag::ClosePath
:
615 // set this early to allow drawing of square caps for "M{x},{y} Z":
616 subpathContainsNonMoveTo
= true;
617 MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT
;
621 case StylePathCommand::Tag::MoveTo
: {
622 MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT
;
623 const Point
& p
= cmd
.move_to
.point
.ConvertsToGfxPoint();
625 cmd
.move_to
.absolute
== StyleIsAbsolute::Yes
? p
: segStart
+ p
;
626 aBuilder
->MoveTo(scale(segEnd
));
627 subpathHasLength
= false;
630 case StylePathCommand::Tag::LineTo
: {
631 const Point
& p
= cmd
.line_to
.point
.ConvertsToGfxPoint();
633 cmd
.line_to
.absolute
== StyleIsAbsolute::Yes
? p
: segStart
+ p
;
634 if (segEnd
!= segStart
) {
635 subpathHasLength
= true;
636 aBuilder
->LineTo(scale(segEnd
));
640 case StylePathCommand::Tag::CurveTo
:
641 cp1
= cmd
.curve_to
.control1
.ConvertsToGfxPoint();
642 cp2
= cmd
.curve_to
.control2
.ConvertsToGfxPoint();
643 segEnd
= cmd
.curve_to
.point
.ConvertsToGfxPoint();
645 if (cmd
.curve_to
.absolute
== StyleIsAbsolute::No
) {
651 if (segEnd
!= segStart
|| segEnd
!= cp1
|| segEnd
!= cp2
) {
652 subpathHasLength
= true;
653 aBuilder
->BezierTo(scale(cp1
), scale(cp2
), scale(segEnd
));
657 case StylePathCommand::Tag::QuadBezierCurveTo
:
658 cp1
= cmd
.quad_bezier_curve_to
.control1
.ConvertsToGfxPoint();
659 segEnd
= cmd
.quad_bezier_curve_to
.point
.ConvertsToGfxPoint();
661 if (cmd
.quad_bezier_curve_to
.absolute
== StyleIsAbsolute::No
) {
663 segEnd
+= segStart
; // set before setting tcp2!
666 // Convert quadratic curve to cubic curve:
667 tcp1
= segStart
+ (cp1
- segStart
) * 2 / 3;
668 tcp2
= cp1
+ (segEnd
- cp1
) / 3;
670 if (segEnd
!= segStart
|| segEnd
!= cp1
) {
671 subpathHasLength
= true;
672 aBuilder
->BezierTo(scale(tcp1
), scale(tcp2
), scale(segEnd
));
676 case StylePathCommand::Tag::EllipticalArc
: {
677 const auto& arc
= cmd
.elliptical_arc
;
678 Point
radii(arc
.rx
, arc
.ry
);
679 segEnd
= arc
.point
.ConvertsToGfxPoint();
680 if (arc
.absolute
== StyleIsAbsolute::No
) {
683 if (segEnd
!= segStart
) {
684 subpathHasLength
= true;
685 if (radii
.x
== 0.0f
|| radii
.y
== 0.0f
) {
686 aBuilder
->LineTo(scale(segEnd
));
688 SVGArcConverter
converter(segStart
, segEnd
, radii
, arc
.angle
,
689 arc
.large_arc_flag
._0
, arc
.sweep_flag
._0
);
690 while (converter
.GetNextSegment(&cp1
, &cp2
, &segEnd
)) {
691 aBuilder
->BezierTo(scale(cp1
), scale(cp2
), scale(segEnd
));
697 case StylePathCommand::Tag::HorizontalLineTo
:
698 if (cmd
.horizontal_line_to
.absolute
== StyleIsAbsolute::Yes
) {
699 segEnd
= Point(cmd
.horizontal_line_to
.x
, segStart
.y
);
701 segEnd
= segStart
+ Point(cmd
.horizontal_line_to
.x
, 0.0f
);
704 if (segEnd
!= segStart
) {
705 subpathHasLength
= true;
706 aBuilder
->LineTo(scale(segEnd
));
710 case StylePathCommand::Tag::VerticalLineTo
:
711 if (cmd
.vertical_line_to
.absolute
== StyleIsAbsolute::Yes
) {
712 segEnd
= Point(segStart
.x
, cmd
.vertical_line_to
.y
);
714 segEnd
= segStart
+ Point(0.0f
, cmd
.vertical_line_to
.y
);
717 if (segEnd
!= segStart
) {
718 subpathHasLength
= true;
719 aBuilder
->LineTo(scale(segEnd
));
723 case StylePathCommand::Tag::SmoothCurveTo
:
724 cp1
= IsCubicType(prevSegType
) ? segStart
* 2 - cp2
: segStart
;
725 cp2
= cmd
.smooth_curve_to
.control2
.ConvertsToGfxPoint();
726 segEnd
= cmd
.smooth_curve_to
.point
.ConvertsToGfxPoint();
728 if (cmd
.smooth_curve_to
.absolute
== StyleIsAbsolute::No
) {
733 if (segEnd
!= segStart
|| segEnd
!= cp1
|| segEnd
!= cp2
) {
734 subpathHasLength
= true;
735 aBuilder
->BezierTo(scale(cp1
), scale(cp2
), scale(segEnd
));
739 case StylePathCommand::Tag::SmoothQuadBezierCurveTo
: {
740 cp1
= IsQuadraticType(prevSegType
) ? segStart
* 2 - cp1
: segStart
;
741 // Convert quadratic curve to cubic curve:
742 tcp1
= segStart
+ (cp1
- segStart
) * 2 / 3;
745 cmd
.smooth_quad_bezier_curve_to
.point
.ConvertsToGfxPoint();
746 // set before setting tcp2!
748 cmd
.smooth_quad_bezier_curve_to
.absolute
== StyleIsAbsolute::Yes
751 tcp2
= cp1
+ (segEnd
- cp1
) / 3;
753 if (segEnd
!= segStart
|| segEnd
!= cp1
) {
754 subpathHasLength
= true;
755 aBuilder
->BezierTo(scale(tcp1
), scale(tcp2
), scale(segEnd
));
759 case StylePathCommand::Tag::Unknown
:
760 MOZ_ASSERT_UNREACHABLE("Unacceptable path segment type");
764 subpathContainsNonMoveTo
= !IsMoveto(segType
);
765 prevSegType
= segType
;
769 MOZ_ASSERT(prevSegType
== segType
,
770 "prevSegType should be left at the final segType");
772 MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT
;
774 return aBuilder
->Finish();
777 static double AngleOfVector(const Point
& aVector
) {
778 // C99 says about atan2 "A domain error may occur if both arguments are
779 // zero" and "On a domain error, the function returns an implementation-
780 // defined value". In the case of atan2 the implementation-defined value
781 // seems to commonly be zero, but it could just as easily be a NaN value.
782 // We specifically want zero in this case, hence the check:
784 return (aVector
!= Point(0.0, 0.0)) ? atan2(aVector
.y
, aVector
.x
) : 0.0;
787 static float AngleOfVector(const Point
& cp1
, const Point
& cp2
) {
788 return static_cast<float>(AngleOfVector(cp1
- cp2
));
791 // This implements F.6.5 and F.6.6 of
792 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
793 static std::tuple
<float, float, float, float>
794 /* rx, ry, segStartAngle, segEndAngle */
795 ComputeSegAnglesAndCorrectRadii(const Point
& aSegStart
, const Point
& aSegEnd
,
796 const float aAngle
, const bool aLargeArcFlag
,
797 const bool aSweepFlag
, const float aRx
,
799 float rx
= fabs(aRx
); // F.6.6.1
800 float ry
= fabs(aRy
);
803 const float angle
= static_cast<float>(aAngle
* M_PI
/ 180.0);
804 double x1p
= cos(angle
) * (aSegStart
.x
- aSegEnd
.x
) / 2.0 +
805 sin(angle
) * (aSegStart
.y
- aSegEnd
.y
) / 2.0;
806 double y1p
= -sin(angle
) * (aSegStart
.x
- aSegEnd
.x
) / 2.0 +
807 cos(angle
) * (aSegStart
.y
- aSegEnd
.y
) / 2.0;
809 // This is the root in F.6.5.2 and the numerator under that root:
812 rx
* rx
* ry
* ry
- rx
* rx
* y1p
* y1p
- ry
* ry
* x1p
* x1p
;
814 if (numerator
>= 0.0) {
815 root
= sqrt(numerator
/ (rx
* rx
* y1p
* y1p
+ ry
* ry
* x1p
* x1p
));
816 if (aLargeArcFlag
== aSweepFlag
) root
= -root
;
818 // F.6.6 step 3 - |numerator < 0.0|. This is equivalent to the result
819 // of F.6.6.2 (lamedh) being greater than one. What we have here is
820 // ellipse radii that are too small for the ellipse to reach between
821 // segStart and segEnd. We scale the radii up uniformly so that the
822 // ellipse is just big enough to fit (i.e. to the point where there is
823 // exactly one solution).
826 1.0 - numerator
/ (rx
* rx
* ry
* ry
); // equiv to eqn F.6.6.2
827 double s
= sqrt(lamedh
);
828 rx
= static_cast<float>((double)rx
* s
); // F.6.6.3
829 ry
= static_cast<float>((double)ry
* s
);
833 double cxp
= root
* rx
* y1p
/ ry
; // F.6.5.2
834 double cyp
= -root
* ry
* x1p
/ rx
;
837 AngleOfVector(Point(static_cast<float>((x1p
- cxp
) / rx
),
838 static_cast<float>((y1p
- cyp
) / ry
))); // F.6.5.5
840 AngleOfVector(Point(static_cast<float>((-x1p
- cxp
) / rx
),
841 static_cast<float>((-y1p
- cyp
) / ry
))) - // F.6.5.6
843 if (!aSweepFlag
&& delta
> 0) {
845 } else if (aSweepFlag
&& delta
< 0) {
849 double tx1
, ty1
, tx2
, ty2
;
850 tx1
= -cos(angle
) * rx
* sin(theta
) - sin(angle
) * ry
* cos(theta
);
851 ty1
= -sin(angle
) * rx
* sin(theta
) + cos(angle
) * ry
* cos(theta
);
852 tx2
= -cos(angle
) * rx
* sin(theta
+ delta
) -
853 sin(angle
) * ry
* cos(theta
+ delta
);
854 ty2
= -sin(angle
) * rx
* sin(theta
+ delta
) +
855 cos(angle
) * ry
* cos(theta
+ delta
);
864 return {rx
, ry
, static_cast<float>(atan2(ty1
, tx1
)),
865 static_cast<float>(atan2(ty2
, tx2
))};
868 void SVGPathData::GetMarkerPositioningData(nsTArray
<SVGMark
>* aMarks
) const {
869 // This code should assume that ANY type of segment can appear at ANY index.
870 // It should also assume that segments such as M and Z can appear in weird
871 // places, and repeat multiple times consecutively.
873 // info on current [sub]path (reset every M command):
874 Point
pathStart(0.0, 0.0);
875 float pathStartAngle
= 0.0f
;
876 uint32_t pathStartIndex
= 0;
878 // info on previous segment:
879 uint16_t prevSegType
= PATHSEG_UNKNOWN
;
880 Point
prevSegEnd(0.0, 0.0);
881 float prevSegEndAngle
= 0.0f
;
882 Point prevCP
; // if prev seg was a bezier, this was its last control point
885 while (i
< mData
.Length()) {
886 // info on current segment:
888 SVGPathSegUtils::DecodeType(mData
[i
++]); // advances i to args
889 Point
& segStart
= prevSegEnd
;
891 float segStartAngle
, segEndAngle
;
893 switch (segType
) // to find segStartAngle, segEnd and segEndAngle
895 case PATHSEG_CLOSEPATH
:
897 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
900 case PATHSEG_MOVETO_ABS
:
901 case PATHSEG_MOVETO_REL
:
902 if (segType
== PATHSEG_MOVETO_ABS
) {
903 segEnd
= Point(mData
[i
], mData
[i
+ 1]);
905 segEnd
= segStart
+ Point(mData
[i
], mData
[i
+ 1]);
908 pathStartIndex
= aMarks
->Length();
909 // If authors are going to specify multiple consecutive moveto commands
910 // with markers, me might as well make the angle do something useful:
911 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
915 case PATHSEG_LINETO_ABS
:
916 case PATHSEG_LINETO_REL
:
917 if (segType
== PATHSEG_LINETO_ABS
) {
918 segEnd
= Point(mData
[i
], mData
[i
+ 1]);
920 segEnd
= segStart
+ Point(mData
[i
], mData
[i
+ 1]);
922 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
926 case PATHSEG_CURVETO_CUBIC_ABS
:
927 case PATHSEG_CURVETO_CUBIC_REL
: {
928 Point cp1
, cp2
; // control points
929 if (segType
== PATHSEG_CURVETO_CUBIC_ABS
) {
930 cp1
= Point(mData
[i
], mData
[i
+ 1]);
931 cp2
= Point(mData
[i
+ 2], mData
[i
+ 3]);
932 segEnd
= Point(mData
[i
+ 4], mData
[i
+ 5]);
934 cp1
= segStart
+ Point(mData
[i
], mData
[i
+ 1]);
935 cp2
= segStart
+ Point(mData
[i
+ 2], mData
[i
+ 3]);
936 segEnd
= segStart
+ Point(mData
[i
+ 4], mData
[i
+ 5]);
939 segStartAngle
= AngleOfVector(
940 cp1
== segStart
? (cp1
== cp2
? segEnd
: cp2
) : cp1
, segStart
);
941 segEndAngle
= AngleOfVector(
942 segEnd
, cp2
== segEnd
? (cp1
== cp2
? segStart
: cp1
) : cp2
);
947 case PATHSEG_CURVETO_QUADRATIC_ABS
:
948 case PATHSEG_CURVETO_QUADRATIC_REL
: {
949 Point cp1
; // control point
950 if (segType
== PATHSEG_CURVETO_QUADRATIC_ABS
) {
951 cp1
= Point(mData
[i
], mData
[i
+ 1]);
952 segEnd
= Point(mData
[i
+ 2], mData
[i
+ 3]);
954 cp1
= segStart
+ Point(mData
[i
], mData
[i
+ 1]);
955 segEnd
= segStart
+ Point(mData
[i
+ 2], mData
[i
+ 3]);
958 segStartAngle
= AngleOfVector(cp1
== segStart
? segEnd
: cp1
, segStart
);
959 segEndAngle
= AngleOfVector(segEnd
, cp1
== segEnd
? segStart
: cp1
);
964 case PATHSEG_ARC_ABS
:
965 case PATHSEG_ARC_REL
: {
967 float ry
= mData
[i
+ 1];
968 float angle
= mData
[i
+ 2];
969 bool largeArcFlag
= mData
[i
+ 3] != 0.0f
;
970 bool sweepFlag
= mData
[i
+ 4] != 0.0f
;
971 if (segType
== PATHSEG_ARC_ABS
) {
972 segEnd
= Point(mData
[i
+ 5], mData
[i
+ 6]);
974 segEnd
= segStart
+ Point(mData
[i
+ 5], mData
[i
+ 6]);
977 // See section F.6 of SVG 1.1 for details on what we're doing here:
978 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
980 if (segStart
== segEnd
) {
981 // F.6.2 says "If the endpoints (x1, y1) and (x2, y2) are identical,
982 // then this is equivalent to omitting the elliptical arc segment
983 // entirely." We take that very literally here, not adding a mark, and
984 // not even setting any of the 'prev' variables so that it's as if
985 // this arc had never existed; note the difference this will make e.g.
986 // if the arc is proceeded by a bezier curve and followed by a
987 // "smooth" bezier curve of the same degree!
992 // Below we have funny interleaving of F.6.6 (Correction of out-of-range
993 // radii) and F.6.5 (Conversion from endpoint to center
994 // parameterization) which is designed to avoid some unnecessary
997 if (rx
== 0.0 || ry
== 0.0) {
998 // F.6.6 step 1 - straight line or coincidental points
999 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
1004 std::tie(rx
, ry
, segStartAngle
, segEndAngle
) =
1005 ComputeSegAnglesAndCorrectRadii(segStart
, segEnd
, angle
,
1006 largeArcFlag
, sweepFlag
, rx
, ry
);
1011 case PATHSEG_LINETO_HORIZONTAL_ABS
:
1012 case PATHSEG_LINETO_HORIZONTAL_REL
:
1013 if (segType
== PATHSEG_LINETO_HORIZONTAL_ABS
) {
1014 segEnd
= Point(mData
[i
++], segStart
.y
);
1016 segEnd
= segStart
+ Point(mData
[i
++], 0.0f
);
1018 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
1021 case PATHSEG_LINETO_VERTICAL_ABS
:
1022 case PATHSEG_LINETO_VERTICAL_REL
:
1023 if (segType
== PATHSEG_LINETO_VERTICAL_ABS
) {
1024 segEnd
= Point(segStart
.x
, mData
[i
++]);
1026 segEnd
= segStart
+ Point(0.0f
, mData
[i
++]);
1028 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
1031 case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS
:
1032 case PATHSEG_CURVETO_CUBIC_SMOOTH_REL
: {
1033 Point cp1
= SVGPathSegUtils::IsCubicType(prevSegType
)
1034 ? segStart
* 2 - prevCP
1037 if (segType
== PATHSEG_CURVETO_CUBIC_SMOOTH_ABS
) {
1038 cp2
= Point(mData
[i
], mData
[i
+ 1]);
1039 segEnd
= Point(mData
[i
+ 2], mData
[i
+ 3]);
1041 cp2
= segStart
+ Point(mData
[i
], mData
[i
+ 1]);
1042 segEnd
= segStart
+ Point(mData
[i
+ 2], mData
[i
+ 3]);
1045 segStartAngle
= AngleOfVector(
1046 cp1
== segStart
? (cp1
== cp2
? segEnd
: cp2
) : cp1
, segStart
);
1047 segEndAngle
= AngleOfVector(
1048 segEnd
, cp2
== segEnd
? (cp1
== cp2
? segStart
: cp1
) : cp2
);
1053 case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS
:
1054 case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL
: {
1055 Point cp1
= SVGPathSegUtils::IsQuadraticType(prevSegType
)
1056 ? segStart
* 2 - prevCP
1058 if (segType
== PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS
) {
1059 segEnd
= Point(mData
[i
], mData
[i
+ 1]);
1061 segEnd
= segStart
+ Point(mData
[i
], mData
[i
+ 1]);
1064 segStartAngle
= AngleOfVector(cp1
== segStart
? segEnd
: cp1
, segStart
);
1065 segEndAngle
= AngleOfVector(segEnd
, cp1
== segEnd
? segStart
: cp1
);
1071 // Leave any existing marks in aMarks so we have a visual indication of
1072 // when things went wrong.
1073 MOZ_ASSERT(false, "Unknown segment type - path corruption?");
1077 // Set the angle of the mark at the start of this segment:
1078 if (aMarks
->Length()) {
1079 SVGMark
& mark
= aMarks
->LastElement();
1080 if (!IsMoveto(segType
) && IsMoveto(prevSegType
)) {
1081 // start of new subpath
1082 pathStartAngle
= mark
.angle
= segStartAngle
;
1083 } else if (IsMoveto(segType
) && !IsMoveto(prevSegType
)) {
1085 if (prevSegType
!= PATHSEG_CLOSEPATH
) mark
.angle
= prevSegEndAngle
;
1087 if (!(segType
== PATHSEG_CLOSEPATH
&& prevSegType
== PATHSEG_CLOSEPATH
))
1089 SVGContentUtils::AngleBisect(prevSegEndAngle
, segStartAngle
);
1093 // Add the mark at the end of this segment, and set its position:
1094 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1095 // pretended earlier.
1096 aMarks
->AppendElement(SVGMark(static_cast<float>(segEnd
.x
),
1097 static_cast<float>(segEnd
.y
), 0.0f
,
1100 if (segType
== PATHSEG_CLOSEPATH
&& prevSegType
!= PATHSEG_CLOSEPATH
) {
1101 aMarks
->LastElement().angle
= aMarks
->ElementAt(pathStartIndex
).angle
=
1102 SVGContentUtils::AngleBisect(segEndAngle
, pathStartAngle
);
1105 prevSegType
= segType
;
1106 prevSegEnd
= segEnd
;
1107 prevSegEndAngle
= segEndAngle
;
1110 MOZ_ASSERT(i
== mData
.Length(), "Very, very bad - mData corrupt");
1112 if (aMarks
->Length()) {
1113 if (prevSegType
!= PATHSEG_CLOSEPATH
) {
1114 aMarks
->LastElement().angle
= prevSegEndAngle
;
1116 aMarks
->LastElement().type
= SVGMark::eEnd
;
1117 aMarks
->ElementAt(0).type
= SVGMark::eStart
;
1121 // Basically, this is identical to the above function, but replace |mData| with
1122 // |aPath|. We probably can factor out some identical calculation, but I believe
1123 // the above one will be removed because we will use any kind of array of
1124 // StylePathCommand for SVG d attribute in the future.
1126 void SVGPathData::GetMarkerPositioningData(Span
<const StylePathCommand
> aPath
,
1127 nsTArray
<SVGMark
>* aMarks
) {
1128 if (aPath
.IsEmpty()) {
1132 // info on current [sub]path (reset every M command):
1133 Point
pathStart(0.0, 0.0);
1134 float pathStartAngle
= 0.0f
;
1135 uint32_t pathStartIndex
= 0;
1137 // info on previous segment:
1138 StylePathCommand::Tag prevSegType
= StylePathCommand::Tag::Unknown
;
1139 Point
prevSegEnd(0.0, 0.0);
1140 float prevSegEndAngle
= 0.0f
;
1141 Point prevCP
; // if prev seg was a bezier, this was its last control point
1143 StylePathCommand::Tag segType
= StylePathCommand::Tag::Unknown
;
1144 for (const StylePathCommand
& cmd
: aPath
) {
1146 Point
& segStart
= prevSegEnd
;
1148 float segStartAngle
, segEndAngle
;
1150 switch (segType
) // to find segStartAngle, segEnd and segEndAngle
1152 case StylePathCommand::Tag::ClosePath
:
1154 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
1157 case StylePathCommand::Tag::MoveTo
: {
1158 const Point
& p
= cmd
.move_to
.point
.ConvertsToGfxPoint();
1159 pathStart
= segEnd
=
1160 cmd
.move_to
.absolute
== StyleIsAbsolute::Yes
? p
: segStart
+ p
;
1161 pathStartIndex
= aMarks
->Length();
1162 // If authors are going to specify multiple consecutive moveto commands
1163 // with markers, me might as well make the angle do something useful:
1164 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
1167 case StylePathCommand::Tag::LineTo
: {
1168 const Point
& p
= cmd
.line_to
.point
.ConvertsToGfxPoint();
1170 cmd
.line_to
.absolute
== StyleIsAbsolute::Yes
? p
: segStart
+ p
;
1171 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
1174 case StylePathCommand::Tag::CurveTo
: {
1175 Point cp1
= cmd
.curve_to
.control1
.ConvertsToGfxPoint();
1176 Point cp2
= cmd
.curve_to
.control2
.ConvertsToGfxPoint();
1177 segEnd
= cmd
.curve_to
.point
.ConvertsToGfxPoint();
1179 if (cmd
.curve_to
.absolute
== StyleIsAbsolute::No
) {
1186 segStartAngle
= AngleOfVector(
1187 cp1
== segStart
? (cp1
== cp2
? segEnd
: cp2
) : cp1
, segStart
);
1188 segEndAngle
= AngleOfVector(
1189 segEnd
, cp2
== segEnd
? (cp1
== cp2
? segStart
: cp1
) : cp2
);
1192 case StylePathCommand::Tag::QuadBezierCurveTo
: {
1193 Point cp1
= cmd
.quad_bezier_curve_to
.control1
.ConvertsToGfxPoint();
1194 segEnd
= cmd
.quad_bezier_curve_to
.point
.ConvertsToGfxPoint();
1196 if (cmd
.quad_bezier_curve_to
.absolute
== StyleIsAbsolute::No
) {
1198 segEnd
+= segStart
; // set before setting tcp2!
1202 segStartAngle
= AngleOfVector(cp1
== segStart
? segEnd
: cp1
, segStart
);
1203 segEndAngle
= AngleOfVector(segEnd
, cp1
== segEnd
? segStart
: cp1
);
1206 case StylePathCommand::Tag::EllipticalArc
: {
1207 const auto& arc
= cmd
.elliptical_arc
;
1210 float angle
= arc
.angle
;
1211 bool largeArcFlag
= arc
.large_arc_flag
._0
;
1212 bool sweepFlag
= arc
.sweep_flag
._0
;
1213 Point
radii(arc
.rx
, arc
.ry
);
1214 segEnd
= arc
.point
.ConvertsToGfxPoint();
1215 if (arc
.absolute
== StyleIsAbsolute::No
) {
1219 // See section F.6 of SVG 1.1 for details on what we're doing here:
1220 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
1222 if (segStart
== segEnd
) {
1223 // F.6.2 says "If the endpoints (x1, y1) and (x2, y2) are identical,
1224 // then this is equivalent to omitting the elliptical arc segment
1225 // entirely." We take that very literally here, not adding a mark, and
1226 // not even setting any of the 'prev' variables so that it's as if
1227 // this arc had never existed; note the difference this will make e.g.
1228 // if the arc is proceeded by a bezier curve and followed by a
1229 // "smooth" bezier curve of the same degree!
1233 // Below we have funny interleaving of F.6.6 (Correction of out-of-range
1234 // radii) and F.6.5 (Conversion from endpoint to center
1235 // parameterization) which is designed to avoid some unnecessary
1238 if (rx
== 0.0 || ry
== 0.0) {
1239 // F.6.6 step 1 - straight line or coincidental points
1240 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
1244 std::tie(rx
, ry
, segStartAngle
, segEndAngle
) =
1245 ComputeSegAnglesAndCorrectRadii(segStart
, segEnd
, angle
,
1246 largeArcFlag
, sweepFlag
, rx
, ry
);
1249 case StylePathCommand::Tag::HorizontalLineTo
: {
1250 if (cmd
.horizontal_line_to
.absolute
== StyleIsAbsolute::Yes
) {
1251 segEnd
= Point(cmd
.horizontal_line_to
.x
, segStart
.y
);
1253 segEnd
= segStart
+ Point(cmd
.horizontal_line_to
.x
, 0.0f
);
1255 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
1258 case StylePathCommand::Tag::VerticalLineTo
: {
1259 if (cmd
.vertical_line_to
.absolute
== StyleIsAbsolute::Yes
) {
1260 segEnd
= Point(segStart
.x
, cmd
.vertical_line_to
.y
);
1262 segEnd
= segStart
+ Point(0.0f
, cmd
.vertical_line_to
.y
);
1264 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
1267 case StylePathCommand::Tag::SmoothCurveTo
: {
1268 Point cp1
= IsCubicType(prevSegType
) ? segStart
* 2 - prevCP
: segStart
;
1269 Point cp2
= cmd
.smooth_curve_to
.control2
.ConvertsToGfxPoint();
1270 segEnd
= cmd
.smooth_curve_to
.point
.ConvertsToGfxPoint();
1272 if (cmd
.smooth_curve_to
.absolute
== StyleIsAbsolute::No
) {
1278 segStartAngle
= AngleOfVector(
1279 cp1
== segStart
? (cp1
== cp2
? segEnd
: cp2
) : cp1
, segStart
);
1280 segEndAngle
= AngleOfVector(
1281 segEnd
, cp2
== segEnd
? (cp1
== cp2
? segStart
: cp1
) : cp2
);
1284 case StylePathCommand::Tag::SmoothQuadBezierCurveTo
: {
1286 IsQuadraticType(prevSegType
) ? segStart
* 2 - prevCP
: segStart
;
1288 cmd
.smooth_quad_bezier_curve_to
.absolute
== StyleIsAbsolute::Yes
1289 ? cmd
.smooth_quad_bezier_curve_to
.point
.ConvertsToGfxPoint()
1290 : segStart
+ cmd
.smooth_quad_bezier_curve_to
.point
1291 .ConvertsToGfxPoint();
1294 segStartAngle
= AngleOfVector(cp1
== segStart
? segEnd
: cp1
, segStart
);
1295 segEndAngle
= AngleOfVector(segEnd
, cp1
== segEnd
? segStart
: cp1
);
1298 case StylePathCommand::Tag::Unknown
:
1299 // Leave any existing marks in aMarks so we have a visual indication of
1300 // when things went wrong.
1301 MOZ_ASSERT_UNREACHABLE("Unknown segment type - path corruption?");
1305 // Set the angle of the mark at the start of this segment:
1306 if (aMarks
->Length()) {
1307 SVGMark
& mark
= aMarks
->LastElement();
1308 if (!IsMoveto(segType
) && IsMoveto(prevSegType
)) {
1309 // start of new subpath
1310 pathStartAngle
= mark
.angle
= segStartAngle
;
1311 } else if (IsMoveto(segType
) && !IsMoveto(prevSegType
)) {
1313 if (prevSegType
!= StylePathCommand::Tag::ClosePath
) {
1314 mark
.angle
= prevSegEndAngle
;
1316 } else if (!(segType
== StylePathCommand::Tag::ClosePath
&&
1317 prevSegType
== StylePathCommand::Tag::ClosePath
)) {
1319 SVGContentUtils::AngleBisect(prevSegEndAngle
, segStartAngle
);
1323 // Add the mark at the end of this segment, and set its position:
1324 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1325 // pretended earlier.
1326 aMarks
->AppendElement(SVGMark(static_cast<float>(segEnd
.x
),
1327 static_cast<float>(segEnd
.y
), 0.0f
,
1330 if (segType
== StylePathCommand::Tag::ClosePath
&&
1331 prevSegType
!= StylePathCommand::Tag::ClosePath
) {
1332 aMarks
->LastElement().angle
= aMarks
->ElementAt(pathStartIndex
).angle
=
1333 SVGContentUtils::AngleBisect(segEndAngle
, pathStartAngle
);
1336 prevSegType
= segType
;
1337 prevSegEnd
= segEnd
;
1338 prevSegEndAngle
= segEndAngle
;
1341 if (aMarks
->Length()) {
1342 if (prevSegType
!= StylePathCommand::Tag::ClosePath
) {
1343 aMarks
->LastElement().angle
= prevSegEndAngle
;
1345 aMarks
->LastElement().type
= SVGMark::eEnd
;
1346 aMarks
->ElementAt(0).type
= SVGMark::eStart
;
1350 size_t SVGPathData::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf
) const {
1351 return mData
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
1354 size_t SVGPathData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
1355 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf
);
1358 } // namespace mozilla