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 "SVGArcConverter.h"
18 #include "nsStyleConsts.h"
19 #include "SVGContentUtils.h"
20 #include "SVGGeometryElement.h"
21 #include "SVGPathSegUtils.h"
24 using namespace mozilla::gfx
;
28 nsresult
SVGPathData::SetValueFromString(const nsACString
& aValue
) {
29 // We don't use a temp variable since the spec says to parse everything up to
30 // the first error. We still return any error though so that callers know if
32 bool ok
= Servo_SVGPathData_Parse(&aValue
, &mData
);
33 return ok
? NS_OK
: NS_ERROR_DOM_SYNTAX_ERR
;
36 void SVGPathData::GetValueAsString(nsACString
& aValue
) const {
37 Servo_SVGPathData_ToString(&mData
, &aValue
);
40 bool SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(
41 FallibleTArray
<double>* aOutput
) const {
42 return GetDistancesFromOriginToEndsOfVisibleSegments(AsSpan(), aOutput
);
46 bool SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(
47 Span
<const StylePathCommand
> aPath
, FallibleTArray
<double>* aOutput
) {
48 SVGPathTraversalState state
;
52 bool firstMoveToIsChecked
= false;
53 for (const auto& cmd
: aPath
) {
54 SVGPathSegUtils::TraversePathSegment(cmd
, state
);
55 if (!std::isfinite(state
.length
)) {
59 // We skip all moveto commands except for the initial moveto.
60 if (!cmd
.IsMove() || !firstMoveToIsChecked
) {
61 if (!aOutput
->AppendElement(state
.length
, fallible
)) {
66 if (cmd
.IsMove() && !firstMoveToIsChecked
) {
67 firstMoveToIsChecked
= true;
75 * The SVG spec says we have to paint stroke caps for zero length subpaths:
77 * http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes
79 * Cairo only does this for |stroke-linecap: round| and not for
80 * |stroke-linecap: square| (since that's what Adobe Acrobat has always done).
81 * Most likely the other backends that DrawTarget uses have the same behavior.
83 * To help us conform to the SVG spec we have this helper function to draw an
84 * approximation of square caps for zero length subpaths. It does this by
85 * inserting a subpath containing a single user space axis aligned straight
86 * line that is as small as it can be while minimizing the risk of it being
87 * thrown away by the DrawTarget's backend for being too small to affect
88 * rendering. The idea is that we'll then get stroke caps drawn for this axis
89 * aligned line, creating an axis aligned rectangle that approximates the
90 * square that would ideally be drawn.
92 * Since we don't have any information about transforms from user space to
93 * device space, we choose the length of the small line that we insert by
94 * making it a small percentage of the stroke width of the path. This should
95 * hopefully allow us to make the line as long as possible (to avoid rounding
96 * issues in the backend resulting in the backend seeing it as having zero
97 * length) while still avoiding the small rectangle being noticeably different
100 * Note that this function inserts a subpath into the current gfx path that
101 * will be present during both fill and stroke operations.
103 static void ApproximateZeroLengthSubpathSquareCaps(PathBuilder
* aPB
,
105 Float aStrokeWidth
) {
106 // Note that caps are proportional to stroke width, so if stroke width is
107 // zero it's actually fine for |tinyLength| below to end up being zero.
108 // However, it would be a waste to inserting a LineTo in that case, so better
110 MOZ_ASSERT(aStrokeWidth
> 0.0f
,
111 "Make the caller check for this, or check it here");
113 // The fraction of the stroke width that we choose for the length of the
114 // line is rather arbitrary, other than being chosen to meet the requirements
115 // described in the comment above.
117 Float tinyLength
= aStrokeWidth
/ SVG_ZERO_LENGTH_PATH_FIX_FACTOR
;
119 aPB
->LineTo(aPoint
+ Point(tinyLength
, 0));
123 #define MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT \
125 if (!subpathHasLength && hasLineCaps && aStrokeWidth > 0 && \
126 subpathContainsNonMoveTo && IsValidType(prevSegType) && \
127 (!IsMoveto(prevSegType) || IsClosePath(segType))) { \
128 ApproximateZeroLengthSubpathSquareCaps(aBuilder, segStart, \
133 already_AddRefed
<Path
> SVGPathData::BuildPath(PathBuilder
* aBuilder
,
134 StyleStrokeLinecap aStrokeLineCap
,
137 return BuildPath(AsSpan(), aBuilder
, aStrokeLineCap
, aStrokeWidth
, {}, {},
141 #undef MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT
143 already_AddRefed
<Path
> SVGPathData::BuildPathForMeasuring(float aZoom
) const {
144 // Since the path that we return will not be used for painting it doesn't
145 // matter what we pass to CreatePathBuilder as aFillRule. Hawever, we do want
146 // to pass something other than NS_STYLE_STROKE_LINECAP_SQUARE as
147 // aStrokeLineCap to avoid the insertion of extra little lines (by
148 // ApproximateZeroLengthSubpathSquareCaps), in which case the value that we
149 // pass as aStrokeWidth doesn't matter (since it's only used to determine the
150 // length of those extra little lines).
152 RefPtr
<DrawTarget
> drawTarget
=
153 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
154 RefPtr
<PathBuilder
> builder
=
155 drawTarget
->CreatePathBuilder(FillRule::FILL_WINDING
);
156 return BuildPath(builder
, StyleStrokeLinecap::Butt
, 0, aZoom
);
160 already_AddRefed
<Path
> SVGPathData::BuildPathForMeasuring(
161 Span
<const StylePathCommand
> aPath
, float aZoom
) {
162 RefPtr
<DrawTarget
> drawTarget
=
163 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
164 RefPtr
<PathBuilder
> builder
=
165 drawTarget
->CreatePathBuilder(FillRule::FILL_WINDING
);
166 return BuildPath(aPath
, builder
, StyleStrokeLinecap::Butt
, 0, {}, {}, aZoom
);
169 static inline StyleCSSFloat
GetRotate(const StyleCSSFloat
& aAngle
) {
173 static inline StyleCSSFloat
GetRotate(const StyleAngle
& aAngle
) {
174 return aAngle
.ToDegrees();
177 static inline StyleCSSFloat
Resolve(const StyleCSSFloat
& aValue
,
182 static inline StyleCSSFloat
Resolve(const LengthPercentage
& aValue
,
184 return aValue
.ResolveToCSSPixels(aBasis
);
187 template <typename Angle
, typename LP
>
188 static already_AddRefed
<Path
> BuildPathInternal(
189 Span
<const StyleGenericShapeCommand
<Angle
, LP
>> aPath
,
190 PathBuilder
* aBuilder
, StyleStrokeLinecap aStrokeLineCap
,
191 Float aStrokeWidth
, const CSSSize
& aPercentageBasis
, const Point
& aOffset
,
193 using Command
= StyleGenericShapeCommand
<Angle
, LP
>;
195 if (aPath
.IsEmpty() || !aPath
[0].IsMove()) {
196 return nullptr; // paths without an initial moveto are invalid
199 bool hasLineCaps
= aStrokeLineCap
!= StyleStrokeLinecap::Butt
;
200 bool subpathHasLength
= false; // visual length
201 bool subpathContainsNonMoveTo
= false;
203 const Command
* seg
= nullptr;
204 const Command
* prevSeg
= nullptr;
205 Point
pathStart(0.0, 0.0); // start point of [sub]path
206 Point
segStart(0.0, 0.0);
208 Point cp1
, cp2
; // previous bezier's control points
209 Point tcp1
, tcp2
; // temporaries
211 auto maybeApproximateZeroLengthSubpathSquareCaps
=
212 [&](const Command
* aPrevSeg
, const Command
* aSeg
) {
213 if (!subpathHasLength
&& hasLineCaps
&& aStrokeWidth
> 0 &&
214 subpathContainsNonMoveTo
&& aPrevSeg
&& aSeg
&&
215 (!aPrevSeg
->IsMove() || aSeg
->IsClose())) {
216 ApproximateZeroLengthSubpathSquareCaps(aBuilder
, segStart
,
221 auto scale
= [aOffset
, aZoomFactor
](const Point
& p
) {
222 return Point(p
.x
* aZoomFactor
, p
.y
* aZoomFactor
) + aOffset
;
225 // Regarding cp1 and cp2: If the previous segment was a cubic bezier curve,
226 // then cp2 is its second control point. If the previous segment was a
227 // quadratic curve, then cp1 is its (only) control point.
229 for (const auto& cmd
: aPath
) {
232 case Command::Tag::Close
:
233 // set this early to allow drawing of square caps for "M{x},{y} Z":
234 subpathContainsNonMoveTo
= true;
235 maybeApproximateZeroLengthSubpathSquareCaps(prevSeg
, seg
);
239 case Command::Tag::Move
: {
240 maybeApproximateZeroLengthSubpathSquareCaps(prevSeg
, seg
);
241 const Point
& p
= cmd
.move
.point
.ToGfxPoint(aPercentageBasis
);
242 pathStart
= segEnd
= cmd
.move
.by_to
== StyleByTo::To
? p
: segStart
+ p
;
243 aBuilder
->MoveTo(scale(segEnd
));
244 subpathHasLength
= false;
247 case Command::Tag::Line
: {
248 const Point
& p
= cmd
.line
.point
.ToGfxPoint(aPercentageBasis
);
249 segEnd
= cmd
.line
.by_to
== StyleByTo::To
? p
: segStart
+ p
;
250 if (segEnd
!= segStart
) {
251 subpathHasLength
= true;
252 aBuilder
->LineTo(scale(segEnd
));
256 case Command::Tag::CubicCurve
:
257 cp1
= cmd
.cubic_curve
.control1
.ToGfxPoint(aPercentageBasis
);
258 cp2
= cmd
.cubic_curve
.control2
.ToGfxPoint(aPercentageBasis
);
259 segEnd
= cmd
.cubic_curve
.point
.ToGfxPoint(aPercentageBasis
);
261 if (cmd
.cubic_curve
.by_to
== StyleByTo::By
) {
267 if (segEnd
!= segStart
|| segEnd
!= cp1
|| segEnd
!= cp2
) {
268 subpathHasLength
= true;
269 aBuilder
->BezierTo(scale(cp1
), scale(cp2
), scale(segEnd
));
273 case Command::Tag::QuadCurve
:
274 cp1
= cmd
.quad_curve
.control1
.ToGfxPoint(aPercentageBasis
);
275 segEnd
= cmd
.quad_curve
.point
.ToGfxPoint(aPercentageBasis
);
277 if (cmd
.quad_curve
.by_to
== StyleByTo::By
) {
279 segEnd
+= segStart
; // set before setting tcp2!
282 // Convert quadratic curve to cubic curve:
283 tcp1
= segStart
+ (cp1
- segStart
) * 2 / 3;
284 tcp2
= cp1
+ (segEnd
- cp1
) / 3;
286 if (segEnd
!= segStart
|| segEnd
!= cp1
) {
287 subpathHasLength
= true;
288 aBuilder
->BezierTo(scale(tcp1
), scale(tcp2
), scale(segEnd
));
292 case Command::Tag::Arc
: {
293 const auto& arc
= cmd
.arc
;
294 const Point
& radii
= arc
.radii
.ToGfxPoint(aPercentageBasis
);
295 segEnd
= arc
.point
.ToGfxPoint(aPercentageBasis
);
296 if (arc
.by_to
== StyleByTo::By
) {
299 if (segEnd
!= segStart
) {
300 subpathHasLength
= true;
301 if (radii
.x
== 0.0f
|| radii
.y
== 0.0f
) {
302 aBuilder
->LineTo(scale(segEnd
));
304 const bool arc_is_large
= arc
.arc_size
== StyleArcSize::Large
;
305 const bool arc_is_cw
= arc
.arc_sweep
== StyleArcSweep::Cw
;
306 SVGArcConverter
converter(segStart
, segEnd
, radii
,
307 GetRotate(arc
.rotate
), arc_is_large
,
309 while (converter
.GetNextSegment(&cp1
, &cp2
, &segEnd
)) {
310 aBuilder
->BezierTo(scale(cp1
), scale(cp2
), scale(segEnd
));
316 case Command::Tag::HLine
: {
317 const float x
= Resolve(cmd
.h_line
.x
, aPercentageBasis
.width
);
318 if (cmd
.h_line
.by_to
== StyleByTo::To
) {
319 segEnd
= Point(x
, segStart
.y
);
321 segEnd
= segStart
+ Point(x
, 0.0f
);
324 if (segEnd
!= segStart
) {
325 subpathHasLength
= true;
326 aBuilder
->LineTo(scale(segEnd
));
330 case Command::Tag::VLine
: {
331 const float y
= Resolve(cmd
.v_line
.y
, aPercentageBasis
.height
);
332 if (cmd
.v_line
.by_to
== StyleByTo::To
) {
333 segEnd
= Point(segStart
.x
, y
);
335 segEnd
= segStart
+ Point(0.0f
, y
);
338 if (segEnd
!= segStart
) {
339 subpathHasLength
= true;
340 aBuilder
->LineTo(scale(segEnd
));
344 case Command::Tag::SmoothCubic
:
345 cp1
= prevSeg
&& prevSeg
->IsCubicType() ? segStart
* 2 - cp2
: segStart
;
346 cp2
= cmd
.smooth_cubic
.control2
.ToGfxPoint(aPercentageBasis
);
347 segEnd
= cmd
.smooth_cubic
.point
.ToGfxPoint(aPercentageBasis
);
349 if (cmd
.smooth_cubic
.by_to
== StyleByTo::By
) {
354 if (segEnd
!= segStart
|| segEnd
!= cp1
|| segEnd
!= cp2
) {
355 subpathHasLength
= true;
356 aBuilder
->BezierTo(scale(cp1
), scale(cp2
), scale(segEnd
));
360 case Command::Tag::SmoothQuad
: {
361 cp1
= prevSeg
&& prevSeg
->IsQuadraticType() ? segStart
* 2 - cp1
363 // Convert quadratic curve to cubic curve:
364 tcp1
= segStart
+ (cp1
- segStart
) * 2 / 3;
366 const Point
& p
= cmd
.smooth_quad
.point
.ToGfxPoint(aPercentageBasis
);
367 // set before setting tcp2!
368 segEnd
= cmd
.smooth_quad
.by_to
== StyleByTo::To
? p
: segStart
+ p
;
369 tcp2
= cp1
+ (segEnd
- cp1
) / 3;
371 if (segEnd
!= segStart
|| segEnd
!= cp1
) {
372 subpathHasLength
= true;
373 aBuilder
->BezierTo(scale(tcp1
), scale(tcp2
), scale(segEnd
));
379 subpathContainsNonMoveTo
= !cmd
.IsMove();
384 MOZ_ASSERT(prevSeg
== seg
, "prevSegType should be left at the final segType");
386 maybeApproximateZeroLengthSubpathSquareCaps(prevSeg
, seg
);
388 return aBuilder
->Finish();
392 already_AddRefed
<Path
> SVGPathData::BuildPath(
393 Span
<const StylePathCommand
> aPath
, PathBuilder
* aBuilder
,
394 StyleStrokeLinecap aStrokeLineCap
, Float aStrokeWidth
,
395 const CSSSize
& aBasis
, const gfx::Point
& aOffset
, float aZoomFactor
) {
396 return BuildPathInternal(aPath
, aBuilder
, aStrokeLineCap
, aStrokeWidth
,
397 aBasis
, aOffset
, aZoomFactor
);
401 already_AddRefed
<Path
> SVGPathData::BuildPath(
402 Span
<const StyleShapeCommand
> aShape
, PathBuilder
* aBuilder
,
403 StyleStrokeLinecap aStrokeLineCap
, Float aStrokeWidth
,
404 const CSSSize
& aBasis
, const gfx::Point
& aOffset
, float aZoomFactor
) {
405 return BuildPathInternal(aShape
, aBuilder
, aStrokeLineCap
, aStrokeWidth
,
406 aBasis
, aOffset
, aZoomFactor
);
409 static double AngleOfVector(const Point
& aVector
) {
410 // C99 says about atan2 "A domain error may occur if both arguments are
411 // zero" and "On a domain error, the function returns an implementation-
412 // defined value". In the case of atan2 the implementation-defined value
413 // seems to commonly be zero, but it could just as easily be a NaN value.
414 // We specifically want zero in this case, hence the check:
416 return (aVector
!= Point(0.0, 0.0)) ? atan2(aVector
.y
, aVector
.x
) : 0.0;
419 static float AngleOfVector(const Point
& cp1
, const Point
& cp2
) {
420 return static_cast<float>(AngleOfVector(cp1
- cp2
));
423 // This implements F.6.5 and F.6.6 of
424 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
425 static std::tuple
<float, float, float, float>
426 /* rx, ry, segStartAngle, segEndAngle */
427 ComputeSegAnglesAndCorrectRadii(const Point
& aSegStart
, const Point
& aSegEnd
,
428 const float aAngle
, const bool aLargeArcFlag
,
429 const bool aSweepFlag
, const float aRx
,
431 float rx
= fabs(aRx
); // F.6.6.1
432 float ry
= fabs(aRy
);
435 const float angle
= static_cast<float>(aAngle
* M_PI
/ 180.0);
436 double x1p
= cos(angle
) * (aSegStart
.x
- aSegEnd
.x
) / 2.0 +
437 sin(angle
) * (aSegStart
.y
- aSegEnd
.y
) / 2.0;
438 double y1p
= -sin(angle
) * (aSegStart
.x
- aSegEnd
.x
) / 2.0 +
439 cos(angle
) * (aSegStart
.y
- aSegEnd
.y
) / 2.0;
441 // This is the root in F.6.5.2 and the numerator under that root:
444 rx
* rx
* ry
* ry
- rx
* rx
* y1p
* y1p
- ry
* ry
* x1p
* x1p
;
446 if (numerator
>= 0.0) {
447 root
= sqrt(numerator
/ (rx
* rx
* y1p
* y1p
+ ry
* ry
* x1p
* x1p
));
448 if (aLargeArcFlag
== aSweepFlag
) root
= -root
;
450 // F.6.6 step 3 - |numerator < 0.0|. This is equivalent to the result
451 // of F.6.6.2 (lamedh) being greater than one. What we have here is
452 // ellipse radii that are too small for the ellipse to reach between
453 // segStart and segEnd. We scale the radii up uniformly so that the
454 // ellipse is just big enough to fit (i.e. to the point where there is
455 // exactly one solution).
458 1.0 - numerator
/ (rx
* rx
* ry
* ry
); // equiv to eqn F.6.6.2
459 double s
= sqrt(lamedh
);
460 rx
= static_cast<float>((double)rx
* s
); // F.6.6.3
461 ry
= static_cast<float>((double)ry
* s
);
465 double cxp
= root
* rx
* y1p
/ ry
; // F.6.5.2
466 double cyp
= -root
* ry
* x1p
/ rx
;
469 AngleOfVector(Point(static_cast<float>((x1p
- cxp
) / rx
),
470 static_cast<float>((y1p
- cyp
) / ry
))); // F.6.5.5
472 AngleOfVector(Point(static_cast<float>((-x1p
- cxp
) / rx
),
473 static_cast<float>((-y1p
- cyp
) / ry
))) - // F.6.5.6
475 if (!aSweepFlag
&& delta
> 0) {
477 } else if (aSweepFlag
&& delta
< 0) {
481 double tx1
, ty1
, tx2
, ty2
;
482 tx1
= -cos(angle
) * rx
* sin(theta
) - sin(angle
) * ry
* cos(theta
);
483 ty1
= -sin(angle
) * rx
* sin(theta
) + cos(angle
) * ry
* cos(theta
);
484 tx2
= -cos(angle
) * rx
* sin(theta
+ delta
) -
485 sin(angle
) * ry
* cos(theta
+ delta
);
486 ty2
= -sin(angle
) * rx
* sin(theta
+ delta
) +
487 cos(angle
) * ry
* cos(theta
+ delta
);
496 return {rx
, ry
, static_cast<float>(atan2(ty1
, tx1
)),
497 static_cast<float>(atan2(ty2
, tx2
))};
500 void SVGPathData::GetMarkerPositioningData(float aZoom
,
501 nsTArray
<SVGMark
>* aMarks
) const {
502 return GetMarkerPositioningData(AsSpan(), aZoom
, aMarks
);
505 // Basically, this is identical to the above function, but replace |mData| with
506 // |aPath|. We probably can factor out some identical calculation, but I believe
507 // the above one will be removed because we will use any kind of array of
508 // StylePathCommand for SVG d attribute in the future.
510 void SVGPathData::GetMarkerPositioningData(Span
<const StylePathCommand
> aPath
,
512 nsTArray
<SVGMark
>* aMarks
) {
513 if (aPath
.IsEmpty()) {
517 // info on current [sub]path (reset every M command):
518 Point
pathStart(0.0, 0.0);
519 float pathStartAngle
= 0.0f
;
520 uint32_t pathStartIndex
= 0;
522 // info on previous segment:
523 const StylePathCommand
* prevSeg
= nullptr;
524 Point
prevSegEnd(0.0, 0.0);
525 float prevSegEndAngle
= 0.0f
;
526 Point prevCP
; // if prev seg was a bezier, this was its last control point
528 for (const StylePathCommand
& cmd
: aPath
) {
529 Point
& segStart
= prevSegEnd
;
531 float segStartAngle
, segEndAngle
;
533 switch (cmd
.tag
) // to find segStartAngle, segEnd and segEndAngle
535 case StylePathCommand::Tag::Close
:
537 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
540 case StylePathCommand::Tag::Move
: {
541 const Point
& p
= cmd
.move
.point
.ToGfxPoint() * aZoom
;
542 pathStart
= segEnd
= cmd
.move
.by_to
== StyleByTo::To
? p
: segStart
+ p
;
543 pathStartIndex
= aMarks
->Length();
544 // If authors are going to specify multiple consecutive moveto commands
545 // with markers, me might as well make the angle do something useful:
546 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
549 case StylePathCommand::Tag::Line
: {
550 const Point
& p
= cmd
.line
.point
.ToGfxPoint() * aZoom
;
551 segEnd
= cmd
.line
.by_to
== StyleByTo::To
? p
: segStart
+ p
;
552 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
555 case StylePathCommand::Tag::CubicCurve
: {
556 Point cp1
= cmd
.cubic_curve
.control1
.ToGfxPoint() * aZoom
;
557 Point cp2
= cmd
.cubic_curve
.control2
.ToGfxPoint() * aZoom
;
558 segEnd
= cmd
.cubic_curve
.point
.ToGfxPoint() * aZoom
;
560 if (cmd
.cubic_curve
.by_to
== StyleByTo::By
) {
567 segStartAngle
= AngleOfVector(
568 cp1
== segStart
? (cp1
== cp2
? segEnd
: cp2
) : cp1
, segStart
);
569 segEndAngle
= AngleOfVector(
570 segEnd
, cp2
== segEnd
? (cp1
== cp2
? segStart
: cp1
) : cp2
);
573 case StylePathCommand::Tag::QuadCurve
: {
574 Point cp1
= cmd
.quad_curve
.control1
.ToGfxPoint() * aZoom
;
575 segEnd
= cmd
.quad_curve
.point
.ToGfxPoint() * aZoom
;
577 if (cmd
.quad_curve
.by_to
== StyleByTo::By
) {
579 segEnd
+= segStart
; // set before setting tcp2!
583 segStartAngle
= AngleOfVector(cp1
== segStart
? segEnd
: cp1
, segStart
);
584 segEndAngle
= AngleOfVector(segEnd
, cp1
== segEnd
? segStart
: cp1
);
587 case StylePathCommand::Tag::Arc
: {
588 const auto& arc
= cmd
.arc
;
589 float rx
= arc
.radii
.x
* aZoom
;
590 float ry
= arc
.radii
.y
* aZoom
;
591 float angle
= arc
.rotate
;
592 bool largeArcFlag
= arc
.arc_size
== StyleArcSize::Large
;
593 bool sweepFlag
= arc
.arc_sweep
== StyleArcSweep::Cw
;
594 segEnd
= arc
.point
.ToGfxPoint() * aZoom
;
595 if (arc
.by_to
== StyleByTo::By
) {
599 // See section F.6 of SVG 1.1 for details on what we're doing here:
600 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
602 if (segStart
== segEnd
) {
603 // F.6.2 says "If the endpoints (x1, y1) and (x2, y2) are identical,
604 // then this is equivalent to omitting the elliptical arc segment
605 // entirely." We take that very literally here, not adding a mark, and
606 // not even setting any of the 'prev' variables so that it's as if
607 // this arc had never existed; note the difference this will make e.g.
608 // if the arc is proceeded by a bezier curve and followed by a
609 // "smooth" bezier curve of the same degree!
613 // Below we have funny interleaving of F.6.6 (Correction of out-of-range
614 // radii) and F.6.5 (Conversion from endpoint to center
615 // parameterization) which is designed to avoid some unnecessary
618 if (rx
== 0.0 || ry
== 0.0) {
619 // F.6.6 step 1 - straight line or coincidental points
620 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
624 std::tie(rx
, ry
, segStartAngle
, segEndAngle
) =
625 ComputeSegAnglesAndCorrectRadii(segStart
, segEnd
, angle
,
626 largeArcFlag
, sweepFlag
, rx
, ry
);
629 case StylePathCommand::Tag::HLine
: {
630 if (cmd
.h_line
.by_to
== StyleByTo::To
) {
631 segEnd
= Point(cmd
.h_line
.x
, segStart
.y
) * aZoom
;
633 segEnd
= segStart
+ Point(cmd
.h_line
.x
, 0.0f
) * aZoom
;
635 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
638 case StylePathCommand::Tag::VLine
: {
639 if (cmd
.v_line
.by_to
== StyleByTo::To
) {
640 segEnd
= Point(segStart
.x
, cmd
.v_line
.y
) * aZoom
;
642 segEnd
= segStart
+ Point(0.0f
, cmd
.v_line
.y
) * aZoom
;
644 segStartAngle
= segEndAngle
= AngleOfVector(segEnd
, segStart
);
647 case StylePathCommand::Tag::SmoothCubic
: {
648 const Point
& cp1
= prevSeg
&& prevSeg
->IsCubicType()
649 ? segStart
* 2 - prevCP
651 Point cp2
= cmd
.smooth_cubic
.control2
.ToGfxPoint() * aZoom
;
652 segEnd
= cmd
.smooth_cubic
.point
.ToGfxPoint() * aZoom
;
654 if (cmd
.smooth_cubic
.by_to
== StyleByTo::By
) {
660 segStartAngle
= AngleOfVector(
661 cp1
== segStart
? (cp1
== cp2
? segEnd
: cp2
) : cp1
, segStart
);
662 segEndAngle
= AngleOfVector(
663 segEnd
, cp2
== segEnd
? (cp1
== cp2
? segStart
: cp1
) : cp2
);
666 case StylePathCommand::Tag::SmoothQuad
: {
667 const Point
& cp1
= prevSeg
&& prevSeg
->IsQuadraticType()
668 ? segStart
* 2 - prevCP
670 segEnd
= cmd
.smooth_quad
.by_to
== StyleByTo::To
671 ? cmd
.smooth_quad
.point
.ToGfxPoint() * aZoom
672 : segStart
+ cmd
.smooth_quad
.point
.ToGfxPoint() * aZoom
;
675 segStartAngle
= AngleOfVector(cp1
== segStart
? segEnd
: cp1
, segStart
);
676 segEndAngle
= AngleOfVector(segEnd
, cp1
== segEnd
? segStart
: cp1
);
681 // Set the angle of the mark at the start of this segment:
682 if (aMarks
->Length()) {
683 SVGMark
& mark
= aMarks
->LastElement();
684 if (!cmd
.IsMove() && prevSeg
&& prevSeg
->IsMove()) {
685 // start of new subpath
686 pathStartAngle
= mark
.angle
= segStartAngle
;
687 } else if (cmd
.IsMove() && !(prevSeg
&& prevSeg
->IsMove())) {
689 if (!(prevSeg
&& prevSeg
->IsClose())) {
690 mark
.angle
= prevSegEndAngle
;
692 } else if (!(cmd
.IsClose() && prevSeg
&& prevSeg
->IsClose())) {
694 SVGContentUtils::AngleBisect(prevSegEndAngle
, segStartAngle
);
698 // Add the mark at the end of this segment, and set its position:
699 // XXX(Bug 1631371) Check if this should use a fallible operation as it
700 // pretended earlier.
701 aMarks
->AppendElement(SVGMark(static_cast<float>(segEnd
.x
),
702 static_cast<float>(segEnd
.y
), 0.0f
,
705 if (cmd
.IsClose() && !(prevSeg
&& prevSeg
->IsClose())) {
706 aMarks
->LastElement().angle
= aMarks
->ElementAt(pathStartIndex
).angle
=
707 SVGContentUtils::AngleBisect(segEndAngle
, pathStartAngle
);
712 prevSegEndAngle
= segEndAngle
;
715 if (!aMarks
->IsEmpty()) {
716 if (!(prevSeg
&& prevSeg
->IsClose())) {
717 aMarks
->LastElement().angle
= prevSegEndAngle
;
719 aMarks
->LastElement().type
= SVGMark::eEnd
;
720 aMarks
->ElementAt(0).type
= SVGMark::eStart
;
724 size_t SVGPathData::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf
) const {
725 // TODO: measure mData if unshared?
729 size_t SVGPathData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
730 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf
);
733 } // namespace mozilla