Line data Source code
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 "nsSVGPathDataParser.h"
18 : #include <stdarg.h>
19 : #include "nsStyleConsts.h"
20 : #include "SVGContentUtils.h"
21 : #include "SVGGeometryElement.h" // for nsSVGMark
22 : #include "SVGPathSegUtils.h"
23 : #include <algorithm>
24 :
25 : using namespace mozilla;
26 : using namespace mozilla::gfx;
27 :
28 28 : static bool IsMoveto(uint16_t aSegType)
29 : {
30 28 : return aSegType == PATHSEG_MOVETO_ABS ||
31 28 : aSegType == PATHSEG_MOVETO_REL;
32 : }
33 :
34 : nsresult
35 44 : SVGPathData::CopyFrom(const SVGPathData& rhs)
36 : {
37 44 : if (!mData.Assign(rhs.mData, fallible)) {
38 0 : return NS_ERROR_OUT_OF_MEMORY;
39 : }
40 44 : return NS_OK;
41 : }
42 :
43 : void
44 0 : SVGPathData::GetValueAsString(nsAString& aValue) const
45 : {
46 : // we need this function in DidChangePathSegList
47 0 : aValue.Truncate();
48 0 : if (!Length()) {
49 0 : return;
50 : }
51 0 : uint32_t i = 0;
52 : for (;;) {
53 0 : nsAutoString segAsString;
54 0 : SVGPathSegUtils::GetValueAsString(&mData[i], segAsString);
55 : // We ignore OOM, since it's not useful for us to return an error.
56 0 : aValue.Append(segAsString);
57 0 : i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);
58 0 : if (i >= mData.Length()) {
59 0 : MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
60 0 : return;
61 : }
62 0 : aValue.Append(' ');
63 0 : }
64 : }
65 :
66 : nsresult
67 44 : SVGPathData::SetValueFromString(const nsAString& aValue)
68 : {
69 : // We don't use a temp variable since the spec says to parse everything up to
70 : // the first error. We still return any error though so that callers know if
71 : // there's a problem.
72 :
73 44 : nsSVGPathDataParser pathParser(aValue, this);
74 44 : return pathParser.Parse() ? NS_OK : NS_ERROR_DOM_SYNTAX_ERR;
75 : }
76 :
77 : nsresult
78 894 : SVGPathData::AppendSeg(uint32_t aType, ...)
79 : {
80 894 : uint32_t oldLength = mData.Length();
81 894 : uint32_t newLength = oldLength + 1 + SVGPathSegUtils::ArgCountForType(aType);
82 894 : if (!mData.SetLength(newLength, fallible)) {
83 0 : return NS_ERROR_OUT_OF_MEMORY;
84 : }
85 :
86 894 : mData[oldLength] = SVGPathSegUtils::EncodeType(aType);
87 : va_list args;
88 894 : va_start(args, aType);
89 4096 : for (uint32_t i = oldLength + 1; i < newLength; ++i) {
90 : // NOTE! 'float' is promoted to 'double' when passed through '...'!
91 3202 : mData[i] = float(va_arg(args, double));
92 : }
93 894 : va_end(args);
94 894 : return NS_OK;
95 : }
96 :
97 : float
98 0 : SVGPathData::GetPathLength() const
99 : {
100 0 : SVGPathTraversalState state;
101 :
102 0 : uint32_t i = 0;
103 0 : while (i < mData.Length()) {
104 0 : SVGPathSegUtils::TraversePathSegment(&mData[i], state);
105 0 : i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);
106 : }
107 :
108 0 : MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
109 :
110 0 : return state.length;
111 : }
112 :
113 : #ifdef DEBUG
114 : uint32_t
115 0 : SVGPathData::CountItems() const
116 : {
117 0 : uint32_t i = 0, count = 0;
118 :
119 0 : while (i < mData.Length()) {
120 0 : i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);
121 0 : count++;
122 : }
123 :
124 0 : MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
125 :
126 0 : return count;
127 : }
128 : #endif
129 :
130 : bool
131 0 : SVGPathData::GetSegmentLengths(nsTArray<double> *aLengths) const
132 : {
133 0 : aLengths->Clear();
134 0 : SVGPathTraversalState state;
135 :
136 0 : uint32_t i = 0;
137 0 : while (i < mData.Length()) {
138 0 : state.length = 0.0;
139 0 : SVGPathSegUtils::TraversePathSegment(&mData[i], state);
140 0 : if (!aLengths->AppendElement(state.length)) {
141 0 : aLengths->Clear();
142 0 : return false;
143 : }
144 0 : i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);
145 : }
146 :
147 0 : MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
148 :
149 0 : return true;
150 : }
151 :
152 : bool
153 0 : SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(FallibleTArray<double> *aOutput) const
154 : {
155 0 : SVGPathTraversalState state;
156 :
157 0 : aOutput->Clear();
158 :
159 0 : uint32_t i = 0;
160 0 : while (i < mData.Length()) {
161 0 : uint32_t segType = SVGPathSegUtils::DecodeType(mData[i]);
162 0 : SVGPathSegUtils::TraversePathSegment(&mData[i], state);
163 :
164 : // We skip all moveto commands except an initial moveto. See the text 'A
165 : // "move to" command does not count as an additional point when dividing up
166 : // the duration...':
167 : //
168 : // http://www.w3.org/TR/SVG11/animate.html#AnimateMotionElement
169 : //
170 : // This is important in the non-default case of calcMode="linear". In
171 : // this case an equal amount of time is spent on each path segment,
172 : // except on moveto segments which are jumped over immediately.
173 :
174 0 : if (i == 0 || (segType != PATHSEG_MOVETO_ABS &&
175 : segType != PATHSEG_MOVETO_REL)) {
176 0 : if (!aOutput->AppendElement(state.length, fallible)) {
177 0 : return false;
178 : }
179 : }
180 0 : i += 1 + SVGPathSegUtils::ArgCountForType(segType);
181 : }
182 :
183 0 : MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt?");
184 :
185 0 : return true;
186 : }
187 :
188 : uint32_t
189 0 : SVGPathData::GetPathSegAtLength(float aDistance) const
190 : {
191 : // TODO [SVGWG issue] get specified what happen if 'aDistance' < 0, or
192 : // 'aDistance' > the length of the path, or the seg list is empty.
193 : // Return -1? Throwing would better help authors avoid tricky bugs (DOM
194 : // could do that if we return -1).
195 :
196 0 : uint32_t i = 0, segIndex = 0;
197 0 : SVGPathTraversalState state;
198 :
199 0 : while (i < mData.Length()) {
200 0 : SVGPathSegUtils::TraversePathSegment(&mData[i], state);
201 0 : if (state.length >= aDistance) {
202 0 : return segIndex;
203 : }
204 0 : i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]);
205 0 : segIndex++;
206 : }
207 :
208 0 : MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
209 :
210 0 : return std::max(1U, segIndex) - 1; // -1 because while loop takes us 1 too far
211 : }
212 :
213 : /**
214 : * The SVG spec says we have to paint stroke caps for zero length subpaths:
215 : *
216 : * http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes
217 : *
218 : * Cairo only does this for |stroke-linecap: round| and not for
219 : * |stroke-linecap: square| (since that's what Adobe Acrobat has always done).
220 : * Most likely the other backends that DrawTarget uses have the same behavior.
221 : *
222 : * To help us conform to the SVG spec we have this helper function to draw an
223 : * approximation of square caps for zero length subpaths. It does this by
224 : * inserting a subpath containing a single user space axis aligned straight
225 : * line that is as small as it can be while minimizing the risk of it being
226 : * thrown away by the DrawTarget's backend for being too small to affect
227 : * rendering. The idea is that we'll then get stroke caps drawn for this axis
228 : * aligned line, creating an axis aligned rectangle that approximates the
229 : * square that would ideally be drawn.
230 : *
231 : * Since we don't have any information about transforms from user space to
232 : * device space, we choose the length of the small line that we insert by
233 : * making it a small percentage of the stroke width of the path. This should
234 : * hopefully allow us to make the line as long as possible (to avoid rounding
235 : * issues in the backend resulting in the backend seeing it as having zero
236 : * length) while still avoiding the small rectangle being noticably different
237 : * from a square.
238 : *
239 : * Note that this function inserts a subpath into the current gfx path that
240 : * will be present during both fill and stroke operations.
241 : */
242 : static void
243 0 : ApproximateZeroLengthSubpathSquareCaps(PathBuilder* aPB,
244 : const Point& aPoint,
245 : Float aStrokeWidth)
246 : {
247 : // Note that caps are proportional to stroke width, so if stroke width is
248 : // zero it's actually fine for |tinyLength| below to end up being zero.
249 : // However, it would be a waste to inserting a LineTo in that case, so better
250 : // not to.
251 0 : MOZ_ASSERT(aStrokeWidth > 0.0f,
252 : "Make the caller check for this, or check it here");
253 :
254 : // The fraction of the stroke width that we choose for the length of the
255 : // line is rather arbitrary, other than being chosen to meet the requirements
256 : // described in the comment above.
257 :
258 0 : Float tinyLength = aStrokeWidth / SVG_ZERO_LENGTH_PATH_FIX_FACTOR;
259 :
260 0 : aPB->LineTo(aPoint + Point(tinyLength, 0));
261 0 : aPB->MoveTo(aPoint);
262 0 : }
263 :
264 : #define MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT \
265 : do { \
266 : if (!subpathHasLength && hasLineCaps && aStrokeWidth > 0 && \
267 : subpathContainsNonMoveTo && \
268 : SVGPathSegUtils::IsValidType(prevSegType) && \
269 : (!IsMoveto(prevSegType) || segType == PATHSEG_CLOSEPATH)) { \
270 : ApproximateZeroLengthSubpathSquareCaps(builder, segStart, aStrokeWidth);\
271 : } \
272 : } while(0)
273 :
274 : already_AddRefed<Path>
275 28 : SVGPathData::BuildPath(PathBuilder* builder,
276 : uint8_t aStrokeLineCap,
277 : Float aStrokeWidth) const
278 : {
279 28 : if (mData.IsEmpty() || !IsMoveto(SVGPathSegUtils::DecodeType(mData[0]))) {
280 0 : return nullptr; // paths without an initial moveto are invalid
281 : }
282 :
283 28 : bool hasLineCaps = aStrokeLineCap != NS_STYLE_STROKE_LINECAP_BUTT;
284 28 : bool subpathHasLength = false; // visual length
285 28 : bool subpathContainsNonMoveTo = false;
286 :
287 28 : uint32_t segType = PATHSEG_UNKNOWN;
288 28 : uint32_t prevSegType = PATHSEG_UNKNOWN;
289 28 : Point pathStart(0.0, 0.0); // start point of [sub]path
290 28 : Point segStart(0.0, 0.0);
291 28 : Point segEnd;
292 28 : Point cp1, cp2; // previous bezier's control points
293 28 : Point tcp1, tcp2; // temporaries
294 :
295 : // Regarding cp1 and cp2: If the previous segment was a cubic bezier curve,
296 : // then cp2 is its second control point. If the previous segment was a
297 : // quadratic curve, then cp1 is its (only) control point.
298 :
299 28 : uint32_t i = 0;
300 1536 : while (i < mData.Length()) {
301 754 : segType = SVGPathSegUtils::DecodeType(mData[i++]);
302 754 : uint32_t argCount = SVGPathSegUtils::ArgCountForType(segType);
303 :
304 754 : switch (segType)
305 : {
306 : case PATHSEG_CLOSEPATH:
307 : // set this early to allow drawing of square caps for "M{x},{y} Z":
308 71 : subpathContainsNonMoveTo = true;
309 71 : MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
310 71 : segEnd = pathStart;
311 71 : builder->Close();
312 71 : break;
313 :
314 : case PATHSEG_MOVETO_ABS:
315 56 : MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
316 56 : pathStart = segEnd = Point(mData[i], mData[i+1]);
317 56 : builder->MoveTo(segEnd);
318 56 : subpathHasLength = false;
319 56 : break;
320 :
321 : case PATHSEG_MOVETO_REL:
322 16 : MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
323 16 : pathStart = segEnd = segStart + Point(mData[i], mData[i+1]);
324 16 : builder->MoveTo(segEnd);
325 16 : subpathHasLength = false;
326 16 : break;
327 :
328 : case PATHSEG_LINETO_ABS:
329 153 : segEnd = Point(mData[i], mData[i+1]);
330 153 : if (segEnd != segStart) {
331 150 : subpathHasLength = true;
332 150 : builder->LineTo(segEnd);
333 : }
334 153 : break;
335 :
336 : case PATHSEG_LINETO_REL:
337 76 : segEnd = segStart + Point(mData[i], mData[i+1]);
338 76 : if (segEnd != segStart) {
339 75 : subpathHasLength = true;
340 75 : builder->LineTo(segEnd);
341 : }
342 76 : break;
343 :
344 : case PATHSEG_CURVETO_CUBIC_ABS:
345 58 : cp1 = Point(mData[i], mData[i+1]);
346 58 : cp2 = Point(mData[i+2], mData[i+3]);
347 58 : segEnd = Point(mData[i+4], mData[i+5]);
348 58 : if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
349 55 : subpathHasLength = true;
350 55 : builder->BezierTo(cp1, cp2, segEnd);
351 : }
352 58 : break;
353 :
354 : case PATHSEG_CURVETO_CUBIC_REL:
355 139 : cp1 = segStart + Point(mData[i], mData[i+1]);
356 139 : cp2 = segStart + Point(mData[i+2], mData[i+3]);
357 139 : segEnd = segStart + Point(mData[i+4], mData[i+5]);
358 139 : if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
359 139 : subpathHasLength = true;
360 139 : builder->BezierTo(cp1, cp2, segEnd);
361 : }
362 139 : break;
363 :
364 : case PATHSEG_CURVETO_QUADRATIC_ABS:
365 0 : cp1 = Point(mData[i], mData[i+1]);
366 : // Convert quadratic curve to cubic curve:
367 0 : tcp1 = segStart + (cp1 - segStart) * 2 / 3;
368 0 : segEnd = Point(mData[i+2], mData[i+3]); // set before setting tcp2!
369 0 : tcp2 = cp1 + (segEnd - cp1) / 3;
370 0 : if (segEnd != segStart || segEnd != cp1) {
371 0 : subpathHasLength = true;
372 0 : builder->BezierTo(tcp1, tcp2, segEnd);
373 : }
374 0 : break;
375 :
376 : case PATHSEG_CURVETO_QUADRATIC_REL:
377 1 : cp1 = segStart + Point(mData[i], mData[i+1]);
378 : // Convert quadratic curve to cubic curve:
379 1 : tcp1 = segStart + (cp1 - segStart) * 2 / 3;
380 1 : segEnd = segStart + Point(mData[i+2], mData[i+3]); // set before setting tcp2!
381 1 : tcp2 = cp1 + (segEnd - cp1) / 3;
382 1 : if (segEnd != segStart || segEnd != cp1) {
383 1 : subpathHasLength = true;
384 1 : builder->BezierTo(tcp1, tcp2, segEnd);
385 : }
386 1 : break;
387 :
388 : case PATHSEG_ARC_ABS:
389 : case PATHSEG_ARC_REL:
390 : {
391 104 : Point radii(mData[i], mData[i+1]);
392 104 : segEnd = Point(mData[i+5], mData[i+6]);
393 104 : if (segType == PATHSEG_ARC_REL) {
394 94 : segEnd += segStart;
395 : }
396 104 : if (segEnd != segStart) {
397 104 : subpathHasLength = true;
398 104 : if (radii.x == 0.0f || radii.y == 0.0f) {
399 0 : builder->LineTo(segEnd);
400 : } else {
401 104 : nsSVGArcConverter converter(segStart, segEnd, radii, mData[i+2],
402 208 : mData[i+3] != 0, mData[i+4] != 0);
403 400 : while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) {
404 148 : builder->BezierTo(cp1, cp2, segEnd);
405 : }
406 : }
407 : }
408 104 : break;
409 : }
410 :
411 : case PATHSEG_LINETO_HORIZONTAL_ABS:
412 12 : segEnd = Point(mData[i], segStart.y);
413 12 : if (segEnd != segStart) {
414 12 : subpathHasLength = true;
415 12 : builder->LineTo(segEnd);
416 : }
417 12 : break;
418 :
419 : case PATHSEG_LINETO_HORIZONTAL_REL:
420 17 : segEnd = segStart + Point(mData[i], 0.0f);
421 17 : if (segEnd != segStart) {
422 15 : subpathHasLength = true;
423 15 : builder->LineTo(segEnd);
424 : }
425 17 : break;
426 :
427 : case PATHSEG_LINETO_VERTICAL_ABS:
428 6 : segEnd = Point(segStart.x, mData[i]);
429 6 : if (segEnd != segStart) {
430 6 : subpathHasLength = true;
431 6 : builder->LineTo(segEnd);
432 : }
433 6 : break;
434 :
435 : case PATHSEG_LINETO_VERTICAL_REL:
436 16 : segEnd = segStart + Point(0.0f, mData[i]);
437 16 : if (segEnd != segStart) {
438 16 : subpathHasLength = true;
439 16 : builder->LineTo(segEnd);
440 : }
441 16 : break;
442 :
443 : case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
444 1 : cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ? segStart * 2 - cp2 : segStart;
445 1 : cp2 = Point(mData[i], mData[i+1]);
446 1 : segEnd = Point(mData[i+2], mData[i+3]);
447 1 : if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
448 1 : subpathHasLength = true;
449 1 : builder->BezierTo(cp1, cp2, segEnd);
450 : }
451 1 : break;
452 :
453 : case PATHSEG_CURVETO_CUBIC_SMOOTH_REL:
454 28 : cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ? segStart * 2 - cp2 : segStart;
455 28 : cp2 = segStart + Point(mData[i], mData[i+1]);
456 28 : segEnd = segStart + Point(mData[i+2], mData[i+3]);
457 28 : if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) {
458 28 : subpathHasLength = true;
459 28 : builder->BezierTo(cp1, cp2, segEnd);
460 : }
461 28 : break;
462 :
463 : case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
464 0 : cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ? segStart * 2 - cp1 : segStart;
465 : // Convert quadratic curve to cubic curve:
466 0 : tcp1 = segStart + (cp1 - segStart) * 2 / 3;
467 0 : segEnd = Point(mData[i], mData[i+1]); // set before setting tcp2!
468 0 : tcp2 = cp1 + (segEnd - cp1) / 3;
469 0 : if (segEnd != segStart || segEnd != cp1) {
470 0 : subpathHasLength = true;
471 0 : builder->BezierTo(tcp1, tcp2, segEnd);
472 : }
473 0 : break;
474 :
475 : case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:
476 0 : cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ? segStart * 2 - cp1 : segStart;
477 : // Convert quadratic curve to cubic curve:
478 0 : tcp1 = segStart + (cp1 - segStart) * 2 / 3;
479 0 : segEnd = segStart + Point(mData[i], mData[i+1]); // changed before setting tcp2!
480 0 : tcp2 = cp1 + (segEnd - cp1) / 3;
481 0 : if (segEnd != segStart || segEnd != cp1) {
482 0 : subpathHasLength = true;
483 0 : builder->BezierTo(tcp1, tcp2, segEnd);
484 : }
485 0 : break;
486 :
487 : default:
488 0 : NS_NOTREACHED("Bad path segment type");
489 0 : return nullptr; // according to spec we'd use everything up to the bad seg anyway
490 : }
491 :
492 754 : subpathContainsNonMoveTo = segType != PATHSEG_MOVETO_ABS &&
493 : segType != PATHSEG_MOVETO_REL;
494 754 : i += argCount;
495 754 : prevSegType = segType;
496 754 : segStart = segEnd;
497 : }
498 :
499 28 : MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
500 28 : MOZ_ASSERT(prevSegType == segType,
501 : "prevSegType should be left at the final segType");
502 :
503 28 : MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
504 :
505 28 : return builder->Finish();
506 : }
507 :
508 : already_AddRefed<Path>
509 0 : SVGPathData::BuildPathForMeasuring() const
510 : {
511 : // Since the path that we return will not be used for painting it doesn't
512 : // matter what we pass to CreatePathBuilder as aFillRule. Hawever, we do want
513 : // to pass something other than NS_STYLE_STROKE_LINECAP_SQUARE as
514 : // aStrokeLineCap to avoid the insertion of extra little lines (by
515 : // ApproximateZeroLengthSubpathSquareCaps), in which case the value that we
516 : // pass as aStrokeWidth doesn't matter (since it's only used to determine the
517 : // length of those extra little lines).
518 :
519 : RefPtr<DrawTarget> drawTarget =
520 0 : gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
521 : RefPtr<PathBuilder> builder =
522 0 : drawTarget->CreatePathBuilder(FillRule::FILL_WINDING);
523 0 : return BuildPath(builder, NS_STYLE_STROKE_LINECAP_BUTT, 0);
524 : }
525 :
526 : static double
527 0 : AngleOfVector(const Point& aVector)
528 : {
529 : // C99 says about atan2 "A domain error may occur if both arguments are
530 : // zero" and "On a domain error, the function returns an implementation-
531 : // defined value". In the case of atan2 the implementation-defined value
532 : // seems to commonly be zero, but it could just as easily be a NaN value.
533 : // We specifically want zero in this case, hence the check:
534 :
535 0 : return (aVector != Point(0.0, 0.0)) ? atan2(aVector.y, aVector.x) : 0.0;
536 : }
537 :
538 : static float
539 0 : AngleOfVector(const Point& cp1, const Point& cp2)
540 : {
541 0 : return static_cast<float>(AngleOfVector(cp1 - cp2));
542 : }
543 :
544 : void
545 0 : SVGPathData::GetMarkerPositioningData(nsTArray<nsSVGMark> *aMarks) const
546 : {
547 : // This code should assume that ANY type of segment can appear at ANY index.
548 : // It should also assume that segments such as M and Z can appear in weird
549 : // places, and repeat multiple times consecutively.
550 :
551 : // info on current [sub]path (reset every M command):
552 0 : Point pathStart(0.0, 0.0);
553 0 : float pathStartAngle = 0.0f;
554 :
555 : // info on previous segment:
556 0 : uint16_t prevSegType = PATHSEG_UNKNOWN;
557 0 : Point prevSegEnd(0.0, 0.0);
558 0 : float prevSegEndAngle = 0.0f;
559 0 : Point prevCP; // if prev seg was a bezier, this was its last control point
560 :
561 0 : uint32_t i = 0;
562 0 : while (i < mData.Length()) {
563 :
564 : // info on current segment:
565 : uint16_t segType =
566 0 : SVGPathSegUtils::DecodeType(mData[i++]); // advances i to args
567 0 : Point& segStart = prevSegEnd;
568 0 : Point segEnd;
569 : float segStartAngle, segEndAngle;
570 :
571 0 : switch (segType) // to find segStartAngle, segEnd and segEndAngle
572 : {
573 : case PATHSEG_CLOSEPATH:
574 0 : segEnd = pathStart;
575 0 : segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
576 0 : break;
577 :
578 : case PATHSEG_MOVETO_ABS:
579 : case PATHSEG_MOVETO_REL:
580 0 : if (segType == PATHSEG_MOVETO_ABS) {
581 0 : segEnd = Point(mData[i], mData[i+1]);
582 : } else {
583 0 : segEnd = segStart + Point(mData[i], mData[i+1]);
584 : }
585 0 : pathStart = segEnd;
586 : // If authors are going to specify multiple consecutive moveto commands
587 : // with markers, me might as well make the angle do something useful:
588 0 : segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
589 0 : i += 2;
590 0 : break;
591 :
592 : case PATHSEG_LINETO_ABS:
593 : case PATHSEG_LINETO_REL:
594 0 : if (segType == PATHSEG_LINETO_ABS) {
595 0 : segEnd = Point(mData[i], mData[i+1]);
596 : } else {
597 0 : segEnd = segStart + Point(mData[i], mData[i+1]);
598 : }
599 0 : segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
600 0 : i += 2;
601 0 : break;
602 :
603 : case PATHSEG_CURVETO_CUBIC_ABS:
604 : case PATHSEG_CURVETO_CUBIC_REL:
605 : {
606 0 : Point cp1, cp2; // control points
607 0 : if (segType == PATHSEG_CURVETO_CUBIC_ABS) {
608 0 : cp1 = Point(mData[i], mData[i+1]);
609 0 : cp2 = Point(mData[i+2], mData[i+3]);
610 0 : segEnd = Point(mData[i+4], mData[i+5]);
611 : } else {
612 0 : cp1 = segStart + Point(mData[i], mData[i+1]);
613 0 : cp2 = segStart + Point(mData[i+2], mData[i+3]);
614 0 : segEnd = segStart + Point(mData[i+4], mData[i+5]);
615 : }
616 0 : prevCP = cp2;
617 : segStartAngle =
618 0 : AngleOfVector(cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart);
619 : segEndAngle =
620 0 : AngleOfVector(segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2);
621 0 : i += 6;
622 0 : break;
623 : }
624 :
625 : case PATHSEG_CURVETO_QUADRATIC_ABS:
626 : case PATHSEG_CURVETO_QUADRATIC_REL:
627 : {
628 0 : Point cp1; // control point
629 0 : if (segType == PATHSEG_CURVETO_QUADRATIC_ABS) {
630 0 : cp1 = Point(mData[i], mData[i+1]);
631 0 : segEnd = Point(mData[i+2], mData[i+3]);
632 : } else {
633 0 : cp1 = segStart + Point(mData[i], mData[i+1]);
634 0 : segEnd = segStart + Point(mData[i+2], mData[i+3]);
635 : }
636 0 : prevCP = cp1;
637 0 : segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart);
638 0 : segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1);
639 0 : i += 4;
640 0 : break;
641 : }
642 :
643 : case PATHSEG_ARC_ABS:
644 : case PATHSEG_ARC_REL:
645 : {
646 0 : double rx = mData[i];
647 0 : double ry = mData[i+1];
648 0 : double angle = mData[i+2];
649 0 : bool largeArcFlag = mData[i+3] != 0.0f;
650 0 : bool sweepFlag = mData[i+4] != 0.0f;
651 0 : if (segType == PATHSEG_ARC_ABS) {
652 0 : segEnd = Point(mData[i+5], mData[i+6]);
653 : } else {
654 0 : segEnd = segStart + Point(mData[i+5], mData[i+6]);
655 : }
656 :
657 : // See section F.6 of SVG 1.1 for details on what we're doing here:
658 : // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
659 :
660 0 : if (segStart == segEnd) {
661 : // F.6.2 says "If the endpoints (x1, y1) and (x2, y2) are identical,
662 : // then this is equivalent to omitting the elliptical arc segment
663 : // entirely." We take that very literally here, not adding a mark, and
664 : // not even setting any of the 'prev' variables so that it's as if this
665 : // arc had never existed; note the difference this will make e.g. if
666 : // the arc is proceeded by a bezier curve and followed by a "smooth"
667 : // bezier curve of the same degree!
668 0 : i += 7;
669 0 : continue;
670 : }
671 :
672 : // Below we have funny interleaving of F.6.6 (Correction of out-of-range
673 : // radii) and F.6.5 (Conversion from endpoint to center parameterization)
674 : // which is designed to avoid some unnecessary calculations.
675 :
676 0 : if (rx == 0.0 || ry == 0.0) {
677 : // F.6.6 step 1 - straight line or coincidental points
678 0 : segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
679 0 : i += 7;
680 0 : break;
681 : }
682 0 : rx = fabs(rx); // F.6.6.1
683 0 : ry = fabs(ry);
684 :
685 : // F.6.5.1:
686 0 : angle = angle * M_PI/180.0;
687 0 : double x1p = cos(angle) * (segStart.x - segEnd.x) / 2.0
688 0 : + sin(angle) * (segStart.y - segEnd.y) / 2.0;
689 0 : double y1p = -sin(angle) * (segStart.x - segEnd.x) / 2.0
690 0 : + cos(angle) * (segStart.y - segEnd.y) / 2.0;
691 :
692 : // This is the root in F.6.5.2 and the numerator under that root:
693 : double root;
694 0 : double numerator = rx*rx*ry*ry - rx*rx*y1p*y1p - ry*ry*x1p*x1p;
695 :
696 0 : if (numerator >= 0.0) {
697 0 : root = sqrt(numerator/(rx*rx*y1p*y1p + ry*ry*x1p*x1p));
698 0 : if (largeArcFlag == sweepFlag)
699 0 : root = -root;
700 : } else {
701 : // F.6.6 step 3 - |numerator < 0.0|. This is equivalent to the result
702 : // of F.6.6.2 (lamedh) being greater than one. What we have here is
703 : // ellipse radii that are too small for the ellipse to reach between
704 : // segStart and segEnd. We scale the radii up uniformly so that the
705 : // ellipse is just big enough to fit (i.e. to the point where there is
706 : // exactly one solution).
707 :
708 0 : double lamedh = 1.0 - numerator/(rx*rx*ry*ry); // equiv to eqn F.6.6.2
709 0 : double s = sqrt(lamedh);
710 0 : rx *= s; // F.6.6.3
711 0 : ry *= s;
712 0 : root = 0.0;
713 : }
714 :
715 0 : double cxp = root * rx * y1p / ry; // F.6.5.2
716 0 : double cyp = -root * ry * x1p / rx;
717 :
718 : double theta, delta;
719 0 : theta = AngleOfVector(Point((x1p-cxp)/rx, (y1p-cyp)/ry)); // F.6.5.5
720 0 : delta = AngleOfVector(Point((-x1p-cxp)/rx, (-y1p-cyp)/ry)) - // F.6.5.6
721 : theta;
722 0 : if (!sweepFlag && delta > 0)
723 0 : delta -= 2.0 * M_PI;
724 0 : else if (sweepFlag && delta < 0)
725 0 : delta += 2.0 * M_PI;
726 :
727 : double tx1, ty1, tx2, ty2;
728 0 : tx1 = -cos(angle)*rx*sin(theta) - sin(angle)*ry*cos(theta);
729 0 : ty1 = -sin(angle)*rx*sin(theta) + cos(angle)*ry*cos(theta);
730 0 : tx2 = -cos(angle)*rx*sin(theta+delta) - sin(angle)*ry*cos(theta+delta);
731 0 : ty2 = -sin(angle)*rx*sin(theta+delta) + cos(angle)*ry*cos(theta+delta);
732 :
733 0 : if (delta < 0.0f) {
734 0 : tx1 = -tx1;
735 0 : ty1 = -ty1;
736 0 : tx2 = -tx2;
737 0 : ty2 = -ty2;
738 : }
739 :
740 0 : segStartAngle = static_cast<float>(atan2(ty1, tx1));
741 0 : segEndAngle = static_cast<float>(atan2(ty2, tx2));
742 0 : i += 7;
743 0 : break;
744 : }
745 :
746 : case PATHSEG_LINETO_HORIZONTAL_ABS:
747 : case PATHSEG_LINETO_HORIZONTAL_REL:
748 0 : if (segType == PATHSEG_LINETO_HORIZONTAL_ABS) {
749 0 : segEnd = Point(mData[i++], segStart.y);
750 : } else {
751 0 : segEnd = segStart + Point(mData[i++], 0.0f);
752 : }
753 0 : segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
754 0 : break;
755 :
756 : case PATHSEG_LINETO_VERTICAL_ABS:
757 : case PATHSEG_LINETO_VERTICAL_REL:
758 0 : if (segType == PATHSEG_LINETO_VERTICAL_ABS) {
759 0 : segEnd = Point(segStart.x, mData[i++]);
760 : } else {
761 0 : segEnd = segStart + Point(0.0f, mData[i++]);
762 : }
763 0 : segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart);
764 0 : break;
765 :
766 : case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
767 : case PATHSEG_CURVETO_CUBIC_SMOOTH_REL:
768 : {
769 0 : Point cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ?
770 0 : segStart * 2 - prevCP : segStart;
771 0 : Point cp2;
772 0 : if (segType == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS) {
773 0 : cp2 = Point(mData[i], mData[i+1]);
774 0 : segEnd = Point(mData[i+2], mData[i+3]);
775 : } else {
776 0 : cp2 = segStart + Point(mData[i], mData[i+1]);
777 0 : segEnd = segStart + Point(mData[i+2], mData[i+3]);
778 : }
779 0 : prevCP = cp2;
780 : segStartAngle =
781 0 : AngleOfVector(cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart);
782 : segEndAngle =
783 0 : AngleOfVector(segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2);
784 0 : i += 4;
785 0 : break;
786 : }
787 :
788 : case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
789 : case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:
790 : {
791 0 : Point cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ?
792 0 : segStart * 2 - prevCP : segStart;
793 0 : if (segType == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS) {
794 0 : segEnd = Point(mData[i], mData[i+1]);
795 : } else {
796 0 : segEnd = segStart + Point(mData[i], mData[i+1]);
797 : }
798 0 : prevCP = cp1;
799 0 : segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart);
800 0 : segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1);
801 0 : i += 2;
802 0 : break;
803 : }
804 :
805 : default:
806 : // Leave any existing marks in aMarks so we have a visual indication of
807 : // when things went wrong.
808 0 : MOZ_ASSERT(false, "Unknown segment type - path corruption?");
809 : return;
810 : }
811 :
812 : // Set the angle of the mark at the start of this segment:
813 0 : if (aMarks->Length()) {
814 0 : nsSVGMark &mark = aMarks->LastElement();
815 0 : if (!IsMoveto(segType) && IsMoveto(prevSegType)) {
816 : // start of new subpath
817 0 : pathStartAngle = mark.angle = segStartAngle;
818 0 : } else if (IsMoveto(segType) && !IsMoveto(prevSegType)) {
819 : // end of a subpath
820 0 : if (prevSegType != PATHSEG_CLOSEPATH)
821 0 : mark.angle = prevSegEndAngle;
822 : } else {
823 0 : if (!(segType == PATHSEG_CLOSEPATH &&
824 : prevSegType == PATHSEG_CLOSEPATH))
825 0 : mark.angle = SVGContentUtils::AngleBisect(prevSegEndAngle, segStartAngle);
826 : }
827 : }
828 :
829 : // Add the mark at the end of this segment, and set its position:
830 0 : if (!aMarks->AppendElement(nsSVGMark(static_cast<float>(segEnd.x),
831 0 : static_cast<float>(segEnd.y),
832 : 0.0f,
833 : nsSVGMark::eMid))) {
834 0 : aMarks->Clear(); // OOM, so try to free some
835 0 : return;
836 : }
837 :
838 0 : if (segType == PATHSEG_CLOSEPATH &&
839 : prevSegType != PATHSEG_CLOSEPATH) {
840 0 : aMarks->LastElement().angle =
841 : //aMarks->ElementAt(pathStartIndex).angle =
842 0 : SVGContentUtils::AngleBisect(segEndAngle, pathStartAngle);
843 : }
844 :
845 0 : prevSegType = segType;
846 0 : prevSegEnd = segEnd;
847 0 : prevSegEndAngle = segEndAngle;
848 : }
849 :
850 0 : MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt");
851 :
852 0 : if (aMarks->Length()) {
853 0 : if (prevSegType != PATHSEG_CLOSEPATH) {
854 0 : aMarks->LastElement().angle = prevSegEndAngle;
855 : }
856 0 : aMarks->LastElement().type = nsSVGMark::eEnd;
857 0 : aMarks->ElementAt(0).type = nsSVGMark::eStart;
858 : }
859 : }
860 :
861 : size_t
862 33 : SVGPathData::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
863 : {
864 33 : return mData.ShallowSizeOfExcludingThis(aMallocSizeOf);
865 : }
866 :
867 : size_t
868 0 : SVGPathData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
869 : {
870 0 : return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
871 : }
872 :
|