Coverage Report

Created: 2018-09-25 14:53

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