1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsSVGPathDataParser.h"
8 #include "mozilla/gfx/Point.h"
9 #include "nsSVGDataParser.h"
10 #include "SVGContentUtils.h"
11 #include "SVGPathData.h"
12 #include "SVGPathSegUtils.h"
14 using namespace mozilla
;
15 using namespace mozilla::gfx
;
17 static inline char16_t
ToUpper(char16_t aCh
)
19 return aCh
>= 'a' && aCh
<= 'z' ? aCh
- 'a' + 'A' : aCh
;
23 nsSVGPathDataParser::Parse()
25 mPathSegList
->Clear();
29 //----------------------------------------------------------------------
32 nsSVGPathDataParser::ParseCoordPair(float& aX
, float& aY
)
34 return SVGContentUtils::ParseNumber(mIter
, mEnd
, aX
) &&
36 SVGContentUtils::ParseNumber(mIter
, mEnd
, aY
);
40 nsSVGPathDataParser::ParseFlag(bool& aFlag
)
42 if (mIter
== mEnd
|| (*mIter
!= '0' && *mIter
!= '1')) {
45 aFlag
= (*mIter
== '1');
51 //----------------------------------------------------------------------
54 nsSVGPathDataParser::ParsePath()
57 if (!ParseSubPath()) {
65 //----------------------------------------------------------------------
68 nsSVGPathDataParser::ParseSubPath()
70 return ParseMoveto() && ParseSubPathElements();
74 nsSVGPathDataParser::ParseSubPathElements()
76 while (SkipWsp() && !IsStartOfSubPath()) {
77 char16_t commandType
= ToUpper(*mIter
);
79 // Upper case commands have absolute co-ordinates,
80 // lower case commands have relative co-ordinates.
81 bool absCoords
= commandType
== *mIter
;
86 if (!ParseSubPathElement(commandType
, absCoords
)) {
94 nsSVGPathDataParser::ParseSubPathElement(char16_t aCommandType
,
97 switch (aCommandType
) {
99 return ParseClosePath();
101 return ParseLineto(aAbsCoords
);
103 return ParseHorizontalLineto(aAbsCoords
);
105 return ParseVerticalLineto(aAbsCoords
);
107 return ParseCurveto(aAbsCoords
);
109 return ParseSmoothCurveto(aAbsCoords
);
111 return ParseQuadBezierCurveto(aAbsCoords
);
113 return ParseSmoothQuadBezierCurveto(aAbsCoords
);
115 return ParseEllipticalArc(aAbsCoords
);
121 nsSVGPathDataParser::IsStartOfSubPath() const
123 return *mIter
== 'm' || *mIter
== 'M';
126 //----------------------------------------------------------------------
129 nsSVGPathDataParser::ParseMoveto()
131 if (!IsStartOfSubPath()) {
135 bool absCoords
= (*mIter
== 'M');
141 if (!ParseCoordPair(x
, y
)) {
145 if (NS_FAILED(mPathSegList
->AppendSeg(
146 absCoords
? PATHSEG_MOVETO_ABS
: PATHSEG_MOVETO_REL
,
151 if (!SkipWsp() || IsAlpha(*mIter
)) {
152 // End of data, or start of a new command
158 // Per SVG 1.1 Section 8.3.2
159 // If a moveto is followed by multiple pairs of coordinates,
160 // the subsequent pairs are treated as implicit lineto commands
161 return ParseLineto(absCoords
);
164 //----------------------------------------------------------------------
167 nsSVGPathDataParser::ParseClosePath()
169 return NS_SUCCEEDED(mPathSegList
->AppendSeg(PATHSEG_CLOSEPATH
));
172 //----------------------------------------------------------------------
175 nsSVGPathDataParser::ParseLineto(bool aAbsCoords
)
179 if (!ParseCoordPair(x
, y
)) {
183 if (NS_FAILED(mPathSegList
->AppendSeg(
184 aAbsCoords
? PATHSEG_LINETO_ABS
: PATHSEG_LINETO_REL
,
189 if (!SkipWsp() || IsAlpha(*mIter
)) {
190 // End of data, or start of a new command
197 //----------------------------------------------------------------------
200 nsSVGPathDataParser::ParseHorizontalLineto(bool aAbsCoords
)
204 if (!SVGContentUtils::ParseNumber(mIter
, mEnd
, x
)) {
208 if (NS_FAILED(mPathSegList
->AppendSeg(
209 aAbsCoords
? PATHSEG_LINETO_HORIZONTAL_ABS
: PATHSEG_LINETO_HORIZONTAL_REL
,
214 if (!SkipWsp() || IsAlpha(*mIter
)) {
215 // End of data, or start of a new command
222 //----------------------------------------------------------------------
225 nsSVGPathDataParser::ParseVerticalLineto(bool aAbsCoords
)
229 if (!SVGContentUtils::ParseNumber(mIter
, mEnd
, y
)) {
233 if (NS_FAILED(mPathSegList
->AppendSeg(
234 aAbsCoords
? PATHSEG_LINETO_VERTICAL_ABS
: PATHSEG_LINETO_VERTICAL_REL
,
239 if (!SkipWsp() || IsAlpha(*mIter
)) {
240 // End of data, or start of a new command
247 //----------------------------------------------------------------------
250 nsSVGPathDataParser::ParseCurveto(bool aAbsCoords
)
253 float x1
, y1
, x2
, y2
, x
, y
;
255 if (!(ParseCoordPair(x1
, y1
) &&
257 ParseCoordPair(x2
, y2
) &&
259 ParseCoordPair(x
, y
))) {
263 if (NS_FAILED(mPathSegList
->AppendSeg(
264 aAbsCoords
? PATHSEG_CURVETO_CUBIC_ABS
: PATHSEG_CURVETO_CUBIC_REL
,
265 x1
, y1
, x2
, y2
, x
, y
))) {
269 if (!SkipWsp() || IsAlpha(*mIter
)) {
270 // End of data, or start of a new command
277 //----------------------------------------------------------------------
280 nsSVGPathDataParser::ParseSmoothCurveto(bool aAbsCoords
)
284 if (!(ParseCoordPair(x2
, y2
) &&
286 ParseCoordPair(x
, y
))) {
290 if (NS_FAILED(mPathSegList
->AppendSeg(
291 aAbsCoords
? PATHSEG_CURVETO_CUBIC_SMOOTH_ABS
: PATHSEG_CURVETO_CUBIC_SMOOTH_REL
,
296 if (!SkipWsp() || IsAlpha(*mIter
)) {
297 // End of data, or start of a new command
304 //----------------------------------------------------------------------
307 nsSVGPathDataParser::ParseQuadBezierCurveto(bool aAbsCoords
)
311 if (!(ParseCoordPair(x1
, y1
) &&
313 ParseCoordPair(x
, y
))) {
317 if (NS_FAILED(mPathSegList
->AppendSeg(
318 aAbsCoords
? PATHSEG_CURVETO_QUADRATIC_ABS
: PATHSEG_CURVETO_QUADRATIC_REL
,
323 if (!SkipWsp() || IsAlpha(*mIter
)) {
324 // Start of a new command
331 //----------------------------------------------------------------------
334 nsSVGPathDataParser::ParseSmoothQuadBezierCurveto(bool aAbsCoords
)
338 if (!ParseCoordPair(x
, y
)) {
342 if (NS_FAILED(mPathSegList
->AppendSeg(
343 aAbsCoords
? PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS
: PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL
,
348 if (!SkipWsp() || IsAlpha(*mIter
)) {
349 // End of data, or start of a new command
356 //----------------------------------------------------------------------
359 nsSVGPathDataParser::ParseEllipticalArc(bool aAbsCoords
)
362 float r1
, r2
, angle
, x
, y
;
363 bool largeArcFlag
, sweepFlag
;
365 if (!(SVGContentUtils::ParseNumber(mIter
, mEnd
, r1
) &&
367 SVGContentUtils::ParseNumber(mIter
, mEnd
, r2
) &&
369 SVGContentUtils::ParseNumber(mIter
, mEnd
, angle
)&&
371 ParseFlag(largeArcFlag
) &&
373 ParseFlag(sweepFlag
) &&
375 ParseCoordPair(x
, y
))) {
379 // We can only pass floats after 'type', and per the SVG spec for arc,
380 // non-zero args are treated at 'true'.
381 if (NS_FAILED(mPathSegList
->AppendSeg(
382 aAbsCoords
? PATHSEG_ARC_ABS
: PATHSEG_ARC_REL
,
384 largeArcFlag
? 1.0f
: 0.0f
,
385 sweepFlag
? 1.0f
: 0.0f
,
390 if (!SkipWsp() || IsAlpha(*mIter
)) {
391 // End of data, or start of a new command
398 //-----------------------------------------------------------------------
404 CalcVectorAngle(double ux
, double uy
, double vx
, double vy
)
406 double ta
= atan2(uy
, ux
);
407 double tb
= atan2(vy
, vx
);
410 return 2 * M_PI
- (ta
-tb
);
414 nsSVGArcConverter::nsSVGArcConverter(const Point
& from
,
421 const double radPerDeg
= M_PI
/180.0;
429 // Convert to center parameterization as shown in
430 // http://www.w3.org/TR/SVG/implnote.html
434 mSinPhi
= sin(angle
*radPerDeg
);
435 mCosPhi
= cos(angle
*radPerDeg
);
437 double x1dash
= mCosPhi
* (from
.x
-to
.x
)/2.0 + mSinPhi
* (from
.y
-to
.y
)/2.0;
438 double y1dash
= -mSinPhi
* (from
.x
-to
.x
)/2.0 + mCosPhi
* (from
.y
-to
.y
)/2.0;
441 double numerator
= mRx
*mRx
*mRy
*mRy
- mRx
*mRx
*y1dash
*y1dash
-
442 mRy
*mRy
*x1dash
*x1dash
;
444 if (numerator
< 0.0) {
445 // If mRx , mRy and are such that there is no solution (basically,
446 // the ellipse is not big enough to reach from 'from' to 'to'
447 // then the ellipse is scaled up uniformly until there is
448 // exactly one solution (until the ellipse is just big enough).
450 // -> find factor s, such that numerator' with mRx'=s*mRx and
451 // mRy'=s*mRy becomes 0 :
452 double s
= sqrt(1.0 - numerator
/(mRx
*mRx
*mRy
*mRy
));
460 root
= (largeArcFlag
== sweepFlag
? -1.0 : 1.0) *
461 sqrt( numerator
/(mRx
*mRx
*y1dash
*y1dash
+ mRy
*mRy
*x1dash
*x1dash
) );
464 double cxdash
= root
*mRx
*y1dash
/mRy
;
465 double cydash
= -root
*mRy
*x1dash
/mRx
;
467 mC
.x
= mCosPhi
* cxdash
- mSinPhi
* cydash
+ (from
.x
+to
.x
)/2.0;
468 mC
.y
= mSinPhi
* cxdash
+ mCosPhi
* cydash
+ (from
.y
+to
.y
)/2.0;
469 mTheta
= CalcVectorAngle(1.0, 0.0, (x1dash
-cxdash
)/mRx
, (y1dash
-cydash
)/mRy
);
470 double dtheta
= CalcVectorAngle((x1dash
-cxdash
)/mRx
, (y1dash
-cydash
)/mRy
,
471 (-x1dash
-cxdash
)/mRx
, (-y1dash
-cydash
)/mRy
);
472 if (!sweepFlag
&& dtheta
>0)
474 else if (sweepFlag
&& dtheta
<0)
477 // Convert into cubic bezier segments <= 90deg
478 mNumSegs
= static_cast<int>(ceil(fabs(dtheta
/(M_PI
/2.0))));
479 mDelta
= dtheta
/mNumSegs
;
480 mT
= 8.0/3.0 * sin(mDelta
/4.0) * sin(mDelta
/4.0) / sin(mDelta
/2.0);
486 nsSVGArcConverter::GetNextSegment(Point
* cp1
, Point
* cp2
, Point
* to
)
488 if (mSegIndex
== mNumSegs
) {
492 double cosTheta1
= cos(mTheta
);
493 double sinTheta1
= sin(mTheta
);
494 double theta2
= mTheta
+ mDelta
;
495 double cosTheta2
= cos(theta2
);
496 double sinTheta2
= sin(theta2
);
498 // a) calculate endpoint of the segment:
499 to
->x
= mCosPhi
* mRx
*cosTheta2
- mSinPhi
* mRy
*sinTheta2
+ mC
.x
;
500 to
->y
= mSinPhi
* mRx
*cosTheta2
+ mCosPhi
* mRy
*sinTheta2
+ mC
.y
;
502 // b) calculate gradients at start/end points of segment:
503 cp1
->x
= mFrom
.x
+ mT
* ( - mCosPhi
* mRx
*sinTheta1
- mSinPhi
* mRy
*cosTheta1
);
504 cp1
->y
= mFrom
.y
+ mT
* ( - mSinPhi
* mRx
*sinTheta1
+ mCosPhi
* mRy
*cosTheta1
);
506 cp2
->x
= to
->x
+ mT
* ( mCosPhi
* mRx
*sinTheta2
+ mSinPhi
* mRy
*cosTheta2
);
507 cp2
->y
= to
->y
+ mT
* ( mSinPhi
* mRx
*sinTheta2
- mCosPhi
* mRy
*cosTheta2
);