SVGKPointsAndPathsParser.m 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907
  1. #import "SVGKPointsAndPathsParser.h"
  2. #import "NSCharacterSet+SVGKExtensions.h"
  3. static inline SVGCurve SVGCurveMakePoint(CGPoint p)
  4. {
  5. SVGCurve curve;
  6. curve.type = SVGCurveTypePoint;
  7. curve.c1 = p;
  8. curve.c2 = p;
  9. curve.p = p;
  10. return curve;
  11. }
  12. static inline CGPoint SVGCurveReflectedControlPoint(SVGCurve prevCurve)
  13. {
  14. return CGPointMake(prevCurve.p.x+(prevCurve.p.x-prevCurve.c2.x), prevCurve.p.y+(prevCurve.p.y-prevCurve.c2.y));
  15. }
  16. @implementation SVGKPointsAndPathsParser
  17. + (SVGCurve) startingCurve
  18. {
  19. return SVGCurveMakePoint(CGPointZero);
  20. }
  21. /* references
  22. http://www.w3.org/TR/2011/REC-SVG11-20110816/paths.html#PathDataBNF
  23. http://www.w3.org/TR/2011/REC-SVG11-20110816/shapes.html#PointsBNF
  24. */
  25. /*
  26. http://www.w3.org/TR/2011/REC-SVG11-20110816/paths.html#PathDataBNF
  27. svg-path:
  28. wsp* moveto-drawto-command-groups? wsp*
  29. moveto-drawto-command-groups:
  30. moveto-drawto-command-group
  31. | moveto-drawto-command-group wsp* moveto-drawto-command-groups
  32. moveto-drawto-command-group:
  33. moveto wsp* drawto-commands?
  34. drawto-commands:
  35. drawto-command
  36. | drawto-command wsp* drawto-commands
  37. drawto-command:
  38. closepath
  39. | lineto
  40. | horizontal-lineto
  41. | vertical-lineto
  42. | curveto
  43. | smooth-curveto
  44. | quadratic-bezier-curveto
  45. | smooth-quadratic-bezier-curveto
  46. | elliptical-arc
  47. moveto:
  48. ( "M" | "m" ) wsp* moveto-argument-sequence
  49. moveto-argument-sequence:
  50. coordinate-pair
  51. | coordinate-pair comma-wsp? lineto-argument-sequence
  52. closepath:
  53. ("Z" | "z")
  54. lineto:
  55. ( "L" | "l" ) wsp* lineto-argument-sequence
  56. lineto-argument-sequence:
  57. coordinate-pair
  58. | coordinate-pair comma-wsp? lineto-argument-sequence
  59. horizontal-lineto:
  60. ( "H" | "h" ) wsp* horizontal-lineto-argument-sequence
  61. horizontal-lineto-argument-sequence:
  62. coordinate
  63. | coordinate comma-wsp? horizontal-lineto-argument-sequence
  64. vertical-lineto:
  65. ( "V" | "v" ) wsp* vertical-lineto-argument-sequence
  66. vertical-lineto-argument-sequence:
  67. coordinate
  68. | coordinate comma-wsp? vertical-lineto-argument-sequence
  69. curveto:
  70. ( "C" | "c" ) wsp* curveto-argument-sequence
  71. curveto-argument-sequence:
  72. curveto-argument
  73. | curveto-argument comma-wsp? curveto-argument-sequence
  74. curveto-argument:
  75. coordinate-pair comma-wsp? coordinate-pair comma-wsp? coordinate-pair
  76. smooth-curveto:
  77. ( "S" | "s" ) wsp* smooth-curveto-argument-sequence
  78. smooth-curveto-argument-sequence:
  79. smooth-curveto-argument
  80. | smooth-curveto-argument comma-wsp? smooth-curveto-argument-sequence
  81. smooth-curveto-argument:
  82. coordinate-pair comma-wsp? coordinate-pair
  83. quadratic-bezier-curveto:
  84. ( "Q" | "q" ) wsp* quadratic-bezier-curveto-argument-sequence
  85. quadratic-bezier-curveto-argument-sequence:
  86. quadratic-bezier-curveto-argument
  87. | quadratic-bezier-curveto-argument comma-wsp?
  88. quadratic-bezier-curveto-argument-sequence
  89. quadratic-bezier-curveto-argument:
  90. coordinate-pair comma-wsp? coordinate-pair
  91. smooth-quadratic-bezier-curveto:
  92. ( "T" | "t" ) wsp* smooth-quadratic-bezier-curveto-argument-sequence
  93. smooth-quadratic-bezier-curveto-argument-sequence:
  94. coordinate-pair
  95. | coordinate-pair comma-wsp? smooth-quadratic-bezier-curveto-argument-sequence
  96. elliptical-arc:
  97. ( "A" | "a" ) wsp* elliptical-arc-argument-sequence
  98. elliptical-arc-argument-sequence:
  99. elliptical-arc-argument
  100. | elliptical-arc-argument comma-wsp? elliptical-arc-argument-sequence
  101. elliptical-arc-argument:
  102. nonnegative-number comma-wsp? nonnegative-number comma-wsp?
  103. number comma-wsp flag comma-wsp? flag comma-wsp? coordinate-pair
  104. coordinate-pair:
  105. coordinate comma-wsp? coordinate
  106. coordinate:
  107. number
  108. nonnegative-number:
  109. integer-constant
  110. | floating-point-constant
  111. number:
  112. sign? integer-constant
  113. | sign? floating-point-constant
  114. flag:
  115. "0" | "1"
  116. comma-wsp:
  117. (wsp+ comma? wsp*) | (comma wsp*)
  118. comma:
  119. ","
  120. integer-constant:
  121. digit-sequence
  122. floating-point-constant:
  123. fractional-constant exponent?
  124. | digit-sequence exponent
  125. fractional-constant:
  126. digit-sequence? "." digit-sequence
  127. | digit-sequence "."
  128. exponent:
  129. ( "e" | "E" ) sign? digit-sequence
  130. sign:
  131. "+" | "-"
  132. digit-sequence:
  133. digit
  134. | digit digit-sequence
  135. digit:
  136. "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
  137. */
  138. /*
  139. http://www.w3.org/TR/2011/REC-SVG11-20110816/shapes.html#PointsBNF
  140. list-of-points:
  141. wsp* coordinate-pairs? wsp*
  142. coordinate-pairs:
  143. coordinate-pair
  144. | coordinate-pair comma-wsp coordinate-pairs
  145. coordinate-pair:
  146. coordinate comma-wsp coordinate
  147. | coordinate negative-coordinate
  148. coordinate:
  149. number
  150. number:
  151. sign? integer-constant
  152. | sign? floating-point-constant
  153. negative-coordinate:
  154. "-" integer-constant
  155. | "-" floating-point-constant
  156. comma-wsp:
  157. (wsp+ comma? wsp*) | (comma wsp*)
  158. comma:
  159. ","
  160. integer-constant:
  161. digit-sequence
  162. floating-point-constant:
  163. fractional-constant exponent?
  164. | digit-sequence exponent
  165. fractional-constant:
  166. digit-sequence? "." digit-sequence
  167. | digit-sequence "."
  168. exponent:
  169. ( "e" | "E" ) sign? digit-sequence
  170. sign:
  171. "+" | "-"
  172. digit-sequence:
  173. digit
  174. | digit digit-sequence
  175. digit:
  176. "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
  177. */
  178. /**
  179. wsp:
  180. (#x20 | #x9 | #xD | #xA)
  181. */
  182. + (void) readWhitespace:(NSScanner*)scanner
  183. {
  184. /** This log message can be called literally hundreds of thousands of times in a single parse, which defeats
  185. even Cocoa Lumberjack.
  186. Even in "verbose" debugging, that's too much!
  187. Hence: commented-out
  188. SVGKitLogVerbose(@"Apple's implementation of scanCharactersFromSet seems to generate large amounts of temporary objects and can cause a crash here by taking literally megabytes of RAM in temporary internal variables. This is surprising, but I can't see anythign we're doing wrong. Adding this autoreleasepool drops memory usage (inside Apple's methods!) massively, so it seems to be the right thing to do");
  189. */
  190. @autoreleasepool
  191. {
  192. [scanner scanCharactersFromSet:[NSCharacterSet SVGWhitespaceCharacterSet]
  193. intoString:NULL];
  194. }
  195. }
  196. + (void) readCommaAndWhitespace:(NSScanner*)scanner
  197. {
  198. [SVGKPointsAndPathsParser readWhitespace:scanner];
  199. static NSString* comma = @",";
  200. [scanner scanString:comma intoString:NULL];
  201. [SVGKPointsAndPathsParser readWhitespace:scanner];
  202. }
  203. /**
  204. moveto-drawto-command-groups:
  205. moveto-drawto-command-group
  206. | moveto-drawto-command-group wsp* moveto-drawto-command-groups
  207. */
  208. + (SVGCurve) readMovetoDrawtoCommandGroups:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
  209. {
  210. #if VERBOSE_PARSE_SVG_COMMAND_STRINGS
  211. SVGKitLogVerbose(@"Parsing command string: move-to, draw-to command");
  212. #endif
  213. SVGCurve lastCurve = [SVGKPointsAndPathsParser readMovetoDrawto:scanner path:path relativeTo:origin isRelative:isRelative];
  214. [SVGKPointsAndPathsParser readWhitespace:scanner];
  215. while (![scanner isAtEnd])
  216. {
  217. [SVGKPointsAndPathsParser readWhitespace:scanner];
  218. /** FIXME: wasn't originally, but maybe should be:
  219. origin = isRelative ? lastCoord : origin;
  220. */
  221. lastCurve = [SVGKPointsAndPathsParser readMovetoDrawto:scanner path:path relativeTo:origin isRelative:isRelative];
  222. }
  223. return lastCurve;
  224. }
  225. /** moveto-drawto-command-group:
  226. moveto wsp* drawto-commands?
  227. */
  228. + (SVGCurve) readMovetoDrawto:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
  229. {
  230. SVGCurve lastCurve = [SVGKPointsAndPathsParser readMoveto:scanner path:path relativeTo:origin isRelative:isRelative];
  231. [SVGKPointsAndPathsParser readWhitespace:scanner];
  232. return lastCurve;
  233. }
  234. /**
  235. moveto:
  236. ( "M" | "m" ) wsp* moveto-argument-sequence
  237. */
  238. + (SVGCurve) readMoveto:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
  239. {
  240. NSString* cmd = nil;
  241. NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Mm"];
  242. if( ! [scanner scanCharactersFromSet:cmdFormat intoString:&cmd] )
  243. {
  244. NSAssert(FALSE, @"failed to scan move to command");
  245. return SVGCurveMakePoint(origin);
  246. }
  247. [SVGKPointsAndPathsParser readWhitespace:scanner];
  248. return [SVGKPointsAndPathsParser readMovetoArgumentSequence:scanner path:path relativeTo:origin isRelative:isRelative];
  249. }
  250. /** moveto-argument-sequence:
  251. coordinate-pair
  252. | coordinate-pair comma-wsp? lineto-argument-sequence
  253. */
  254. + (SVGCurve) readMovetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
  255. {
  256. CGPoint coord = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
  257. coord.x += origin.x;
  258. coord.y += origin.y;
  259. CGPathMoveToPoint(path, NULL, coord.x, coord.y);
  260. #if DEBUG_PATH_CREATION
  261. SVGKitLogWarn(@"[%@] PATH: MOVED to %2.2f, %2.2f", [SVGKPointsAndPathsParser class], coord.x, coord.y );
  262. #endif
  263. [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
  264. if ([scanner isAtEnd]) {
  265. return SVGCurveMakePoint(coord);
  266. } else {
  267. return [SVGKPointsAndPathsParser readLinetoArgumentSequence:scanner path:path relativeTo:(isRelative)?coord:origin isRelative:isRelative];
  268. }
  269. }
  270. /**
  271. coordinate-pair:
  272. coordinate comma-wsp? coordinate
  273. */
  274. + (CGPoint) readCoordinatePair:(NSScanner*)scanner
  275. {
  276. CGPoint p;
  277. [SVGKPointsAndPathsParser readCoordinate:scanner intoFloat:&p.x];
  278. [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
  279. [SVGKPointsAndPathsParser readCoordinate:scanner intoFloat:&p.y];
  280. return p;
  281. }
  282. + (void) readCoordinate:(NSScanner*)scanner intoFloat:(CGFloat*) floatPointer
  283. {
  284. #if CGFLOAT_IS_DOUBLE
  285. if( ![scanner scanDouble:floatPointer])
  286. NSAssert(FALSE, @"invalid coord");
  287. #else
  288. if( ![scanner scanFloat:floatPointer])
  289. NSAssert(FALSE, @"invalid coord");
  290. #endif
  291. }
  292. /**
  293. lineto:
  294. ( "L" | "l" ) wsp* lineto-argument-sequence
  295. */
  296. + (SVGCurve) readLinetoCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
  297. {
  298. #if VERBOSE_PARSE_SVG_COMMAND_STRINGS
  299. SVGKitLogVerbose(@"Parsing command string: line-to command");
  300. #endif
  301. NSString* cmd = nil;
  302. NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Ll"];
  303. if( ! [scanner scanCharactersFromSet:cmdFormat intoString:&cmd] )
  304. {
  305. NSAssert( FALSE, @"failed to scan line to command");
  306. return SVGCurveMakePoint(origin);
  307. }
  308. [SVGKPointsAndPathsParser readWhitespace:scanner];
  309. return [SVGKPointsAndPathsParser readLinetoArgumentSequence:scanner path:path relativeTo:origin isRelative:isRelative];
  310. }
  311. /**
  312. lineto-argument-sequence:
  313. coordinate-pair
  314. | coordinate-pair comma-wsp? lineto-argument-sequence
  315. */
  316. + (SVGCurve) readLinetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
  317. {
  318. CGPoint p = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
  319. CGPoint coord = CGPointMake(p.x+origin.x, p.y+origin.y);
  320. CGPathAddLineToPoint(path, NULL, coord.x, coord.y);
  321. #if DEBUG_PATH_CREATION
  322. SVGKitLogWarn(@"[%@] PATH: LINE to %2.2f, %2.2f", [SVGKPointsAndPathsParser class], coord.x, coord.y );
  323. #endif
  324. [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
  325. while( ![scanner isAtEnd])
  326. {
  327. origin = (isRelative)?coord:origin;
  328. p = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
  329. coord = CGPointMake(p.x+origin.x, p.y+origin.y);
  330. CGPathAddLineToPoint(path, NULL, coord.x, coord.y);
  331. #if DEBUG_PATH_CREATION
  332. SVGKitLogWarn(@"[%@] PATH: LINE to %2.2f, %2.2f", [SVGKPointsAndPathsParser class], coord.x, coord.y );
  333. #endif
  334. [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
  335. }
  336. return SVGCurveMakePoint(coord);
  337. }
  338. /**
  339. quadratic-bezier-curveto:
  340. ( "Q" | "q" ) wsp* quadratic-bezier-curveto-argument-sequence
  341. */
  342. + (SVGCurve) readQuadraticCurvetoCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
  343. {
  344. #if VERBOSE_PARSE_SVG_COMMAND_STRINGS
  345. SVGKitLogVerbose(@"Parsing command string: quadratic-bezier-curve-to command");
  346. #endif
  347. NSString* cmd = nil;
  348. NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Qq"];
  349. if( ! [scanner scanCharactersFromSet:cmdFormat intoString:&cmd] )
  350. {
  351. NSAssert( FALSE, @"failed to scan quadratic curve to command");
  352. return SVGCurveMakePoint(origin);
  353. }
  354. [SVGKPointsAndPathsParser readWhitespace:scanner];
  355. return [SVGKPointsAndPathsParser readQuadraticCurvetoArgumentSequence:scanner path:path relativeTo:origin isRelative:isRelative];
  356. }
  357. /**
  358. quadratic-bezier-curveto-argument-sequence:
  359. quadratic-bezier-curveto-argument
  360. | quadratic-bezier-curveto-argument comma-wsp? quadratic-bezier-curveto-argument-sequence
  361. */
  362. + (SVGCurve) readQuadraticCurvetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
  363. {
  364. SVGCurve curve = [SVGKPointsAndPathsParser readQuadraticCurvetoArgument:scanner path:path relativeTo:origin];
  365. while(![scanner isAtEnd])
  366. {
  367. curve = [SVGKPointsAndPathsParser readQuadraticCurvetoArgument:scanner path:path relativeTo:(isRelative ? curve.p : origin)];
  368. }
  369. return curve;
  370. }
  371. /**
  372. quadratic-bezier-curveto-argument:
  373. coordinate-pair comma-wsp? coordinate-pair
  374. */
  375. + (SVGCurve) readQuadraticCurvetoArgument:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin
  376. {
  377. SVGCurve curveResult;
  378. curveResult.type = SVGCurveTypeQuadratic;
  379. curveResult.c1 = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
  380. curveResult.c1.x += origin.x;
  381. curveResult.c1.y += origin.y;
  382. [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
  383. curveResult.c2 = curveResult.c1;
  384. curveResult.p = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
  385. curveResult.p.x += origin.x;
  386. curveResult.p.y += origin.y;
  387. [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
  388. CGPathAddQuadCurveToPoint(path, NULL, curveResult.c1.x, curveResult.c1.y, curveResult.p.x, curveResult.p.y);
  389. #if DEBUG_PATH_CREATION
  390. SVGKitLogWarn(@"[%@] PATH: QUADRATIC CURVE to (%2.2f, %2.2f)..(%2.2f, %2.2f)", [SVGKPointsAndPathsParser class], curveResult.c1.x, curveResult.c1.y, curveResult.p.x, curveResult.p.y);
  391. #endif
  392. return curveResult;
  393. }
  394. /**
  395. smooth-quadratic-bezier-curveto:
  396. ( "T" | "t" ) wsp* smooth-quadratic-bezier-curveto-argument-sequence
  397. */
  398. + (SVGCurve) readSmoothQuadraticCurvetoCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(SVGCurve)prevCurve
  399. {
  400. #if VERBOSE_PARSE_SVG_COMMAND_STRINGS
  401. SVGKitLogVerbose(@"Parsing command string: smooth-quadratic-bezier-curve-to command");
  402. #endif
  403. NSString* cmd = nil;
  404. NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Tt"];
  405. if( ! [scanner scanCharactersFromSet:cmdFormat intoString:&cmd] )
  406. {
  407. NSAssert( FALSE, @"failed to scan smooth quadratic curve to command");
  408. return prevCurve;
  409. }
  410. [SVGKPointsAndPathsParser readWhitespace:scanner];
  411. return [SVGKPointsAndPathsParser readSmoothQuadraticCurvetoArgumentSequence:scanner path:path relativeTo:origin withPrevCurve:prevCurve];
  412. }
  413. /**
  414. smooth-quadratic-bezier-curveto-argument-sequence:
  415. smooth-quadratic-bezier-curveto-argument
  416. | smooth-quadratic-bezier-curveto-argument comma-wsp? smooth-quadratic-bezier-curveto-argument-sequence
  417. */
  418. + (SVGCurve) readSmoothQuadraticCurvetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(SVGCurve)prevCurve
  419. {
  420. SVGCurve curve = [SVGKPointsAndPathsParser readSmoothQuadraticCurvetoArgument:scanner path:path relativeTo:origin withPrevCurve:prevCurve];
  421. if (![scanner isAtEnd]) {
  422. curve = [SVGKPointsAndPathsParser readSmoothQuadraticCurvetoArgumentSequence:scanner path:path relativeTo:curve.p withPrevCurve:curve];
  423. }
  424. return curve;
  425. }
  426. /**
  427. smooth-quadratic-bezier-curveto-argument:
  428. coordinate-pair comma-wsp? coordinate-pair
  429. */
  430. + (SVGCurve) readSmoothQuadraticCurvetoArgument:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(SVGCurve)prevCurve
  431. {
  432. SVGCurve thisCurve;
  433. thisCurve.type = SVGCurveTypeQuadratic;
  434. thisCurve.c2 = (prevCurve.type == thisCurve.type) ? SVGCurveReflectedControlPoint(prevCurve) : prevCurve.p;
  435. thisCurve.c1 = thisCurve.c2; // this coordinate is never used, but c2 is better/safer than CGPointZero
  436. thisCurve.p = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
  437. thisCurve.p.x += origin.x;
  438. thisCurve.p.y += origin.y;
  439. [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
  440. CGPathAddQuadCurveToPoint(path, NULL, thisCurve.c2.x, thisCurve.c2.y, thisCurve.p.x, thisCurve.p.y );
  441. #if DEBUG_PATH_CREATION
  442. SVGKitLogWarn(@"[%@] PATH: SMOOTH QUADRATIC CURVE to (%2.2f, %2.2f)..(%2.2f, %2.2f)", [SVGKPointsAndPathsParser class], thisCurve.c1.x, thisCurve.c1.y, thisCurve.p.x, thisCurve.p.y );
  443. #endif
  444. return thisCurve;
  445. }
  446. /**
  447. curveto:
  448. ( "C" | "c" ) wsp* curveto-argument-sequence
  449. */
  450. + (SVGCurve) readCurvetoCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
  451. {
  452. #if VERBOSE_PARSE_SVG_COMMAND_STRINGS
  453. SVGKitLogVerbose(@"Parsing command string: curve-to command");
  454. #endif
  455. NSString* cmd = nil;
  456. NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Cc"];
  457. if( ! [scanner scanCharactersFromSet:cmdFormat intoString:&cmd])
  458. {
  459. NSAssert( FALSE, @"failed to scan curve to command");
  460. return SVGCurveMakePoint(origin);
  461. }
  462. [SVGKPointsAndPathsParser readWhitespace:scanner];
  463. return [SVGKPointsAndPathsParser readCurvetoArgumentSequence:scanner path:path relativeTo:origin isRelative:isRelative];
  464. }
  465. /**
  466. curveto-argument-sequence:
  467. curveto-argument
  468. | curveto-argument comma-wsp? curveto-argument-sequence
  469. */
  470. + (SVGCurve) readCurvetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
  471. {
  472. SVGCurve curve = [SVGKPointsAndPathsParser readCurvetoArgument:scanner path:path relativeTo:origin];
  473. while( ![scanner isAtEnd])
  474. {
  475. CGPoint newOrigin = isRelative ? curve.p : origin;
  476. curve = [SVGKPointsAndPathsParser readCurvetoArgument:scanner path:path relativeTo:newOrigin];
  477. }
  478. return curve;
  479. }
  480. /**
  481. curveto-argument:
  482. coordinate-pair comma-wsp? coordinate-pair comma-wsp? coordinate-pair
  483. */
  484. + (SVGCurve) readCurvetoArgument:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin
  485. {
  486. SVGCurve curveResult;
  487. curveResult.type = SVGCurveTypeCubic;
  488. curveResult.c1 = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
  489. curveResult.c1.x += origin.x; // avoid allocating a new struct, an allocation here could happen MILLIONS of times in a large parse!
  490. curveResult.c1.y += origin.y;
  491. [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
  492. curveResult.c2 = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
  493. curveResult.c2.x += origin.x; // avoid allocating a new struct, an allocation here could happen MILLIONS of times in a large parse!
  494. curveResult.c2.y += origin.y;
  495. [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
  496. curveResult.p = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
  497. curveResult.p.x += origin.x; // avoid allocating a new struct, an allocation here could happen MILLIONS of times in a large parse!
  498. curveResult.p.y += origin.y;
  499. [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
  500. CGPathAddCurveToPoint(path, NULL, curveResult.c1.x, curveResult.c1.y, curveResult.c2.x, curveResult.c2.y, curveResult.p.x, curveResult.p.y);
  501. #if DEBUG_PATH_CREATION
  502. SVGKitLogWarn(@"[%@] PATH: CURVE to (%2.2f, %2.2f)..(%2.2f, %2.2f)..(%2.2f, %2.2f)", [SVGKPointsAndPathsParser class], curveResult.c1.x, curveResult.c1.y, curveResult.c2.x, curveResult.c2.y, curveResult.p.x, curveResult.p.y);
  503. #endif
  504. return curveResult;
  505. }
  506. /**
  507. smooth-curveto:
  508. ( "S" | "s" ) wsp* smooth-curveto-argument-sequence
  509. */
  510. + (SVGCurve) readSmoothCurvetoCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(SVGCurve)prevCurve isRelative:(BOOL) isRelative
  511. {
  512. NSString* cmd = nil;
  513. NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Ss"];
  514. BOOL ok = [scanner scanCharactersFromSet:cmdFormat intoString:&cmd];
  515. NSAssert(ok, @"failed to scan smooth curve to command");
  516. if (!ok) return prevCurve;
  517. [SVGKPointsAndPathsParser readWhitespace:scanner];
  518. return [SVGKPointsAndPathsParser readSmoothCurvetoArgumentSequence:scanner path:path relativeTo:origin withPrevCurve:prevCurve isRelative:isRelative];
  519. }
  520. /**
  521. smooth-curveto-argument-sequence:
  522. smooth-curveto-argument
  523. | smooth-curveto-argument comma-wsp? smooth-curveto-argument-sequence
  524. */
  525. + (SVGCurve) readSmoothCurvetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(SVGCurve)prevCurve isRelative:(BOOL) isRelative
  526. {
  527. SVGCurve curve = [SVGKPointsAndPathsParser readSmoothCurvetoArgument:scanner path:path relativeTo:origin withPrevCurve:prevCurve];
  528. if (![scanner isAtEnd]) {
  529. CGPoint newOrigin = isRelative ? curve.p : origin;
  530. curve = [SVGKPointsAndPathsParser readSmoothCurvetoArgumentSequence:scanner path:path relativeTo:newOrigin withPrevCurve:curve isRelative: isRelative];
  531. }
  532. return curve;
  533. }
  534. /**
  535. smooth-curveto-argument:
  536. coordinate-pair comma-wsp? coordinate-pair
  537. */
  538. + (SVGCurve) readSmoothCurvetoArgument:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(SVGCurve)prevCurve
  539. {
  540. SVGCurve thisCurve;
  541. thisCurve.type = SVGCurveTypeCubic;
  542. thisCurve.c1 = (prevCurve.type == thisCurve.type) ? SVGCurveReflectedControlPoint(prevCurve) : prevCurve.p;
  543. [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
  544. thisCurve.c2 = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
  545. thisCurve.c2.x += origin.x;
  546. thisCurve.c2.y += origin.y;
  547. [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
  548. thisCurve.p = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
  549. thisCurve.p.x += origin.x;
  550. thisCurve.p.y += origin.y;
  551. CGPathAddCurveToPoint(path, NULL, thisCurve.c1.x, thisCurve.c1.y, thisCurve.c2.x, thisCurve.c2.y, thisCurve.p.x, thisCurve.p.y);
  552. #if DEBUG_PATH_CREATION
  553. SVGKitLogWarn(@"[%@] PATH: SMOOTH CURVE to (%2.2f, %2.2f)..(%2.2f, %2.2f)..(%2.2f, %2.2f)", [SVGKPointsAndPathsParser class], thisCurve.c1.x, thisCurve.c1.y, thisCurve.c2.x, thisCurve.c2.y, thisCurve.p.x, thisCurve.p.y );
  554. #endif
  555. return thisCurve;
  556. }
  557. /**
  558. vertical-lineto-argument-sequence:
  559. coordinate
  560. | coordinate comma-wsp? vertical-lineto-argument-sequence
  561. */
  562. + (SVGCurve) readVerticalLinetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin
  563. {
  564. // FIXME: reduce the allocations here; make one CGPoint and update it, not multiple
  565. CGFloat yValue;
  566. [SVGKPointsAndPathsParser readCoordinate:scanner intoFloat:&yValue];
  567. CGPoint vertCoord = CGPointMake(origin.x, origin.y+yValue);
  568. CGPoint currentPoint = CGPathGetCurrentPoint(path);
  569. CGPoint coord = CGPointMake(currentPoint.x, currentPoint.y+(vertCoord.y-currentPoint.y));
  570. CGPathAddLineToPoint(path, NULL, coord.x, coord.y);
  571. #if DEBUG_PATH_CREATION
  572. SVGKitLogWarn(@"[%@] PATH: VERTICAL LINE to (%2.2f, %2.2f)", [SVGKPointsAndPathsParser class], coord.x, coord.y );
  573. #endif
  574. return SVGCurveMakePoint(coord);
  575. }
  576. /**
  577. vertical-lineto:
  578. ( "V" | "v" ) wsp* vertical-lineto-argument-sequence
  579. */
  580. + (SVGCurve) readVerticalLinetoCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin
  581. {
  582. #if VERBOSE_PARSE_SVG_COMMAND_STRINGS
  583. SVGKitLogVerbose(@"Parsing command string: vertical-line-to command");
  584. #endif
  585. NSString* cmd = nil;
  586. NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Vv"];
  587. BOOL ok = [scanner scanCharactersFromSet:cmdFormat intoString:&cmd];
  588. NSAssert(ok, @"failed to scan vertical line to command");
  589. if (!ok) return SVGCurveMakePoint(origin);
  590. [SVGKPointsAndPathsParser readWhitespace:scanner];
  591. return [SVGKPointsAndPathsParser readVerticalLinetoArgumentSequence:scanner path:path relativeTo:origin];
  592. }
  593. /**
  594. horizontal-lineto-argument-sequence:
  595. coordinate
  596. | coordinate comma-wsp? horizontal-lineto-argument-sequence
  597. */
  598. + (SVGCurve) readHorizontalLinetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin
  599. {
  600. // FIXME: reduce the allocations here; make one CGPoint and update it, not multiple
  601. CGFloat xValue;
  602. [SVGKPointsAndPathsParser readCoordinate:scanner intoFloat:&xValue];
  603. CGPoint horizCoord = CGPointMake(origin.x+xValue, origin.y);
  604. CGPoint currentPoint = CGPathGetCurrentPoint(path);
  605. CGPoint coord = CGPointMake(currentPoint.x+(horizCoord.x-currentPoint.x), currentPoint.y);
  606. CGPathAddLineToPoint(path, NULL, coord.x, coord.y);
  607. #if DEBUG_PATH_CREATION
  608. SVGKitLogWarn(@"[%@] PATH: HORIZONTAL LINE to (%2.2f, %2.2f)", [SVGKPointsAndPathsParser class], coord.x, coord.y );
  609. #endif
  610. return SVGCurveMakePoint(coord);
  611. }
  612. /**
  613. horizontal-lineto:
  614. ( "H" | "h" ) wsp* horizontal-lineto-argument-sequence
  615. */
  616. + (SVGCurve) readHorizontalLinetoCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin
  617. {
  618. #if VERBOSE_PARSE_SVG_COMMAND_STRINGS
  619. SVGKitLogVerbose(@"Parsing command string: horizontal-line-to command");
  620. #endif
  621. NSString* cmd = nil;
  622. NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Hh"];
  623. if( ! [scanner scanCharactersFromSet:cmdFormat intoString:&cmd] )
  624. {
  625. NSAssert( FALSE, @"failed to scan horizontal line to command");
  626. return SVGCurveMakePoint(origin);
  627. }
  628. [SVGKPointsAndPathsParser readWhitespace:scanner];
  629. return [SVGKPointsAndPathsParser readHorizontalLinetoArgumentSequence:scanner path:path relativeTo:origin];
  630. }
  631. + (SVGCurve) readCloseCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin
  632. {
  633. #if VERBOSE_PARSE_SVG_COMMAND_STRINGS
  634. SVGKitLogVerbose(@"Parsing command string: close command");
  635. #endif
  636. NSString* cmd = nil;
  637. NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Zz"];
  638. if( ! [scanner scanCharactersFromSet:cmdFormat intoString:&cmd] )
  639. {
  640. NSAssert( FALSE, @"failed to scan close command");
  641. return SVGCurveMakePoint(origin);
  642. }
  643. CGPathCloseSubpath(path);
  644. #if DEBUG_PATH_CREATION
  645. SVGKitLogWarn(@"[%@] PATH: finished path", [SVGKPointsAndPathsParser class] );
  646. #endif
  647. return SVGCurveMakePoint(CGPathGetCurrentPoint(path));
  648. }
  649. + (SVGCurve) readEllipticalArcArguments:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
  650. {
  651. NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Aa"];
  652. BOOL ok = [scanner scanCharactersFromSet:cmdFormat intoString:nil];
  653. NSAssert(ok, @"failed to scan arc to command");
  654. if (!ok) return SVGCurveMakePoint(origin);
  655. CGPoint endPoint = [SVGKPointsAndPathsParser readEllipticalArcArgumentsSequence:scanner path:path relativeTo:origin];
  656. while (![scanner isAtEnd]) {
  657. CGPoint newOrigin = isRelative ? endPoint : origin;
  658. endPoint = [SVGKPointsAndPathsParser readEllipticalArcArgumentsSequence:scanner path:path relativeTo:newOrigin];
  659. }
  660. return SVGCurveMakePoint(endPoint);
  661. }
  662. + (CGPoint)readEllipticalArcArgumentsSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin
  663. {
  664. [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
  665. // need to find the center point of the ellipse from the two points and an angle
  666. // see http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes for these calculations
  667. CGPoint currentPt = CGPathGetCurrentPoint(path);
  668. CGFloat x1 = currentPt.x;
  669. CGFloat y1 = currentPt.y;
  670. CGPoint radii = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
  671. CGFloat rx = fabs(radii.x);
  672. CGFloat ry = fabs(radii.y);
  673. [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
  674. CGFloat phi;
  675. [SVGKPointsAndPathsParser readCoordinate:scanner intoFloat:&phi];
  676. phi *= M_PI/180.;
  677. phi = fmod(phi, 2 * M_PI);
  678. [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
  679. CGPoint flags = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
  680. BOOL largeArcFlag = flags.x != 0.;
  681. BOOL sweepFlag = flags.y != 0.;
  682. [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
  683. CGPoint endPoint = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
  684. // end parsing
  685. CGFloat x2 = origin.x + endPoint.x;
  686. CGFloat y2 = origin.y + endPoint.y;
  687. if (rx == 0 || ry == 0)
  688. {
  689. CGPathAddLineToPoint(path, NULL, x2, y2);
  690. return CGPointMake(x2, y2);
  691. }
  692. CGFloat cosPhi = cos(phi);
  693. CGFloat sinPhi = sin(phi);
  694. CGFloat x1p = cosPhi * (x1-x2)/2. + sinPhi * (y1-y2)/2.;
  695. CGFloat y1p = -sinPhi * (x1-x2)/2. + cosPhi * (y1-y2)/2.;
  696. CGFloat lhs;
  697. {
  698. CGFloat rx_2 = rx * rx;
  699. CGFloat ry_2 = ry * ry;
  700. CGFloat xp_2 = x1p * x1p;
  701. CGFloat yp_2 = y1p * y1p;
  702. CGFloat delta = xp_2/rx_2 + yp_2/ry_2;
  703. if (delta > 1.0)
  704. {
  705. rx *= sqrt(delta);
  706. ry *= sqrt(delta);
  707. rx_2 = rx * rx;
  708. ry_2 = ry * ry;
  709. }
  710. CGFloat sign = (largeArcFlag == sweepFlag) ? -1 : 1;
  711. CGFloat numerator = rx_2 * ry_2 - rx_2 * yp_2 - ry_2 * xp_2;
  712. CGFloat denom = rx_2 * yp_2 + ry_2 * xp_2;
  713. numerator = MAX(0, numerator);
  714. if (denom == 0) {
  715. lhs = 0;
  716. }else {
  717. lhs = sign * sqrt(numerator/denom);
  718. }
  719. }
  720. CGFloat cxp = lhs * (rx*y1p)/ry;
  721. CGFloat cyp = lhs * -((ry * x1p)/rx);
  722. CGFloat cx = cosPhi * cxp + -sinPhi * cyp + (x1+x2)/2.;
  723. CGFloat cy = cxp * sinPhi + cyp * cosPhi + (y1+y2)/2.;
  724. // transform our ellipse into the unit circle
  725. CGAffineTransform tr = CGAffineTransformMakeScale(1./rx, 1./ry);
  726. tr = CGAffineTransformRotate(tr, -phi);
  727. tr = CGAffineTransformTranslate(tr, -cx, -cy);
  728. CGPoint arcPt1 = CGPointApplyAffineTransform(CGPointMake(x1, y1), tr);
  729. CGPoint arcPt2 = CGPointApplyAffineTransform(CGPointMake(x2, y2), tr);
  730. CGFloat startAngle = atan2(arcPt1.y, arcPt1.x);
  731. CGFloat endAngle = atan2(arcPt2.y, arcPt2.x);
  732. CGFloat angleDelta = endAngle - startAngle;;
  733. if (sweepFlag)
  734. {
  735. if (angleDelta < 0)
  736. angleDelta += 2. * M_PI;
  737. }
  738. else
  739. {
  740. if (angleDelta > 0)
  741. angleDelta = angleDelta - 2 * M_PI;
  742. }
  743. // construct the inverse transform
  744. CGAffineTransform trInv = CGAffineTransformMakeTranslation( cx, cy);
  745. trInv = CGAffineTransformRotate(trInv, phi);
  746. trInv = CGAffineTransformScale(trInv, rx, ry);
  747. // add a inversely transformed circular arc to the current path
  748. CGPathAddRelativeArc( path, &trInv, 0, 0, 1., startAngle, angleDelta);
  749. return CGPointMake(x2, y2);
  750. }
  751. @end