Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/svg/nsSVGPathDataParser.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 "nsSVGPathDataParser.h"
8
9
#include "mozilla/gfx/Point.h"
10
#include "nsSVGDataParser.h"
11
#include "SVGContentUtils.h"
12
#include "SVGPathData.h"
13
#include "SVGPathSegUtils.h"
14
15
using namespace mozilla;
16
using namespace mozilla::dom::SVGPathSeg_Binding;
17
using namespace mozilla::gfx;
18
19
static inline char16_t ToUpper(char16_t aCh)
20
0
{
21
0
  return aCh >= 'a' && aCh <= 'z' ? aCh - 'a' + 'A' : aCh;
22
0
}
23
24
bool
25
nsSVGPathDataParser::Parse()
26
0
{
27
0
  mPathSegList->Clear();
28
0
  return ParsePath();
29
0
}
30
31
//----------------------------------------------------------------------
32
33
bool
34
nsSVGPathDataParser::ParseCoordPair(float& aX, float& aY)
35
0
{
36
0
  return SVGContentUtils::ParseNumber(mIter, mEnd, aX) &&
37
0
         SkipCommaWsp() &&
38
0
         SVGContentUtils::ParseNumber(mIter, mEnd, aY);
39
0
}
40
41
bool
42
nsSVGPathDataParser::ParseFlag(bool& aFlag)
43
0
{
44
0
  if (mIter == mEnd || (*mIter != '0' && *mIter != '1')) {
45
0
    return false;
46
0
  }
47
0
  aFlag = (*mIter == '1');
48
0
49
0
  ++mIter;
50
0
  return true;
51
0
}
52
53
//----------------------------------------------------------------------
54
55
bool
56
nsSVGPathDataParser::ParsePath()
57
0
{
58
0
  while (SkipWsp()) {
59
0
    if (!ParseSubPath()) {
60
0
      return false;
61
0
    }
62
0
  }
63
0
64
0
  return true;
65
0
}
66
67
//----------------------------------------------------------------------
68
69
bool
70
nsSVGPathDataParser::ParseSubPath()
71
0
{
72
0
  return ParseMoveto() && ParseSubPathElements();
73
0
}
74
75
bool
76
nsSVGPathDataParser::ParseSubPathElements()
77
0
{
78
0
  while (SkipWsp() && !IsStartOfSubPath()) {
79
0
    char16_t commandType = ToUpper(*mIter);
80
0
81
0
    // Upper case commands have absolute co-ordinates,
82
0
    // lower case commands have relative co-ordinates.
83
0
    bool absCoords = commandType == *mIter;
84
0
85
0
    ++mIter;
86
0
    SkipWsp();
87
0
88
0
    if (!ParseSubPathElement(commandType, absCoords)) {
89
0
      return false;
90
0
    }
91
0
  }
92
0
  return true;
93
0
}
94
95
bool
96
nsSVGPathDataParser::ParseSubPathElement(char16_t aCommandType,
97
                                         bool aAbsCoords)
98
0
{
99
0
  switch (aCommandType) {
100
0
    case 'Z':
101
0
      return ParseClosePath();
102
0
    case 'L':
103
0
      return ParseLineto(aAbsCoords);
104
0
    case 'H':
105
0
      return ParseHorizontalLineto(aAbsCoords);
106
0
    case 'V':
107
0
      return ParseVerticalLineto(aAbsCoords);
108
0
    case 'C':
109
0
      return ParseCurveto(aAbsCoords);
110
0
    case 'S':
111
0
      return ParseSmoothCurveto(aAbsCoords);
112
0
    case 'Q':
113
0
      return ParseQuadBezierCurveto(aAbsCoords);
114
0
    case 'T':
115
0
      return ParseSmoothQuadBezierCurveto(aAbsCoords);
116
0
    case 'A':
117
0
      return ParseEllipticalArc(aAbsCoords);
118
0
  }
119
0
  return false;
120
0
}
121
122
bool
123
nsSVGPathDataParser::IsStartOfSubPath() const
124
0
{
125
0
  return *mIter == 'm' || *mIter == 'M';
126
0
}
127
128
//----------------------------------------------------------------------
129
130
bool
131
nsSVGPathDataParser::ParseMoveto()
132
0
{
133
0
  if (!IsStartOfSubPath()) {
134
0
    return false;
135
0
  }
136
0
137
0
  bool absCoords = (*mIter == 'M');
138
0
139
0
  ++mIter;
140
0
  SkipWsp();
141
0
142
0
  float x, y;
143
0
  if (!ParseCoordPair(x, y)) {
144
0
    return false;
145
0
  }
146
0
147
0
  if (NS_FAILED(mPathSegList->AppendSeg(
148
0
                  absCoords ? PATHSEG_MOVETO_ABS : PATHSEG_MOVETO_REL,
149
0
                  x, y))) {
150
0
    return false;
151
0
  }
152
0
153
0
  if (!SkipWsp() || IsAlpha(*mIter)) {
154
0
    // End of data, or start of a new command
155
0
    return true;
156
0
  }
157
0
158
0
  SkipCommaWsp();
159
0
160
0
  // Per SVG 1.1 Section 8.3.2
161
0
  // If a moveto is followed by multiple pairs of coordinates,
162
0
  // the subsequent pairs are treated as implicit lineto commands
163
0
  return ParseLineto(absCoords);
164
0
}
165
166
//----------------------------------------------------------------------
167
168
bool
169
nsSVGPathDataParser::ParseClosePath()
170
0
{
171
0
  return NS_SUCCEEDED(mPathSegList->AppendSeg(PATHSEG_CLOSEPATH));
172
0
}
173
174
//----------------------------------------------------------------------
175
176
bool
177
nsSVGPathDataParser::ParseLineto(bool aAbsCoords)
178
0
{
179
0
  while (true) {
180
0
    float x, y;
181
0
    if (!ParseCoordPair(x, y)) {
182
0
      return false;
183
0
    }
184
0
185
0
    if (NS_FAILED(mPathSegList->AppendSeg(
186
0
                   aAbsCoords ? PATHSEG_LINETO_ABS : PATHSEG_LINETO_REL,
187
0
                   x, y))) {
188
0
      return false;
189
0
    }
190
0
191
0
    if (!SkipWsp() || IsAlpha(*mIter)) {
192
0
      // End of data, or start of a new command
193
0
      return true;
194
0
    }
195
0
    SkipCommaWsp();
196
0
  }
197
0
}
198
199
//----------------------------------------------------------------------
200
201
bool
202
nsSVGPathDataParser::ParseHorizontalLineto(bool aAbsCoords)
203
0
{
204
0
  while (true) {
205
0
    float x;
206
0
    if (!SVGContentUtils::ParseNumber(mIter, mEnd, x)) {
207
0
      return false;
208
0
    }
209
0
210
0
    if (NS_FAILED(mPathSegList->AppendSeg(
211
0
                    aAbsCoords ? PATHSEG_LINETO_HORIZONTAL_ABS : PATHSEG_LINETO_HORIZONTAL_REL,
212
0
                    x))) {
213
0
      return false;
214
0
    }
215
0
216
0
    if (!SkipWsp() || IsAlpha(*mIter)) {
217
0
      // End of data, or start of a new command
218
0
      return true;
219
0
    }
220
0
    SkipCommaWsp();
221
0
  }
222
0
}
223
224
//----------------------------------------------------------------------
225
226
bool
227
nsSVGPathDataParser::ParseVerticalLineto(bool aAbsCoords)
228
0
{
229
0
  while (true) {
230
0
    float y;
231
0
    if (!SVGContentUtils::ParseNumber(mIter, mEnd, y)) {
232
0
      return false;
233
0
    }
234
0
235
0
    if (NS_FAILED(mPathSegList->AppendSeg(
236
0
                    aAbsCoords ? PATHSEG_LINETO_VERTICAL_ABS : PATHSEG_LINETO_VERTICAL_REL,
237
0
                    y))) {
238
0
      return false;
239
0
    }
240
0
241
0
    if (!SkipWsp() || IsAlpha(*mIter)) {
242
0
      // End of data, or start of a new command
243
0
      return true;
244
0
    }
245
0
    SkipCommaWsp();
246
0
  }
247
0
}
248
249
//----------------------------------------------------------------------
250
251
bool
252
nsSVGPathDataParser::ParseCurveto(bool aAbsCoords)
253
0
{
254
0
  while (true) {
255
0
    float x1, y1, x2, y2, x, y;
256
0
257
0
    if (!(ParseCoordPair(x1, y1) &&
258
0
          SkipCommaWsp() &&
259
0
          ParseCoordPair(x2, y2) &&
260
0
          SkipCommaWsp() &&
261
0
          ParseCoordPair(x, y))) {
262
0
      return false;
263
0
    }
264
0
265
0
    if (NS_FAILED(mPathSegList->AppendSeg(
266
0
                    aAbsCoords ? PATHSEG_CURVETO_CUBIC_ABS : PATHSEG_CURVETO_CUBIC_REL,
267
0
                    x1, y1, x2, y2, x, y))) {
268
0
      return false;
269
0
    }
270
0
271
0
    if (!SkipWsp() || IsAlpha(*mIter)) {
272
0
      // End of data, or start of a new command
273
0
      return true;
274
0
    }
275
0
    SkipCommaWsp();
276
0
  }
277
0
}
278
279
//----------------------------------------------------------------------
280
281
bool
282
nsSVGPathDataParser::ParseSmoothCurveto(bool aAbsCoords)
283
0
{
284
0
  while (true) {
285
0
    float x2, y2, x, y;
286
0
    if (!(ParseCoordPair(x2, y2) &&
287
0
          SkipCommaWsp() &&
288
0
          ParseCoordPair(x, y))) {
289
0
      return false;
290
0
    }
291
0
292
0
    if (NS_FAILED(mPathSegList->AppendSeg(
293
0
                    aAbsCoords ? PATHSEG_CURVETO_CUBIC_SMOOTH_ABS : PATHSEG_CURVETO_CUBIC_SMOOTH_REL,
294
0
                    x2, y2, x, y))) {
295
0
      return false;
296
0
    }
297
0
298
0
    if (!SkipWsp() || IsAlpha(*mIter)) {
299
0
      // End of data, or start of a new command
300
0
      return true;
301
0
    }
302
0
    SkipCommaWsp();
303
0
  }
304
0
}
305
306
//----------------------------------------------------------------------
307
308
bool
309
nsSVGPathDataParser::ParseQuadBezierCurveto(bool aAbsCoords)
310
0
{
311
0
  while (true) {
312
0
    float x1, y1, x, y;
313
0
    if (!(ParseCoordPair(x1, y1) &&
314
0
         SkipCommaWsp() &&
315
0
         ParseCoordPair(x, y))) {
316
0
      return false;
317
0
    }
318
0
319
0
    if (NS_FAILED(mPathSegList->AppendSeg(
320
0
                    aAbsCoords ? PATHSEG_CURVETO_QUADRATIC_ABS : PATHSEG_CURVETO_QUADRATIC_REL,
321
0
                    x1, y1, x, y))) {
322
0
      return false;
323
0
    }
324
0
325
0
    if (!SkipWsp() || IsAlpha(*mIter)) {
326
0
      // Start of a new command
327
0
      return true;
328
0
    }
329
0
    SkipCommaWsp();
330
0
  }
331
0
}
332
333
//----------------------------------------------------------------------
334
335
bool
336
nsSVGPathDataParser::ParseSmoothQuadBezierCurveto(bool aAbsCoords)
337
0
{
338
0
  while (true) {
339
0
    float x, y;
340
0
    if (!ParseCoordPair(x, y)) {
341
0
      return false;
342
0
    }
343
0
344
0
    if (NS_FAILED(mPathSegList->AppendSeg(
345
0
                    aAbsCoords ? PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS : PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL,
346
0
                    x, y))) {
347
0
      return false;
348
0
    }
349
0
350
0
    if (!SkipWsp() || IsAlpha(*mIter)) {
351
0
      // End of data, or start of a new command
352
0
      return true;
353
0
    }
354
0
    SkipCommaWsp();
355
0
  }
356
0
}
357
358
//----------------------------------------------------------------------
359
360
bool
361
nsSVGPathDataParser::ParseEllipticalArc(bool aAbsCoords)
362
0
{
363
0
  while (true) {
364
0
    float r1, r2, angle, x, y;
365
0
    bool largeArcFlag, sweepFlag;
366
0
367
0
    if (!(SVGContentUtils::ParseNumber(mIter, mEnd, r1) &&
368
0
          SkipCommaWsp() &&
369
0
          SVGContentUtils::ParseNumber(mIter, mEnd, r2) &&
370
0
          SkipCommaWsp() &&
371
0
          SVGContentUtils::ParseNumber(mIter, mEnd, angle)&&
372
0
          SkipCommaWsp() &&
373
0
          ParseFlag(largeArcFlag) &&
374
0
          SkipCommaWsp() &&
375
0
          ParseFlag(sweepFlag) &&
376
0
          SkipCommaWsp() &&
377
0
          ParseCoordPair(x, y))) {
378
0
      return false;
379
0
    }
380
0
381
0
    // We can only pass floats after 'type', and per the SVG spec for arc,
382
0
    // non-zero args are treated at 'true'.
383
0
    if (NS_FAILED(mPathSegList->AppendSeg(
384
0
                    aAbsCoords ? PATHSEG_ARC_ABS : PATHSEG_ARC_REL,
385
0
                    r1, r2, angle,
386
0
                    largeArcFlag ? 1.0f : 0.0f,
387
0
                    sweepFlag ? 1.0f : 0.0f,
388
0
                    x, y))) {
389
0
      return false;
390
0
    }
391
0
392
0
    if (!SkipWsp() || IsAlpha(*mIter)) {
393
0
      // End of data, or start of a new command
394
0
      return true;
395
0
    }
396
0
    SkipCommaWsp();
397
0
  }
398
0
}
399
400
//-----------------------------------------------------------------------
401
402
403
404
405
static double
406
CalcVectorAngle(double ux, double uy, double vx, double vy)
407
0
{
408
0
  double ta = atan2(uy, ux);
409
0
  double tb = atan2(vy, vx);
410
0
  if (tb >= ta)
411
0
    return tb-ta;
412
0
  return 2 * M_PI - (ta-tb);
413
0
}
414
415
416
nsSVGArcConverter::nsSVGArcConverter(const Point& from,
417
                                     const Point& to,
418
                                     const Point& radii,
419
                                     double angle,
420
                                     bool largeArcFlag,
421
                                     bool sweepFlag)
422
0
{
423
0
  MOZ_ASSERT(radii.x != 0.0f && radii.y != 0.0f, "Bad radii");
424
0
425
0
  const double radPerDeg = M_PI/180.0;
426
0
  mSegIndex = 0;
427
0
428
0
  if (from == to) {
429
0
    mNumSegs = 0;
430
0
    return;
431
0
  }
432
0
433
0
  // Convert to center parameterization as shown in
434
0
  // http://www.w3.org/TR/SVG/implnote.html
435
0
  mRx = fabs(radii.x);
436
0
  mRy = fabs(radii.y);
437
0
438
0
  mSinPhi = sin(angle*radPerDeg);
439
0
  mCosPhi = cos(angle*radPerDeg);
440
0
441
0
  double x1dash =  mCosPhi * (from.x-to.x)/2.0 + mSinPhi * (from.y-to.y)/2.0;
442
0
  double y1dash = -mSinPhi * (from.x-to.x)/2.0 + mCosPhi * (from.y-to.y)/2.0;
443
0
444
0
  double root;
445
0
  double numerator = mRx*mRx*mRy*mRy - mRx*mRx*y1dash*y1dash -
446
0
                     mRy*mRy*x1dash*x1dash;
447
0
448
0
  if (numerator < 0.0) {
449
0
    //  If mRx , mRy and are such that there is no solution (basically,
450
0
    //  the ellipse is not big enough to reach from 'from' to 'to'
451
0
    //  then the ellipse is scaled up uniformly until there is
452
0
    //  exactly one solution (until the ellipse is just big enough).
453
0
454
0
    // -> find factor s, such that numerator' with mRx'=s*mRx and
455
0
    //    mRy'=s*mRy becomes 0 :
456
0
    double s = sqrt(1.0 - numerator/(mRx*mRx*mRy*mRy));
457
0
458
0
    mRx *= s;
459
0
    mRy *= s;
460
0
    root = 0.0;
461
0
462
0
  }
463
0
  else {
464
0
    root = (largeArcFlag == sweepFlag ? -1.0 : 1.0) *
465
0
      sqrt( numerator/(mRx*mRx*y1dash*y1dash + mRy*mRy*x1dash*x1dash) );
466
0
  }
467
0
468
0
  double cxdash = root*mRx*y1dash/mRy;
469
0
  double cydash = -root*mRy*x1dash/mRx;
470
0
471
0
  mC.x = mCosPhi * cxdash - mSinPhi * cydash + (from.x+to.x)/2.0;
472
0
  mC.y = mSinPhi * cxdash + mCosPhi * cydash + (from.y+to.y)/2.0;
473
0
  mTheta = CalcVectorAngle(1.0, 0.0, (x1dash-cxdash)/mRx, (y1dash-cydash)/mRy);
474
0
  double dtheta = CalcVectorAngle((x1dash-cxdash)/mRx, (y1dash-cydash)/mRy,
475
0
                                  (-x1dash-cxdash)/mRx, (-y1dash-cydash)/mRy);
476
0
  if (!sweepFlag && dtheta>0)
477
0
    dtheta -= 2.0*M_PI;
478
0
  else if (sweepFlag && dtheta<0)
479
0
    dtheta += 2.0*M_PI;
480
0
481
0
  // Convert into cubic bezier segments <= 90deg
482
0
  mNumSegs = static_cast<int>(ceil(fabs(dtheta/(M_PI/2.0))));
483
0
  mDelta = dtheta/mNumSegs;
484
0
  mT = 8.0/3.0 * sin(mDelta/4.0) * sin(mDelta/4.0) / sin(mDelta/2.0);
485
0
486
0
  mFrom = from;
487
0
}
488
489
bool
490
nsSVGArcConverter::GetNextSegment(Point* cp1, Point* cp2, Point* to)
491
0
{
492
0
  if (mSegIndex == mNumSegs) {
493
0
    return false;
494
0
  }
495
0
496
0
  double cosTheta1 = cos(mTheta);
497
0
  double sinTheta1 = sin(mTheta);
498
0
  double theta2 = mTheta + mDelta;
499
0
  double cosTheta2 = cos(theta2);
500
0
  double sinTheta2 = sin(theta2);
501
0
502
0
  // a) calculate endpoint of the segment:
503
0
  to->x = mCosPhi * mRx*cosTheta2 - mSinPhi * mRy*sinTheta2 + mC.x;
504
0
  to->y = mSinPhi * mRx*cosTheta2 + mCosPhi * mRy*sinTheta2 + mC.y;
505
0
506
0
  // b) calculate gradients at start/end points of segment:
507
0
  cp1->x = mFrom.x + mT * ( - mCosPhi * mRx*sinTheta1 - mSinPhi * mRy*cosTheta1);
508
0
  cp1->y = mFrom.y + mT * ( - mSinPhi * mRx*sinTheta1 + mCosPhi * mRy*cosTheta1);
509
0
510
0
  cp2->x = to->x + mT * ( mCosPhi * mRx*sinTheta2 + mSinPhi * mRy*cosTheta2);
511
0
  cp2->y = to->y + mT * ( mSinPhi * mRx*sinTheta2 - mCosPhi * mRy*cosTheta2);
512
0
513
0
  // do next segment
514
0
  mTheta = theta2;
515
0
  mFrom = *to;
516
0
  ++mSegIndex;
517
0
518
0
  return true;
519
0
}