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 "SVGPathSegUtils.h"
38 : #include "nsSVGElement.h"
39 : #include "nsSVGSVGElement.h"
40 : #include "nsSVGPathDataParser.h"
41 : #include "nsString.h"
42 : #include "nsSVGUtils.h"
43 : #include "nsContentUtils.h"
44 : #include "nsTextFormatter.h"
45 : #include "prdtoa.h"
46 : #include <limits>
47 : #include "nsMathUtils.h"
48 : #include "prtypes.h"
49 :
50 : using namespace mozilla;
51 :
52 : static const float PATH_SEG_LENGTH_TOLERANCE = 0.0000001f;
53 : static const PRUint32 MAX_RECURSION = 10;
54 :
55 :
56 : /* static */ void
57 0 : SVGPathSegUtils::GetValueAsString(const float* aSeg, nsAString& aValue)
58 : {
59 : // Adding new seg type? Is the formatting below acceptable for the new types?
60 : PR_STATIC_ASSERT(NS_SVG_PATH_SEG_LAST_VALID_TYPE ==
61 : nsIDOMSVGPathSeg::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL);
62 : PR_STATIC_ASSERT(NS_SVG_PATH_SEG_MAX_ARGS == 7);
63 :
64 0 : PRUint32 type = DecodeType(aSeg[0]);
65 0 : PRUnichar typeAsChar = GetPathSegTypeAsLetter(type);
66 :
67 : // Special case arcs:
68 0 : if (IsArcType(type)) {
69 0 : bool largeArcFlag = aSeg[4] != 0.0f;
70 0 : bool sweepFlag = aSeg[5] != 0.0f;
71 : nsTextFormatter::ssprintf(aValue,
72 0 : NS_LITERAL_STRING("%c%g,%g %g %d,%d %g,%g").get(),
73 0 : typeAsChar, aSeg[1], aSeg[2], aSeg[3],
74 0 : largeArcFlag, sweepFlag, aSeg[6], aSeg[7]);
75 : } else {
76 :
77 0 : switch (ArgCountForType(type)) {
78 : case 0:
79 0 : aValue = typeAsChar;
80 0 : break;
81 :
82 : case 1:
83 0 : nsTextFormatter::ssprintf(aValue, NS_LITERAL_STRING("%c%g").get(),
84 0 : typeAsChar, aSeg[1]);
85 0 : break;
86 :
87 : case 2:
88 0 : nsTextFormatter::ssprintf(aValue, NS_LITERAL_STRING("%c%g,%g").get(),
89 0 : typeAsChar, aSeg[1], aSeg[2]);
90 0 : break;
91 :
92 : case 4:
93 0 : nsTextFormatter::ssprintf(aValue, NS_LITERAL_STRING("%c%g,%g %g,%g").get(),
94 0 : typeAsChar, aSeg[1], aSeg[2], aSeg[3], aSeg[4]);
95 0 : break;
96 :
97 : case 6:
98 : nsTextFormatter::ssprintf(aValue,
99 0 : NS_LITERAL_STRING("%c%g,%g %g,%g %g,%g").get(),
100 0 : typeAsChar, aSeg[1], aSeg[2], aSeg[3], aSeg[4],
101 0 : aSeg[5], aSeg[6]);
102 0 : break;
103 :
104 : default:
105 0 : NS_ABORT_IF_FALSE(false, "Unknown segment type");
106 0 : aValue = NS_LITERAL_STRING("<unknown-segment-type>").get();
107 0 : return;
108 : }
109 : }
110 :
111 : // nsTextFormatter::ssprintf is one of the nsTextFormatter methods that
112 : // randomly appends '\0' to its output string, which means that the length
113 : // of the output string is one too long. We need to manually remove that '\0'
114 : // until nsTextFormatter is fixed.
115 : //
116 0 : if (aValue[aValue.Length() - 1] == PRUnichar('\0')) {
117 0 : aValue.SetLength(aValue.Length() - 1);
118 : }
119 : }
120 :
121 :
122 : static float
123 0 : CalcDistanceBetweenPoints(const gfxPoint& aP1, const gfxPoint& aP2)
124 : {
125 0 : return NS_hypot(aP2.x - aP1.x, aP2.y - aP1.y);
126 : }
127 :
128 :
129 : static void
130 0 : SplitQuadraticBezier(const gfxPoint* aCurve, gfxPoint* aLeft, gfxPoint* aRight)
131 : {
132 0 : aLeft[0].x = aCurve[0].x;
133 0 : aLeft[0].y = aCurve[0].y;
134 0 : aRight[2].x = aCurve[2].x;
135 0 : aRight[2].y = aCurve[2].y;
136 0 : aLeft[1].x = (aCurve[0].x + aCurve[1].x) / 2;
137 0 : aLeft[1].y = (aCurve[0].y + aCurve[1].y) / 2;
138 0 : aRight[1].x = (aCurve[1].x + aCurve[2].x) / 2;
139 0 : aRight[1].y = (aCurve[1].y + aCurve[2].y) / 2;
140 0 : aLeft[2].x = aRight[0].x = (aLeft[1].x + aRight[1].x) / 2;
141 0 : aLeft[2].y = aRight[0].y = (aLeft[1].y + aRight[1].y) / 2;
142 0 : }
143 :
144 : static void
145 0 : SplitCubicBezier(const gfxPoint* aCurve, gfxPoint* aLeft, gfxPoint* aRight)
146 : {
147 0 : gfxPoint tmp;
148 0 : tmp.x = (aCurve[1].x + aCurve[2].x) / 4;
149 0 : tmp.y = (aCurve[1].y + aCurve[2].y) / 4;
150 0 : aLeft[0].x = aCurve[0].x;
151 0 : aLeft[0].y = aCurve[0].y;
152 0 : aRight[3].x = aCurve[3].x;
153 0 : aRight[3].y = aCurve[3].y;
154 0 : aLeft[1].x = (aCurve[0].x + aCurve[1].x) / 2;
155 0 : aLeft[1].y = (aCurve[0].y + aCurve[1].y) / 2;
156 0 : aRight[2].x = (aCurve[2].x + aCurve[3].x) / 2;
157 0 : aRight[2].y = (aCurve[2].y + aCurve[3].y) / 2;
158 0 : aLeft[2].x = aLeft[1].x / 2 + tmp.x;
159 0 : aLeft[2].y = aLeft[1].y / 2 + tmp.y;
160 0 : aRight[1].x = aRight[2].x / 2 + tmp.x;
161 0 : aRight[1].y = aRight[2].y / 2 + tmp.y;
162 0 : aLeft[3].x = aRight[0].x = (aLeft[2].x + aRight[1].x) / 2;
163 0 : aLeft[3].y = aRight[0].y = (aLeft[2].y + aRight[1].y) / 2;
164 0 : }
165 :
166 : static gfxFloat
167 0 : CalcBezLengthHelper(gfxPoint* aCurve, PRUint32 aNumPts,
168 : PRUint32 aRecursionCount,
169 : void (*aSplit)(const gfxPoint*, gfxPoint*, gfxPoint*))
170 : {
171 0 : gfxPoint left[4];
172 0 : gfxPoint right[4];
173 0 : gfxFloat length = 0, dist;
174 0 : for (PRUint32 i = 0; i < aNumPts - 1; i++) {
175 0 : length += CalcDistanceBetweenPoints(aCurve[i], aCurve[i+1]);
176 : }
177 0 : dist = CalcDistanceBetweenPoints(aCurve[0], aCurve[aNumPts - 1]);
178 0 : if (length - dist > PATH_SEG_LENGTH_TOLERANCE &&
179 : aRecursionCount < MAX_RECURSION) {
180 0 : aSplit(aCurve, left, right);
181 0 : ++aRecursionCount;
182 0 : return CalcBezLengthHelper(left, aNumPts, aRecursionCount, aSplit) +
183 0 : CalcBezLengthHelper(right, aNumPts, aRecursionCount, aSplit);
184 : }
185 0 : return length;
186 : }
187 :
188 : static inline gfxFloat
189 0 : CalcLengthOfCubicBezier(const gfxPoint& aPos, const gfxPoint &aCP1,
190 : const gfxPoint& aCP2, const gfxPoint &aTo)
191 : {
192 0 : gfxPoint curve[4] = { aPos, aCP1, aCP2, aTo };
193 0 : return CalcBezLengthHelper(curve, 4, 0, SplitCubicBezier);
194 : }
195 :
196 : static inline gfxFloat
197 0 : CalcLengthOfQuadraticBezier(const gfxPoint& aPos, const gfxPoint& aCP,
198 : const gfxPoint& aTo)
199 : {
200 0 : gfxPoint curve[3] = { aPos, aCP, aTo };
201 0 : return CalcBezLengthHelper(curve, 3, 0, SplitQuadraticBezier);
202 : }
203 :
204 :
205 : static void
206 0 : TraverseClosePath(const float* aArgs, SVGPathTraversalState& aState)
207 : {
208 0 : if (aState.ShouldUpdateLengthAndControlPoints()) {
209 0 : aState.length += CalcDistanceBetweenPoints(aState.pos, aState.start);
210 0 : aState.cp1 = aState.cp2 = aState.start;
211 : }
212 0 : aState.pos = aState.start;
213 0 : }
214 :
215 : static void
216 0 : TraverseMovetoAbs(const float* aArgs, SVGPathTraversalState& aState)
217 : {
218 0 : aState.start = aState.pos = gfxPoint(aArgs[0], aArgs[1]);
219 0 : if (aState.ShouldUpdateLengthAndControlPoints()) {
220 : // aState.length is unchanged, since move commands don't affect path length.
221 0 : aState.cp1 = aState.cp2 = aState.start;
222 : }
223 0 : }
224 :
225 : static void
226 0 : TraverseMovetoRel(const float* aArgs, SVGPathTraversalState& aState)
227 : {
228 0 : aState.start = aState.pos += gfxPoint(aArgs[0], aArgs[1]);
229 0 : if (aState.ShouldUpdateLengthAndControlPoints()) {
230 : // aState.length is unchanged, since move commands don't affect path length.
231 0 : aState.cp1 = aState.cp2 = aState.start;
232 : }
233 0 : }
234 :
235 : static void
236 0 : TraverseLinetoAbs(const float* aArgs, SVGPathTraversalState& aState)
237 : {
238 0 : gfxPoint to(aArgs[0], aArgs[1]);
239 0 : if (aState.ShouldUpdateLengthAndControlPoints()) {
240 0 : aState.length += CalcDistanceBetweenPoints(aState.pos, to);
241 0 : aState.cp1 = aState.cp2 = to;
242 : }
243 0 : aState.pos = to;
244 0 : }
245 :
246 : static void
247 0 : TraverseLinetoRel(const float* aArgs, SVGPathTraversalState& aState)
248 : {
249 0 : gfxPoint to = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
250 0 : if (aState.ShouldUpdateLengthAndControlPoints()) {
251 0 : aState.length += CalcDistanceBetweenPoints(aState.pos, to);
252 0 : aState.cp1 = aState.cp2 = to;
253 : }
254 0 : aState.pos = to;
255 0 : }
256 :
257 : static void
258 0 : TraverseLinetoHorizontalAbs(const float* aArgs, SVGPathTraversalState& aState)
259 : {
260 0 : gfxPoint to(aArgs[0], aState.pos.y);
261 0 : if (aState.ShouldUpdateLengthAndControlPoints()) {
262 0 : aState.length += fabs(to.x - aState.pos.x);
263 0 : aState.cp1 = aState.cp2 = to;
264 : }
265 0 : aState.pos = to;
266 0 : }
267 :
268 : static void
269 0 : TraverseLinetoHorizontalRel(const float* aArgs, SVGPathTraversalState& aState)
270 : {
271 0 : aState.pos.x += aArgs[0];
272 0 : if (aState.ShouldUpdateLengthAndControlPoints()) {
273 0 : aState.length += fabs(aArgs[0]);
274 0 : aState.cp1 = aState.cp2 = aState.pos;
275 : }
276 0 : }
277 :
278 : static void
279 0 : TraverseLinetoVerticalAbs(const float* aArgs, SVGPathTraversalState& aState)
280 : {
281 0 : gfxPoint to(aState.pos.x, aArgs[0]);
282 0 : if (aState.ShouldUpdateLengthAndControlPoints()) {
283 0 : aState.length += fabs(to.y - aState.pos.y);
284 0 : aState.cp1 = aState.cp2 = to;
285 : }
286 0 : aState.pos = to;
287 0 : }
288 :
289 : static void
290 0 : TraverseLinetoVerticalRel(const float* aArgs, SVGPathTraversalState& aState)
291 : {
292 0 : aState.pos.y += aArgs[0];
293 0 : if (aState.ShouldUpdateLengthAndControlPoints()) {
294 0 : aState.length += fabs(aArgs[0]);
295 0 : aState.cp1 = aState.cp2 = aState.pos;
296 : }
297 0 : }
298 :
299 : static void
300 0 : TraverseCurvetoCubicAbs(const float* aArgs, SVGPathTraversalState& aState)
301 : {
302 0 : gfxPoint to(aArgs[4], aArgs[5]);
303 0 : if (aState.ShouldUpdateLengthAndControlPoints()) {
304 0 : gfxPoint cp1(aArgs[0], aArgs[1]);
305 0 : gfxPoint cp2(aArgs[2], aArgs[3]);
306 0 : aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
307 0 : aState.cp2 = cp2;
308 0 : aState.cp1 = to;
309 : }
310 0 : aState.pos = to;
311 0 : }
312 :
313 : static void
314 0 : TraverseCurvetoCubicSmoothAbs(const float* aArgs, SVGPathTraversalState& aState)
315 : {
316 0 : gfxPoint to(aArgs[2], aArgs[3]);
317 0 : if (aState.ShouldUpdateLengthAndControlPoints()) {
318 0 : gfxPoint cp1 = aState.pos - (aState.cp2 - aState.pos);
319 0 : gfxPoint cp2(aArgs[0], aArgs[1]);
320 0 : aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
321 0 : aState.cp2 = cp2;
322 0 : aState.cp1 = to;
323 : }
324 0 : aState.pos = to;
325 0 : }
326 :
327 : static void
328 0 : TraverseCurvetoCubicRel(const float* aArgs, SVGPathTraversalState& aState)
329 : {
330 0 : gfxPoint to = aState.pos + gfxPoint(aArgs[4], aArgs[5]);
331 0 : if (aState.ShouldUpdateLengthAndControlPoints()) {
332 0 : gfxPoint cp1 = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
333 0 : gfxPoint cp2 = aState.pos + gfxPoint(aArgs[2], aArgs[3]);
334 0 : aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
335 0 : aState.cp2 = cp2;
336 0 : aState.cp1 = to;
337 : }
338 0 : aState.pos = to;
339 0 : }
340 :
341 : static void
342 0 : TraverseCurvetoCubicSmoothRel(const float* aArgs, SVGPathTraversalState& aState)
343 : {
344 0 : gfxPoint to = aState.pos + gfxPoint(aArgs[2], aArgs[3]);
345 0 : if (aState.ShouldUpdateLengthAndControlPoints()) {
346 0 : gfxPoint cp1 = aState.pos - (aState.cp2 - aState.pos);
347 0 : gfxPoint cp2 = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
348 0 : aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to);
349 0 : aState.cp2 = cp2;
350 0 : aState.cp1 = to;
351 : }
352 0 : aState.pos = to;
353 0 : }
354 :
355 : static void
356 0 : TraverseCurvetoQuadraticAbs(const float* aArgs, SVGPathTraversalState& aState)
357 : {
358 0 : gfxPoint to(aArgs[2], aArgs[3]);
359 0 : if (aState.ShouldUpdateLengthAndControlPoints()) {
360 0 : gfxPoint cp(aArgs[0], aArgs[1]);
361 0 : aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
362 0 : aState.cp1 = cp;
363 0 : aState.cp2 = to;
364 : }
365 0 : aState.pos = to;
366 0 : }
367 :
368 : static void
369 0 : TraverseCurvetoQuadraticSmoothAbs(const float* aArgs,
370 : SVGPathTraversalState& aState)
371 : {
372 0 : gfxPoint to(aArgs[0], aArgs[1]);
373 0 : if (aState.ShouldUpdateLengthAndControlPoints()) {
374 0 : gfxPoint cp = aState.pos - (aState.cp1 - aState.pos);
375 0 : aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
376 0 : aState.cp1 = cp;
377 0 : aState.cp2 = to;
378 : }
379 0 : aState.pos = to;
380 0 : }
381 :
382 : static void
383 0 : TraverseCurvetoQuadraticRel(const float* aArgs, SVGPathTraversalState& aState)
384 : {
385 0 : gfxPoint to = aState.pos + gfxPoint(aArgs[2], aArgs[3]);
386 0 : if (aState.ShouldUpdateLengthAndControlPoints()) {
387 0 : gfxPoint cp = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
388 0 : aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
389 0 : aState.cp1 = cp;
390 0 : aState.cp2 = to;
391 : }
392 0 : aState.pos = to;
393 0 : }
394 :
395 : static void
396 0 : TraverseCurvetoQuadraticSmoothRel(const float* aArgs,
397 : SVGPathTraversalState& aState)
398 : {
399 0 : gfxPoint to = aState.pos + gfxPoint(aArgs[0], aArgs[1]);
400 0 : if (aState.ShouldUpdateLengthAndControlPoints()) {
401 0 : gfxPoint cp = aState.pos - (aState.cp1 - aState.pos);
402 0 : aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to);
403 0 : aState.cp1 = cp;
404 0 : aState.cp2 = to;
405 : }
406 0 : aState.pos = to;
407 0 : }
408 :
409 : static void
410 0 : TraverseArcAbs(const float* aArgs, SVGPathTraversalState& aState)
411 : {
412 0 : gfxPoint to(aArgs[5], aArgs[6]);
413 0 : if (aState.ShouldUpdateLengthAndControlPoints()) {
414 0 : float dist = 0;
415 0 : gfxPoint radii(aArgs[0], aArgs[1]);
416 : gfxPoint bez[4] = { aState.pos, gfxPoint(0, 0),
417 0 : gfxPoint(0, 0), gfxPoint(0, 0) };
418 0 : nsSVGArcConverter converter(aState.pos, to, radii, aArgs[2],
419 0 : aArgs[3] != 0, aArgs[4] != 0);
420 0 : while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3])) {
421 0 : dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier);
422 0 : bez[0] = bez[3];
423 : }
424 0 : aState.length += dist;
425 0 : aState.cp1 = aState.cp2 = to;
426 : }
427 0 : aState.pos = to;
428 0 : }
429 :
430 : static void
431 0 : TraverseArcRel(const float* aArgs, SVGPathTraversalState& aState)
432 : {
433 0 : gfxPoint to = aState.pos + gfxPoint(aArgs[5], aArgs[6]);
434 0 : if (aState.ShouldUpdateLengthAndControlPoints()) {
435 0 : float dist = 0;
436 0 : gfxPoint radii(aArgs[0], aArgs[1]);
437 : gfxPoint bez[4] = { aState.pos, gfxPoint(0, 0),
438 0 : gfxPoint(0, 0), gfxPoint(0, 0) };
439 0 : nsSVGArcConverter converter(aState.pos, to, radii, aArgs[2],
440 0 : aArgs[3] != 0, aArgs[4] != 0);
441 0 : while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3])) {
442 0 : dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier);
443 0 : bez[0] = bez[3];
444 : }
445 0 : aState.length += dist;
446 0 : aState.cp1 = aState.cp2 = to;
447 : }
448 0 : aState.pos = to;
449 0 : }
450 :
451 :
452 : typedef void (*TraverseFunc)(const float*, SVGPathTraversalState&);
453 :
454 : static TraverseFunc gTraverseFuncTable[NS_SVG_PATH_SEG_TYPE_COUNT] = {
455 : nsnull, // 0 == PATHSEG_UNKNOWN
456 : TraverseClosePath,
457 : TraverseMovetoAbs,
458 : TraverseMovetoRel,
459 : TraverseLinetoAbs,
460 : TraverseLinetoRel,
461 : TraverseCurvetoCubicAbs,
462 : TraverseCurvetoCubicRel,
463 : TraverseCurvetoQuadraticAbs,
464 : TraverseCurvetoQuadraticRel,
465 : TraverseArcAbs,
466 : TraverseArcRel,
467 : TraverseLinetoHorizontalAbs,
468 : TraverseLinetoHorizontalRel,
469 : TraverseLinetoVerticalAbs,
470 : TraverseLinetoVerticalRel,
471 : TraverseCurvetoCubicSmoothAbs,
472 : TraverseCurvetoCubicSmoothRel,
473 : TraverseCurvetoQuadraticSmoothAbs,
474 : TraverseCurvetoQuadraticSmoothRel
475 : };
476 :
477 : /* static */ void
478 0 : SVGPathSegUtils::TraversePathSegment(const float* aData,
479 : SVGPathTraversalState& aState)
480 : {
481 : PR_STATIC_ASSERT(NS_ARRAY_LENGTH(gTraverseFuncTable) ==
482 : NS_SVG_PATH_SEG_TYPE_COUNT);
483 0 : PRUint32 type = DecodeType(aData[0]);
484 0 : gTraverseFuncTable[type](aData + 1, aState);
485 0 : }
|