nsSVGPathDataParser.cpp 13 KB

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