Requires

Provides

Class to generate a valid SVG path using method calls.

Authors:
[Valerio Proietti](http://mad4milk.net), [Sebastian Markbåge](http://calyptus.eu/)
  1. 12
(function(){

private functions

  1. 16
  2. 17
  3. 18
  4. 19
  5. 20
  6. 21
  7. 22
  8. 23
  9. 24
  10. 25
  11. 26
  12. 27
  13. 28
  14. 29
  15. 30
  16. 31
  17. 32
  18. 33
  19. 34
  20. 35
  21. 36
  22. 37
  23. 38
  24. 39
  25. 40
  26. 41
  27. 42
  28. 43
  29. 44
  30. 45
  31. 46
  32. 47
  33. 48
  34. 49
  35. 50
  36. 51
  37. 52
  38. 53
  39. 54
var parameterCount = { l: 2, z: 0, h: 1, v: 1, c: 6, s: 4, q: 4, t: 2, a: 7 }; function parse(path){ if (!path) return []; var parts = [], index = -1, bits = path.match(/[a-df-z]|[\-+]?(?:[\d\.]e[\-+]?|[^\s\-+,a-z])+/ig), command, part, paramCount = 0; for (var i = 0, l = bits.length; i < l; i++){ var bit = bits[i]; if (bit.match(/^[a-z]/i)){ command = bit; parts[++index] = part = [command]; if (command == 'm') command = 'l'; else if (command == 'M') command = 'L'; paramCount = parameterCount[command.toLowerCase()]; } else { if (part.length > paramCount) parts[++index] = part = [command]; part.push(Number(bit)); } } return parts; }; function visitCurve(sx, sy, c1x, c1y, c2x, c2y, ex, ey, lineTo){ var ax = sx - c1x, ay = sy - c1y, bx = c1x - c2x, by = c1y - c2y, cx = c2x - ex, cy = c2y - ey, dx = ex - sx, dy = ey - sy;

TODO: Faster algorithm without sqrts

  1. 57
  2. 58
  3. 59
  4. 60
  5. 61
  6. 62
  7. 63
  8. 64
  9. 65
var err = Math.sqrt(ax * ax + ay * ay) + Math.sqrt(bx * bx + by * by) + Math.sqrt(cx * cx + cy * cy) - Math.sqrt(dx * dx + dy * dy); if (err <= 0.0001){ lineTo(sx, sy, ex, ey); return; }

Split curve

  1. 68
  2. 69
  3. 70
  4. 71
  5. 72
  6. 73
var s1x = (c1x + c2x) / 2, s1y = (c1y + c2y) / 2, l1x = (c1x + sx) / 2, l1y = (c1y + sy) / 2, l2x = (l1x + s1x) / 2, l2y = (l1y + s1y) / 2, r2x = (ex + c2x) / 2, r2y = (ey + c2y) / 2, r1x = (r2x + s1x) / 2, r1y = (r2y + s1y) / 2, l2r1x = (l2x + r1x) / 2, l2r1y = (l2y + r1y) / 2;

TODO: Manual stack if necessary. Currently recursive without tail optimization.

  1. 76
  2. 77
  3. 78
  4. 79
  5. 80
  6. 81
  7. 82
  8. 83
  9. 84
visitCurve(sx, sy, l1x, l1y, l2x, l2y, l2r1x, l2r1y, lineTo); visitCurve(l2r1x, l2r1y, r1x, r1y, r2x, r2y, ex, ey, lineTo); }; var circle = Math.PI * 2; function visitArc(rx, ry, rotation, large, clockwise, x, y, tX, tY, curveTo, arcTo){ var rad = rotation * Math.PI / 180, cos = Math.cos(rad), sin = Math.sin(rad); x -= tX; y -= tY;

Ellipse Center

  1. 87
  2. 88
  3. 89
  4. 90
  5. 91
  6. 92
  7. 93
  8. 94
  9. 95
  10. 96
  11. 97
  12. 98
  13. 99
  14. 100
  15. 101
  16. 102
  17. 103
  18. 104
  19. 105
var cx = cos * x / 2 + sin * y / 2, cy = -sin * x / 2 + cos * y / 2, rxry = rx * rx * ry * ry, rycx = ry * ry * cx * cx, rxcy = rx * rx * cy * cy, a = rxry - rxcy - rycx; if (a < 0){ a = Math.sqrt(1 - a / rxry); rx *= a; ry *= a; cx = x / 2; cy = y / 2; } else { a = Math.sqrt(a / (rxcy + rycx)); if (large == clockwise) a = -a; var cxd = -a * cy * rx / ry, cyd = a * cx * ry / rx; cx = cos * cxd - sin * cyd + x / 2; cy = sin * cxd + cos * cyd + y / 2; }

Rotation + Scale Transform

  1. 108
  2. 109
var xx = cos / rx, yx = sin / rx, xy = -sin / ry, yy = cos / ry;

Start and End Angle

  1. 112
  2. 113
  3. 114
  4. 115
  5. 116
var sa = Math.atan2(xy * -cx + yy * -cy, xx * -cx + yx * -cy), ea = Math.atan2(xy * (x - cx) + yy * (y - cy), xx * (x - cx) + yx * (y - cy)); cx += tX; cy += tY; x += tX; y += tY;

Circular Arc

  1. 119
  2. 120
  3. 121
  4. 122
  5. 123
  6. 124
  7. 125
if (rx == ry && arcTo){ arcTo( tX, tY, x, y, cx, cy, rx, sa, ea, !clockwise ); return; }

Inverse Rotation + Scale Transform

  1. 128
  2. 129
xx = cos * rx; yx = -sin * ry; xy = sin * rx; yy = cos * ry;

Bezier Curve Approximation

  1. 132
  2. 133
  3. 134
  4. 135
  5. 136
  6. 137
  7. 138
  8. 139
  9. 140
  10. 141
  11. 142
  12. 143
  13. 144
  14. 145
  15. 146
  16. 147
  17. 148
  18. 149
  19. 150
  20. 151
  21. 152
  22. 153
  23. 154
  24. 155
  25. 156
  26. 157
  27. 158
var arc = ea - sa; if (arc < 0 && clockwise) arc += circle; else if (arc > 0 && !clockwise) arc -= circle; var n = Math.ceil(Math.abs(arc / (circle / 4))), step = arc / n, k = (4 / 3) * Math.tan(step / 4), a = sa; x = Math.cos(a); y = Math.sin(a); for (var i = 0; i < n; i++){ var cp1x = x - k * y, cp1y = y + k * x; a += step; x = Math.cos(a); y = Math.sin(a); var cp2x = x + k * y, cp2y = y - k * x; curveTo( tX, tY, cx + xx * cp1x + yx * cp1y, cy + xy * cp1x + yy * cp1y, cx + xx * cp2x + yx * cp2y, cy + xy * cp2x + yy * cp2y, (tX = (cx + xx * x + yx * y)), (tY = (cy + xy * x + yy * y)) ); } };

Measure bounds

  1. 162
  2. 163
  3. 164
  4. 165
  5. 166
  6. 167
  7. 168
  8. 169
  9. 170
  10. 171
  11. 172
  12. 173
  13. 174
  14. 175
  15. 176
  16. 177
  17. 178
  18. 179
  19. 180
  20. 181
  21. 182
var left, right, top, bottom; function lineBounds(sx, sy, x, y){ left = Math.min(left, sx, x); right = Math.max(right, sx, x); top = Math.min(top, sy, y); bottom = Math.max(bottom, sy, y); }; function curveBounds(sx, sy, p1x, p1y, p2x, p2y, x, y){ left = Math.min(left, sx, p1x, p2x, x); right = Math.max(right, sx, p1x, p2x, x); top = Math.min(top, sy, p1y, p2y, y); bottom = Math.max(bottom, sy, p1y, p2y, y); }; var west = circle / 2, south = west / 2, north = -south, east = 0; function arcBounds(sx, sy, ex, ey, cx, cy, r, sa, ea, ccw){ var bbsa = ccw ? ea : sa, bbea = ccw ? sa : ea; if (bbea < bbsa) bbea += circle;

Bounds

  1. 185
  2. 186
  3. 187
  4. 188
  5. 189
  6. 190
  7. 191
  8. 192
  9. 193
  10. 194
var bbl = (bbea > west) ? (cx - r) : (ex), bbr = (bbea > circle + east || (bbsa < east && bbea > east)) ? (cx + r) : (ex), bbt = (bbea > circle + north || (bbsa < north && bbea > north)) ? (cy - r) : (ey), bbb = (bbea > circle + south || (bbsa < south && bbea > south)) ? (cy + r) : (ey); left = Math.min(left, sx, bbl, bbr); right = Math.max(right, sx, bbl, bbr); top = Math.min(top, sy, bbt, bbb); bottom = Math.max(bottom, sy, bbt, bbb); };

Measure length

  1. 198
  2. 199
  3. 200
  4. 201
  5. 202
  6. 203
  7. 204
  8. 205
  9. 206
  10. 207
  11. 208
  12. 209
  13. 210
  14. 211
  15. 212
  16. 213
  17. 214
  18. 215
  19. 216
  20. 217
  21. 218
var length, desiredLength, desiredPoint; function traverseLine(sx, sy, ex, ey){ var x = ex - sx, y = ey - sy, l = Math.sqrt(x * x + y * y); length += l; if (length >= desiredLength){ var offset = (length - desiredLength) / l, cos = x / l, sin = y / l; ex -= x * offset; ey -= y * offset; desiredPoint = new ART.Transform(cos, sin, -sin, cos, ex, ey); desiredLength = Infinity; } }; function measureLine(sx, sy, ex, ey){ var x = ex - sx, y = ey - sy; length += Math.sqrt(x * x + y * y); };

Utility command factories

  1. 222
  2. 223
  3. 224
  4. 225
  5. 226
  6. 227
  7. 228
  8. 229
  9. 230
  10. 231
  11. 232
  12. 233
  13. 234
  14. 235
  15. 236
  16. 237
  17. 238
  18. 239
  19. 240
var point = function(c){ return function(x, y){ return this.push(c, x, y); }; }; var arc = function(c, cc){ return function(x, y, rx, ry, outer){ return this.push(c, Math.abs(rx || x), Math.abs(ry || rx || y), 0, outer ? 1 : 0, cc, x, y); }; }; var curve = function(t, q, c){ return function(c1x, c1y, c2x, c2y, ex, ey){ var args = Array.slice(arguments), l = args.length; args.unshift(l < 4 ? t : l < 6 ? q : c); return this.push.apply(this, args); }; };

Path Class

  1. 244
  2. 245
  3. 246
  4. 247
  5. 248
  6. 249
  7. 250
  8. 251
  9. 252
  10. 253
  11. 254
  12. 255
  13. 256
  14. 257
  15. 258
  16. 259
  17. 260
  18. 261
  19. 262
  20. 263
  21. 264
  22. 265
  23. 266
ART.Path = new Class({ initialize: function(path){ if (path instanceof ART.Path){ //already a path, copying this.path = Array.slice(path.path); this.cache = path.cache; } else { this.path = (path == null) ? [] : parse(path); this.cache = { svg: String(path) }; } }, push: function(){ //modifying the current path resets the memoized values. this.cache = {}; this.path.push(Array.slice(arguments)); return this; }, reset: function(){ this.cache = {}; this.path = []; return this; },

utility

  1. 270
  2. 271
  3. 272
  4. 273
  5. 274
  6. 275
  7. 276
  8. 277
  9. 278
  10. 279
  11. 280
  12. 281
  13. 282
  14. 283
  15. 284
  16. 285
  17. 286
  18. 287
move: point('m'), moveTo: point('M'), line: point('l'), lineTo: point('L'), curve: curve('t', 'q', 'c'), curveTo: curve('T', 'Q', 'C'), arc: arc('a', 1), arcTo: arc('A', 1), counterArc: arc('a', 0), counterArcTo: arc('A', 0), close: function(){ return this.push('z'); },

visitor

  1. 291
  2. 292
  3. 293
  4. 294
  5. 295
  6. 296
  7. 297
  8. 298
  9. 299
  10. 300
  11. 301
  12. 302
  13. 303
  14. 304
  15. 305
  16. 306
  17. 307
  18. 308
  19. 309
  20. 310
  21. 311
  22. 312
  23. 313
  24. 314
  25. 315
  26. 316
  27. 317
  28. 318
  29. 319
  30. 320
  31. 321
  32. 322
  33. 323
  34. 324
  35. 325
  36. 326
  37. 327
  38. 328
  39. 329
  40. 330
  41. 331
  42. 332
  43. 333
  44. 334
  45. 335
  46. 336
  47. 337
  48. 338
  49. 339
  50. 340
  51. 341
  52. 342
  53. 343
  54. 344
  55. 345
  56. 346
  57. 347
  58. 348
  59. 349
  60. 350
  61. 351
  62. 352
  63. 353
  64. 354
  65. 355
  66. 356
  67. 357
  68. 358
  69. 359
  70. 360
  71. 361
  72. 362
  73. 363
  74. 364
  75. 365
  76. 366
  77. 367
  78. 368
  79. 369
  80. 370
  81. 371
  82. 372
  83. 373
  84. 374
  85. 375
  86. 376
  87. 377
  88. 378
visit: function(lineTo, curveTo, arcTo, moveTo, close){ var reflect = function(sx, sy, ex, ey){ return [ex * 2 - sx, ey * 2 - sy]; }; if (!curveTo) curveTo = function(sx, sy, c1x, c1y, c2x, c2y, ex, ey){ visitCurve(sx, sy, c1x, c1y, c2x, c2y, ex, ey, lineTo); }; var X = 0, Y = 0, px = 0, py = 0, r, inX, inY; var parts = this.path; for (i = 0; i < parts.length; i++){ var v = Array.slice(parts[i]), f = v.shift(), l = f.toLowerCase(); var refX = l == f ? X : 0, refY = l == f ? Y : 0; if (l != 'm' && l != 'z' && inX == null){ inX = X; inY = Y; } switch (l){ case 'm': if (moveTo) moveTo(X, Y, X = refX + v[0], Y = refY + v[1]); else { X = refX + v[0]; Y = refY + v[1]; } break; case 'l': lineTo(X, Y, X = refX + v[0], Y = refY + v[1]); break; case 'c': px = refX + v[2]; py = refY + v[3]; curveTo(X, Y, refX + v[0], refY + v[1], px, py, X = refX + v[4], Y = refY + v[5]); break; case 's': r = reflect(px, py, X, Y); px = refX + v[0]; py = refY + v[1]; curveTo(X, Y, r[0], r[1], px, py, X = refX + v[2], Y = refY + v[3]); break; case 'q': px = (refX + v[0]); py = (refY + v[1]); curveTo(X, Y, (X + px * 2) / 3, (Y + py * 2) / 3, ((X = refX + v[2]) + px * 2) / 3, ((Y = refY + v[3]) + py * 2) / 3, X, Y); break; case 't': r = reflect(px, py, X, Y); px = r[0]; py = r[1]; curveTo(X, Y, (X + px * 2) / 3, (Y + py * 2) / 3, ((X = refX + v[0]) + px * 2) / 3, ((Y = refY + v[1]) + py * 2) / 3, X, Y); break; case 'a': px = refX + v[5]; py = refY + v[6]; if (!v[0] || !v[1] || (px == X && py == Y)) lineTo(X, Y, px, py); else visitArc(v[0], v[1], v[2], v[3], v[4], px, py, X, Y, curveTo, arcTo); X = px; Y = py; break; case 'h': lineTo(X, Y, X = refX + v[0], Y); break; case 'v': lineTo(X, Y, X, Y = refY + v[0]); break; case 'z': if (inX != null){ if (close){ close(); if (moveTo) moveTo(X, Y, X = inX, Y = inY); else { X = inX; Y = inY; } } else { lineTo(X, Y, X = inX, Y = inY); } inX = null; } break; } if (l != 's' && l != 'c' && l != 't' && l != 'q'){ px = X; py = Y; } } },

transformation, measurement

  1. 382
  2. 383
  3. 384
  4. 385
  5. 386
  6. 387
  7. 388
  8. 389
  9. 390
  10. 391
  11. 392
  12. 393
  13. 394
  14. 395
  15. 396
  16. 397
  17. 398
  18. 399
  19. 400
  20. 401
  21. 402
  22. 403
  23. 404
  24. 405
  25. 406
  26. 407
  27. 408
  28. 409
  29. 410
  30. 411
  31. 412
  32. 413
  33. 414
  34. 415
  35. 416
  36. 417
  37. 418
  38. 419
  39. 420
  40. 421
  41. 422
toSVG: function(){ if (this.cache.svg == null){ var path = ''; for (var i = 0, l = this.path.length; i < l; i++) path += this.path[i].join(' '); this.cache.svg = path; } return this.cache.svg; }, measure: function(){ if (this.cache.box == null){ left = top = Infinity; right = bottom = -Infinity; this.visit(lineBounds, curveBounds, arcBounds); if (left == Infinity) this.cache.box = {left: 0, top: 0, right: 0, bottom: 0, width: 0, height: 0}; else this.cache.box = {left: left, top: top, right: right, bottom: bottom, width: right - left, height: bottom - top }; } return this.cache.box; }, point: function(lengthToPoint){ length = 0; desiredLength = lengthToPoint; desiredPoint = null; this.visit(traverseLine); return desiredPoint; }, measureLength: function(){ length = 0; this.visit(measureLine); return length; } }); ART.Path.prototype.toString = ART.Path.prototype.toSVG; })();