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