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/. */
6
7 #include "SVGPathData.h"
8
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>
24
25 using namespace mozilla::dom::SVGPathSeg_Binding;
26 using namespace mozilla::gfx;
27
28 namespace mozilla {
29
IsMoveto(uint16_t aSegType)30 static inline bool IsMoveto(uint16_t aSegType) {
31 return aSegType == PATHSEG_MOVETO_ABS || aSegType == PATHSEG_MOVETO_REL;
32 }
33
IsMoveto(StylePathCommand::Tag aSegType)34 static inline bool IsMoveto(StylePathCommand::Tag aSegType) {
35 return aSegType == StylePathCommand::Tag::MoveTo;
36 }
37
IsValidType(uint16_t aSegType)38 static inline bool IsValidType(uint16_t aSegType) {
39 return SVGPathSegUtils::IsValidType(aSegType);
40 }
41
IsValidType(StylePathCommand::Tag aSegType)42 static inline bool IsValidType(StylePathCommand::Tag aSegType) {
43 return aSegType != StylePathCommand::Tag::Unknown;
44 }
45
IsClosePath(uint16_t aSegType)46 static inline bool IsClosePath(uint16_t aSegType) {
47 return aSegType == PATHSEG_CLOSEPATH;
48 }
49
IsClosePath(StylePathCommand::Tag aSegType)50 static inline bool IsClosePath(StylePathCommand::Tag aSegType) {
51 return aSegType == StylePathCommand::Tag::ClosePath;
52 }
53
ToGfxPoint(const StyleCoordPair & aPair)54 static inline Point ToGfxPoint(const StyleCoordPair& aPair) {
55 return {aPair._0, aPair._1};
56 }
57
IsCubicType(StylePathCommand::Tag aType)58 static inline bool IsCubicType(StylePathCommand::Tag aType) {
59 return aType == StylePathCommand::Tag::CurveTo ||
60 aType == StylePathCommand::Tag::SmoothCurveTo;
61 }
62
IsQuadraticType(StylePathCommand::Tag aType)63 static inline bool IsQuadraticType(StylePathCommand::Tag aType) {
64 return aType == StylePathCommand::Tag::QuadBezierCurveTo ||
65 aType == StylePathCommand::Tag::SmoothQuadBezierCurveTo;
66 }
67
CopyFrom(const SVGPathData & rhs)68 nsresult SVGPathData::CopyFrom(const SVGPathData& rhs) {
69 if (!mData.Assign(rhs.mData, fallible)) {
70 return NS_ERROR_OUT_OF_MEMORY;
71 }
72 return NS_OK;
73 }
74
GetValueAsString(nsAString & aValue) const75 void SVGPathData::GetValueAsString(nsAString& aValue) const {
76 // we need this function in DidChangePathSegList
77 aValue.Truncate();
78 if (!Length()) {
79 return;
80 }
81 uint32_t i = 0;
82 for (;;) {
83 nsAutoString segAsString;
84 SVGPathSegUtils::GetValueAsString(&mData[i], segAsString);
85 // We ignore OOM, since it's not useful for us to return an error.
86 aValue.Append(segAsString);
87 i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);
88 if (i >= mData.Length()) {
89 MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
90 return;
91 }
92 aValue.Append(' ');
93 }
94 }
95
SetValueFromString(const nsAString & aValue)96 nsresult SVGPathData::SetValueFromString(const nsAString& aValue) {
97 // We don't use a temp variable since the spec says to parse everything up to
98 // the first error. We still return any error though so that callers know if
99 // there's a problem.
100
101 SVGPathDataParser pathParser(aValue, this);
102 return pathParser.Parse() ? NS_OK : NS_ERROR_DOM_SYNTAX_ERR;
103 }
104
AppendSeg(uint32_t aType,...)105 nsresult SVGPathData::AppendSeg(uint32_t aType, ...) {
106 uint32_t oldLength = mData.Length();
107 uint32_t newLength = oldLength + 1 + SVGPathSegUtils::ArgCountForType(aType);
108 if (!mData.SetLength(newLength, fallible)) {
109 return NS_ERROR_OUT_OF_MEMORY;
110 }
111
112 mData[oldLength] = SVGPathSegUtils::EncodeType(aType);
113 va_list args;
114 va_start(args, aType);
115 for (uint32_t i = oldLength + 1; i < newLength; ++i) {
116 // NOTE! 'float' is promoted to 'double' when passed through '...'!
117 mData[i] = float(va_arg(args, double));
118 }
119 va_end(args);
120 return NS_OK;
121 }
122
GetPathLength() const123 float SVGPathData::GetPathLength() const {
124 SVGPathTraversalState state;
125
126 uint32_t i = 0;
127 while (i < mData.Length()) {
128 SVGPathSegUtils::TraversePathSegment(&mData[i], state);
129 i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);
130 }
131
132 MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
133
134 return state.length;
135 }
136
137 #ifdef DEBUG
CountItems() const138 uint32_t SVGPathData::CountItems() const {
139 uint32_t i = 0, count = 0;
140
141 while (i < mData.Length()) {
142 i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);
143 count++;
144 }
145
146 MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
147
148 return count;
149 }
150 #endif
151
GetDistancesFromOriginToEndsOfVisibleSegments(FallibleTArray<double> * aOutput) const152 bool SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(
153 FallibleTArray<double>* aOutput) const {
154 SVGPathTraversalState state;
155
156 aOutput->Clear();
157
158 uint32_t i = 0;
159 while (i < mData.Length()) {
160 uint32_t segType = SVGPathSegUtils::DecodeType(mData[i]);
161 SVGPathSegUtils::TraversePathSegment(&mData[i], state);
162
163 // With degenerately large point coordinates, TraversePathSegment can fail
164 // and end up producing NaNs.
165 if (!std::isfinite(state.length)) {
166 return false;
167 }
168
169 // We skip all moveto commands except an initial moveto. See the text 'A
170 // "move to" command does not count as an additional point when dividing up
171 // the duration...':
172 //
173 // http://www.w3.org/TR/SVG11/animate.html#AnimateMotionElement
174 //
175 // This is important in the non-default case of calcMode="linear". In
176 // this case an equal amount of time is spent on each path segment,
177 // except on moveto segments which are jumped over immediately.
178
179 if (i == 0 || !IsMoveto(segType)) {
180 if (!aOutput->AppendElement(state.length, fallible)) {
181 return false;
182 }
183 }
184 i += 1 + SVGPathSegUtils::ArgCountForType(segType);
185 }
186
187 MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt?");
188
189 return true;
190 }
191
192 /* static */
GetDistancesFromOriginToEndsOfVisibleSegments(Span<const StylePathCommand> aPath,FallibleTArray<double> * aOutput)193 bool SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(
194 Span<const StylePathCommand> aPath, FallibleTArray<double>* aOutput) {
195 SVGPathTraversalState state;
196
197 aOutput->Clear();
198
199 bool firstMoveToIsChecked = false;
200 for (const auto& cmd : aPath) {
201 SVGPathSegUtils::TraversePathSegment(cmd, state);
202 if (!std::isfinite(state.length)) {
203 return false;
204 }
205
206 // We skip all moveto commands except for the initial moveto.
207 if (!cmd.IsMoveTo() || !firstMoveToIsChecked) {
208 if (!aOutput->AppendElement(state.length, fallible)) {
209 return false;
210 }
211 }
212
213 if (cmd.IsMoveTo() && !firstMoveToIsChecked) {
214 firstMoveToIsChecked = true;
215 }
216 }
217
218 return true;
219 }
220
GetPathSegAtLength(float aDistance) const221 uint32_t SVGPathData::GetPathSegAtLength(float aDistance) const {
222 // TODO [SVGWG issue] get specified what happen if 'aDistance' < 0, or
223 // 'aDistance' > the length of the path, or the seg list is empty.
224 // Return -1? Throwing would better help authors avoid tricky bugs (DOM
225 // could do that if we return -1).
226
227 uint32_t i = 0, segIndex = 0;
228 SVGPathTraversalState state;
229
230 while (i < mData.Length()) {
231 SVGPathSegUtils::TraversePathSegment(&mData[i], state);
232 if (state.length >= aDistance) {
233 return segIndex;
234 }
235 i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);
236 segIndex++;
237 }
238
239 MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
240
241 return std::max(1U, segIndex) -
242 1; // -1 because while loop takes us 1 too far
243 }
244
245 /* static */
GetPathSegAtLength(Span<const StylePathCommand> aPath,float aDistance)246 uint32_t SVGPathData::GetPathSegAtLength(Span<const StylePathCommand> aPath,
247 float aDistance) {
248 uint32_t segIndex = 0;
249 SVGPathTraversalState state;
250
251 for (const auto& cmd : aPath) {
252 SVGPathSegUtils::TraversePathSegment(cmd, state);
253 if (state.length >= aDistance) {
254 return segIndex;
255 }
256 segIndex++;
257 }
258
259 return std::max(1U, segIndex) - 1;
260 }
261
262 /**
263 * The SVG spec says we have to paint stroke caps for zero length subpaths:
264 *
265 * http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes
266 *
267 * Cairo only does this for |stroke-linecap: round| and not for
268 * |stroke-linecap: square| (since that's what Adobe Acrobat has always done).
269 * Most likely the other backends that DrawTarget uses have the same behavior.
270 *
271 * To help us conform to the SVG spec we have this helper function to draw an
272 * approximation of square caps for zero length subpaths. It does this by
273 * inserting a subpath containing a single user space axis aligned straight
274 * line that is as small as it can be while minimizing the risk of it being
275 * thrown away by the DrawTarget's backend for being too small to affect
276 * rendering. The idea is that we'll then get stroke caps drawn for this axis
277 * aligned line, creating an axis aligned rectangle that approximates the
278 * square that would ideally be drawn.
279 *
280 * Since we don't have any information about transforms from user space to
281 * device space, we choose the length of the small line that we insert by
282 * making it a small percentage of the stroke width of the path. This should
283 * hopefully allow us to make the line as long as possible (to avoid rounding
284 * issues in the backend resulting in the backend seeing it as having zero
285 * length) while still avoiding the small rectangle being noticeably different
286 * from a square.
287 *
288 * Note that this function inserts a subpath into the current gfx path that
289 * will be present during both fill and stroke operations.
290 */
ApproximateZeroLengthSubpathSquareCaps(PathBuilder * aPB,const Point & aPoint,Float aStrokeWidth)291 static void ApproximateZeroLengthSubpathSquareCaps(PathBuilder* aPB,
292 const Point& aPoint,
293 Float aStrokeWidth) {
294 // Note that caps are proportional to stroke width, so if stroke width is
295 // zero it's actually fine for |tinyLength| below to end up being zero.
296 // However, it would be a waste to inserting a LineTo in that case, so better
297 // not to.
298 MOZ_ASSERT(aStrokeWidth > 0.0f,
299 "Make the caller check for this, or check it here");
300
301 // The fraction of the stroke width that we choose for the length of the
302 // line is rather arbitrary, other than being chosen to meet the requirements
303 // described in the comment above.
304
305 Float tinyLength = aStrokeWidth / SVG_ZERO_LENGTH_PATH_FIX_FACTOR;
306
307 aPB->LineTo(aPoint + Point(tinyLength, 0));
308 aPB->MoveTo(aPoint);
309 }
310
311 #define MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT \
312 do { \
313 if (!subpathHasLength && hasLineCaps && aStrokeWidth > 0 && \
314 subpathContainsNonMoveTo && IsValidType(prevSegType) && \
315 (!IsMoveto(prevSegType) || IsClosePath(segType))) { \
316 ApproximateZeroLengthSubpathSquareCaps(aBuilder, segStart, \
317 aStrokeWidth); \
318 } \
319 } while (0)
320
BuildPath(PathBuilder * aBuilder,StyleStrokeLinecap aStrokeLineCap,Float aStrokeWidth) const321 already_AddRefed<Path> SVGPathData::BuildPath(PathBuilder* aBuilder,
322 StyleStrokeLinecap aStrokeLineCap,
323 Float aStrokeWidth) const {
324 if (mData.IsEmpty() || !IsMoveto(SVGPathSegUtils::DecodeType(mData[0]))) {
325 return nullptr; // paths without an initial moveto are invalid
326 }
327
328 bool hasLineCaps = aStrokeLineCap != StyleStrokeLinecap::Butt;
329 bool subpathHasLength = false; // visual length
330 bool subpathContainsNonMoveTo = false;
331
332 uint32_t segType = PATHSEG_UNKNOWN;
333 uint32_t prevSegType = PATHSEG_UNKNOWN;
334 Point pathStart(0.0, 0.0); // start point of [sub]path
335 Point segStart(0.0, 0.0);
336 Point segEnd;
337 Point cp1, cp2; // previous bezier's control points
338 Point tcp1, tcp2; // temporaries
339
340 // Regarding cp1 and cp2: If the previous segment was a cubic bezier curve,
341 // then cp2 is its second control point. If the previous segment was a
342 // quadratic curve, then cp1 is its (only) control point.
343
344 uint32_t i = 0;
345 while (i < mData.Length()) {
346 segType = SVGPathSegUtils::DecodeType(mData[i++]);
347 uint32_t argCount = SVGPathSegUtils::ArgCountForType(segType);
348
349 switch (segType) {
350 case PATHSEG_CLOSEPATH:
351 // set this early to allow drawing of square caps for "M{x},{y} Z":
352 subpathContainsNonMoveTo = true;
353 MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
354 segEnd = pathStart;
355 aBuilder->Close();
356 break;
357
358 case PATHSEG_MOVETO_ABS:
359 MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
360 pathStart = segEnd = Point(mData[i], mData[i + 1]);
361 aBuilder->MoveTo(segEnd);
362 subpathHasLength = false;
363 break;
364
365 case PATHSEG_MOVETO_REL:
366 MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
367 pathStart = segEnd = segStart + Point(mData[i], mData[i + 1]);
368 aBuilder->MoveTo(segEnd);
369 subpathHasLength = false;
370 break;
371
372 case PATHSEG_LINETO_ABS:
373 segEnd = Point(mData[i], mData[i + 1]);
374 if (segEnd != segStart) {
375 subpathHasLength = true;
376 aBuilder->LineTo(segEnd);
377 }
378 break;
379
380 case PATHSEG_LINETO_REL:
381 segEnd = segStart + Point(mData[i], mData[i + 1]);
382 if (segEnd != segStart) {
383 subpathHasLength = true;
384 aBuilder->LineTo(segEnd);
385 }
386 break;
387
388 case PATHSEG_CURVETO_CUBIC_ABS:
389 cp1 = Point(mData[i], mData[i + 1]);
390 cp2 = Point(mData[i + 2], mData[i + 3]);
391 segEnd = Point(mData[i + 4], mData[i + 5]);
392 if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
393 subpathHasLength = true;
394 aBuilder->BezierTo(cp1, cp2, segEnd);
395 }
396 break;
397
398 case PATHSEG_CURVETO_CUBIC_REL:
399 cp1 = segStart + Point(mData[i], mData[i + 1]);
400 cp2 = segStart + Point(mData[i + 2], mData[i + 3]);
401 segEnd = segStart + Point(mData[i + 4], mData[i + 5]);
402 if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
403 subpathHasLength = true;
404 aBuilder->BezierTo(cp1, cp2, segEnd);
405 }
406 break;
407
408 case PATHSEG_CURVETO_QUADRATIC_ABS:
409 cp1 = Point(mData[i], mData[i + 1]);
410 // Convert quadratic curve to cubic curve:
411 tcp1 = segStart + (cp1 - segStart) * 2 / 3;
412 segEnd = Point(mData[i + 2], mData[i + 3]); // set before setting tcp2!
413 tcp2 = cp1 + (segEnd - cp1) / 3;
414 if (segEnd != segStart || segEnd != cp1) {
415 subpathHasLength = true;
416 aBuilder->BezierTo(tcp1, tcp2, segEnd);
417 }
418 break;
419
420 case PATHSEG_CURVETO_QUADRATIC_REL:
421 cp1 = segStart + Point(mData[i], mData[i + 1]);
422 // Convert quadratic curve to cubic curve:
423 tcp1 = segStart + (cp1 - segStart) * 2 / 3;
424 segEnd = segStart +
425 Point(mData[i + 2], mData[i + 3]); // set before setting tcp2!
426 tcp2 = cp1 + (segEnd - cp1) / 3;
427 if (segEnd != segStart || segEnd != cp1) {
428 subpathHasLength = true;
429 aBuilder->BezierTo(tcp1, tcp2, segEnd);
430 }
431 break;
432
433 case PATHSEG_ARC_ABS:
434 case PATHSEG_ARC_REL: {
435 Point radii(mData[i], mData[i + 1]);
436 segEnd = Point(mData[i + 5], mData[i + 6]);
437 if (segType == PATHSEG_ARC_REL) {
438 segEnd += segStart;
439 }
440 if (segEnd != segStart) {
441 subpathHasLength = true;
442 if (radii.x == 0.0f || radii.y == 0.0f) {
443 aBuilder->LineTo(segEnd);
444 } else {
445 SVGArcConverter converter(segStart, segEnd, radii, mData[i + 2],
446 mData[i + 3] != 0, mData[i + 4] != 0);
447 while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) {
448 aBuilder->BezierTo(cp1, cp2, segEnd);
449 }
450 }
451 }
452 break;
453 }
454
455 case PATHSEG_LINETO_HORIZONTAL_ABS:
456 segEnd = Point(mData[i], segStart.y);
457 if (segEnd != segStart) {
458 subpathHasLength = true;
459 aBuilder->LineTo(segEnd);
460 }
461 break;
462
463 case PATHSEG_LINETO_HORIZONTAL_REL:
464 segEnd = segStart + Point(mData[i], 0.0f);
465 if (segEnd != segStart) {
466 subpathHasLength = true;
467 aBuilder->LineTo(segEnd);
468 }
469 break;
470
471 case PATHSEG_LINETO_VERTICAL_ABS:
472 segEnd = Point(segStart.x, mData[i]);
473 if (segEnd != segStart) {
474 subpathHasLength = true;
475 aBuilder->LineTo(segEnd);
476 }
477 break;
478
479 case PATHSEG_LINETO_VERTICAL_REL:
480 segEnd = segStart + Point(0.0f, mData[i]);
481 if (segEnd != segStart) {
482 subpathHasLength = true;
483 aBuilder->LineTo(segEnd);
484 }
485 break;
486
487 case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
488 cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ? segStart * 2 - cp2
489 : segStart;
490 cp2 = Point(mData[i], mData[i + 1]);
491 segEnd = Point(mData[i + 2], mData[i + 3]);
492 if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
493 subpathHasLength = true;
494 aBuilder->BezierTo(cp1, cp2, segEnd);
495 }
496 break;
497
498 case PATHSEG_CURVETO_CUBIC_SMOOTH_REL:
499 cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ? segStart * 2 - cp2
500 : segStart;
501 cp2 = segStart + Point(mData[i], mData[i + 1]);
502 segEnd = segStart + Point(mData[i + 2], mData[i + 3]);
503 if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
504 subpathHasLength = true;
505 aBuilder->BezierTo(cp1, cp2, segEnd);
506 }
507 break;
508
509 case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
510 cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ? segStart * 2 - cp1
511 : segStart;
512 // Convert quadratic curve to cubic curve:
513 tcp1 = segStart + (cp1 - segStart) * 2 / 3;
514 segEnd = Point(mData[i], mData[i + 1]); // set before setting tcp2!
515 tcp2 = cp1 + (segEnd - cp1) / 3;
516 if (segEnd != segStart || segEnd != cp1) {
517 subpathHasLength = true;
518 aBuilder->BezierTo(tcp1, tcp2, segEnd);
519 }
520 break;
521
522 case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:
523 cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ? segStart * 2 - cp1
524 : segStart;
525 // Convert quadratic curve to cubic curve:
526 tcp1 = segStart + (cp1 - segStart) * 2 / 3;
527 segEnd = segStart +
528 Point(mData[i], mData[i + 1]); // changed before setting tcp2!
529 tcp2 = cp1 + (segEnd - cp1) / 3;
530 if (segEnd != segStart || segEnd != cp1) {
531 subpathHasLength = true;
532 aBuilder->BezierTo(tcp1, tcp2, segEnd);
533 }
534 break;
535
536 default:
537 MOZ_ASSERT_UNREACHABLE("Bad path segment type");
538 return nullptr; // according to spec we'd use everything up to the bad
539 // seg anyway
540 }
541
542 subpathContainsNonMoveTo = !IsMoveto(segType);
543 i += argCount;
544 prevSegType = segType;
545 segStart = segEnd;
546 }
547
548 MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
549 MOZ_ASSERT(prevSegType == segType,
550 "prevSegType should be left at the final segType");
551
552 MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
553
554 return aBuilder->Finish();
555 }
556
BuildPathForMeasuring() const557 already_AddRefed<Path> SVGPathData::BuildPathForMeasuring() const {
558 // Since the path that we return will not be used for painting it doesn't
559 // matter what we pass to CreatePathBuilder as aFillRule. Hawever, we do want
560 // to pass something other than NS_STYLE_STROKE_LINECAP_SQUARE as
561 // aStrokeLineCap to avoid the insertion of extra little lines (by
562 // ApproximateZeroLengthSubpathSquareCaps), in which case the value that we
563 // pass as aStrokeWidth doesn't matter (since it's only used to determine the
564 // length of those extra little lines).
565
566 RefPtr<DrawTarget> drawTarget =
567 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
568 RefPtr<PathBuilder> builder =
569 drawTarget->CreatePathBuilder(FillRule::FILL_WINDING);
570 return BuildPath(builder, StyleStrokeLinecap::Butt, 0);
571 }
572
573 /* static */
BuildPathForMeasuring(Span<const StylePathCommand> aPath)574 already_AddRefed<Path> SVGPathData::BuildPathForMeasuring(
575 Span<const StylePathCommand> aPath) {
576 RefPtr<DrawTarget> drawTarget =
577 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
578 RefPtr<PathBuilder> builder =
579 drawTarget->CreatePathBuilder(FillRule::FILL_WINDING);
580 return BuildPath(aPath, builder, StyleStrokeLinecap::Butt, 0);
581 }
582
583 // We could simplify this function because this is only used by CSS motion path
584 // and clip-path, which don't render the SVG Path. i.e. The returned path is
585 // used as a reference.
586 /* static */
BuildPath(Span<const StylePathCommand> aPath,PathBuilder * aBuilder,StyleStrokeLinecap aStrokeLineCap,Float aStrokeWidth,float aZoomFactor)587 already_AddRefed<Path> SVGPathData::BuildPath(
588 Span<const StylePathCommand> aPath, PathBuilder* aBuilder,
589 StyleStrokeLinecap aStrokeLineCap, Float aStrokeWidth, float aZoomFactor) {
590 if (aPath.IsEmpty() || !aPath[0].IsMoveTo()) {
591 return nullptr; // paths without an initial moveto are invalid
592 }
593
594 bool hasLineCaps = aStrokeLineCap != StyleStrokeLinecap::Butt;
595 bool subpathHasLength = false; // visual length
596 bool subpathContainsNonMoveTo = false;
597
598 StylePathCommand::Tag segType = StylePathCommand::Tag::Unknown;
599 StylePathCommand::Tag prevSegType = StylePathCommand::Tag::Unknown;
600 Point pathStart(0.0, 0.0); // start point of [sub]path
601 Point segStart(0.0, 0.0);
602 Point segEnd;
603 Point cp1, cp2; // previous bezier's control points
604 Point tcp1, tcp2; // temporaries
605
606 auto scale = [aZoomFactor](const Point& p) {
607 return Point(p.x * aZoomFactor, p.y * aZoomFactor);
608 };
609
610 // Regarding cp1 and cp2: If the previous segment was a cubic bezier curve,
611 // then cp2 is its second control point. If the previous segment was a
612 // quadratic curve, then cp1 is its (only) control point.
613
614 for (const StylePathCommand& cmd : aPath) {
615 segType = cmd.tag;
616 switch (segType) {
617 case StylePathCommand::Tag::ClosePath:
618 // set this early to allow drawing of square caps for "M{x},{y} Z":
619 subpathContainsNonMoveTo = true;
620 MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
621 segEnd = pathStart;
622 aBuilder->Close();
623 break;
624 case StylePathCommand::Tag::MoveTo: {
625 MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
626 const Point& p = ToGfxPoint(cmd.move_to.point);
627 pathStart = segEnd =
628 cmd.move_to.absolute == StyleIsAbsolute::Yes ? p : segStart + p;
629 aBuilder->MoveTo(scale(segEnd));
630 subpathHasLength = false;
631 break;
632 }
633 case StylePathCommand::Tag::LineTo: {
634 const Point& p = ToGfxPoint(cmd.line_to.point);
635 segEnd =
636 cmd.line_to.absolute == StyleIsAbsolute::Yes ? p : segStart + p;
637 if (segEnd != segStart) {
638 subpathHasLength = true;
639 aBuilder->LineTo(scale(segEnd));
640 }
641 break;
642 }
643 case StylePathCommand::Tag::CurveTo:
644 cp1 = ToGfxPoint(cmd.curve_to.control1);
645 cp2 = ToGfxPoint(cmd.curve_to.control2);
646 segEnd = ToGfxPoint(cmd.curve_to.point);
647
648 if (cmd.curve_to.absolute == StyleIsAbsolute::No) {
649 cp1 += segStart;
650 cp2 += segStart;
651 segEnd += segStart;
652 }
653
654 if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
655 subpathHasLength = true;
656 aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd));
657 }
658 break;
659
660 case StylePathCommand::Tag::QuadBezierCurveTo:
661 cp1 = ToGfxPoint(cmd.quad_bezier_curve_to.control1);
662 segEnd = ToGfxPoint(cmd.quad_bezier_curve_to.point);
663
664 if (cmd.quad_bezier_curve_to.absolute == StyleIsAbsolute::No) {
665 cp1 += segStart;
666 segEnd += segStart; // set before setting tcp2!
667 }
668
669 // Convert quadratic curve to cubic curve:
670 tcp1 = segStart + (cp1 - segStart) * 2 / 3;
671 tcp2 = cp1 + (segEnd - cp1) / 3;
672
673 if (segEnd != segStart || segEnd != cp1) {
674 subpathHasLength = true;
675 aBuilder->BezierTo(scale(tcp1), scale(tcp2), scale(segEnd));
676 }
677 break;
678
679 case StylePathCommand::Tag::EllipticalArc: {
680 const auto& arc = cmd.elliptical_arc;
681 Point radii(arc.rx, arc.ry);
682 segEnd = ToGfxPoint(arc.point);
683 if (arc.absolute == StyleIsAbsolute::No) {
684 segEnd += segStart;
685 }
686 if (segEnd != segStart) {
687 subpathHasLength = true;
688 if (radii.x == 0.0f || radii.y == 0.0f) {
689 aBuilder->LineTo(scale(segEnd));
690 } else {
691 SVGArcConverter converter(segStart, segEnd, radii, arc.angle,
692 arc.large_arc_flag._0, arc.sweep_flag._0);
693 while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) {
694 aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd));
695 }
696 }
697 }
698 break;
699 }
700 case StylePathCommand::Tag::HorizontalLineTo:
701 if (cmd.horizontal_line_to.absolute == StyleIsAbsolute::Yes) {
702 segEnd = Point(cmd.horizontal_line_to.x, segStart.y);
703 } else {
704 segEnd = segStart + Point(cmd.horizontal_line_to.x, 0.0f);
705 }
706
707 if (segEnd != segStart) {
708 subpathHasLength = true;
709 aBuilder->LineTo(scale(segEnd));
710 }
711 break;
712
713 case StylePathCommand::Tag::VerticalLineTo:
714 if (cmd.vertical_line_to.absolute == StyleIsAbsolute::Yes) {
715 segEnd = Point(segStart.x, cmd.vertical_line_to.y);
716 } else {
717 segEnd = segStart + Point(0.0f, cmd.vertical_line_to.y);
718 }
719
720 if (segEnd != segStart) {
721 subpathHasLength = true;
722 aBuilder->LineTo(scale(segEnd));
723 }
724 break;
725
726 case StylePathCommand::Tag::SmoothCurveTo:
727 cp1 = IsCubicType(prevSegType) ? segStart * 2 - cp2 : segStart;
728 cp2 = ToGfxPoint(cmd.smooth_curve_to.control2);
729 segEnd = ToGfxPoint(cmd.smooth_curve_to.point);
730
731 if (cmd.smooth_curve_to.absolute == StyleIsAbsolute::No) {
732 cp2 += segStart;
733 segEnd += segStart;
734 }
735
736 if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
737 subpathHasLength = true;
738 aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd));
739 }
740 break;
741
742 case StylePathCommand::Tag::SmoothQuadBezierCurveTo: {
743 cp1 = IsQuadraticType(prevSegType) ? segStart * 2 - cp1 : segStart;
744 // Convert quadratic curve to cubic curve:
745 tcp1 = segStart + (cp1 - segStart) * 2 / 3;
746
747 const Point& p = ToGfxPoint(cmd.smooth_quad_bezier_curve_to.point);
748 // set before setting tcp2!
749 segEnd =
750 cmd.smooth_quad_bezier_curve_to.absolute == StyleIsAbsolute::Yes
751 ? p
752 : segStart + p;
753 tcp2 = cp1 + (segEnd - cp1) / 3;
754
755 if (segEnd != segStart || segEnd != cp1) {
756 subpathHasLength = true;
757 aBuilder->BezierTo(scale(tcp1), scale(tcp2), scale(segEnd));
758 }
759 break;
760 }
761 case StylePathCommand::Tag::Unknown:
762 MOZ_ASSERT_UNREACHABLE("Unacceptable path segment type");
763 return nullptr;
764 }
765
766 subpathContainsNonMoveTo = !IsMoveto(segType);
767 prevSegType = segType;
768 segStart = segEnd;
769 }
770
771 MOZ_ASSERT(prevSegType == segType,
772 "prevSegType should be left at the final segType");
773
774 MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
775
776 return aBuilder->Finish();
777 }
778
AngleOfVector(const Point & aVector)779 static double AngleOfVector(const Point& aVector) {
780 // C99 says about atan2 "A domain error may occur if both arguments are
781 // zero" and "On a domain error, the function returns an implementation-
782 // defined value". In the case of atan2 the implementation-defined value
783 // seems to commonly be zero, but it could just as easily be a NaN value.
784 // We specifically want zero in this case, hence the check:
785
786 return (aVector != Point(0.0, 0.0)) ? atan2(aVector.y, aVector.x) : 0.0;
787 }
788
AngleOfVector(const Point & cp1,const Point & cp2)789 static float AngleOfVector(const Point& cp1, const Point& cp2) {
790 return static_cast<float>(AngleOfVector(cp1 - cp2));
791 }
792
793 // This implements F.6.5 and F.6.6 of
794 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
795 static std::tuple<float, float, float, float>
796 /* rx, ry, segStartAngle, segEndAngle */
ComputeSegAnglesAndCorrectRadii(const Point & aSegStart,const Point & aSegEnd,const float aAngle,const bool aLargeArcFlag,const bool aSweepFlag,const float aRx,const float aRy)797 ComputeSegAnglesAndCorrectRadii(const Point& aSegStart, const Point& aSegEnd,
798 const float aAngle, const bool aLargeArcFlag,
799 const bool aSweepFlag, const float aRx,
800 const float aRy) {
801 float rx = fabs(aRx); // F.6.6.1
802 float ry = fabs(aRy);
803
804 // F.6.5.1:
805 const float angle = static_cast<float>(aAngle * M_PI / 180.0);
806 double x1p = cos(angle) * (aSegStart.x - aSegEnd.x) / 2.0 +
807 sin(angle) * (aSegStart.y - aSegEnd.y) / 2.0;
808 double y1p = -sin(angle) * (aSegStart.x - aSegEnd.x) / 2.0 +
809 cos(angle) * (aSegStart.y - aSegEnd.y) / 2.0;
810
811 // This is the root in F.6.5.2 and the numerator under that root:
812 double root;
813 double numerator =
814 rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;
815
816 if (numerator >= 0.0) {
817 root = sqrt(numerator / (rx * rx * y1p * y1p + ry * ry * x1p * x1p));
818 if (aLargeArcFlag == aSweepFlag) root = -root;
819 } else {
820 // F.6.6 step 3 - |numerator < 0.0|. This is equivalent to the result
821 // of F.6.6.2 (lamedh) being greater than one. What we have here is
822 // ellipse radii that are too small for the ellipse to reach between
823 // segStart and segEnd. We scale the radii up uniformly so that the
824 // ellipse is just big enough to fit (i.e. to the point where there is
825 // exactly one solution).
826
827 double lamedh =
828 1.0 - numerator / (rx * rx * ry * ry); // equiv to eqn F.6.6.2
829 double s = sqrt(lamedh);
830 rx = static_cast<float>((double)rx * s); // F.6.6.3
831 ry = static_cast<float>((double)ry * s);
832 root = 0.0;
833 }
834
835 double cxp = root * rx * y1p / ry; // F.6.5.2
836 double cyp = -root * ry * x1p / rx;
837
838 double theta =
839 AngleOfVector(Point(static_cast<float>((x1p - cxp) / rx),
840 static_cast<float>((y1p - cyp) / ry))); // F.6.5.5
841 double delta =
842 AngleOfVector(Point(static_cast<float>((-x1p - cxp) / rx),
843 static_cast<float>((-y1p - cyp) / ry))) - // F.6.5.6
844 theta;
845 if (!aSweepFlag && delta > 0) {
846 delta -= 2.0 * M_PI;
847 } else if (aSweepFlag && delta < 0) {
848 delta += 2.0 * M_PI;
849 }
850
851 double tx1, ty1, tx2, ty2;
852 tx1 = -cos(angle) * rx * sin(theta) - sin(angle) * ry * cos(theta);
853 ty1 = -sin(angle) * rx * sin(theta) + cos(angle) * ry * cos(theta);
854 tx2 = -cos(angle) * rx * sin(theta + delta) -
855 sin(angle) * ry * cos(theta + delta);
856 ty2 = -sin(angle) * rx * sin(theta + delta) +
857 cos(angle) * ry * cos(theta + delta);
858
859 if (delta < 0.0f) {
860 tx1 = -tx1;
861 ty1 = -ty1;
862 tx2 = -tx2;
863 ty2 = -ty2;
864 }
865
866 return {rx, ry, static_cast<float>(atan2(ty1, tx1)),
867 static_cast<float>(atan2(ty2, tx2))};
868 }
869
GetMarkerPositioningData(nsTArray<SVGMark> * aMarks) const870 void SVGPathData::GetMarkerPositioningData(nsTArray<SVGMark>* aMarks) const {
871 // This code should assume that ANY type of segment can appear at ANY index.
872 // It should also assume that segments such as M and Z can appear in weird
873 // places, and repeat multiple times consecutively.
874
875 // info on current [sub]path (reset every M command):
876 Point pathStart(0.0, 0.0);
877 float pathStartAngle = 0.0f;
878 uint32_t pathStartIndex = 0;
879
880 // info on previous segment:
881 uint16_t prevSegType = PATHSEG_UNKNOWN;
882 Point prevSegEnd(0.0, 0.0);
883 float prevSegEndAngle = 0.0f;
884 Point prevCP; // if prev seg was a bezier, this was its last control point
885
886 uint32_t i = 0;
887 while (i < mData.Length()) {
888 // info on current segment:
889 uint16_t segType =
890 SVGPathSegUtils::DecodeType(mData[i++]); // advances i to args
891 Point& segStart = prevSegEnd;
892 Point segEnd;
893 float segStartAngle, segEndAngle;
894
895 switch (segType) // to find segStartAngle, segEnd and segEndAngle
896 {
897 case PATHSEG_CLOSEPATH:
898 segEnd = pathStart;
899 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
900 break;
901
902 case PATHSEG_MOVETO_ABS:
903 case PATHSEG_MOVETO_REL:
904 if (segType == PATHSEG_MOVETO_ABS) {
905 segEnd = Point(mData[i], mData[i + 1]);
906 } else {
907 segEnd = segStart + Point(mData[i], mData[i + 1]);
908 }
909 pathStart = segEnd;
910 pathStartIndex = aMarks->Length();
911 // If authors are going to specify multiple consecutive moveto commands
912 // with markers, me might as well make the angle do something useful:
913 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
914 i += 2;
915 break;
916
917 case PATHSEG_LINETO_ABS:
918 case PATHSEG_LINETO_REL:
919 if (segType == PATHSEG_LINETO_ABS) {
920 segEnd = Point(mData[i], mData[i + 1]);
921 } else {
922 segEnd = segStart + Point(mData[i], mData[i + 1]);
923 }
924 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
925 i += 2;
926 break;
927
928 case PATHSEG_CURVETO_CUBIC_ABS:
929 case PATHSEG_CURVETO_CUBIC_REL: {
930 Point cp1, cp2; // control points
931 if (segType == PATHSEG_CURVETO_CUBIC_ABS) {
932 cp1 = Point(mData[i], mData[i + 1]);
933 cp2 = Point(mData[i + 2], mData[i + 3]);
934 segEnd = Point(mData[i + 4], mData[i + 5]);
935 } else {
936 cp1 = segStart + Point(mData[i], mData[i + 1]);
937 cp2 = segStart + Point(mData[i + 2], mData[i + 3]);
938 segEnd = segStart + Point(mData[i + 4], mData[i + 5]);
939 }
940 prevCP = cp2;
941 segStartAngle = AngleOfVector(
942 cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart);
943 segEndAngle = AngleOfVector(
944 segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2);
945 i += 6;
946 break;
947 }
948
949 case PATHSEG_CURVETO_QUADRATIC_ABS:
950 case PATHSEG_CURVETO_QUADRATIC_REL: {
951 Point cp1; // control point
952 if (segType == PATHSEG_CURVETO_QUADRATIC_ABS) {
953 cp1 = Point(mData[i], mData[i + 1]);
954 segEnd = Point(mData[i + 2], mData[i + 3]);
955 } else {
956 cp1 = segStart + Point(mData[i], mData[i + 1]);
957 segEnd = segStart + Point(mData[i + 2], mData[i + 3]);
958 }
959 prevCP = cp1;
960 segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart);
961 segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1);
962 i += 4;
963 break;
964 }
965
966 case PATHSEG_ARC_ABS:
967 case PATHSEG_ARC_REL: {
968 float rx = mData[i];
969 float ry = mData[i + 1];
970 float angle = mData[i + 2];
971 bool largeArcFlag = mData[i + 3] != 0.0f;
972 bool sweepFlag = mData[i + 4] != 0.0f;
973 if (segType == PATHSEG_ARC_ABS) {
974 segEnd = Point(mData[i + 5], mData[i + 6]);
975 } else {
976 segEnd = segStart + Point(mData[i + 5], mData[i + 6]);
977 }
978
979 // See section F.6 of SVG 1.1 for details on what we're doing here:
980 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
981
982 if (segStart == segEnd) {
983 // F.6.2 says "If the endpoints (x1, y1) and (x2, y2) are identical,
984 // then this is equivalent to omitting the elliptical arc segment
985 // entirely." We take that very literally here, not adding a mark, and
986 // not even setting any of the 'prev' variables so that it's as if
987 // this arc had never existed; note the difference this will make e.g.
988 // if the arc is proceeded by a bezier curve and followed by a
989 // "smooth" bezier curve of the same degree!
990 i += 7;
991 continue;
992 }
993
994 // Below we have funny interleaving of F.6.6 (Correction of out-of-range
995 // radii) and F.6.5 (Conversion from endpoint to center
996 // parameterization) which is designed to avoid some unnecessary
997 // calculations.
998
999 if (rx == 0.0 || ry == 0.0) {
1000 // F.6.6 step 1 - straight line or coincidental points
1001 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
1002 i += 7;
1003 break;
1004 }
1005
1006 std::tie(rx, ry, segStartAngle, segEndAngle) =
1007 ComputeSegAnglesAndCorrectRadii(segStart, segEnd, angle,
1008 largeArcFlag, sweepFlag, rx, ry);
1009 i += 7;
1010 break;
1011 }
1012
1013 case PATHSEG_LINETO_HORIZONTAL_ABS:
1014 case PATHSEG_LINETO_HORIZONTAL_REL:
1015 if (segType == PATHSEG_LINETO_HORIZONTAL_ABS) {
1016 segEnd = Point(mData[i++], segStart.y);
1017 } else {
1018 segEnd = segStart + Point(mData[i++], 0.0f);
1019 }
1020 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
1021 break;
1022
1023 case PATHSEG_LINETO_VERTICAL_ABS:
1024 case PATHSEG_LINETO_VERTICAL_REL:
1025 if (segType == PATHSEG_LINETO_VERTICAL_ABS) {
1026 segEnd = Point(segStart.x, mData[i++]);
1027 } else {
1028 segEnd = segStart + Point(0.0f, mData[i++]);
1029 }
1030 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
1031 break;
1032
1033 case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
1034 case PATHSEG_CURVETO_CUBIC_SMOOTH_REL: {
1035 Point cp1 = SVGPathSegUtils::IsCubicType(prevSegType)
1036 ? segStart * 2 - prevCP
1037 : segStart;
1038 Point cp2;
1039 if (segType == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS) {
1040 cp2 = Point(mData[i], mData[i + 1]);
1041 segEnd = Point(mData[i + 2], mData[i + 3]);
1042 } else {
1043 cp2 = segStart + Point(mData[i], mData[i + 1]);
1044 segEnd = segStart + Point(mData[i + 2], mData[i + 3]);
1045 }
1046 prevCP = cp2;
1047 segStartAngle = AngleOfVector(
1048 cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart);
1049 segEndAngle = AngleOfVector(
1050 segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2);
1051 i += 4;
1052 break;
1053 }
1054
1055 case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
1056 case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL: {
1057 Point cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType)
1058 ? segStart * 2 - prevCP
1059 : segStart;
1060 if (segType == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS) {
1061 segEnd = Point(mData[i], mData[i + 1]);
1062 } else {
1063 segEnd = segStart + Point(mData[i], mData[i + 1]);
1064 }
1065 prevCP = cp1;
1066 segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart);
1067 segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1);
1068 i += 2;
1069 break;
1070 }
1071
1072 default:
1073 // Leave any existing marks in aMarks so we have a visual indication of
1074 // when things went wrong.
1075 MOZ_ASSERT(false, "Unknown segment type - path corruption?");
1076 return;
1077 }
1078
1079 // Set the angle of the mark at the start of this segment:
1080 if (aMarks->Length()) {
1081 SVGMark& mark = aMarks->LastElement();
1082 if (!IsMoveto(segType) && IsMoveto(prevSegType)) {
1083 // start of new subpath
1084 pathStartAngle = mark.angle = segStartAngle;
1085 } else if (IsMoveto(segType) && !IsMoveto(prevSegType)) {
1086 // end of a subpath
1087 if (prevSegType != PATHSEG_CLOSEPATH) mark.angle = prevSegEndAngle;
1088 } else {
1089 if (!(segType == PATHSEG_CLOSEPATH && prevSegType == PATHSEG_CLOSEPATH))
1090 mark.angle =
1091 SVGContentUtils::AngleBisect(prevSegEndAngle, segStartAngle);
1092 }
1093 }
1094
1095 // Add the mark at the end of this segment, and set its position:
1096 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1097 // pretended earlier.
1098 aMarks->AppendElement(SVGMark(static_cast<float>(segEnd.x),
1099 static_cast<float>(segEnd.y), 0.0f,
1100 SVGMark::eMid));
1101
1102 if (segType == PATHSEG_CLOSEPATH && prevSegType != PATHSEG_CLOSEPATH) {
1103 aMarks->LastElement().angle = aMarks->ElementAt(pathStartIndex).angle =
1104 SVGContentUtils::AngleBisect(segEndAngle, pathStartAngle);
1105 }
1106
1107 prevSegType = segType;
1108 prevSegEnd = segEnd;
1109 prevSegEndAngle = segEndAngle;
1110 }
1111
1112 MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
1113
1114 if (aMarks->Length()) {
1115 if (prevSegType != PATHSEG_CLOSEPATH) {
1116 aMarks->LastElement().angle = prevSegEndAngle;
1117 }
1118 aMarks->LastElement().type = SVGMark::eEnd;
1119 aMarks->ElementAt(0).type = SVGMark::eStart;
1120 }
1121 }
1122
1123 // Basically, this is identical to the above function, but replace |mData| with
1124 // |aPath|. We probably can factor out some identical calculation, but I believe
1125 // the above one will be removed because we will use any kind of array of
1126 // StylePathCommand for SVG d attribute in the future.
1127 /* static */
GetMarkerPositioningData(Span<const StylePathCommand> aPath,nsTArray<SVGMark> * aMarks)1128 void SVGPathData::GetMarkerPositioningData(Span<const StylePathCommand> aPath,
1129 nsTArray<SVGMark>* aMarks) {
1130 if (aPath.IsEmpty()) {
1131 return;
1132 }
1133
1134 // info on current [sub]path (reset every M command):
1135 Point pathStart(0.0, 0.0);
1136 float pathStartAngle = 0.0f;
1137 uint32_t pathStartIndex = 0;
1138
1139 // info on previous segment:
1140 StylePathCommand::Tag prevSegType = StylePathCommand::Tag::Unknown;
1141 Point prevSegEnd(0.0, 0.0);
1142 float prevSegEndAngle = 0.0f;
1143 Point prevCP; // if prev seg was a bezier, this was its last control point
1144
1145 StylePathCommand::Tag segType = StylePathCommand::Tag::Unknown;
1146 for (const StylePathCommand& cmd : aPath) {
1147 segType = cmd.tag;
1148 Point& segStart = prevSegEnd;
1149 Point segEnd;
1150 float segStartAngle, segEndAngle;
1151
1152 switch (segType) // to find segStartAngle, segEnd and segEndAngle
1153 {
1154 case StylePathCommand::Tag::ClosePath:
1155 segEnd = pathStart;
1156 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
1157 break;
1158
1159 case StylePathCommand::Tag::MoveTo: {
1160 const Point& p = ToGfxPoint(cmd.move_to.point);
1161 pathStart = segEnd =
1162 cmd.move_to.absolute == StyleIsAbsolute::Yes ? p : segStart + p;
1163 pathStartIndex = aMarks->Length();
1164 // If authors are going to specify multiple consecutive moveto commands
1165 // with markers, me might as well make the angle do something useful:
1166 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
1167 break;
1168 }
1169 case StylePathCommand::Tag::LineTo: {
1170 const Point& p = ToGfxPoint(cmd.line_to.point);
1171 segEnd =
1172 cmd.line_to.absolute == StyleIsAbsolute::Yes ? p : segStart + p;
1173 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
1174 break;
1175 }
1176 case StylePathCommand::Tag::CurveTo: {
1177 Point cp1 = ToGfxPoint(cmd.curve_to.control1);
1178 Point cp2 = ToGfxPoint(cmd.curve_to.control2);
1179 segEnd = ToGfxPoint(cmd.curve_to.point);
1180
1181 if (cmd.curve_to.absolute == StyleIsAbsolute::No) {
1182 cp1 += segStart;
1183 cp2 += segStart;
1184 segEnd += segStart;
1185 }
1186
1187 prevCP = cp2;
1188 segStartAngle = AngleOfVector(
1189 cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart);
1190 segEndAngle = AngleOfVector(
1191 segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2);
1192 break;
1193 }
1194 case StylePathCommand::Tag::QuadBezierCurveTo: {
1195 Point cp1 = ToGfxPoint(cmd.quad_bezier_curve_to.control1);
1196 segEnd = ToGfxPoint(cmd.quad_bezier_curve_to.point);
1197
1198 if (cmd.quad_bezier_curve_to.absolute == StyleIsAbsolute::No) {
1199 cp1 += segStart;
1200 segEnd += segStart; // set before setting tcp2!
1201 }
1202
1203 prevCP = cp1;
1204 segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart);
1205 segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1);
1206 break;
1207 }
1208 case StylePathCommand::Tag::EllipticalArc: {
1209 const auto& arc = cmd.elliptical_arc;
1210 float rx = arc.rx;
1211 float ry = arc.ry;
1212 float angle = arc.angle;
1213 bool largeArcFlag = arc.large_arc_flag._0;
1214 bool sweepFlag = arc.sweep_flag._0;
1215 Point radii(arc.rx, arc.ry);
1216 segEnd = ToGfxPoint(arc.point);
1217 if (arc.absolute == StyleIsAbsolute::No) {
1218 segEnd += segStart;
1219 }
1220
1221 // See section F.6 of SVG 1.1 for details on what we're doing here:
1222 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
1223
1224 if (segStart == segEnd) {
1225 // F.6.2 says "If the endpoints (x1, y1) and (x2, y2) are identical,
1226 // then this is equivalent to omitting the elliptical arc segment
1227 // entirely." We take that very literally here, not adding a mark, and
1228 // not even setting any of the 'prev' variables so that it's as if
1229 // this arc had never existed; note the difference this will make e.g.
1230 // if the arc is proceeded by a bezier curve and followed by a
1231 // "smooth" bezier curve of the same degree!
1232 continue;
1233 }
1234
1235 // Below we have funny interleaving of F.6.6 (Correction of out-of-range
1236 // radii) and F.6.5 (Conversion from endpoint to center
1237 // parameterization) which is designed to avoid some unnecessary
1238 // calculations.
1239
1240 if (rx == 0.0 || ry == 0.0) {
1241 // F.6.6 step 1 - straight line or coincidental points
1242 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
1243 break;
1244 }
1245
1246 std::tie(rx, ry, segStartAngle, segEndAngle) =
1247 ComputeSegAnglesAndCorrectRadii(segStart, segEnd, angle,
1248 largeArcFlag, sweepFlag, rx, ry);
1249 break;
1250 }
1251 case StylePathCommand::Tag::HorizontalLineTo: {
1252 if (cmd.horizontal_line_to.absolute == StyleIsAbsolute::Yes) {
1253 segEnd = Point(cmd.horizontal_line_to.x, segStart.y);
1254 } else {
1255 segEnd = segStart + Point(cmd.horizontal_line_to.x, 0.0f);
1256 }
1257 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
1258 break;
1259 }
1260 case StylePathCommand::Tag::VerticalLineTo: {
1261 if (cmd.vertical_line_to.absolute == StyleIsAbsolute::Yes) {
1262 segEnd = Point(segStart.x, cmd.vertical_line_to.y);
1263 } else {
1264 segEnd = segStart + Point(0.0f, cmd.vertical_line_to.y);
1265 }
1266 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
1267 break;
1268 }
1269 case StylePathCommand::Tag::SmoothCurveTo: {
1270 Point cp1 = IsCubicType(prevSegType) ? segStart * 2 - prevCP : segStart;
1271 Point cp2 = ToGfxPoint(cmd.smooth_curve_to.control2);
1272 segEnd = ToGfxPoint(cmd.smooth_curve_to.point);
1273
1274 if (cmd.smooth_curve_to.absolute == StyleIsAbsolute::No) {
1275 cp2 += segStart;
1276 segEnd += segStart;
1277 }
1278
1279 prevCP = cp2;
1280 segStartAngle = AngleOfVector(
1281 cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart);
1282 segEndAngle = AngleOfVector(
1283 segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2);
1284 break;
1285 }
1286 case StylePathCommand::Tag::SmoothQuadBezierCurveTo: {
1287 Point cp1 =
1288 IsQuadraticType(prevSegType) ? segStart * 2 - prevCP : segStart;
1289 segEnd =
1290 cmd.smooth_quad_bezier_curve_to.absolute == StyleIsAbsolute::Yes
1291 ? ToGfxPoint(cmd.smooth_quad_bezier_curve_to.point)
1292 : segStart + ToGfxPoint(cmd.smooth_quad_bezier_curve_to.point);
1293
1294 prevCP = cp1;
1295 segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart);
1296 segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1);
1297 break;
1298 }
1299 case StylePathCommand::Tag::Unknown:
1300 // Leave any existing marks in aMarks so we have a visual indication of
1301 // when things went wrong.
1302 MOZ_ASSERT_UNREACHABLE("Unknown segment type - path corruption?");
1303 return;
1304 }
1305
1306 // Set the angle of the mark at the start of this segment:
1307 if (aMarks->Length()) {
1308 SVGMark& mark = aMarks->LastElement();
1309 if (!IsMoveto(segType) && IsMoveto(prevSegType)) {
1310 // start of new subpath
1311 pathStartAngle = mark.angle = segStartAngle;
1312 } else if (IsMoveto(segType) && !IsMoveto(prevSegType)) {
1313 // end of a subpath
1314 if (prevSegType != StylePathCommand::Tag::ClosePath) {
1315 mark.angle = prevSegEndAngle;
1316 }
1317 } else if (!(segType == StylePathCommand::Tag::ClosePath &&
1318 prevSegType == StylePathCommand::Tag::ClosePath)) {
1319 mark.angle =
1320 SVGContentUtils::AngleBisect(prevSegEndAngle, segStartAngle);
1321 }
1322 }
1323
1324 // Add the mark at the end of this segment, and set its position:
1325 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1326 // pretended earlier.
1327 aMarks->AppendElement(SVGMark(static_cast<float>(segEnd.x),
1328 static_cast<float>(segEnd.y), 0.0f,
1329 SVGMark::eMid));
1330
1331 if (segType == StylePathCommand::Tag::ClosePath &&
1332 prevSegType != StylePathCommand::Tag::ClosePath) {
1333 aMarks->LastElement().angle = aMarks->ElementAt(pathStartIndex).angle =
1334 SVGContentUtils::AngleBisect(segEndAngle, pathStartAngle);
1335 }
1336
1337 prevSegType = segType;
1338 prevSegEnd = segEnd;
1339 prevSegEndAngle = segEndAngle;
1340 }
1341
1342 if (aMarks->Length()) {
1343 if (prevSegType != StylePathCommand::Tag::ClosePath) {
1344 aMarks->LastElement().angle = prevSegEndAngle;
1345 }
1346 aMarks->LastElement().type = SVGMark::eEnd;
1347 aMarks->ElementAt(0).type = SVGMark::eStart;
1348 }
1349 }
1350
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const1351 size_t SVGPathData::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
1352 return mData.ShallowSizeOfExcludingThis(aMallocSizeOf);
1353 }
1354
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const1355 size_t SVGPathData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
1356 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
1357 }
1358
1359 } // namespace mozilla
1360