Bug 1861403: update link to RFC doc for pref "network.http.http3.priority". r=necko...
[gecko.git] / dom / svg / SVGPathData.cpp
blob4dde07fcbe04f8be1a0c69802cd2ce64ef8d24db
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 "SVGPathDataParser.h"
18 #include <stdarg.h>
19 #include "nsStyleConsts.h"
20 #include "SVGContentUtils.h"
21 #include "SVGGeometryElement.h"
22 #include "SVGPathSegUtils.h"
23 #include <algorithm>
25 using namespace mozilla::dom::SVGPathSeg_Binding;
26 using namespace mozilla::gfx;
28 namespace mozilla {
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;
68 return NS_OK;
71 void SVGPathData::GetValueAsString(nsAString& aValue) const {
72 // we need this function in DidChangePathSegList
73 aValue.Truncate();
74 if (!Length()) {
75 return;
77 uint32_t i = 0;
78 for (;;) {
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");
86 return;
88 aValue.Append(' ');
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
95 // there's a problem.
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);
109 va_list args;
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));
115 va_end(args);
116 return NS_OK;
119 float SVGPathData::GetPathLength() const {
120 SVGPathTraversalState state;
122 uint32_t i = 0;
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");
130 return state.length;
133 #ifdef DEBUG
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]);
139 count++;
142 MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
144 return count;
146 #endif
148 bool SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(
149 FallibleTArray<double>* aOutput) const {
150 SVGPathTraversalState state;
152 aOutput->Clear();
154 uint32_t i = 0;
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)) {
162 return false;
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
167 // the duration...':
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)) {
177 return false;
180 i += 1 + SVGPathSegUtils::ArgCountForType(segType);
183 MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt?");
185 return true;
188 /* static */
189 bool SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(
190 Span<const StylePathCommand> aPath, FallibleTArray<double>* aOutput) {
191 SVGPathTraversalState state;
193 aOutput->Clear();
195 bool firstMoveToIsChecked = false;
196 for (const auto& cmd : aPath) {
197 SVGPathSegUtils::TraversePathSegment(cmd, state);
198 if (!std::isfinite(state.length)) {
199 return false;
202 // We skip all moveto commands except for the initial moveto.
203 if (!cmd.IsMoveTo() || !firstMoveToIsChecked) {
204 if (!aOutput->AppendElement(state.length, fallible)) {
205 return false;
209 if (cmd.IsMoveTo() && !firstMoveToIsChecked) {
210 firstMoveToIsChecked = true;
214 return 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) {
229 return segIndex;
231 i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);
232 segIndex++;
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
241 /* static */
242 uint32_t SVGPathData::GetPathSegAtLength(Span<const StylePathCommand> aPath,
243 float aDistance) {
244 uint32_t segIndex = 0;
245 SVGPathTraversalState state;
247 for (const auto& cmd : aPath) {
248 SVGPathSegUtils::TraversePathSegment(cmd, state);
249 if (state.length >= aDistance) {
250 return segIndex;
252 segIndex++;
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
282 * from a square.
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,
288 const Point& aPoint,
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
293 // not to.
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));
304 aPB->MoveTo(aPoint);
307 #define MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT \
308 do { \
309 if (!subpathHasLength && hasLineCaps && aStrokeWidth > 0 && \
310 subpathContainsNonMoveTo && IsValidType(prevSegType) && \
311 (!IsMoveto(prevSegType) || IsClosePath(segType))) { \
312 ApproximateZeroLengthSubpathSquareCaps(aBuilder, segStart, \
313 aStrokeWidth); \
315 } while (0)
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);
332 Point segEnd;
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.
340 uint32_t i = 0;
341 while (i < mData.Length()) {
342 segType = SVGPathSegUtils::DecodeType(mData[i++]);
343 uint32_t argCount = SVGPathSegUtils::ArgCountForType(segType);
345 switch (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;
350 segEnd = pathStart;
351 aBuilder->Close();
352 break;
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;
359 break;
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;
366 break;
368 case PATHSEG_LINETO_ABS:
369 segEnd = Point(mData[i], mData[i + 1]);
370 if (segEnd != segStart) {
371 subpathHasLength = true;
372 aBuilder->LineTo(segEnd);
374 break;
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);
382 break;
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);
392 break;
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);
402 break;
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);
414 break;
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;
420 segEnd = segStart +
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);
427 break;
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) {
434 segEnd += segStart;
436 if (segEnd != segStart) {
437 subpathHasLength = true;
438 if (radii.x == 0.0f || radii.y == 0.0f) {
439 aBuilder->LineTo(segEnd);
440 } else {
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);
448 break;
451 case PATHSEG_LINETO_HORIZONTAL_ABS:
452 segEnd = Point(mData[i], segStart.y);
453 if (segEnd != segStart) {
454 subpathHasLength = true;
455 aBuilder->LineTo(segEnd);
457 break;
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);
465 break;
467 case PATHSEG_LINETO_VERTICAL_ABS:
468 segEnd = Point(segStart.x, mData[i]);
469 if (segEnd != segStart) {
470 subpathHasLength = true;
471 aBuilder->LineTo(segEnd);
473 break;
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);
481 break;
483 case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
484 cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ? segStart * 2 - cp2
485 : segStart;
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);
492 break;
494 case PATHSEG_CURVETO_CUBIC_SMOOTH_REL:
495 cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ? segStart * 2 - cp2
496 : segStart;
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);
503 break;
505 case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
506 cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ? segStart * 2 - cp1
507 : segStart;
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);
516 break;
518 case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:
519 cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ? segStart * 2 - cp1
520 : segStart;
521 // Convert quadratic curve to cubic curve:
522 tcp1 = segStart + (cp1 - segStart) * 2 / 3;
523 segEnd = segStart +
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);
530 break;
532 default:
533 MOZ_ASSERT_UNREACHABLE("Bad path segment type");
534 return nullptr; // according to spec we'd use everything up to the bad
535 // seg anyway
538 subpathContainsNonMoveTo = !IsMoveto(segType);
539 i += argCount;
540 prevSegType = segType;
541 segStart = segEnd;
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);
569 /* static */
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.
582 /* static */
583 already_AddRefed<Path> SVGPathData::BuildPath(
584 Span<const StylePathCommand> aPath, PathBuilder* aBuilder,
585 StyleStrokeLinecap aStrokeLineCap, Float aStrokeWidth, float aZoomFactor) {
586 if (aPath.IsEmpty() || !aPath[0].IsMoveTo()) {
587 return nullptr; // paths without an initial moveto are invalid
590 bool hasLineCaps = aStrokeLineCap != StyleStrokeLinecap::Butt;
591 bool subpathHasLength = false; // visual length
592 bool subpathContainsNonMoveTo = false;
594 StylePathCommand::Tag segType = StylePathCommand::Tag::Unknown;
595 StylePathCommand::Tag prevSegType = StylePathCommand::Tag::Unknown;
596 Point pathStart(0.0, 0.0); // start point of [sub]path
597 Point segStart(0.0, 0.0);
598 Point segEnd;
599 Point cp1, cp2; // previous bezier's control points
600 Point tcp1, tcp2; // temporaries
602 auto scale = [aZoomFactor](const Point& p) {
603 return Point(p.x * aZoomFactor, p.y * aZoomFactor);
606 // Regarding cp1 and cp2: If the previous segment was a cubic bezier curve,
607 // then cp2 is its second control point. If the previous segment was a
608 // quadratic curve, then cp1 is its (only) control point.
610 for (const StylePathCommand& cmd : aPath) {
611 segType = cmd.tag;
612 switch (segType) {
613 case StylePathCommand::Tag::ClosePath:
614 // set this early to allow drawing of square caps for "M{x},{y} Z":
615 subpathContainsNonMoveTo = true;
616 MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
617 segEnd = pathStart;
618 aBuilder->Close();
619 break;
620 case StylePathCommand::Tag::MoveTo: {
621 MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
622 const Point& p = cmd.move_to.point.ConvertsToGfxPoint();
623 pathStart = segEnd =
624 cmd.move_to.absolute == StyleIsAbsolute::Yes ? p : segStart + p;
625 aBuilder->MoveTo(scale(segEnd));
626 subpathHasLength = false;
627 break;
629 case StylePathCommand::Tag::LineTo: {
630 const Point& p = cmd.line_to.point.ConvertsToGfxPoint();
631 segEnd =
632 cmd.line_to.absolute == StyleIsAbsolute::Yes ? p : segStart + p;
633 if (segEnd != segStart) {
634 subpathHasLength = true;
635 aBuilder->LineTo(scale(segEnd));
637 break;
639 case StylePathCommand::Tag::CurveTo:
640 cp1 = cmd.curve_to.control1.ConvertsToGfxPoint();
641 cp2 = cmd.curve_to.control2.ConvertsToGfxPoint();
642 segEnd = cmd.curve_to.point.ConvertsToGfxPoint();
644 if (cmd.curve_to.absolute == StyleIsAbsolute::No) {
645 cp1 += segStart;
646 cp2 += segStart;
647 segEnd += segStart;
650 if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
651 subpathHasLength = true;
652 aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd));
654 break;
656 case StylePathCommand::Tag::QuadBezierCurveTo:
657 cp1 = cmd.quad_bezier_curve_to.control1.ConvertsToGfxPoint();
658 segEnd = cmd.quad_bezier_curve_to.point.ConvertsToGfxPoint();
660 if (cmd.quad_bezier_curve_to.absolute == StyleIsAbsolute::No) {
661 cp1 += segStart;
662 segEnd += segStart; // set before setting tcp2!
665 // Convert quadratic curve to cubic curve:
666 tcp1 = segStart + (cp1 - segStart) * 2 / 3;
667 tcp2 = cp1 + (segEnd - cp1) / 3;
669 if (segEnd != segStart || segEnd != cp1) {
670 subpathHasLength = true;
671 aBuilder->BezierTo(scale(tcp1), scale(tcp2), scale(segEnd));
673 break;
675 case StylePathCommand::Tag::EllipticalArc: {
676 const auto& arc = cmd.elliptical_arc;
677 Point radii(arc.rx, arc.ry);
678 segEnd = arc.point.ConvertsToGfxPoint();
679 if (arc.absolute == StyleIsAbsolute::No) {
680 segEnd += segStart;
682 if (segEnd != segStart) {
683 subpathHasLength = true;
684 if (radii.x == 0.0f || radii.y == 0.0f) {
685 aBuilder->LineTo(scale(segEnd));
686 } else {
687 SVGArcConverter converter(segStart, segEnd, radii, arc.angle,
688 arc.large_arc_flag._0, arc.sweep_flag._0);
689 while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) {
690 aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd));
694 break;
696 case StylePathCommand::Tag::HorizontalLineTo:
697 if (cmd.horizontal_line_to.absolute == StyleIsAbsolute::Yes) {
698 segEnd = Point(cmd.horizontal_line_to.x, segStart.y);
699 } else {
700 segEnd = segStart + Point(cmd.horizontal_line_to.x, 0.0f);
703 if (segEnd != segStart) {
704 subpathHasLength = true;
705 aBuilder->LineTo(scale(segEnd));
707 break;
709 case StylePathCommand::Tag::VerticalLineTo:
710 if (cmd.vertical_line_to.absolute == StyleIsAbsolute::Yes) {
711 segEnd = Point(segStart.x, cmd.vertical_line_to.y);
712 } else {
713 segEnd = segStart + Point(0.0f, cmd.vertical_line_to.y);
716 if (segEnd != segStart) {
717 subpathHasLength = true;
718 aBuilder->LineTo(scale(segEnd));
720 break;
722 case StylePathCommand::Tag::SmoothCurveTo:
723 cp1 = IsCubicType(prevSegType) ? segStart * 2 - cp2 : segStart;
724 cp2 = cmd.smooth_curve_to.control2.ConvertsToGfxPoint();
725 segEnd = cmd.smooth_curve_to.point.ConvertsToGfxPoint();
727 if (cmd.smooth_curve_to.absolute == StyleIsAbsolute::No) {
728 cp2 += segStart;
729 segEnd += segStart;
732 if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
733 subpathHasLength = true;
734 aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd));
736 break;
738 case StylePathCommand::Tag::SmoothQuadBezierCurveTo: {
739 cp1 = IsQuadraticType(prevSegType) ? segStart * 2 - cp1 : segStart;
740 // Convert quadratic curve to cubic curve:
741 tcp1 = segStart + (cp1 - segStart) * 2 / 3;
743 const Point& p =
744 cmd.smooth_quad_bezier_curve_to.point.ConvertsToGfxPoint();
745 // set before setting tcp2!
746 segEnd =
747 cmd.smooth_quad_bezier_curve_to.absolute == StyleIsAbsolute::Yes
749 : segStart + p;
750 tcp2 = cp1 + (segEnd - cp1) / 3;
752 if (segEnd != segStart || segEnd != cp1) {
753 subpathHasLength = true;
754 aBuilder->BezierTo(scale(tcp1), scale(tcp2), scale(segEnd));
756 break;
758 case StylePathCommand::Tag::Unknown:
759 MOZ_ASSERT_UNREACHABLE("Unacceptable path segment type");
760 return nullptr;
763 subpathContainsNonMoveTo = !IsMoveto(segType);
764 prevSegType = segType;
765 segStart = segEnd;
768 MOZ_ASSERT(prevSegType == segType,
769 "prevSegType should be left at the final segType");
771 MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
773 return aBuilder->Finish();
776 static double AngleOfVector(const Point& aVector) {
777 // C99 says about atan2 "A domain error may occur if both arguments are
778 // zero" and "On a domain error, the function returns an implementation-
779 // defined value". In the case of atan2 the implementation-defined value
780 // seems to commonly be zero, but it could just as easily be a NaN value.
781 // We specifically want zero in this case, hence the check:
783 return (aVector != Point(0.0, 0.0)) ? atan2(aVector.y, aVector.x) : 0.0;
786 static float AngleOfVector(const Point& cp1, const Point& cp2) {
787 return static_cast<float>(AngleOfVector(cp1 - cp2));
790 // This implements F.6.5 and F.6.6 of
791 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
792 static std::tuple<float, float, float, float>
793 /* rx, ry, segStartAngle, segEndAngle */
794 ComputeSegAnglesAndCorrectRadii(const Point& aSegStart, const Point& aSegEnd,
795 const float aAngle, const bool aLargeArcFlag,
796 const bool aSweepFlag, const float aRx,
797 const float aRy) {
798 float rx = fabs(aRx); // F.6.6.1
799 float ry = fabs(aRy);
801 // F.6.5.1:
802 const float angle = static_cast<float>(aAngle * M_PI / 180.0);
803 double x1p = cos(angle) * (aSegStart.x - aSegEnd.x) / 2.0 +
804 sin(angle) * (aSegStart.y - aSegEnd.y) / 2.0;
805 double y1p = -sin(angle) * (aSegStart.x - aSegEnd.x) / 2.0 +
806 cos(angle) * (aSegStart.y - aSegEnd.y) / 2.0;
808 // This is the root in F.6.5.2 and the numerator under that root:
809 double root;
810 double numerator =
811 rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;
813 if (numerator >= 0.0) {
814 root = sqrt(numerator / (rx * rx * y1p * y1p + ry * ry * x1p * x1p));
815 if (aLargeArcFlag == aSweepFlag) root = -root;
816 } else {
817 // F.6.6 step 3 - |numerator < 0.0|. This is equivalent to the result
818 // of F.6.6.2 (lamedh) being greater than one. What we have here is
819 // ellipse radii that are too small for the ellipse to reach between
820 // segStart and segEnd. We scale the radii up uniformly so that the
821 // ellipse is just big enough to fit (i.e. to the point where there is
822 // exactly one solution).
824 double lamedh =
825 1.0 - numerator / (rx * rx * ry * ry); // equiv to eqn F.6.6.2
826 double s = sqrt(lamedh);
827 rx = static_cast<float>((double)rx * s); // F.6.6.3
828 ry = static_cast<float>((double)ry * s);
829 root = 0.0;
832 double cxp = root * rx * y1p / ry; // F.6.5.2
833 double cyp = -root * ry * x1p / rx;
835 double theta =
836 AngleOfVector(Point(static_cast<float>((x1p - cxp) / rx),
837 static_cast<float>((y1p - cyp) / ry))); // F.6.5.5
838 double delta =
839 AngleOfVector(Point(static_cast<float>((-x1p - cxp) / rx),
840 static_cast<float>((-y1p - cyp) / ry))) - // F.6.5.6
841 theta;
842 if (!aSweepFlag && delta > 0) {
843 delta -= 2.0 * M_PI;
844 } else if (aSweepFlag && delta < 0) {
845 delta += 2.0 * M_PI;
848 double tx1, ty1, tx2, ty2;
849 tx1 = -cos(angle) * rx * sin(theta) - sin(angle) * ry * cos(theta);
850 ty1 = -sin(angle) * rx * sin(theta) + cos(angle) * ry * cos(theta);
851 tx2 = -cos(angle) * rx * sin(theta + delta) -
852 sin(angle) * ry * cos(theta + delta);
853 ty2 = -sin(angle) * rx * sin(theta + delta) +
854 cos(angle) * ry * cos(theta + delta);
856 if (delta < 0.0f) {
857 tx1 = -tx1;
858 ty1 = -ty1;
859 tx2 = -tx2;
860 ty2 = -ty2;
863 return {rx, ry, static_cast<float>(atan2(ty1, tx1)),
864 static_cast<float>(atan2(ty2, tx2))};
867 void SVGPathData::GetMarkerPositioningData(nsTArray<SVGMark>* aMarks) const {
868 // This code should assume that ANY type of segment can appear at ANY index.
869 // It should also assume that segments such as M and Z can appear in weird
870 // places, and repeat multiple times consecutively.
872 // info on current [sub]path (reset every M command):
873 Point pathStart(0.0, 0.0);
874 float pathStartAngle = 0.0f;
875 uint32_t pathStartIndex = 0;
877 // info on previous segment:
878 uint16_t prevSegType = PATHSEG_UNKNOWN;
879 Point prevSegEnd(0.0, 0.0);
880 float prevSegEndAngle = 0.0f;
881 Point prevCP; // if prev seg was a bezier, this was its last control point
883 uint32_t i = 0;
884 while (i < mData.Length()) {
885 // info on current segment:
886 uint16_t segType =
887 SVGPathSegUtils::DecodeType(mData[i++]); // advances i to args
888 Point& segStart = prevSegEnd;
889 Point segEnd;
890 float segStartAngle, segEndAngle;
892 switch (segType) // to find segStartAngle, segEnd and segEndAngle
894 case PATHSEG_CLOSEPATH:
895 segEnd = pathStart;
896 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
897 break;
899 case PATHSEG_MOVETO_ABS:
900 case PATHSEG_MOVETO_REL:
901 if (segType == PATHSEG_MOVETO_ABS) {
902 segEnd = Point(mData[i], mData[i + 1]);
903 } else {
904 segEnd = segStart + Point(mData[i], mData[i + 1]);
906 pathStart = segEnd;
907 pathStartIndex = aMarks->Length();
908 // If authors are going to specify multiple consecutive moveto commands
909 // with markers, me might as well make the angle do something useful:
910 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
911 i += 2;
912 break;
914 case PATHSEG_LINETO_ABS:
915 case PATHSEG_LINETO_REL:
916 if (segType == PATHSEG_LINETO_ABS) {
917 segEnd = Point(mData[i], mData[i + 1]);
918 } else {
919 segEnd = segStart + Point(mData[i], mData[i + 1]);
921 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
922 i += 2;
923 break;
925 case PATHSEG_CURVETO_CUBIC_ABS:
926 case PATHSEG_CURVETO_CUBIC_REL: {
927 Point cp1, cp2; // control points
928 if (segType == PATHSEG_CURVETO_CUBIC_ABS) {
929 cp1 = Point(mData[i], mData[i + 1]);
930 cp2 = Point(mData[i + 2], mData[i + 3]);
931 segEnd = Point(mData[i + 4], mData[i + 5]);
932 } else {
933 cp1 = segStart + Point(mData[i], mData[i + 1]);
934 cp2 = segStart + Point(mData[i + 2], mData[i + 3]);
935 segEnd = segStart + Point(mData[i + 4], mData[i + 5]);
937 prevCP = cp2;
938 segStartAngle = AngleOfVector(
939 cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart);
940 segEndAngle = AngleOfVector(
941 segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2);
942 i += 6;
943 break;
946 case PATHSEG_CURVETO_QUADRATIC_ABS:
947 case PATHSEG_CURVETO_QUADRATIC_REL: {
948 Point cp1; // control point
949 if (segType == PATHSEG_CURVETO_QUADRATIC_ABS) {
950 cp1 = Point(mData[i], mData[i + 1]);
951 segEnd = Point(mData[i + 2], mData[i + 3]);
952 } else {
953 cp1 = segStart + Point(mData[i], mData[i + 1]);
954 segEnd = segStart + Point(mData[i + 2], mData[i + 3]);
956 prevCP = cp1;
957 segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart);
958 segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1);
959 i += 4;
960 break;
963 case PATHSEG_ARC_ABS:
964 case PATHSEG_ARC_REL: {
965 float rx = mData[i];
966 float ry = mData[i + 1];
967 float angle = mData[i + 2];
968 bool largeArcFlag = mData[i + 3] != 0.0f;
969 bool sweepFlag = mData[i + 4] != 0.0f;
970 if (segType == PATHSEG_ARC_ABS) {
971 segEnd = Point(mData[i + 5], mData[i + 6]);
972 } else {
973 segEnd = segStart + Point(mData[i + 5], mData[i + 6]);
976 // See section F.6 of SVG 1.1 for details on what we're doing here:
977 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
979 if (segStart == segEnd) {
980 // F.6.2 says "If the endpoints (x1, y1) and (x2, y2) are identical,
981 // then this is equivalent to omitting the elliptical arc segment
982 // entirely." We take that very literally here, not adding a mark, and
983 // not even setting any of the 'prev' variables so that it's as if
984 // this arc had never existed; note the difference this will make e.g.
985 // if the arc is proceeded by a bezier curve and followed by a
986 // "smooth" bezier curve of the same degree!
987 i += 7;
988 continue;
991 // Below we have funny interleaving of F.6.6 (Correction of out-of-range
992 // radii) and F.6.5 (Conversion from endpoint to center
993 // parameterization) which is designed to avoid some unnecessary
994 // calculations.
996 if (rx == 0.0 || ry == 0.0) {
997 // F.6.6 step 1 - straight line or coincidental points
998 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
999 i += 7;
1000 break;
1003 std::tie(rx, ry, segStartAngle, segEndAngle) =
1004 ComputeSegAnglesAndCorrectRadii(segStart, segEnd, angle,
1005 largeArcFlag, sweepFlag, rx, ry);
1006 i += 7;
1007 break;
1010 case PATHSEG_LINETO_HORIZONTAL_ABS:
1011 case PATHSEG_LINETO_HORIZONTAL_REL:
1012 if (segType == PATHSEG_LINETO_HORIZONTAL_ABS) {
1013 segEnd = Point(mData[i++], segStart.y);
1014 } else {
1015 segEnd = segStart + Point(mData[i++], 0.0f);
1017 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
1018 break;
1020 case PATHSEG_LINETO_VERTICAL_ABS:
1021 case PATHSEG_LINETO_VERTICAL_REL:
1022 if (segType == PATHSEG_LINETO_VERTICAL_ABS) {
1023 segEnd = Point(segStart.x, mData[i++]);
1024 } else {
1025 segEnd = segStart + Point(0.0f, mData[i++]);
1027 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
1028 break;
1030 case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
1031 case PATHSEG_CURVETO_CUBIC_SMOOTH_REL: {
1032 Point cp1 = SVGPathSegUtils::IsCubicType(prevSegType)
1033 ? segStart * 2 - prevCP
1034 : segStart;
1035 Point cp2;
1036 if (segType == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS) {
1037 cp2 = Point(mData[i], mData[i + 1]);
1038 segEnd = Point(mData[i + 2], mData[i + 3]);
1039 } else {
1040 cp2 = segStart + Point(mData[i], mData[i + 1]);
1041 segEnd = segStart + Point(mData[i + 2], mData[i + 3]);
1043 prevCP = cp2;
1044 segStartAngle = AngleOfVector(
1045 cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart);
1046 segEndAngle = AngleOfVector(
1047 segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2);
1048 i += 4;
1049 break;
1052 case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
1053 case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL: {
1054 Point cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType)
1055 ? segStart * 2 - prevCP
1056 : segStart;
1057 if (segType == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS) {
1058 segEnd = Point(mData[i], mData[i + 1]);
1059 } else {
1060 segEnd = segStart + Point(mData[i], mData[i + 1]);
1062 prevCP = cp1;
1063 segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart);
1064 segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1);
1065 i += 2;
1066 break;
1069 default:
1070 // Leave any existing marks in aMarks so we have a visual indication of
1071 // when things went wrong.
1072 MOZ_ASSERT(false, "Unknown segment type - path corruption?");
1073 return;
1076 // Set the angle of the mark at the start of this segment:
1077 if (aMarks->Length()) {
1078 SVGMark& mark = aMarks->LastElement();
1079 if (!IsMoveto(segType) && IsMoveto(prevSegType)) {
1080 // start of new subpath
1081 pathStartAngle = mark.angle = segStartAngle;
1082 } else if (IsMoveto(segType) && !IsMoveto(prevSegType)) {
1083 // end of a subpath
1084 if (prevSegType != PATHSEG_CLOSEPATH) mark.angle = prevSegEndAngle;
1085 } else {
1086 if (!(segType == PATHSEG_CLOSEPATH && prevSegType == PATHSEG_CLOSEPATH))
1087 mark.angle =
1088 SVGContentUtils::AngleBisect(prevSegEndAngle, segStartAngle);
1092 // Add the mark at the end of this segment, and set its position:
1093 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1094 // pretended earlier.
1095 aMarks->AppendElement(SVGMark(static_cast<float>(segEnd.x),
1096 static_cast<float>(segEnd.y), 0.0f,
1097 SVGMark::eMid));
1099 if (segType == PATHSEG_CLOSEPATH && prevSegType != PATHSEG_CLOSEPATH) {
1100 aMarks->LastElement().angle = aMarks->ElementAt(pathStartIndex).angle =
1101 SVGContentUtils::AngleBisect(segEndAngle, pathStartAngle);
1104 prevSegType = segType;
1105 prevSegEnd = segEnd;
1106 prevSegEndAngle = segEndAngle;
1109 MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
1111 if (aMarks->Length()) {
1112 if (prevSegType != PATHSEG_CLOSEPATH) {
1113 aMarks->LastElement().angle = prevSegEndAngle;
1115 aMarks->LastElement().type = SVGMark::eEnd;
1116 aMarks->ElementAt(0).type = SVGMark::eStart;
1120 // Basically, this is identical to the above function, but replace |mData| with
1121 // |aPath|. We probably can factor out some identical calculation, but I believe
1122 // the above one will be removed because we will use any kind of array of
1123 // StylePathCommand for SVG d attribute in the future.
1124 /* static */
1125 void SVGPathData::GetMarkerPositioningData(Span<const StylePathCommand> aPath,
1126 nsTArray<SVGMark>* aMarks) {
1127 if (aPath.IsEmpty()) {
1128 return;
1131 // info on current [sub]path (reset every M command):
1132 Point pathStart(0.0, 0.0);
1133 float pathStartAngle = 0.0f;
1134 uint32_t pathStartIndex = 0;
1136 // info on previous segment:
1137 StylePathCommand::Tag prevSegType = StylePathCommand::Tag::Unknown;
1138 Point prevSegEnd(0.0, 0.0);
1139 float prevSegEndAngle = 0.0f;
1140 Point prevCP; // if prev seg was a bezier, this was its last control point
1142 StylePathCommand::Tag segType = StylePathCommand::Tag::Unknown;
1143 for (const StylePathCommand& cmd : aPath) {
1144 segType = cmd.tag;
1145 Point& segStart = prevSegEnd;
1146 Point segEnd;
1147 float segStartAngle, segEndAngle;
1149 switch (segType) // to find segStartAngle, segEnd and segEndAngle
1151 case StylePathCommand::Tag::ClosePath:
1152 segEnd = pathStart;
1153 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
1154 break;
1156 case StylePathCommand::Tag::MoveTo: {
1157 const Point& p = cmd.move_to.point.ConvertsToGfxPoint();
1158 pathStart = segEnd =
1159 cmd.move_to.absolute == StyleIsAbsolute::Yes ? p : segStart + p;
1160 pathStartIndex = aMarks->Length();
1161 // If authors are going to specify multiple consecutive moveto commands
1162 // with markers, me might as well make the angle do something useful:
1163 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
1164 break;
1166 case StylePathCommand::Tag::LineTo: {
1167 const Point& p = cmd.line_to.point.ConvertsToGfxPoint();
1168 segEnd =
1169 cmd.line_to.absolute == StyleIsAbsolute::Yes ? p : segStart + p;
1170 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
1171 break;
1173 case StylePathCommand::Tag::CurveTo: {
1174 Point cp1 = cmd.curve_to.control1.ConvertsToGfxPoint();
1175 Point cp2 = cmd.curve_to.control2.ConvertsToGfxPoint();
1176 segEnd = cmd.curve_to.point.ConvertsToGfxPoint();
1178 if (cmd.curve_to.absolute == StyleIsAbsolute::No) {
1179 cp1 += segStart;
1180 cp2 += segStart;
1181 segEnd += segStart;
1184 prevCP = cp2;
1185 segStartAngle = AngleOfVector(
1186 cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart);
1187 segEndAngle = AngleOfVector(
1188 segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2);
1189 break;
1191 case StylePathCommand::Tag::QuadBezierCurveTo: {
1192 Point cp1 = cmd.quad_bezier_curve_to.control1.ConvertsToGfxPoint();
1193 segEnd = cmd.quad_bezier_curve_to.point.ConvertsToGfxPoint();
1195 if (cmd.quad_bezier_curve_to.absolute == StyleIsAbsolute::No) {
1196 cp1 += segStart;
1197 segEnd += segStart; // set before setting tcp2!
1200 prevCP = cp1;
1201 segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart);
1202 segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1);
1203 break;
1205 case StylePathCommand::Tag::EllipticalArc: {
1206 const auto& arc = cmd.elliptical_arc;
1207 float rx = arc.rx;
1208 float ry = arc.ry;
1209 float angle = arc.angle;
1210 bool largeArcFlag = arc.large_arc_flag._0;
1211 bool sweepFlag = arc.sweep_flag._0;
1212 Point radii(arc.rx, arc.ry);
1213 segEnd = arc.point.ConvertsToGfxPoint();
1214 if (arc.absolute == StyleIsAbsolute::No) {
1215 segEnd += segStart;
1218 // See section F.6 of SVG 1.1 for details on what we're doing here:
1219 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
1221 if (segStart == segEnd) {
1222 // F.6.2 says "If the endpoints (x1, y1) and (x2, y2) are identical,
1223 // then this is equivalent to omitting the elliptical arc segment
1224 // entirely." We take that very literally here, not adding a mark, and
1225 // not even setting any of the 'prev' variables so that it's as if
1226 // this arc had never existed; note the difference this will make e.g.
1227 // if the arc is proceeded by a bezier curve and followed by a
1228 // "smooth" bezier curve of the same degree!
1229 continue;
1232 // Below we have funny interleaving of F.6.6 (Correction of out-of-range
1233 // radii) and F.6.5 (Conversion from endpoint to center
1234 // parameterization) which is designed to avoid some unnecessary
1235 // calculations.
1237 if (rx == 0.0 || ry == 0.0) {
1238 // F.6.6 step 1 - straight line or coincidental points
1239 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
1240 break;
1243 std::tie(rx, ry, segStartAngle, segEndAngle) =
1244 ComputeSegAnglesAndCorrectRadii(segStart, segEnd, angle,
1245 largeArcFlag, sweepFlag, rx, ry);
1246 break;
1248 case StylePathCommand::Tag::HorizontalLineTo: {
1249 if (cmd.horizontal_line_to.absolute == StyleIsAbsolute::Yes) {
1250 segEnd = Point(cmd.horizontal_line_to.x, segStart.y);
1251 } else {
1252 segEnd = segStart + Point(cmd.horizontal_line_to.x, 0.0f);
1254 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
1255 break;
1257 case StylePathCommand::Tag::VerticalLineTo: {
1258 if (cmd.vertical_line_to.absolute == StyleIsAbsolute::Yes) {
1259 segEnd = Point(segStart.x, cmd.vertical_line_to.y);
1260 } else {
1261 segEnd = segStart + Point(0.0f, cmd.vertical_line_to.y);
1263 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
1264 break;
1266 case StylePathCommand::Tag::SmoothCurveTo: {
1267 Point cp1 = IsCubicType(prevSegType) ? segStart * 2 - prevCP : segStart;
1268 Point cp2 = cmd.smooth_curve_to.control2.ConvertsToGfxPoint();
1269 segEnd = cmd.smooth_curve_to.point.ConvertsToGfxPoint();
1271 if (cmd.smooth_curve_to.absolute == StyleIsAbsolute::No) {
1272 cp2 += segStart;
1273 segEnd += segStart;
1276 prevCP = cp2;
1277 segStartAngle = AngleOfVector(
1278 cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart);
1279 segEndAngle = AngleOfVector(
1280 segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2);
1281 break;
1283 case StylePathCommand::Tag::SmoothQuadBezierCurveTo: {
1284 Point cp1 =
1285 IsQuadraticType(prevSegType) ? segStart * 2 - prevCP : segStart;
1286 segEnd =
1287 cmd.smooth_quad_bezier_curve_to.absolute == StyleIsAbsolute::Yes
1288 ? cmd.smooth_quad_bezier_curve_to.point.ConvertsToGfxPoint()
1289 : segStart + cmd.smooth_quad_bezier_curve_to.point
1290 .ConvertsToGfxPoint();
1292 prevCP = cp1;
1293 segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart);
1294 segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1);
1295 break;
1297 case StylePathCommand::Tag::Unknown:
1298 // Leave any existing marks in aMarks so we have a visual indication of
1299 // when things went wrong.
1300 MOZ_ASSERT_UNREACHABLE("Unknown segment type - path corruption?");
1301 return;
1304 // Set the angle of the mark at the start of this segment:
1305 if (aMarks->Length()) {
1306 SVGMark& mark = aMarks->LastElement();
1307 if (!IsMoveto(segType) && IsMoveto(prevSegType)) {
1308 // start of new subpath
1309 pathStartAngle = mark.angle = segStartAngle;
1310 } else if (IsMoveto(segType) && !IsMoveto(prevSegType)) {
1311 // end of a subpath
1312 if (prevSegType != StylePathCommand::Tag::ClosePath) {
1313 mark.angle = prevSegEndAngle;
1315 } else if (!(segType == StylePathCommand::Tag::ClosePath &&
1316 prevSegType == StylePathCommand::Tag::ClosePath)) {
1317 mark.angle =
1318 SVGContentUtils::AngleBisect(prevSegEndAngle, segStartAngle);
1322 // Add the mark at the end of this segment, and set its position:
1323 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1324 // pretended earlier.
1325 aMarks->AppendElement(SVGMark(static_cast<float>(segEnd.x),
1326 static_cast<float>(segEnd.y), 0.0f,
1327 SVGMark::eMid));
1329 if (segType == StylePathCommand::Tag::ClosePath &&
1330 prevSegType != StylePathCommand::Tag::ClosePath) {
1331 aMarks->LastElement().angle = aMarks->ElementAt(pathStartIndex).angle =
1332 SVGContentUtils::AngleBisect(segEndAngle, pathStartAngle);
1335 prevSegType = segType;
1336 prevSegEnd = segEnd;
1337 prevSegEndAngle = segEndAngle;
1340 if (aMarks->Length()) {
1341 if (prevSegType != StylePathCommand::Tag::ClosePath) {
1342 aMarks->LastElement().angle = prevSegEndAngle;
1344 aMarks->LastElement().type = SVGMark::eEnd;
1345 aMarks->ElementAt(0).type = SVGMark::eStart;
1349 size_t SVGPathData::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
1350 return mData.ShallowSizeOfExcludingThis(aMallocSizeOf);
1353 size_t SVGPathData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
1354 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
1357 } // namespace mozilla