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