Bug 1936278 - Prevent search mode chiclet from being dismissed when clicking in page...
[gecko.git] / dom / svg / SVGPathData.cpp
blobcdf4635977938c03fa628ee0eb09093d2526ff94
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"
9 #include "gfx2DGlue.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"
15 #include "nsError.h"
16 #include "nsString.h"
17 #include "SVGArcConverter.h"
18 #include "nsStyleConsts.h"
19 #include "SVGContentUtils.h"
20 #include "SVGGeometryElement.h"
21 #include "SVGPathSegUtils.h"
22 #include <algorithm>
24 using namespace mozilla::gfx;
26 namespace mozilla {
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
31 // there's a problem.
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);
45 /* static */
46 bool SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(
47 Span<const StylePathCommand> aPath, FallibleTArray<double>* aOutput) {
48 SVGPathTraversalState state;
50 aOutput->Clear();
52 bool firstMoveToIsChecked = false;
53 for (const auto& cmd : aPath) {
54 SVGPathSegUtils::TraversePathSegment(cmd, state);
55 if (!std::isfinite(state.length)) {
56 return false;
59 // We skip all moveto commands except for the initial moveto.
60 if (!cmd.IsMove() || !firstMoveToIsChecked) {
61 if (!aOutput->AppendElement(state.length, fallible)) {
62 return false;
66 if (cmd.IsMove() && !firstMoveToIsChecked) {
67 firstMoveToIsChecked = true;
71 return true;
74 /**
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
98 * from a square.
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,
104 const Point& aPoint,
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
109 // not to.
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));
120 aPB->MoveTo(aPoint);
123 #define MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT \
124 do { \
125 if (!subpathHasLength && hasLineCaps && aStrokeWidth > 0 && \
126 subpathContainsNonMoveTo && IsValidType(prevSegType) && \
127 (!IsMoveto(prevSegType) || IsClosePath(segType))) { \
128 ApproximateZeroLengthSubpathSquareCaps(aBuilder, segStart, \
129 aStrokeWidth); \
131 } while (0)
133 already_AddRefed<Path> SVGPathData::BuildPath(PathBuilder* aBuilder,
134 StyleStrokeLinecap aStrokeLineCap,
135 Float aStrokeWidth,
136 float aZoom) const {
137 return BuildPath(AsSpan(), aBuilder, aStrokeLineCap, aStrokeWidth, {}, {},
138 aZoom);
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);
159 /* static */
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) {
170 return aAngle;
173 static inline StyleCSSFloat GetRotate(const StyleAngle& aAngle) {
174 return aAngle.ToDegrees();
177 static inline StyleCSSFloat Resolve(const StyleCSSFloat& aValue,
178 CSSCoord aBasis) {
179 return aValue;
182 static inline StyleCSSFloat Resolve(const LengthPercentage& aValue,
183 CSSCoord aBasis) {
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,
192 float aZoomFactor) {
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);
207 Point segEnd;
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,
217 aStrokeWidth);
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) {
230 seg = &cmd;
231 switch (cmd.tag) {
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);
236 segEnd = pathStart;
237 aBuilder->Close();
238 break;
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;
245 break;
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));
254 break;
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) {
262 cp1 += segStart;
263 cp2 += segStart;
264 segEnd += segStart;
267 if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
268 subpathHasLength = true;
269 aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd));
271 break;
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) {
278 cp1 += segStart;
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));
290 break;
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) {
297 segEnd += segStart;
299 if (segEnd != segStart) {
300 subpathHasLength = true;
301 if (radii.x == 0.0f || radii.y == 0.0f) {
302 aBuilder->LineTo(scale(segEnd));
303 } else {
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,
308 arc_is_cw);
309 while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) {
310 aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd));
314 break;
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);
320 } else {
321 segEnd = segStart + Point(x, 0.0f);
324 if (segEnd != segStart) {
325 subpathHasLength = true;
326 aBuilder->LineTo(scale(segEnd));
328 break;
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);
334 } else {
335 segEnd = segStart + Point(0.0f, y);
338 if (segEnd != segStart) {
339 subpathHasLength = true;
340 aBuilder->LineTo(scale(segEnd));
342 break;
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) {
350 cp2 += segStart;
351 segEnd += segStart;
354 if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
355 subpathHasLength = true;
356 aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd));
358 break;
360 case Command::Tag::SmoothQuad: {
361 cp1 = prevSeg && prevSeg->IsQuadraticType() ? segStart * 2 - cp1
362 : segStart;
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));
375 break;
379 subpathContainsNonMoveTo = !cmd.IsMove();
380 prevSeg = seg;
381 segStart = segEnd;
384 MOZ_ASSERT(prevSeg == seg, "prevSegType should be left at the final segType");
386 maybeApproximateZeroLengthSubpathSquareCaps(prevSeg, seg);
388 return aBuilder->Finish();
391 /* static */
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);
400 /* static */
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,
430 const float aRy) {
431 float rx = fabs(aRx); // F.6.6.1
432 float ry = fabs(aRy);
434 // F.6.5.1:
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:
442 double root;
443 double numerator =
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;
449 } else {
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).
457 double lamedh =
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);
462 root = 0.0;
465 double cxp = root * rx * y1p / ry; // F.6.5.2
466 double cyp = -root * ry * x1p / rx;
468 double theta =
469 AngleOfVector(Point(static_cast<float>((x1p - cxp) / rx),
470 static_cast<float>((y1p - cyp) / ry))); // F.6.5.5
471 double delta =
472 AngleOfVector(Point(static_cast<float>((-x1p - cxp) / rx),
473 static_cast<float>((-y1p - cyp) / ry))) - // F.6.5.6
474 theta;
475 if (!aSweepFlag && delta > 0) {
476 delta -= 2.0 * M_PI;
477 } else if (aSweepFlag && delta < 0) {
478 delta += 2.0 * M_PI;
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);
489 if (delta < 0.0f) {
490 tx1 = -tx1;
491 ty1 = -ty1;
492 tx2 = -tx2;
493 ty2 = -ty2;
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.
509 /* static */
510 void SVGPathData::GetMarkerPositioningData(Span<const StylePathCommand> aPath,
511 float aZoom,
512 nsTArray<SVGMark>* aMarks) {
513 if (aPath.IsEmpty()) {
514 return;
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;
530 Point segEnd;
531 float segStartAngle, segEndAngle;
533 switch (cmd.tag) // to find segStartAngle, segEnd and segEndAngle
535 case StylePathCommand::Tag::Close:
536 segEnd = pathStart;
537 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
538 break;
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);
547 break;
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);
553 break;
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) {
561 cp1 += segStart;
562 cp2 += segStart;
563 segEnd += segStart;
566 prevCP = cp2;
567 segStartAngle = AngleOfVector(
568 cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart);
569 segEndAngle = AngleOfVector(
570 segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2);
571 break;
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) {
578 cp1 += segStart;
579 segEnd += segStart; // set before setting tcp2!
582 prevCP = cp1;
583 segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart);
584 segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1);
585 break;
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) {
596 segEnd += segStart;
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!
610 continue;
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
616 // calculations.
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);
621 break;
624 std::tie(rx, ry, segStartAngle, segEndAngle) =
625 ComputeSegAnglesAndCorrectRadii(segStart, segEnd, angle,
626 largeArcFlag, sweepFlag, rx, ry);
627 break;
629 case StylePathCommand::Tag::HLine: {
630 if (cmd.h_line.by_to == StyleByTo::To) {
631 segEnd = Point(cmd.h_line.x, segStart.y) * aZoom;
632 } else {
633 segEnd = segStart + Point(cmd.h_line.x, 0.0f) * aZoom;
635 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
636 break;
638 case StylePathCommand::Tag::VLine: {
639 if (cmd.v_line.by_to == StyleByTo::To) {
640 segEnd = Point(segStart.x, cmd.v_line.y) * aZoom;
641 } else {
642 segEnd = segStart + Point(0.0f, cmd.v_line.y) * aZoom;
644 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
645 break;
647 case StylePathCommand::Tag::SmoothCubic: {
648 const Point& cp1 = prevSeg && prevSeg->IsCubicType()
649 ? segStart * 2 - prevCP
650 : segStart;
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) {
655 cp2 += segStart;
656 segEnd += segStart;
659 prevCP = cp2;
660 segStartAngle = AngleOfVector(
661 cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart);
662 segEndAngle = AngleOfVector(
663 segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2);
664 break;
666 case StylePathCommand::Tag::SmoothQuad: {
667 const Point& cp1 = prevSeg && prevSeg->IsQuadraticType()
668 ? segStart * 2 - prevCP
669 : segStart;
670 segEnd = cmd.smooth_quad.by_to == StyleByTo::To
671 ? cmd.smooth_quad.point.ToGfxPoint() * aZoom
672 : segStart + cmd.smooth_quad.point.ToGfxPoint() * aZoom;
674 prevCP = cp1;
675 segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart);
676 segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1);
677 break;
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())) {
688 // end of a subpath
689 if (!(prevSeg && prevSeg->IsClose())) {
690 mark.angle = prevSegEndAngle;
692 } else if (!(cmd.IsClose() && prevSeg && prevSeg->IsClose())) {
693 mark.angle =
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,
703 SVGMark::eMid));
705 if (cmd.IsClose() && !(prevSeg && prevSeg->IsClose())) {
706 aMarks->LastElement().angle = aMarks->ElementAt(pathStartIndex).angle =
707 SVGContentUtils::AngleBisect(segEndAngle, pathStartAngle);
710 prevSeg = &cmd;
711 prevSegEnd = segEnd;
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?
726 return 0;
729 size_t SVGPathData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
730 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
733 } // namespace mozilla