Requires

Provides

SVG implementation for ART

  1. 11
  2. 12
  3. 13
  4. 14
  5. 15
  6. 16
  7. 17
  8. 18
  9. 19
  10. 20
(function(){ var NS = 'http://www.w3.org/2000/svg', XLINK = 'http://www.w3.org/1999/xlink', XML = 'http://www.w3.org/XML/1998/namespace', UID = 0, createElement = function(tag){ return document.createElementNS(NS, tag); }; var ua = navigator && navigator.userAgent, hasBaseline = !(/opera|safari|ie/i).test(ua) || (/chrome/i).test(ua);

SVG Base Class

  1. 24
  2. 25
  3. 26
  4. 27
  5. 28
  6. 29
  7. 30
  8. 31
  9. 32
  10. 33
  11. 34
  12. 35
  13. 36
  14. 37
  15. 38
  16. 39
  17. 40
  18. 41
  19. 42
  20. 43
  21. 44
  22. 45
  23. 46
  24. 47
  25. 48
  26. 49
  27. 50
  28. 51
ART.SVG = new Class({ Extends: ART.Element, Implements: ART.Container, initialize: function(width, height){ var element = this.element = createElement('svg'); element.setAttribute('xmlns', NS); element.setAttribute('version', 1.1); var defs = this.defs = createElement('defs'); element.appendChild(defs); if (width != null && height != null) this.resize(width, height); }, resize: function(width, height){ var element = this.element; element.setAttribute('width', width); element.setAttribute('height', height); this.width = width; this.height = height; return this; }, toElement: function(){ return this.element; } });

SVG Element Class

  1. 55
  2. 56
  3. 57
  4. 58
  5. 59
  6. 60
  7. 61
  8. 62
  9. 63
  10. 64
  11. 65
ART.SVG.Element = new Class({ Extends: ART.Element, Implements: ART.Transform, initialize: function(tag){ this.uid = String.uniqueID(); var element = this.element = createElement(tag); element.setAttribute('id', 'e' + this.uid); },

transforms

  1. 69
  2. 70
  3. 71
  4. 72
  5. 73
  6. 74
  7. 75
  8. 76
  9. 77
_transform: function(){ var m = this; this.element.setAttribute('transform', 'matrix(' + [m.xx, m.yx, m.xy, m.yy, m.x, m.y] + ')'); }, blend: function(opacity){ this.element.setAttribute('opacity', opacity); return this; },

visibility

  1. 81
  2. 82
  3. 83
  4. 84
  5. 85
  6. 86
  7. 87
  8. 88
  9. 89
hide: function(){ this.element.setAttribute('display', 'none'); return this; }, show: function(){ this.element.setAttribute('display', ''); return this; },

interaction

  1. 93
  2. 94
  3. 95
  4. 96
  5. 97
  6. 98
  7. 99
  8. 100
  9. 101
  10. 102
  11. 103
  12. 104
  13. 105
  14. 106
  15. 107
  16. 108
  17. 109
indicate: function(cursor, tooltip){ var element = this.element; if (cursor) this.element.style.cursor = cursor; if (tooltip){ var title = this.titleElement; if (title){ title.firstChild.nodeValue = tooltip; } else { this.titleElement = title = createElement('title'); title.appendChild(document.createTextNode(tooltip)); element.insertBefore(title, element.firstChild); } } return this; } });

SVG Group Class

  1. 113
  2. 114
  3. 115
  4. 116
  5. 117
  6. 118
  7. 119
  8. 120
  9. 121
  10. 122
  11. 123
  12. 124
  13. 125
  14. 126
ART.SVG.Group = new Class({ Extends: ART.SVG.Element, Implements: ART.Container, initialize: function(width, height){ this.parent('g'); this.width = width; this.height = height; this.defs = createElement('defs'); this.element.appendChild(this.defs); } });

SVG Base Shape Class

  1. 130
  2. 131
  3. 132
  4. 133
  5. 134
  6. 135
  7. 136
  8. 137
  9. 138
ART.SVG.Base = new Class({ Extends: ART.SVG.Element, initialize: function(tag){ this.parent(tag); this.fill(); this.stroke(); },

insertions

  1. 142
  2. 143
  3. 144
  4. 145
  5. 146
  6. 147
  7. 148
  8. 149
  9. 150
  10. 151
  11. 152
  12. 153
  13. 154
  14. 155
  15. 156
  16. 157
  17. 158
  18. 159
  19. 160
  20. 161
  21. 162
  22. 163
  23. 164
  24. 165
  25. 166
  26. 167
  27. 168
  28. 169
  29. 170
  30. 171
inject: function(container){ this.eject(); this.container = container; this._injectBrush('fill'); this._injectBrush('stroke'); this.parent(container); return this; }, eject: function(){ if (this.container){ this.parent(); this._ejectBrush('fill'); this._ejectBrush('stroke'); this.container = null; } return this; }, _injectBrush: function(type){ if (!this.container) return; var brush = this[type + 'Brush']; if (brush) this.container.defs.appendChild(brush); }, _ejectBrush: function(type){ if (!this.container) return; var brush = this[type + 'Brush']; if (brush) this.container.defs.removeChild(brush); },

styles

  1. 175
  2. 176
  3. 177
  4. 178
  5. 179
  6. 180
  7. 181
  8. 182
  9. 183
  10. 184
  11. 185
  12. 186
  13. 187
  14. 188
  15. 189
  16. 190
  17. 191
  18. 192
  19. 193
  20. 194
  21. 195
  22. 196
  23. 197
  24. 198
  25. 199
  26. 200
  27. 201
_createBrush: function(type, tag){ this._ejectBrush(type); var brush = createElement(tag); this[type + 'Brush'] = brush; var id = type + '-brush-e' + this.uid; brush.setAttribute('id', id); this._injectBrush(type); this.element.setAttribute(type, 'url(#' + id + ')'); return brush; }, _createGradient: function(type, style, stops){ var gradient = this._createBrush(type, style); var addColor = function(offset, color){ color = Color.detach(color); var stop = createElement('stop'); stop.setAttribute('offset', offset); stop.setAttribute('stop-color', color[0]); stop.setAttribute('stop-opacity', color[1]); gradient.appendChild(stop); };

Enumerate stops, assumes offsets are enumerated in order TODO: Sort. Chrome doesn’t always enumerate in expected order but requires stops to be specified in order.

  1. 205
  2. 206
  3. 207
  4. 208
  5. 209
  6. 210
  7. 211
  8. 212
  9. 213
  10. 214
  11. 215
  12. 216
  13. 217
  14. 218
  15. 219
  16. 220
  17. 221
  18. 222
  19. 223
  20. 224
  21. 225
  22. 226
  23. 227
  24. 228
  25. 229
  26. 230
  27. 231
  28. 232
  29. 233
  30. 234
  31. 235
  32. 236
  33. 237
  34. 238
  35. 239
  36. 240
  37. 241
  38. 242
  39. 243
  40. 244
  41. 245
  42. 246
  43. 247
  44. 248
  45. 249
  46. 250
  47. 251
  48. 252
  49. 253
  50. 254
  51. 255
  52. 256
  53. 257
  54. 258
  55. 259
  56. 260
  57. 261
  58. 262
  59. 263
  60. 264
  61. 265
  62. 266
  63. 267
  64. 268
  65. 269
  66. 270
  67. 271
  68. 272
  69. 273
  70. 274
  71. 275
  72. 276
  73. 277
  74. 278
  75. 279
  76. 280
  77. 281
  78. 282
  79. 283
  80. 284
  81. 285
  82. 286
  83. 287
  84. 288
  85. 289
  86. 290
  87. 291
  88. 292
  89. 293
  90. 294
  91. 295
  92. 296
  93. 297
  94. 298
  95. 299
  96. 300
  97. 301
  98. 302
  99. 303
  100. 304
  101. 305
  102. 306
  103. 307
  104. 308
  105. 309
  106. 310
  107. 311
  108. 312
  109. 313
  110. 314
  111. 315
  112. 316
  113. 317
  114. 318
  115. 319
  116. 320
  117. 321
  118. 322
  119. 323
  120. 324
  121. 325
  122. 326
  123. 327
  124. 328
  125. 329
  126. 330
  127. 331
  128. 332
  129. 333
  130. 334
  131. 335
  132. 336
  133. 337
  134. 338
  135. 339
  136. 340
  137. 341
  138. 342
if ('length' in stops) for (var i = 0, l = stops.length - 1; i <= l; i++) addColor(i / l, stops[i]); else for (var offset in stops) addColor(offset, stops[offset]); gradient.setAttribute('spreadMethod', 'reflect'); // Closer to the VML gradient this.element.removeAttribute('fill-opacity'); return gradient; }, _setColor: function(type, color){ this._ejectBrush(type); this[type + 'Brush'] = null; var element = this.element; if (color == null){ element.setAttribute(type, 'none'); element.removeAttribute(type + '-opacity'); } else { color = Color.detach(color); element.setAttribute(type, color[0]); element.setAttribute(type + '-opacity', color[1]); } }, fill: function(color){ if (arguments.length > 1) this.fillLinear(arguments); else this._setColor('fill', color); return this; }, fillRadial: function(stops, focusX, focusY, radiusX, radiusY, centerX, centerY){ var gradient = this._createGradient('fill', 'radialGradient', stops); gradient.setAttribute('gradientUnits', 'userSpaceOnUse'); if (focusX == null) focusX = (this.left || 0) + (this.width || 0) * 0.5; if (focusY == null) focusY = (this.top || 0) + (this.height || 0) * 0.5; if (radiusY == null) radiusY = radiusX || (this.height * 0.5) || 0; if (radiusX == null) radiusX = (this.width || 0) * 0.5; if (centerX == null) centerX = focusX; if (centerY == null) centerY = focusY; var ys = radiusY / radiusX; gradient.setAttribute('fx', focusX); gradient.setAttribute('fy', focusY / ys); gradient.setAttribute('r', radiusX); if (ys != 1) gradient.setAttribute('gradientTransform', 'scale(1,' + ys + ')'); gradient.setAttribute('cx', centerX); gradient.setAttribute('cy', centerY / ys); return this; }, fillLinear: function(stops, x1, y1, x2, y2){ var gradient = this._createGradient('fill', 'linearGradient', stops); if (arguments.length == 5){ gradient.setAttribute('gradientUnits', 'userSpaceOnUse'); } else { var angle = ((x1 == null) ? 270 : x1) * Math.PI / 180; var x = Math.cos(angle), y = -Math.sin(angle), l = (Math.abs(x) + Math.abs(y)) / 2; x *= l; y *= l; x1 = 0.5 - x; x2 = 0.5 + x; y1 = 0.5 - y; y2 = 0.5 + y; } gradient.setAttribute('x1', x1); gradient.setAttribute('y1', y1); gradient.setAttribute('x2', x2); gradient.setAttribute('y2', y2); return this; }, fillImage: function(url, width, height, left, top, color1, color2){ var pattern = this._createBrush('fill', 'pattern'); var image = createElement('image'); image.setAttributeNS(XLINK, 'href', url); image.setAttribute('width', width); image.setAttribute('height', height); image.setAttribute('preserveAspectRatio', 'none'); // none, xMidYMid slice, xMidYMid meet if (color1 != null){ color1 = new Color(color1); if (color2 == null){ color2 = new Color(color1); color2.alpha = 0; } else { color2 = new Color(color2); } var r = (color1.red - color2.red) / (255 * 3), g = (color1.green - color2.green) / (255 * 3), b = (color1.blue - color2.blue) / (255 * 3), a = (color1.alpha - color2.alpha) / 3; var matrix = [ r, r, r, 0, color2.red / 255, g, g, g, 0, color2.green / 255, b, b, b, 0, color2.blue / 255, a, a, a, 0, color2.alpha ]; var filter = createElement('filter'); filter.setAttribute('id', 'testfilter' + this.uid); var cm = createElement('feColorMatrix'); cm.setAttribute('type', 'matrix'); cm.setAttribute('values', matrix.join(' ')); image.setAttribute('fill', '#000'); image.setAttribute('filter', 'url(#testfilter' + this.uid + ')'); filter.appendChild(cm); pattern.appendChild(filter); } pattern.appendChild(image); pattern.setAttribute('patternUnits', 'userSpaceOnUse'); pattern.setAttribute('patternContentsUnits', 'userSpaceOnUse'); pattern.setAttribute('x', left || 0); pattern.setAttribute('y', top || 0); pattern.setAttribute('width', width); pattern.setAttribute('height', height);

pattern.setAttribute(‘viewBox’, ‘0 0 75 50’); pattern.setAttribute(‘preserveAspectRatio’, ‘xMidYMid slice’);

  1. 347
  2. 348
  3. 349
  4. 350
  5. 351
  6. 352
  7. 353
  8. 354
  9. 355
  10. 356
  11. 357
  12. 358
  13. 359
  14. 360
return this; }, stroke: function(color, width, cap, join){ var element = this.element; element.setAttribute('stroke-width', (width != null) ? width : 1); element.setAttribute('stroke-linecap', (cap != null) ? cap : 'round'); element.setAttribute('stroke-linejoin', (join != null) ? join : 'round'); this._setColor('stroke', color); return this; } });

SVG Shape Class

  1. 364
  2. 365
  3. 366
  4. 367
  5. 368
  6. 369
  7. 370
  8. 371
  9. 372
  10. 373
  11. 374
  12. 375
  13. 376
  14. 377
  15. 378
  16. 379
  17. 380
  18. 381
  19. 382
  20. 383
  21. 384
  22. 385
  23. 386
  24. 387
  25. 388
  26. 389
  27. 390
  28. 391
  29. 392
  30. 393
  31. 394
  32. 395
  33. 396
  34. 397
  35. 398
  36. 399
  37. 400
  38. 401
  39. 402
  40. 403
  41. 404
  42. 405
  43. 406
  44. 407
  45. 408
ART.SVG.Shape = new Class({ Extends: ART.SVG.Base, initialize: function(path, width, height){ this.parent('path'); this.element.setAttribute('fill-rule', 'evenodd'); this.width = width; this.height = height; if (path != null) this.draw(path); }, draw: function(path, width, height){ if (!(path instanceof ART.Path)) path = new ART.Path(path); this.element.setAttribute('d', path.toSVG()); if (width != null) this.width = width; if (height != null) this.height = height; return this; } }); ART.SVG.Image = new Class({ Extends: ART.SVG.Base, initialize: function(src, width, height){ this.parent('image'); if (arguments.length == 3) this.draw.apply(this, arguments); }, draw: function(src, width, height){ var element = this.element; element.setAttributeNS(XLINK, 'href', src); element.setAttribute('width', width); element.setAttribute('height', height); this.width = width; this.height = height; return this; } }); var fontAnchors = { left: 'start', center: 'middle', right: 'end' }, fontAnchorOffsets = { middle: '50%', end: '100%' };

split each continuous line into individual paths

  1. 412
  2. 413
  3. 414
  4. 415
  5. 416
  6. 417
  7. 418
  8. 419
  9. 420
  10. 421
  11. 422
  12. 423
  13. 424
  14. 425
  15. 426
  16. 427
  17. 428
  18. 429
  19. 430
  20. 431
  21. 432
  22. 433
  23. 434
  24. 435
  25. 436
  26. 437
  27. 438
  28. 439
  29. 440
  30. 441
  31. 442
  32. 443
  33. 444
var splitPaths, splitPath; function splitMove(sx, sy, x, y){ if (splitPath.length > 3) splitPaths.push(splitPath); splitPath = ['M', x, y]; }; function splitLine(sx, sy, x, y){ splitPath.push('L', x, y); }; function splitCurve(sx, sy, p1x, p1y, p2x, p2y, x, y){ splitPath.push('C', p1x, p1y, p2x, p2y, x, y); }; ART.SVG.Text = new Class({ Extends: ART.SVG.Base, initialize: function(text, font, alignment, path){ this.parent('text'); this.draw.apply(this, arguments); }, draw: function(text, font, alignment, path){ var element = this.element; if (font){ if (typeof font == 'string'){ element.style.font = font; } else { for (var key in font){ var ckey = key.camelCase ? key.camelCase() : key;

NOT UNIVERSALLY SUPPORTED OPTIONS if (ckey == ‘kerning’) element.setAttribute(‘kerning’, font[key] ? ‘auto’ : ‘0’); else if (ckey == ‘letterSpacing’) element.setAttribute(‘letter-spacing’, Number(font[key]) + ‘ex’); else if (ckey == ‘rotateGlyphs’) element.setAttribute(‘glyph-orientation-horizontal’, font[key] ? ‘270deg’ : ‘’); else

  1. 450
  2. 451
  3. 452
  4. 453
  5. 454
  6. 455
  7. 456
  8. 457
  9. 458
  10. 459
  11. 460
  12. 461
  13. 462
  14. 463
  15. 464
  16. 465
  17. 466
  18. 467
  19. 468
  20. 469
element.style[ckey] = font[key]; } element.style.lineHeight = '0.5em'; } } if (alignment) element.setAttribute('text-anchor', this.textAnchor = (fontAnchors[alignment] || alignment)); if (path && typeof path != 'number'){ this._createPaths(new ART.Path(path)); } else if (path === false){ this._ejectPaths(); this.pathElements = null; } var paths = this.pathElements, child; while ((child = element.firstChild)){ element.removeChild(child); }

Note: Gecko will (incorrectly) align gradients for each row, while others applies one for the entire element

  1. 473
  2. 474
  3. 475
  4. 476
  5. 477
  6. 478
  7. 479
  8. 480
  9. 481
  10. 482
  11. 483
  12. 484
  13. 485
  14. 486
  15. 487
  16. 488
  17. 489
  18. 490
  19. 491
  20. 492
  21. 493
  22. 494
  23. 495
  24. 496
  25. 497
  26. 498
  27. 499
  28. 500
  29. 501
  30. 502
  31. 503
  32. 504
  33. 505
  34. 506
  35. 507
var lines = String(text).split(/\r?\n/), l = lines.length, baseline = 'central'; if (paths && l > paths.length) l = paths.length; if (hasBaseline) element.setAttribute('dominant-baseline', baseline); element.setAttributeNS(XML, 'space', 'preserve'); for (var i = 0; i < l; i++){ var line = lines[i], row, content; if (paths){ row = createElement('textPath'); row.setAttributeNS(XLINK, 'href', '#' + paths[i].getAttribute('id')); row.setAttribute('startOffset', fontAnchorOffsets[this.textAnchor] || 0); } else { row = createElement('tspan'); row.setAttribute('x', 0); row.setAttribute('y', (i * 1.1 + 0.5) + 'em'); } if (hasBaseline){ row.setAttribute('dominant-baseline', baseline); content = row; } else if (paths){ content = createElement('tspan'); content.setAttribute('dy', '0.35em'); row.appendChild(content); } else { content = row; row.setAttribute('y', (i * 1.1 + 0.85) + 'em'); } content.setAttributeNS(XML, 'space', 'preserve'); content.appendChild(document.createTextNode(line)); element.appendChild(row); }

Measure TODO: Move to lazy ES5 left/top/width/height/bottom/right property getters

  1. 511
  2. 512
  3. 513
  4. 514
  5. 515
  6. 516
  7. 517
  8. 518
  9. 519
  10. 520
  11. 521
  12. 522
var bb; try { bb = element.getBBox(); } catch (x){ } if (!bb || !bb.width) bb = this._whileInDocument(element.getBBox, element); this.left = bb.x; this.top = bb.y; this.width = bb.width; this.height = bb.height; this.right = bb.x + bb.width; this.bottom = bb.y + bb.height; return this; },

TODO: Unify path injection with gradients and imagefills

  1. 526
  2. 527
  3. 528
  4. 529
  5. 530
  6. 531
  7. 532
  8. 533
  9. 534
  10. 535
  11. 536
  12. 537
  13. 538
  14. 539
  15. 540
  16. 541
  17. 542
  18. 543
  19. 544
  20. 545
  21. 546
  22. 547
  23. 548
  24. 549
  25. 550
  26. 551
  27. 552
  28. 553
  29. 554
  30. 555
  31. 556
  32. 557
  33. 558
  34. 559
  35. 560
  36. 561
  37. 562
  38. 563
  39. 564
  40. 565
  41. 566
  42. 567
  43. 568
  44. 569
  45. 570
  46. 571
  47. 572
  48. 573
  49. 574
  50. 575
  51. 576
inject: function(container){ this.parent(container); this._injectPaths(); return this; }, eject: function(){ if (this.container){ this._ejectPaths(); this.parent(); this.container = null; } return this; }, _injectPaths: function(){ var paths = this.pathElements; if (!this.container || !paths) return; var defs = this.container.defs; for (var i = 0, l = paths.length; i < l; i++) defs.appendChild(paths[i]); }, _ejectPaths: function(){ var paths = this.pathElements; if (!this.container || !paths) return; var defs = this.container.defs; for (var i = 0, l = paths; i < l; i++) defs.removeChild(paths[i]); }, _createPaths: function(path){ this._ejectPaths(); var id = 'p' + String.uniqueID() + '-'; splitPaths = []; splitPath = ['M', 0, 0]; path.visit(splitLine, splitCurve, null, splitMove); splitPaths.push(splitPath); var result = []; for (var i = 0, l = splitPaths.length; i < l; i++){ var p = createElement('path'); p.setAttribute('d', splitPaths[i].join(' ')); p.setAttribute('id', id + i); result.push(p); } this.pathElements = result; this._injectPaths(); }, _whileInDocument: function(fn, bind){

Temporarily inject into the document

  1. 578
  2. 579
  3. 580
  4. 581
  5. 582
  6. 583
  7. 584
  8. 585
  9. 586
  10. 587
  11. 588
  12. 589
  13. 590
  14. 591
  15. 592
  16. 593
  17. 594
var element = this.element, container = this.container, parent = element.parentNode, sibling = element.nextSibling, body = element.ownerDocument.body, canvas = new ART.SVG(1, 1).inject(body); this.inject(canvas); var result = fn.call(bind); canvas.eject(); if (container) this.inject(container); if (parent) parent.insertBefore(element, sibling); return result; } }); })();