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