@joint/core 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/LICENSE +376 -0
  2. package/README.md +49 -0
  3. package/dist/geometry.js +6486 -0
  4. package/dist/geometry.min.js +8 -0
  5. package/dist/joint.d.ts +5536 -0
  6. package/dist/joint.js +39629 -0
  7. package/dist/joint.min.js +8 -0
  8. package/dist/joint.nowrap.js +39626 -0
  9. package/dist/joint.nowrap.min.js +8 -0
  10. package/dist/vectorizer.js +9135 -0
  11. package/dist/vectorizer.min.js +8 -0
  12. package/dist/version.mjs +3 -0
  13. package/index.js +3 -0
  14. package/joint.mjs +27 -0
  15. package/package.json +192 -0
  16. package/src/V/annotation.mjs +0 -0
  17. package/src/V/index.mjs +2642 -0
  18. package/src/anchors/index.mjs +123 -0
  19. package/src/config/index.mjs +12 -0
  20. package/src/connectionPoints/index.mjs +202 -0
  21. package/src/connectionStrategies/index.mjs +73 -0
  22. package/src/connectors/curve.mjs +553 -0
  23. package/src/connectors/index.mjs +6 -0
  24. package/src/connectors/jumpover.mjs +452 -0
  25. package/src/connectors/normal.mjs +12 -0
  26. package/src/connectors/rounded.mjs +17 -0
  27. package/src/connectors/smooth.mjs +44 -0
  28. package/src/connectors/straight.mjs +110 -0
  29. package/src/dia/Cell.mjs +945 -0
  30. package/src/dia/CellView.mjs +1316 -0
  31. package/src/dia/Element.mjs +519 -0
  32. package/src/dia/ElementView.mjs +859 -0
  33. package/src/dia/Graph.mjs +1112 -0
  34. package/src/dia/HighlighterView.mjs +319 -0
  35. package/src/dia/Link.mjs +565 -0
  36. package/src/dia/LinkView.mjs +2207 -0
  37. package/src/dia/Paper.mjs +3171 -0
  38. package/src/dia/PaperLayer.mjs +75 -0
  39. package/src/dia/ToolView.mjs +69 -0
  40. package/src/dia/ToolsView.mjs +128 -0
  41. package/src/dia/attributes/calc.mjs +128 -0
  42. package/src/dia/attributes/connection.mjs +75 -0
  43. package/src/dia/attributes/defs.mjs +76 -0
  44. package/src/dia/attributes/eval.mjs +64 -0
  45. package/src/dia/attributes/index.mjs +69 -0
  46. package/src/dia/attributes/legacy.mjs +148 -0
  47. package/src/dia/attributes/offset.mjs +53 -0
  48. package/src/dia/attributes/props.mjs +30 -0
  49. package/src/dia/attributes/shape.mjs +92 -0
  50. package/src/dia/attributes/text.mjs +180 -0
  51. package/src/dia/index.mjs +13 -0
  52. package/src/dia/layers/GridLayer.mjs +176 -0
  53. package/src/dia/ports.mjs +874 -0
  54. package/src/elementTools/Control.mjs +153 -0
  55. package/src/elementTools/HoverConnect.mjs +37 -0
  56. package/src/elementTools/index.mjs +5 -0
  57. package/src/env/index.mjs +43 -0
  58. package/src/g/bezier.mjs +175 -0
  59. package/src/g/curve.mjs +956 -0
  60. package/src/g/ellipse.mjs +245 -0
  61. package/src/g/extend.mjs +64 -0
  62. package/src/g/geometry.helpers.mjs +58 -0
  63. package/src/g/index.mjs +17 -0
  64. package/src/g/intersection.mjs +511 -0
  65. package/src/g/line.bearing.mjs +30 -0
  66. package/src/g/line.length.mjs +5 -0
  67. package/src/g/line.mjs +356 -0
  68. package/src/g/line.squaredLength.mjs +10 -0
  69. package/src/g/path.mjs +2260 -0
  70. package/src/g/point.mjs +375 -0
  71. package/src/g/points.mjs +247 -0
  72. package/src/g/polygon.mjs +51 -0
  73. package/src/g/polyline.mjs +523 -0
  74. package/src/g/rect.mjs +556 -0
  75. package/src/g/types.mjs +10 -0
  76. package/src/highlighters/addClass.mjs +27 -0
  77. package/src/highlighters/index.mjs +5 -0
  78. package/src/highlighters/list.mjs +111 -0
  79. package/src/highlighters/mask.mjs +220 -0
  80. package/src/highlighters/opacity.mjs +17 -0
  81. package/src/highlighters/stroke.mjs +100 -0
  82. package/src/layout/index.mjs +4 -0
  83. package/src/layout/ports/port.mjs +188 -0
  84. package/src/layout/ports/portLabel.mjs +224 -0
  85. package/src/linkAnchors/index.mjs +76 -0
  86. package/src/linkTools/Anchor.mjs +235 -0
  87. package/src/linkTools/Arrowhead.mjs +103 -0
  88. package/src/linkTools/Boundary.mjs +48 -0
  89. package/src/linkTools/Button.mjs +121 -0
  90. package/src/linkTools/Connect.mjs +85 -0
  91. package/src/linkTools/HoverConnect.mjs +161 -0
  92. package/src/linkTools/Segments.mjs +393 -0
  93. package/src/linkTools/Vertices.mjs +253 -0
  94. package/src/linkTools/helpers.mjs +33 -0
  95. package/src/linkTools/index.mjs +8 -0
  96. package/src/mvc/Collection.mjs +560 -0
  97. package/src/mvc/Data.mjs +46 -0
  98. package/src/mvc/Dom/Dom.mjs +587 -0
  99. package/src/mvc/Dom/Event.mjs +130 -0
  100. package/src/mvc/Dom/animations.mjs +122 -0
  101. package/src/mvc/Dom/events.mjs +69 -0
  102. package/src/mvc/Dom/index.mjs +13 -0
  103. package/src/mvc/Dom/methods.mjs +392 -0
  104. package/src/mvc/Dom/props.mjs +77 -0
  105. package/src/mvc/Dom/vars.mjs +5 -0
  106. package/src/mvc/Events.mjs +337 -0
  107. package/src/mvc/Listener.mjs +33 -0
  108. package/src/mvc/Model.mjs +239 -0
  109. package/src/mvc/View.mjs +323 -0
  110. package/src/mvc/ViewBase.mjs +182 -0
  111. package/src/mvc/index.mjs +9 -0
  112. package/src/mvc/mvcUtils.mjs +90 -0
  113. package/src/polyfills/array.js +4 -0
  114. package/src/polyfills/base64.js +68 -0
  115. package/src/polyfills/index.mjs +5 -0
  116. package/src/polyfills/number.js +3 -0
  117. package/src/polyfills/string.js +3 -0
  118. package/src/polyfills/typedArray.js +47 -0
  119. package/src/routers/index.mjs +6 -0
  120. package/src/routers/manhattan.mjs +856 -0
  121. package/src/routers/metro.mjs +91 -0
  122. package/src/routers/normal.mjs +6 -0
  123. package/src/routers/oneSide.mjs +60 -0
  124. package/src/routers/orthogonal.mjs +323 -0
  125. package/src/routers/rightAngle.mjs +1056 -0
  126. package/src/shapes/index.mjs +3 -0
  127. package/src/shapes/standard.mjs +755 -0
  128. package/src/util/cloneCells.mjs +67 -0
  129. package/src/util/getRectPoint.mjs +65 -0
  130. package/src/util/index.mjs +5 -0
  131. package/src/util/svgTagTemplate.mjs +110 -0
  132. package/src/util/util.mjs +1754 -0
  133. package/src/util/utilHelpers.mjs +2402 -0
  134. package/src/util/wrappers.mjs +56 -0
  135. package/types/geometry.d.ts +815 -0
  136. package/types/index.d.ts +53 -0
  137. package/types/joint.d.ts +4391 -0
  138. package/types/joint.head.d.ts +12 -0
  139. package/types/vectorizer.d.ts +327 -0
@@ -0,0 +1,2642 @@
1
+ // Vectorizer.
2
+ // -----------
3
+
4
+ // A tiny library for making your life easier when dealing with SVG.
5
+ // The only Vectorizer dependency is the Geometry library.
6
+
7
+ import * as g from '../g/index.mjs';
8
+
9
+ const V = (function() {
10
+
11
+ var hasSvg = typeof window === 'object' && !!window.SVGAngle;
12
+
13
+ // SVG support is required.
14
+ if (!hasSvg) {
15
+
16
+ // Return a function that throws an error when it is used.
17
+ return function() {
18
+ throw new Error('SVG is required to use Vectorizer.');
19
+ };
20
+ }
21
+
22
+ // XML namespaces.
23
+ var ns = {
24
+ svg: 'http://www.w3.org/2000/svg',
25
+ xmlns: 'http://www.w3.org/2000/xmlns/',
26
+ xml: 'http://www.w3.org/XML/1998/namespace',
27
+ xlink: 'http://www.w3.org/1999/xlink',
28
+ xhtml: 'http://www.w3.org/1999/xhtml'
29
+ };
30
+
31
+ var SVGVersion = '1.1';
32
+
33
+ // Declare shorthands to the most used math functions.
34
+ var math = Math;
35
+ var PI = math.PI;
36
+ var atan2 = math.atan2;
37
+ var sqrt = math.sqrt;
38
+ var min = math.min;
39
+ var max = math.max;
40
+ var cos = math.cos;
41
+ var sin = math.sin;
42
+
43
+ var V = function(el, attrs, children) {
44
+
45
+ // This allows using V() without the new keyword.
46
+ if (!(this instanceof V)) {
47
+ return V.apply(Object.create(V.prototype), arguments);
48
+ }
49
+
50
+ if (!el) return;
51
+
52
+ if (V.isV(el)) {
53
+ el = el.node;
54
+ }
55
+
56
+ attrs = attrs || {};
57
+
58
+ if (V.isString(el)) {
59
+
60
+ el = el.trim();
61
+
62
+ if (el.toLowerCase() === 'svg') {
63
+
64
+ // Create a new SVG canvas.
65
+ el = V.createSvgDocument();
66
+
67
+ } else if (el[0] === '<') {
68
+
69
+ // Create element from an SVG string.
70
+ // Allows constructs of type: `document.appendChild(V('<rect></rect>').node)`.
71
+
72
+ var svgDoc = V.createSvgDocument(el);
73
+
74
+ // Note that `V()` might also return an array should the SVG string passed as
75
+ // the first argument contain more than one root element.
76
+ if (svgDoc.childNodes.length > 1) {
77
+
78
+ // Map child nodes to `V`s.
79
+ var arrayOfVels = [];
80
+ var i, len;
81
+
82
+ for (i = 0, len = svgDoc.childNodes.length; i < len; i++) {
83
+
84
+ var childNode = svgDoc.childNodes[i];
85
+ arrayOfVels.push(new V(document.importNode(childNode, true)));
86
+ }
87
+
88
+ return arrayOfVels;
89
+ }
90
+
91
+ el = document.importNode(svgDoc.firstChild, true);
92
+
93
+ } else {
94
+
95
+ el = document.createElementNS(ns.svg, el);
96
+ }
97
+
98
+ V.ensureId(el);
99
+ }
100
+
101
+ this.node = el;
102
+
103
+ this.setAttributes(attrs);
104
+
105
+ if (children) {
106
+ this.append(children);
107
+ }
108
+
109
+ return this;
110
+ };
111
+
112
+ var VPrototype = V.prototype;
113
+
114
+ Object.defineProperty(VPrototype, 'id', {
115
+ enumerable: true,
116
+ get: function() {
117
+ return this.node.id;
118
+ },
119
+ set: function(id) {
120
+ this.node.id = id;
121
+ }
122
+ });
123
+
124
+ /**
125
+ * @param {SVGGElement} toElem
126
+ * @returns {SVGMatrix}
127
+ */
128
+ VPrototype.getTransformToElement = function(target) {
129
+ var node = this.node;
130
+ if (V.isSVGGraphicsElement(target) && V.isSVGGraphicsElement(node)) {
131
+ var targetCTM = V.toNode(target).getScreenCTM();
132
+ var nodeCTM = node.getScreenCTM();
133
+ if (targetCTM && nodeCTM) {
134
+ return targetCTM.inverse().multiply(nodeCTM);
135
+ }
136
+ }
137
+ // Could not get actual transformation matrix
138
+ return V.createSVGMatrix();
139
+ };
140
+
141
+ /**
142
+ * @param {SVGMatrix} matrix
143
+ * @param {Object=} opt
144
+ * @returns {Vectorizer|SVGMatrix} Setter / Getter
145
+ */
146
+ VPrototype.transform = function(matrix, opt) {
147
+
148
+ var node = this.node;
149
+ if (V.isUndefined(matrix)) {
150
+ return V.transformStringToMatrix(this.attr('transform'));
151
+ }
152
+
153
+ if (opt && opt.absolute) {
154
+ return this.attr('transform', V.matrixToTransformString(matrix));
155
+ }
156
+
157
+ var svgTransform = V.createSVGTransform(matrix);
158
+ node.transform.baseVal.appendItem(svgTransform);
159
+ return this;
160
+ };
161
+
162
+ VPrototype.translate = function(tx, ty, opt) {
163
+
164
+ opt = opt || {};
165
+ ty = ty || 0;
166
+
167
+ var transformAttr = this.attr('transform') || '';
168
+ var transform = V.parseTransformString(transformAttr);
169
+ transformAttr = transform.value;
170
+ // Is it a getter?
171
+ if (V.isUndefined(tx)) {
172
+ return transform.translate;
173
+ }
174
+
175
+ transformAttr = transformAttr.replace(/translate\([^)]*\)/g, '').trim();
176
+
177
+ var newTx = opt.absolute ? tx : transform.translate.tx + tx;
178
+ var newTy = opt.absolute ? ty : transform.translate.ty + ty;
179
+ var newTranslate = 'translate(' + newTx + ',' + newTy + ')';
180
+
181
+ // Note that `translate()` is always the first transformation. This is
182
+ // usually the desired case.
183
+ this.attr('transform', (newTranslate + ' ' + transformAttr).trim());
184
+ return this;
185
+ };
186
+
187
+ VPrototype.rotate = function(angle, cx, cy, opt) {
188
+
189
+ opt = opt || {};
190
+
191
+ var transformAttr = this.attr('transform') || '';
192
+ var transform = V.parseTransformString(transformAttr);
193
+ transformAttr = transform.value;
194
+
195
+ // Is it a getter?
196
+ if (V.isUndefined(angle)) {
197
+ return transform.rotate;
198
+ }
199
+
200
+ transformAttr = transformAttr.replace(/rotate\([^)]*\)/g, '').trim();
201
+
202
+ angle %= 360;
203
+
204
+ var newAngle = opt.absolute ? angle : transform.rotate.angle + angle;
205
+ var newOrigin = (cx !== undefined && cy !== undefined) ? ',' + cx + ',' + cy : '';
206
+ var newRotate = 'rotate(' + newAngle + newOrigin + ')';
207
+
208
+ this.attr('transform', (transformAttr + ' ' + newRotate).trim());
209
+ return this;
210
+ };
211
+
212
+ // Note that `scale` as the only transformation does not combine with previous values.
213
+ VPrototype.scale = function(sx, sy) {
214
+
215
+ sy = V.isUndefined(sy) ? sx : sy;
216
+
217
+ var transformAttr = this.attr('transform') || '';
218
+ var transform = V.parseTransformString(transformAttr);
219
+ transformAttr = transform.value;
220
+
221
+ // Is it a getter?
222
+ if (V.isUndefined(sx)) {
223
+ return transform.scale;
224
+ }
225
+
226
+ transformAttr = transformAttr.replace(/scale\([^)]*\)/g, '').trim();
227
+
228
+ var newScale = 'scale(' + sx + ',' + sy + ')';
229
+
230
+ this.attr('transform', (transformAttr + ' ' + newScale).trim());
231
+ return this;
232
+ };
233
+
234
+ // Get SVGRect that contains coordinates and dimension of the real bounding box,
235
+ // i.e. after transformations are applied.
236
+ // If `target` is specified, bounding box will be computed relatively to `target` element.
237
+ VPrototype.bbox = function(withoutTransformations, target) {
238
+
239
+ var box;
240
+ var node = this.node;
241
+ var ownerSVGElement = node.ownerSVGElement;
242
+
243
+ // If the element is not in the live DOM, it does not have a bounding box defined and
244
+ // so fall back to 'zero' dimension element.
245
+ if (!ownerSVGElement) {
246
+ return new g.Rect(0, 0, 0, 0);
247
+ }
248
+
249
+ try {
250
+
251
+ box = node.getBBox();
252
+
253
+ } catch (e) {
254
+
255
+ // Fallback for IE.
256
+ box = {
257
+ x: node.clientLeft,
258
+ y: node.clientTop,
259
+ width: node.clientWidth,
260
+ height: node.clientHeight
261
+ };
262
+ }
263
+
264
+ if (withoutTransformations) {
265
+ return new g.Rect(box);
266
+ }
267
+
268
+ var matrix = this.getTransformToElement(target || ownerSVGElement);
269
+
270
+ return V.transformRect(box, matrix);
271
+ };
272
+
273
+ // Returns an SVGRect that contains coordinates and dimensions of the real bounding box,
274
+ // i.e. after transformations are applied.
275
+ // Fixes a browser implementation bug that returns incorrect bounding boxes for groups of svg elements.
276
+ // Takes an (Object) `opt` argument (optional) with the following attributes:
277
+ // (Object) `target` (optional): if not undefined, transform bounding boxes relative to `target`; if undefined, transform relative to this
278
+ // (Boolean) `recursive` (optional): if true, recursively enter all groups and get a union of element bounding boxes (svg bbox fix); if false or undefined, return result of native function this.node.getBBox();
279
+ VPrototype.getBBox = function(opt) {
280
+
281
+ var options = {};
282
+
283
+ var outputBBox;
284
+ var node = this.node;
285
+ var ownerSVGElement = node.ownerSVGElement;
286
+
287
+ // If the element is not in the live DOM, it does not have a bounding box defined and
288
+ // so fall back to 'zero' dimension element.
289
+ // If the element is not an SVGGraphicsElement, we could not measure the bounding box either
290
+ if (!ownerSVGElement || !V.isSVGGraphicsElement(node)) {
291
+ return new g.Rect(0, 0, 0, 0);
292
+ }
293
+
294
+ if (opt) {
295
+ if (opt.target) { // check if target exists
296
+ options.target = V.toNode(opt.target); // works for V objects, jquery objects, and node objects
297
+ }
298
+ if (opt.recursive) {
299
+ options.recursive = opt.recursive;
300
+ }
301
+ }
302
+
303
+ if (!options.recursive) {
304
+ try {
305
+ outputBBox = node.getBBox();
306
+ } catch (e) {
307
+ // Fallback for IE.
308
+ outputBBox = {
309
+ x: node.clientLeft,
310
+ y: node.clientTop,
311
+ width: node.clientWidth,
312
+ height: node.clientHeight
313
+ };
314
+ }
315
+
316
+ if (!options.target) {
317
+ // transform like this (that is, not at all)
318
+ return new g.Rect(outputBBox);
319
+ } else {
320
+ // transform like target
321
+ var matrix = this.getTransformToElement(options.target);
322
+ return V.transformRect(outputBBox, matrix);
323
+ }
324
+ } else { // if we want to calculate the bbox recursively
325
+ // browsers report correct bbox around svg elements (one that envelops the path lines tightly)
326
+ // but some browsers fail to report the same bbox when the elements are in a group (returning a looser bbox that also includes control points, like node.getClientRect())
327
+ // this happens even if we wrap a single svg element into a group!
328
+ // this option setting makes the function recursively enter all the groups from this and deeper, get bboxes of the elements inside, then return a union of those bboxes
329
+
330
+ var children = this.children();
331
+ var n = children.length;
332
+
333
+ if (n === 0) {
334
+ return this.getBBox({ target: options.target, recursive: false });
335
+ }
336
+
337
+ // recursion's initial pass-through setting:
338
+ // recursive passes-through just keep the target as whatever was set up here during the initial pass-through
339
+ if (!options.target) {
340
+ // transform children/descendants like this (their parent/ancestor)
341
+ options.target = this;
342
+ } // else transform children/descendants like target
343
+
344
+ for (var i = 0; i < n; i++) {
345
+ var currentChild = children[i];
346
+
347
+ var childBBox;
348
+
349
+ // if currentChild is not a group element, get its bbox with a nonrecursive call
350
+ if (currentChild.children().length === 0) {
351
+ childBBox = currentChild.getBBox({ target: options.target, recursive: false });
352
+ } else {
353
+ // if currentChild is a group element (determined by checking the number of children), enter it with a recursive call
354
+ childBBox = currentChild.getBBox({ target: options.target, recursive: true });
355
+ }
356
+
357
+ if (!outputBBox) {
358
+ // if this is the first iteration
359
+ outputBBox = childBBox;
360
+ } else {
361
+ // make a new bounding box rectangle that contains this child's bounding box and previous bounding box
362
+ outputBBox = outputBBox.union(childBBox);
363
+ }
364
+ }
365
+
366
+ return outputBBox;
367
+ }
368
+ };
369
+
370
+ // Text() helpers
371
+
372
+ function createTextPathNode(attrs, vel) {
373
+ attrs || (attrs = {});
374
+ var textPathElement = V('textPath');
375
+ var d = attrs.d;
376
+ if (d && attrs['xlink:href'] === undefined) {
377
+ // If `opt.attrs` is a plain string, consider it to be directly the
378
+ // SVG path data for the text to go along (this is a shortcut).
379
+ // Otherwise if it is an object and contains the `d` property, then this is our path.
380
+ // Wrap the text in the SVG <textPath> element that points
381
+ // to a path defined by `opt.attrs` inside the `<defs>` element.
382
+ var linkedPath = V('path').attr('d', d).appendTo(vel.defs());
383
+ textPathElement.attr('xlink:href', '#' + linkedPath.id);
384
+ }
385
+ if (V.isObject(attrs)) {
386
+ // Set attributes on the `<textPath>`. The most important one
387
+ // is the `xlink:href` that points to our newly created `<path/>` element in `<defs/>`.
388
+ // Note that we also allow the following construct:
389
+ // `t.text('my text', { textPath: { 'xlink:href': '#my-other-path' } })`.
390
+ // In other words, one can completely skip the auto-creation of the path
391
+ // and use any other arbitrary path that is in the document.
392
+ textPathElement.attr(attrs);
393
+ }
394
+ return textPathElement.node;
395
+ }
396
+
397
+ function annotateTextLine(lineNode, lineAnnotations, opt) {
398
+ opt || (opt = {});
399
+ var includeAnnotationIndices = opt.includeAnnotationIndices;
400
+ var eol = opt.eol;
401
+ var lineHeight = opt.lineHeight;
402
+ var baseSize = opt.baseSize;
403
+ var maxFontSize = 0;
404
+ var fontMetrics = {};
405
+ var lastJ = lineAnnotations.length - 1;
406
+ for (var j = 0; j <= lastJ; j++) {
407
+ var annotation = lineAnnotations[j];
408
+ var fontSize = null;
409
+ if (V.isObject(annotation)) {
410
+ var annotationAttrs = annotation.attrs;
411
+ var vTSpan = V('tspan', annotationAttrs);
412
+ var tspanNode = vTSpan.node;
413
+ var t = annotation.t;
414
+ if (eol && j === lastJ) t += eol;
415
+ tspanNode.textContent = t;
416
+ // Per annotation className
417
+ var annotationClass = annotationAttrs['class'];
418
+ if (annotationClass) vTSpan.addClass(annotationClass);
419
+ // If `opt.includeAnnotationIndices` is `true`,
420
+ // set the list of indices of all the applied annotations
421
+ // in the `annotations` attribute. This list is a comma
422
+ // separated list of indices.
423
+ if (includeAnnotationIndices) vTSpan.attr('annotations', annotation.annotations);
424
+ // Check for max font size
425
+ fontSize = parseFloat(annotationAttrs['font-size']);
426
+ if (!isFinite(fontSize)) fontSize = baseSize;
427
+ if (fontSize && fontSize > maxFontSize) maxFontSize = fontSize;
428
+ } else {
429
+ if (eol && j === lastJ) annotation += eol;
430
+ tspanNode = document.createTextNode(annotation || ' ');
431
+ if (baseSize && baseSize > maxFontSize) maxFontSize = baseSize;
432
+ }
433
+ lineNode.appendChild(tspanNode);
434
+ }
435
+
436
+ if (maxFontSize) fontMetrics.maxFontSize = maxFontSize;
437
+ if (lineHeight) {
438
+ fontMetrics.lineHeight = lineHeight;
439
+ } else if (maxFontSize) {
440
+ fontMetrics.lineHeight = (maxFontSize * 1.2);
441
+ }
442
+ return fontMetrics;
443
+ }
444
+
445
+ var emRegex = /em$/;
446
+
447
+ function convertEmToPx(em, fontSize) {
448
+ var numerical = parseFloat(em);
449
+ if (emRegex.test(em)) return numerical * fontSize;
450
+ return numerical;
451
+ }
452
+
453
+ function calculateDY(alignment, linesMetrics, baseSizePx, lineHeight) {
454
+ if (!Array.isArray(linesMetrics)) return 0;
455
+ var n = linesMetrics.length;
456
+ if (!n) return 0;
457
+ var lineMetrics = linesMetrics[0];
458
+ var flMaxFont = convertEmToPx(lineMetrics.maxFontSize, baseSizePx) || baseSizePx;
459
+ var rLineHeights = 0;
460
+ var lineHeightPx = convertEmToPx(lineHeight, baseSizePx);
461
+ for (var i = 1; i < n; i++) {
462
+ lineMetrics = linesMetrics[i];
463
+ var iLineHeight = convertEmToPx(lineMetrics.lineHeight, baseSizePx) || lineHeightPx;
464
+ rLineHeights += iLineHeight;
465
+ }
466
+ var llMaxFont = convertEmToPx(lineMetrics.maxFontSize, baseSizePx) || baseSizePx;
467
+ var dy;
468
+ switch (alignment) {
469
+ case 'middle':
470
+ dy = (flMaxFont / 2) - (0.15 * llMaxFont) - (rLineHeights / 2);
471
+ break;
472
+ case 'bottom':
473
+ dy = -(0.25 * llMaxFont) - rLineHeights;
474
+ break;
475
+ default:
476
+ case 'top':
477
+ dy = (0.8 * flMaxFont);
478
+ break;
479
+ }
480
+ return dy;
481
+ }
482
+
483
+ VPrototype.text = function(content, opt) {
484
+
485
+ if (content && typeof content !== 'string') throw new Error('Vectorizer: text() expects the first argument to be a string.');
486
+
487
+ // Replace all spaces with the Unicode No-break space (http://www.fileformat.info/info/unicode/char/a0/index.htm).
488
+ // IE would otherwise collapse all spaces into one.
489
+ content = V.sanitizeText(content);
490
+ opt || (opt = {});
491
+ // Should we allow the text to be selected?
492
+ var displayEmpty = opt.displayEmpty;
493
+ // End of Line character
494
+ var eol = opt.eol;
495
+ // Text along path
496
+ var textPath = opt.textPath;
497
+ // Vertical shift
498
+ var verticalAnchor = opt.textVerticalAnchor;
499
+ var namedVerticalAnchor = (verticalAnchor === 'middle' || verticalAnchor === 'bottom' || verticalAnchor === 'top');
500
+ // Horizontal shift applied to all the lines but the first.
501
+ var x = opt.x;
502
+ if (x === undefined) x = this.attr('x') || 0;
503
+ // Annotations
504
+ var iai = opt.includeAnnotationIndices;
505
+ var annotations = opt.annotations;
506
+ if (annotations && !V.isArray(annotations)) annotations = [annotations];
507
+ // Shift all the <tspan> but first by one line (`1em`)
508
+ var defaultLineHeight = opt.lineHeight;
509
+ var autoLineHeight = (defaultLineHeight === 'auto');
510
+ var lineHeight = (autoLineHeight) ? '1.5em' : (defaultLineHeight || '1em');
511
+ // Clearing the element
512
+ this.empty();
513
+ this.attr({
514
+ // Preserve spaces. In other words, we do not want consecutive spaces to get collapsed to one.
515
+ 'xml:space': 'preserve',
516
+ // An empty text gets rendered into the DOM in webkit-based browsers.
517
+ // In order to unify this behaviour across all browsers
518
+ // we rather hide the text element when it's empty.
519
+ 'display': (content || displayEmpty) ? null : 'none'
520
+ });
521
+
522
+ // Set default font-size if none
523
+ var fontSize = parseFloat(this.attr('font-size'));
524
+ if (!fontSize) {
525
+ fontSize = 16;
526
+ if (namedVerticalAnchor || annotations) this.attr('font-size', fontSize);
527
+ }
528
+
529
+ var doc = document;
530
+ var containerNode;
531
+ if (textPath) {
532
+ // Now all the `<tspan>`s will be inside the `<textPath>`.
533
+ if (typeof textPath === 'string') textPath = { d: textPath };
534
+ containerNode = createTextPathNode(textPath, this);
535
+ } else {
536
+ containerNode = doc.createDocumentFragment();
537
+ }
538
+ var offset = 0;
539
+ var lines = content.split('\n');
540
+ var linesMetrics = [];
541
+ var annotatedY;
542
+ for (var i = 0, lastI = lines.length - 1; i <= lastI; i++) {
543
+ var dy = lineHeight;
544
+ var lineClassName = 'v-line';
545
+ var lineNode = doc.createElementNS(ns.svg, 'tspan');
546
+ var line = lines[i];
547
+ var lineMetrics;
548
+ if (line) {
549
+ if (annotations) {
550
+ // Find the *compacted* annotations for this line.
551
+ var lineAnnotations = V.annotateString(line, annotations, {
552
+ offset: -offset,
553
+ includeAnnotationIndices: iai
554
+ });
555
+ lineMetrics = annotateTextLine(lineNode, lineAnnotations, {
556
+ includeAnnotationIndices: iai,
557
+ eol: (i !== lastI && eol),
558
+ lineHeight: (autoLineHeight) ? null : lineHeight,
559
+ baseSize: fontSize
560
+ });
561
+ // Get the line height based on the biggest font size in the annotations for this line.
562
+ var iLineHeight = lineMetrics.lineHeight;
563
+ if (iLineHeight && autoLineHeight && i !== 0) dy = iLineHeight;
564
+ if (i === 0) annotatedY = lineMetrics.maxFontSize * 0.8;
565
+ } else {
566
+ if (eol && i !== lastI) line += eol;
567
+ lineNode.textContent = line;
568
+ }
569
+ } else {
570
+ // Make sure the textContent is never empty. If it is, add a dummy
571
+ // character and make it invisible, making the following lines correctly
572
+ // relatively positioned. `dy=1em` won't work with empty lines otherwise.
573
+ lineNode.textContent = '-';
574
+ lineClassName += ' v-empty-line';
575
+ // 'opacity' needs to be specified with fill, stroke. Opacity without specification
576
+ // is not applied in Firefox
577
+ var lineNodeStyle = lineNode.style;
578
+ lineNodeStyle.fillOpacity = 0;
579
+ lineNodeStyle.strokeOpacity = 0;
580
+ if (annotations) {
581
+ // Empty line with annotations.
582
+ lineMetrics = {};
583
+ lineAnnotations = V.findAnnotationsAtIndex(annotations, offset);
584
+ let lineFontSize = fontSize;
585
+ // Check if any of the annotations overrides the font size.
586
+ for (let j = lineAnnotations.length; j > 0; j--) {
587
+ const attrs = lineAnnotations[j - 1].attrs;
588
+ if (!attrs || !('font-size' in attrs)) continue;
589
+ const fs = parseFloat(attrs['font-size']);
590
+ if (isFinite(fs)) {
591
+ lineFontSize = fs;
592
+ break;
593
+ }
594
+ }
595
+ if (autoLineHeight) {
596
+ if (i > 0) {
597
+ dy = lineFontSize * 1.2;
598
+ } else {
599
+ annotatedY = lineFontSize * 0.8;
600
+ }
601
+ }
602
+ // The font size is important for the native selection box height.
603
+ lineNode.setAttribute('font-size', lineFontSize);
604
+ lineMetrics.maxFontSize = lineFontSize;
605
+ }
606
+ }
607
+ if (lineMetrics) linesMetrics.push(lineMetrics);
608
+ if (i > 0) lineNode.setAttribute('dy', dy);
609
+ // Firefox requires 'x' to be set on the first line when inside a text path
610
+ if (i > 0 || textPath) lineNode.setAttribute('x', x);
611
+ lineNode.className.baseVal = lineClassName;
612
+ containerNode.appendChild(lineNode);
613
+ offset += line.length + 1; // + 1 = newline character.
614
+ }
615
+ // Y Alignment calculation
616
+ if (namedVerticalAnchor) {
617
+ if (annotations) {
618
+ dy = calculateDY(verticalAnchor, linesMetrics, fontSize, lineHeight);
619
+ } else if (verticalAnchor === 'top') {
620
+ // A shortcut for top alignment. It does not depend on font-size nor line-height
621
+ dy = '0.8em';
622
+ } else {
623
+ var rh; // remaining height
624
+ if (lastI > 0) {
625
+ rh = parseFloat(lineHeight) || 1;
626
+ rh *= lastI;
627
+ if (!emRegex.test(lineHeight)) rh /= fontSize;
628
+ } else {
629
+ // Single-line text
630
+ rh = 0;
631
+ }
632
+ switch (verticalAnchor) {
633
+ case 'middle':
634
+ dy = (0.3 - (rh / 2)) + 'em';
635
+ break;
636
+ case 'bottom':
637
+ dy = (-rh - 0.3) + 'em';
638
+ break;
639
+ }
640
+ }
641
+ } else {
642
+ if (verticalAnchor === 0) {
643
+ dy = '0em';
644
+ } else if (verticalAnchor) {
645
+ dy = verticalAnchor;
646
+ } else {
647
+ // No vertical anchor is defined
648
+ dy = 0;
649
+ // Backwards compatibility - we change the `y` attribute instead of `dy`.
650
+ if (this.attr('y') === null) this.attr('y', annotatedY || '0.8em');
651
+ }
652
+ }
653
+ containerNode.firstChild.setAttribute('dy', dy);
654
+ // Appending lines to the element.
655
+ this.append(containerNode);
656
+ return this;
657
+ };
658
+
659
+ /**
660
+ * @public
661
+ * @param {string} name
662
+ * @returns {Vectorizer}
663
+ */
664
+ VPrototype.removeAttr = function(name) {
665
+
666
+ const trueName = attributeNames[name];
667
+
668
+ const { ns, local } = V.qualifyAttr(trueName);
669
+ const el = this.node;
670
+
671
+ if (ns) {
672
+ if (el.hasAttributeNS(ns, local)) {
673
+ el.removeAttributeNS(ns, local);
674
+ }
675
+ } else if (el.hasAttribute(trueName)) {
676
+ el.removeAttribute(trueName);
677
+ }
678
+ return this;
679
+ };
680
+
681
+ VPrototype.attr = function(name, value) {
682
+
683
+ if (V.isUndefined(name)) {
684
+
685
+ // Return all attributes.
686
+ var attributes = this.node.attributes;
687
+ var attrs = {};
688
+
689
+ for (var i = 0; i < attributes.length; i++) {
690
+ attrs[attributes[i].name] = attributes[i].value;
691
+ }
692
+
693
+ return attrs;
694
+ }
695
+
696
+ if (V.isString(name) && V.isUndefined(value)) {
697
+ return this.node.getAttribute(attributeNames[name]);
698
+ }
699
+
700
+ if (typeof name === 'object') {
701
+
702
+ for (var attrName in name) {
703
+ if (name.hasOwnProperty(attrName)) {
704
+ this.setAttribute(attrName, name[attrName]);
705
+ }
706
+ }
707
+
708
+ } else {
709
+
710
+ this.setAttribute(name, value);
711
+ }
712
+
713
+ return this;
714
+ };
715
+
716
+ VPrototype.normalizePath = function() {
717
+
718
+ var tagName = this.tagName();
719
+ if (tagName === 'PATH') {
720
+ this.attr('d', V.normalizePathData(this.attr('d')));
721
+ }
722
+
723
+ return this;
724
+ };
725
+
726
+ VPrototype.remove = function() {
727
+
728
+ if (this.node.parentNode) {
729
+ this.node.parentNode.removeChild(this.node);
730
+ }
731
+
732
+ return this;
733
+ };
734
+
735
+ VPrototype.empty = function() {
736
+
737
+ while (this.node.firstChild) {
738
+ this.node.removeChild(this.node.firstChild);
739
+ }
740
+
741
+ return this;
742
+ };
743
+
744
+ /**
745
+ * @private
746
+ * @param {object} attrs
747
+ * @returns {Vectorizer}
748
+ */
749
+ VPrototype.setAttributes = function(attrs) {
750
+
751
+ for (var key in attrs) {
752
+ if (attrs.hasOwnProperty(key)) {
753
+ this.setAttribute(key, attrs[key]);
754
+ }
755
+ }
756
+
757
+ return this;
758
+ };
759
+
760
+ VPrototype.append = function(els) {
761
+
762
+ if (!V.isArray(els)) {
763
+ els = [els];
764
+ }
765
+
766
+ for (var i = 0, len = els.length; i < len; i++) {
767
+ this.node.appendChild(V.toNode(els[i])); // lgtm [js/xss-through-dom]
768
+ }
769
+
770
+ return this;
771
+ };
772
+
773
+ VPrototype.prepend = function(els) {
774
+
775
+ var child = this.node.firstChild;
776
+ return child ? V(child).before(els) : this.append(els);
777
+ };
778
+
779
+ VPrototype.before = function(els) {
780
+
781
+ var node = this.node;
782
+ var parent = node.parentNode;
783
+
784
+ if (parent) {
785
+
786
+ if (!V.isArray(els)) {
787
+ els = [els];
788
+ }
789
+
790
+ for (var i = 0, len = els.length; i < len; i++) {
791
+ parent.insertBefore(V.toNode(els[i]), node);
792
+ }
793
+ }
794
+
795
+ return this;
796
+ };
797
+
798
+ VPrototype.appendTo = function(node) {
799
+ V.toNode(node).appendChild(this.node); // lgtm [js/xss-through-dom]
800
+ return this;
801
+ };
802
+
803
+ VPrototype.svg = function() {
804
+
805
+ return this.node instanceof window.SVGSVGElement ? this : V(this.node.ownerSVGElement);
806
+ };
807
+
808
+ VPrototype.tagName = function() {
809
+
810
+ return this.node.tagName.toUpperCase();
811
+ };
812
+
813
+ VPrototype.defs = function() {
814
+ var context = this.svg() || this;
815
+ var defsNode = context.node.getElementsByTagName('defs')[0];
816
+ if (defsNode) return V(defsNode);
817
+ return V('defs').appendTo(context);
818
+ };
819
+
820
+ VPrototype.clone = function() {
821
+
822
+ var clone = V(this.node.cloneNode(true/* deep */));
823
+ // Note that clone inherits also ID. Therefore, we need to change it here.
824
+ clone.node.id = V.uniqueId();
825
+ return clone;
826
+ };
827
+
828
+ VPrototype.findOne = function(selector) {
829
+
830
+ var found = this.node.querySelector(selector);
831
+ return found ? V(found) : undefined;
832
+ };
833
+
834
+ VPrototype.find = function(selector) {
835
+
836
+ var vels = [];
837
+ var nodes = this.node.querySelectorAll(selector);
838
+
839
+ if (nodes) {
840
+
841
+ // Map DOM elements to `V`s.
842
+ for (var i = 0; i < nodes.length; i++) {
843
+ vels.push(V(nodes[i]));
844
+ }
845
+ }
846
+
847
+ return vels;
848
+ };
849
+
850
+ // Returns an array of V elements made from children of this.node.
851
+ VPrototype.children = function() {
852
+
853
+ var children = this.node.childNodes;
854
+
855
+ var outputArray = [];
856
+ for (var i = 0; i < children.length; i++) {
857
+ var currentChild = children[i];
858
+ if (currentChild.nodeType === 1) {
859
+ outputArray.push(V(children[i]));
860
+ }
861
+ }
862
+ return outputArray;
863
+ };
864
+
865
+ // Returns the V element from parentNode of this.node.
866
+ VPrototype.parent = function() {
867
+ return V(this.node.parentNode) || null;
868
+ },
869
+
870
+ // Find an index of an element inside its container.
871
+ VPrototype.index = function() {
872
+
873
+ var index = 0;
874
+ var node = this.node.previousSibling;
875
+
876
+ while (node) {
877
+ // nodeType 1 for ELEMENT_NODE
878
+ if (node.nodeType === 1) index++;
879
+ node = node.previousSibling;
880
+ }
881
+
882
+ return index;
883
+ };
884
+
885
+ VPrototype.findParentByClass = function(className, terminator) {
886
+
887
+ var ownerSVGElement = this.node.ownerSVGElement;
888
+ var node = this.node.parentNode;
889
+
890
+ while (node && node !== terminator && node !== ownerSVGElement) {
891
+
892
+ var vel = V(node);
893
+ if (vel.hasClass(className)) {
894
+ return vel;
895
+ }
896
+
897
+ node = node.parentNode;
898
+ }
899
+
900
+ return null;
901
+ };
902
+
903
+ // https://jsperf.com/get-common-parent
904
+ VPrototype.contains = function(el) {
905
+
906
+ var a = this.node;
907
+ var b = V.toNode(el);
908
+ var bup = b && b.parentNode;
909
+
910
+ return (a === bup) || !!(bup && bup.nodeType === 1 && (a.compareDocumentPosition(bup) & 16));
911
+ };
912
+
913
+ // Convert global point into the coordinate space of this element.
914
+ VPrototype.toLocalPoint = function(x, y) {
915
+
916
+ var svg = this.svg().node;
917
+
918
+ var p = svg.createSVGPoint();
919
+ p.x = x;
920
+ p.y = y;
921
+
922
+ try {
923
+
924
+ var globalPoint = p.matrixTransform(svg.getScreenCTM().inverse());
925
+ var globalToLocalMatrix = this.getTransformToElement(svg).inverse();
926
+
927
+ } catch (e) {
928
+ // IE9 throws an exception in odd cases. (`Unexpected call to method or property access`)
929
+ // We have to make do with the original coordianates.
930
+ return p;
931
+ }
932
+
933
+ return globalPoint.matrixTransform(globalToLocalMatrix);
934
+ };
935
+
936
+ VPrototype.translateCenterToPoint = function(p) {
937
+
938
+ var bbox = this.getBBox({ target: this.svg() });
939
+ var center = bbox.center();
940
+
941
+ this.translate(p.x - center.x, p.y - center.y);
942
+ return this;
943
+ };
944
+
945
+ // Efficiently auto-orient an element. This basically implements the orient=auto attribute
946
+ // of markers. The easiest way of understanding on what this does is to imagine the element is an
947
+ // arrowhead. Calling this method on the arrowhead makes it point to the `position` point while
948
+ // being auto-oriented (properly rotated) towards the `reference` point.
949
+ // `target` is the element relative to which the transformations are applied. Usually a viewport.
950
+ VPrototype.translateAndAutoOrient = function(position, reference, target) {
951
+
952
+ position = new g.Point(position);
953
+ reference = new g.Point(reference);
954
+ target || (target = this.svg());
955
+
956
+ // Clean-up previously set transformations except the scale. If we didn't clean up the
957
+ // previous transformations then they'd add up with the old ones. Scale is an exception as
958
+ // it doesn't add up, consider: `this.scale(2).scale(2).scale(2)`. The result is that the
959
+ // element is scaled by the factor 2, not 8.
960
+ var scale = this.scale();
961
+ this.attr('transform', '');
962
+ var bbox = this.getBBox({ target: target }).scale(scale.sx, scale.sy);
963
+
964
+ // 1. Translate to origin.
965
+ var translateToOrigin = V.createSVGTransform();
966
+ translateToOrigin.setTranslate(-bbox.x - bbox.width / 2, -bbox.y - bbox.height / 2);
967
+
968
+ // 2. Rotate around origin.
969
+ var rotateAroundOrigin = V.createSVGTransform();
970
+ var angle = position.angleBetween(reference, position.clone().offset(1, 0));
971
+ if (angle) rotateAroundOrigin.setRotate(angle, 0, 0);
972
+
973
+ // 3. Translate to the `position` + the offset (half my width) towards the `reference` point.
974
+ var translateFromOrigin = V.createSVGTransform();
975
+ var finalPosition = position.clone().move(reference, bbox.width / 2);
976
+ translateFromOrigin.setTranslate(2 * position.x - finalPosition.x, 2 * position.y - finalPosition.y);
977
+
978
+ // 4. Get the current transformation matrix of this node
979
+ var ctm = this.getTransformToElement(target);
980
+
981
+ // 5. Apply transformations and the scale
982
+ var transform = V.createSVGTransform();
983
+ transform.setMatrix(
984
+ translateFromOrigin.matrix.multiply(
985
+ rotateAroundOrigin.matrix.multiply(
986
+ translateToOrigin.matrix.multiply(
987
+ ctm.scale(scale.sx, scale.sy)))));
988
+
989
+ this.attr('transform', V.matrixToTransformString(transform.matrix));
990
+
991
+ return this;
992
+ };
993
+
994
+ VPrototype.animateAlongPath = function(attrs, path) {
995
+
996
+ path = V.toNode(path);
997
+
998
+ var id = V.ensureId(path);
999
+ var animateMotion = V('animateMotion', attrs);
1000
+ var mpath = V('mpath', { 'xlink:href': '#' + id });
1001
+
1002
+ animateMotion.append(mpath);
1003
+
1004
+ this.append(animateMotion);
1005
+ try {
1006
+ animateMotion.node.beginElement();
1007
+ } catch (e) {
1008
+ // Fallback for IE 9.
1009
+ // Run the animation programmatically if FakeSmile (`http://leunen.me/fakesmile/`) present
1010
+ if (document.documentElement.getAttribute('smiling') === 'fake') {
1011
+ /* global getTargets:true, Animator:true, animators:true id2anim:true */
1012
+ // Register the animation. (See `https://answers.launchpad.net/smil/+question/203333`)
1013
+ var animation = animateMotion.node;
1014
+ animation.animators = [];
1015
+
1016
+ var animationID = animation.getAttribute('id');
1017
+ if (animationID) id2anim[animationID] = animation;
1018
+
1019
+ var targets = getTargets(animation);
1020
+ for (var i = 0, len = targets.length; i < len; i++) {
1021
+ var target = targets[i];
1022
+ var animator = new Animator(animation, target, i);
1023
+ animators.push(animator);
1024
+ animation.animators[i] = animator;
1025
+ animator.register();
1026
+ }
1027
+ }
1028
+ }
1029
+ return this;
1030
+ };
1031
+
1032
+
1033
+ // Split a string into an array of tokens.
1034
+ // https://infra.spec.whatwg.org/#ascii-whitespace
1035
+ const noHTMLWhitespaceRegex = /[^\x20\t\r\n\f]+/g;
1036
+ function getTokenList(str) {
1037
+ if (!V.isString(str)) return [];
1038
+ return str.trim().match(noHTMLWhitespaceRegex) || [];
1039
+ }
1040
+
1041
+ VPrototype.hasClass = function(className) {
1042
+ if (!V.isString(className)) return false;
1043
+ return this.node.classList.contains(className.trim());
1044
+ };
1045
+
1046
+ VPrototype.addClass = function(className) {
1047
+ this.node.classList.add(...getTokenList(className));
1048
+ return this;
1049
+ };
1050
+
1051
+ VPrototype.removeClass = function(className) {
1052
+ this.node.classList.remove(...getTokenList(className));
1053
+ return this;
1054
+ };
1055
+
1056
+ VPrototype.toggleClass = function(className, toAdd) {
1057
+ const tokens = getTokenList(className);
1058
+ for (let i = 0; i < tokens.length; i++) {
1059
+ this.node.classList.toggle(tokens[i], toAdd);
1060
+ }
1061
+ return this;
1062
+ };
1063
+
1064
+ // Interpolate path by discrete points. The precision of the sampling
1065
+ // is controlled by `interval`. In other words, `sample()` will generate
1066
+ // a point on the path starting at the beginning of the path going to the end
1067
+ // every `interval` pixels.
1068
+ // The sampler can be very useful for e.g. finding intersection between two
1069
+ // paths (finding the two closest points from two samples).
1070
+ VPrototype.sample = function(interval) {
1071
+
1072
+ interval = interval || 1;
1073
+ var node = this.node;
1074
+ var length = node.getTotalLength();
1075
+ var samples = [];
1076
+ var distance = 0;
1077
+ var sample;
1078
+ while (distance < length) {
1079
+ sample = node.getPointAtLength(distance);
1080
+ samples.push({ x: sample.x, y: sample.y, distance: distance });
1081
+ distance += interval;
1082
+ }
1083
+ return samples;
1084
+ };
1085
+
1086
+ VPrototype.convertToPath = function() {
1087
+
1088
+ var path = V('path');
1089
+ path.attr(this.attr());
1090
+ var d = this.convertToPathData();
1091
+ if (d) {
1092
+ path.attr('d', d);
1093
+ }
1094
+ return path;
1095
+ };
1096
+
1097
+ VPrototype.convertToPathData = function() {
1098
+
1099
+ var tagName = this.tagName();
1100
+
1101
+ switch (tagName) {
1102
+ case 'PATH':
1103
+ return this.attr('d');
1104
+ case 'LINE':
1105
+ return V.convertLineToPathData(this.node);
1106
+ case 'POLYGON':
1107
+ return V.convertPolygonToPathData(this.node);
1108
+ case 'POLYLINE':
1109
+ return V.convertPolylineToPathData(this.node);
1110
+ case 'ELLIPSE':
1111
+ return V.convertEllipseToPathData(this.node);
1112
+ case 'CIRCLE':
1113
+ return V.convertCircleToPathData(this.node);
1114
+ case 'RECT':
1115
+ return V.convertRectToPathData(this.node);
1116
+ }
1117
+
1118
+ throw new Error(tagName + ' cannot be converted to PATH.');
1119
+ };
1120
+
1121
+ V.prototype.toGeometryShape = function() {
1122
+ var x, y, width, height, cx, cy, r, rx, ry, points, d, x1, x2, y1, y2;
1123
+ switch (this.tagName()) {
1124
+
1125
+ case 'RECT':
1126
+ x = parseFloat(this.attr('x')) || 0;
1127
+ y = parseFloat(this.attr('y')) || 0;
1128
+ width = parseFloat(this.attr('width')) || 0;
1129
+ height = parseFloat(this.attr('height')) || 0;
1130
+ return new g.Rect(x, y, width, height);
1131
+
1132
+ case 'CIRCLE':
1133
+ cx = parseFloat(this.attr('cx')) || 0;
1134
+ cy = parseFloat(this.attr('cy')) || 0;
1135
+ r = parseFloat(this.attr('r')) || 0;
1136
+ return new g.Ellipse({ x: cx, y: cy }, r, r);
1137
+
1138
+ case 'ELLIPSE':
1139
+ cx = parseFloat(this.attr('cx')) || 0;
1140
+ cy = parseFloat(this.attr('cy')) || 0;
1141
+ rx = parseFloat(this.attr('rx')) || 0;
1142
+ ry = parseFloat(this.attr('ry')) || 0;
1143
+ return new g.Ellipse({ x: cx, y: cy }, rx, ry);
1144
+
1145
+ case 'POLYLINE':
1146
+ points = V.getPointsFromSvgNode(this);
1147
+ return new g.Polyline(points);
1148
+
1149
+ case 'POLYGON':
1150
+ points = V.getPointsFromSvgNode(this);
1151
+ if (points.length > 1) points.push(points[0]);
1152
+ return new g.Polyline(points);
1153
+
1154
+ case 'PATH':
1155
+ d = this.attr('d');
1156
+ if (!g.Path.isDataSupported(d)) d = V.normalizePathData(d);
1157
+ return new g.Path(d);
1158
+
1159
+ case 'LINE':
1160
+ x1 = parseFloat(this.attr('x1')) || 0;
1161
+ y1 = parseFloat(this.attr('y1')) || 0;
1162
+ x2 = parseFloat(this.attr('x2')) || 0;
1163
+ y2 = parseFloat(this.attr('y2')) || 0;
1164
+ return new g.Line({ x: x1, y: y1 }, { x: x2, y: y2 });
1165
+ }
1166
+
1167
+ // Anything else is a rectangle
1168
+ return this.getBBox();
1169
+ };
1170
+
1171
+ // Find the intersection of a line starting in the center
1172
+ // of the SVG `node` ending in the point `ref`.
1173
+ // `target` is an SVG element to which `node`s transformations are relative to.
1174
+ // Note that `ref` point must be in the coordinate system of the `target` for this function to work properly.
1175
+ // Returns a point in the `target` coordinate system (the same system as `ref` is in) if
1176
+ // an intersection is found. Returns `undefined` otherwise.
1177
+ VPrototype.findIntersection = function(ref, target) {
1178
+
1179
+ var svg = this.svg().node;
1180
+ target = target || svg;
1181
+ var bbox = this.getBBox({ target: target });
1182
+ var center = bbox.center();
1183
+
1184
+ if (!bbox.intersectionWithLineFromCenterToPoint(ref)) return undefined;
1185
+
1186
+ var spot;
1187
+ var tagName = this.tagName();
1188
+
1189
+ // Little speed up optimization for `<rect>` element. We do not do conversion
1190
+ // to path element and sampling but directly calculate the intersection through
1191
+ // a transformed geometrical rectangle.
1192
+ if (tagName === 'RECT') {
1193
+
1194
+ var gRect = new g.Rect(
1195
+ parseFloat(this.attr('x') || 0),
1196
+ parseFloat(this.attr('y') || 0),
1197
+ parseFloat(this.attr('width')),
1198
+ parseFloat(this.attr('height'))
1199
+ );
1200
+ // Get the rect transformation matrix with regards to the SVG document.
1201
+ var rectMatrix = this.getTransformToElement(target);
1202
+ // Decompose the matrix to find the rotation angle.
1203
+ var rectMatrixComponents = V.decomposeMatrix(rectMatrix);
1204
+ // Now we want to rotate the rectangle back so that we
1205
+ // can use `intersectionWithLineFromCenterToPoint()` passing the angle as the second argument.
1206
+ var resetRotation = svg.createSVGTransform();
1207
+ resetRotation.setRotate(-rectMatrixComponents.rotation, center.x, center.y);
1208
+ var rect = V.transformRect(gRect, resetRotation.matrix.multiply(rectMatrix));
1209
+ spot = (new g.Rect(rect)).intersectionWithLineFromCenterToPoint(ref, rectMatrixComponents.rotation);
1210
+
1211
+ } else if (tagName === 'PATH' || tagName === 'POLYGON' || tagName === 'POLYLINE' || tagName === 'CIRCLE' || tagName === 'ELLIPSE') {
1212
+
1213
+ var pathNode = (tagName === 'PATH') ? this : this.convertToPath();
1214
+ var samples = pathNode.sample();
1215
+ var minDistance = Infinity;
1216
+ var closestSamples = [];
1217
+
1218
+ var i, sample, gp, centerDistance, refDistance, distance;
1219
+
1220
+ for (i = 0; i < samples.length; i++) {
1221
+
1222
+ sample = samples[i];
1223
+ // Convert the sample point in the local coordinate system to the global coordinate system.
1224
+ gp = V.createSVGPoint(sample.x, sample.y);
1225
+ gp = gp.matrixTransform(this.getTransformToElement(target));
1226
+ sample = new g.Point(gp);
1227
+ centerDistance = sample.distance(center);
1228
+ // Penalize a higher distance to the reference point by 10%.
1229
+ // This gives better results. This is due to
1230
+ // inaccuracies introduced by rounding errors and getPointAtLength() returns.
1231
+ refDistance = sample.distance(ref) * 1.1;
1232
+ distance = centerDistance + refDistance;
1233
+
1234
+ if (distance < minDistance) {
1235
+ minDistance = distance;
1236
+ closestSamples = [{ sample: sample, refDistance: refDistance }];
1237
+ } else if (distance < minDistance + 1) {
1238
+ closestSamples.push({ sample: sample, refDistance: refDistance });
1239
+ }
1240
+ }
1241
+
1242
+ closestSamples.sort(function(a, b) {
1243
+ return a.refDistance - b.refDistance;
1244
+ });
1245
+
1246
+ if (closestSamples[0]) {
1247
+ spot = closestSamples[0].sample;
1248
+ }
1249
+ }
1250
+
1251
+ return spot;
1252
+ };
1253
+
1254
+ /**
1255
+ * @private
1256
+ * @param {string} name
1257
+ * @param {string} value
1258
+ * @returns {Vectorizer}
1259
+ */
1260
+ VPrototype.setAttribute = function(name, value) {
1261
+
1262
+ const el = this.node;
1263
+
1264
+ if (value === null) {
1265
+ this.removeAttr(name);
1266
+ return this;
1267
+ }
1268
+
1269
+ const trueName = attributeNames[name];
1270
+
1271
+ const { ns } = V.qualifyAttr(trueName);
1272
+ if (ns) {
1273
+ // Attribute names can be namespaced. E.g. `image` elements
1274
+ // have a `xlink:href` attribute to set the source of the image.
1275
+ el.setAttributeNS(ns, trueName, value);
1276
+ } else if (trueName === 'id') {
1277
+ el.id = value;
1278
+ } else {
1279
+ el.setAttribute(trueName, value);
1280
+ }
1281
+
1282
+ return this;
1283
+ };
1284
+
1285
+ // Create an SVG document element.
1286
+ // If `content` is passed, it will be used as the SVG content of the `<svg>` root element.
1287
+ V.createSvgDocument = function(content) {
1288
+
1289
+ if (content) {
1290
+ const XMLString = `<svg xmlns="${ns.svg}" xmlns:xlink="${ns.xlink}" version="${SVGVersion}">${content}</svg>`;
1291
+ const { documentElement } = V.parseXML(XMLString, { async: false });
1292
+ return documentElement;
1293
+ }
1294
+
1295
+ const svg = document.createElementNS(ns.svg, 'svg');
1296
+ svg.setAttributeNS(ns.xmlns, 'xmlns:xlink', ns.xlink);
1297
+ svg.setAttribute('version', SVGVersion);
1298
+ return svg;
1299
+ };
1300
+
1301
+ V.createSVGStyle = function(stylesheet) {
1302
+ const { node } = V('style', { type: 'text/css' }, [
1303
+ V.createCDATASection(stylesheet)
1304
+ ]);
1305
+ return node;
1306
+ },
1307
+
1308
+ V.createCDATASection = function(data = '') {
1309
+ const xml = document.implementation.createDocument(null, 'xml', null);
1310
+ return xml.createCDATASection(data);
1311
+ };
1312
+
1313
+ V.idCounter = 0;
1314
+
1315
+ // A function returning a unique identifier for this client session with every call.
1316
+ V.uniqueId = function() {
1317
+
1318
+ return 'v-' + (++V.idCounter);
1319
+ };
1320
+
1321
+ V.toNode = function(el) {
1322
+
1323
+ return V.isV(el) ? el.node : (el.nodeName && el || el[0]);
1324
+ };
1325
+
1326
+ V.ensureId = function(node) {
1327
+
1328
+ node = V.toNode(node);
1329
+ return node.id || (node.id = V.uniqueId());
1330
+ };
1331
+
1332
+ // Replace all spaces with the Unicode No-break space (http://www.fileformat.info/info/unicode/char/a0/index.htm).
1333
+ // IE would otherwise collapse all spaces into one. This is used in the text() method but it is
1334
+ // also exposed so that the programmer can use it in case he needs to. This is useful e.g. in tests
1335
+ // when you want to compare the actual DOM text content without having to add the unicode character in
1336
+ // the place of all spaces.
1337
+ V.sanitizeText = function(text) {
1338
+
1339
+ return (text || '').replace(/ /g, '\u00A0');
1340
+ };
1341
+
1342
+ V.isUndefined = function(value) {
1343
+
1344
+ return typeof value === 'undefined';
1345
+ };
1346
+
1347
+ V.isString = function(value) {
1348
+
1349
+ return typeof value === 'string';
1350
+ };
1351
+
1352
+ V.isObject = function(value) {
1353
+
1354
+ return value && (typeof value === 'object');
1355
+ };
1356
+
1357
+ V.isArray = Array.isArray;
1358
+
1359
+ V.parseXML = function(data, opt) {
1360
+
1361
+ opt = opt || {};
1362
+
1363
+ var xml;
1364
+
1365
+ try {
1366
+ var parser = new DOMParser();
1367
+
1368
+ if (!V.isUndefined(opt.async)) {
1369
+ parser.async = opt.async;
1370
+ }
1371
+
1372
+ xml = parser.parseFromString(data, 'text/xml');
1373
+ } catch (error) {
1374
+ xml = undefined;
1375
+ }
1376
+
1377
+ if (!xml || xml.getElementsByTagName('parsererror').length) {
1378
+ throw new Error('Invalid XML: ' + data);
1379
+ }
1380
+
1381
+ return xml;
1382
+ };
1383
+
1384
+ // Create an empty object which does not inherit any properties from `Object.prototype`.
1385
+ // This is useful when we want to use an object as a dictionary without having to
1386
+ // worry about inherited properties such as `toString`, `valueOf` etc.
1387
+ const _attributeNames = Object.create(null);
1388
+
1389
+ // List of attributes for which not to split camel case words.
1390
+ // It contains known SVG attribute names and may be extended with user-defined attribute names.
1391
+ [
1392
+ 'baseFrequency',
1393
+ 'baseProfile',
1394
+ 'clipPathUnits',
1395
+ 'contentScriptType',
1396
+ 'contentStyleType',
1397
+ 'diffuseConstant',
1398
+ 'edgeMode',
1399
+ 'externalResourcesRequired',
1400
+ 'filterRes', // deprecated
1401
+ 'filterUnits',
1402
+ 'gradientTransform',
1403
+ 'gradientUnits',
1404
+ 'kernelMatrix',
1405
+ 'kernelUnitLength',
1406
+ 'keyPoints',
1407
+ 'lengthAdjust',
1408
+ 'limitingConeAngle',
1409
+ 'markerHeight',
1410
+ 'markerUnits',
1411
+ 'markerWidth',
1412
+ 'maskContentUnits',
1413
+ 'maskUnits',
1414
+ 'numOctaves',
1415
+ 'pathLength',
1416
+ 'patternContentUnits',
1417
+ 'patternTransform',
1418
+ 'patternUnits',
1419
+ 'pointsAtX',
1420
+ 'pointsAtY',
1421
+ 'pointsAtZ',
1422
+ 'preserveAlpha',
1423
+ 'preserveAspectRatio',
1424
+ 'primitiveUnits',
1425
+ 'refX',
1426
+ 'refY',
1427
+ 'requiredExtensions',
1428
+ 'requiredFeatures',
1429
+ 'specularConstant',
1430
+ 'specularExponent',
1431
+ 'spreadMethod',
1432
+ 'startOffset',
1433
+ 'stdDeviation',
1434
+ 'stitchTiles',
1435
+ 'surfaceScale',
1436
+ 'systemLanguage',
1437
+ 'tableValues',
1438
+ 'targetX',
1439
+ 'targetY',
1440
+ 'textLength',
1441
+ 'viewBox',
1442
+ 'viewTarget', // deprecated
1443
+ 'xChannelSelector',
1444
+ 'yChannelSelector',
1445
+ 'zoomAndPan' // deprecated
1446
+ ].forEach((name) => _attributeNames[name] = name);
1447
+
1448
+ _attributeNames['xlinkShow'] = 'xlink:show';
1449
+ _attributeNames['xlinkRole'] = 'xlink:role';
1450
+ _attributeNames['xlinkActuate'] = 'xlink:actuate';
1451
+ _attributeNames['xlinkHref'] = 'xlink:href';
1452
+ _attributeNames['xlinkType'] = 'xlink:type';
1453
+ _attributeNames['xlinkTitle'] = 'xlink:title';
1454
+ _attributeNames['xmlBase'] = 'xml:base';
1455
+ _attributeNames['xmlLang'] = 'xml:lang';
1456
+ _attributeNames['xmlSpace'] = 'xml:space';
1457
+
1458
+ const attributeNames = new Proxy(_attributeNames, {
1459
+ get(cache, name) {
1460
+ // The cache is a dictionary of attribute names. See `_attributeNames` above.
1461
+ // If the attribute name is not in the cache, it means that it is not
1462
+ // a camel-case attribute name. In that case, we need to convert
1463
+ // the attribute name to dash-separated words.
1464
+ if (!V.supportCamelCaseAttributes) return name;
1465
+ if (name in cache) {
1466
+ return cache[name];
1467
+ }
1468
+ // Convert camel case to dash-separated words.
1469
+ return (cache[name] = name.replace(/[A-Z]/g, '-$&').toLowerCase());
1470
+ }
1471
+ });
1472
+
1473
+ // Dictionary of attribute names
1474
+ Object.defineProperty(V, 'attributeNames', {
1475
+ enumerable: true,
1476
+ value: attributeNames,
1477
+ writable: false,
1478
+ });
1479
+
1480
+ // Should camel case attributes be supported?
1481
+ Object.defineProperty(V, 'supportCamelCaseAttributes', {
1482
+ enumerable: true,
1483
+ value: true,
1484
+ writable: true,
1485
+ });
1486
+
1487
+ /**
1488
+ * @param {string} name
1489
+ * @returns {{ns: string|null, local: string}} namespace and attribute name
1490
+ */
1491
+ V.qualifyAttr = function(name) {
1492
+
1493
+ if (name.indexOf(':') !== -1) {
1494
+ var combinedKey = name.split(':');
1495
+ return {
1496
+ ns: ns[combinedKey[0]],
1497
+ local: combinedKey[1]
1498
+ };
1499
+ }
1500
+
1501
+ return {
1502
+ ns: null,
1503
+ local: name
1504
+ };
1505
+ };
1506
+
1507
+ // Note: This regex allows multiple commas as separator which is incorrect in SVG
1508
+ // This regex is used by `split()`, so it doesn't need to use /g
1509
+ V.transformSeparatorRegex = /[ ,]+/;
1510
+ // Note: All following regexes are more restrictive than SVG specification
1511
+ // ReDoS mitigation: Use an anchor at the beginning of the match
1512
+ // ReDoS mitigation: Avoid backtracking (uses `[^()]+` instead of `.*?`)
1513
+ // ReDoS mitigation: Don't match initial `(` inside repeated part
1514
+ // The following regex needs to use /g (= cannot use capturing groups)
1515
+ V.transformRegex = /\b\w+\([^()]+\)/g;
1516
+ // The following regexes need to use capturing groups (= cannot use /g)
1517
+ V.transformFunctionRegex = /\b(\w+)\(([^()]+)\)/;
1518
+ V.transformTranslateRegex = /\btranslate\(([^()]+)\)/;
1519
+ V.transformRotateRegex = /\brotate\(([^()]+)\)/;
1520
+ V.transformScaleRegex = /\bscale\(([^()]+)\)/;
1521
+
1522
+ V.transformStringToMatrix = function(transform) {
1523
+
1524
+ // Initialize result matrix as identity matrix
1525
+ let transformationMatrix = V.createSVGMatrix();
1526
+
1527
+ // Note: Multiple transform functions are allowed in `transform` string
1528
+ // `match()` returns `null` if none found
1529
+ const transformMatches = transform && transform.match(V.transformRegex);
1530
+ if (!transformMatches) {
1531
+ // Return identity matrix
1532
+ return transformationMatrix;
1533
+ }
1534
+
1535
+ const numMatches = transformMatches.length;
1536
+ for (let i = 0; i < numMatches; i++) {
1537
+
1538
+ const transformMatch = transformMatches[i];
1539
+ // Use same regex as above, but with capturing groups
1540
+ // `match()` returns values of capturing groups as `[1]`, `[2]`
1541
+ const transformFunctionMatch = transformMatch.match(V.transformFunctionRegex);
1542
+ if (transformFunctionMatch) {
1543
+
1544
+ let sx, sy, tx, ty, angle;
1545
+ let ctm = V.createSVGMatrix();
1546
+ const transformFunction = transformFunctionMatch[1].toLowerCase();
1547
+ const args = transformFunctionMatch[2].split(V.transformSeparatorRegex);
1548
+ switch (transformFunction) {
1549
+
1550
+ case 'scale':
1551
+ sx = parseFloat(args[0]);
1552
+ sy = (args[1] === undefined) ? sx : parseFloat(args[1]);
1553
+ ctm = ctm.scaleNonUniform(sx, sy);
1554
+ break;
1555
+
1556
+ case 'translate':
1557
+ tx = parseFloat(args[0]);
1558
+ ty = parseFloat(args[1]);
1559
+ ctm = ctm.translate(tx, ty);
1560
+ break;
1561
+
1562
+ case 'rotate':
1563
+ angle = parseFloat(args[0]);
1564
+ tx = parseFloat(args[1]) || 0;
1565
+ ty = parseFloat(args[2]) || 0;
1566
+ if (tx !== 0 || ty !== 0) {
1567
+ ctm = ctm.translate(tx, ty).rotate(angle).translate(-tx, -ty);
1568
+ } else {
1569
+ ctm = ctm.rotate(angle);
1570
+ }
1571
+ break;
1572
+
1573
+ case 'skewx':
1574
+ angle = parseFloat(args[0]);
1575
+ ctm = ctm.skewX(angle);
1576
+ break;
1577
+
1578
+ case 'skewy':
1579
+ angle = parseFloat(args[0]);
1580
+ ctm = ctm.skewY(angle);
1581
+ break;
1582
+
1583
+ case 'matrix':
1584
+ ctm.a = parseFloat(args[0]);
1585
+ ctm.b = parseFloat(args[1]);
1586
+ ctm.c = parseFloat(args[2]);
1587
+ ctm.d = parseFloat(args[3]);
1588
+ ctm.e = parseFloat(args[4]);
1589
+ ctm.f = parseFloat(args[5]);
1590
+ break;
1591
+
1592
+ default:
1593
+ continue;
1594
+ }
1595
+
1596
+ // Multiply current transformation into result matrix
1597
+ transformationMatrix = transformationMatrix.multiply(ctm);
1598
+ }
1599
+
1600
+ }
1601
+ return transformationMatrix;
1602
+ };
1603
+
1604
+ V.matrixToTransformString = function(matrix) {
1605
+ matrix || (matrix = true);
1606
+
1607
+ return 'matrix(' +
1608
+ (matrix.a !== undefined ? matrix.a : 1) + ',' +
1609
+ (matrix.b !== undefined ? matrix.b : 0) + ',' +
1610
+ (matrix.c !== undefined ? matrix.c : 0) + ',' +
1611
+ (matrix.d !== undefined ? matrix.d : 1) + ',' +
1612
+ (matrix.e !== undefined ? matrix.e : 0) + ',' +
1613
+ (matrix.f !== undefined ? matrix.f : 0) +
1614
+ ')';
1615
+ };
1616
+
1617
+ V.parseTransformString = function(transform) {
1618
+
1619
+ var translate, rotate, scale;
1620
+
1621
+ if (transform) {
1622
+
1623
+ var separator = V.transformSeparatorRegex;
1624
+
1625
+ // Special handling for `transform` with one or more matrix functions
1626
+ if (transform.trim().indexOf('matrix') >= 0) {
1627
+
1628
+ // Convert EVERYTHING in `transform` string to a matrix
1629
+ // Will combine ALL matrixes * ALL translates * ALL scales * ALL rotates
1630
+ // Note: In non-matrix case, we only take first one of each (if any)
1631
+ var matrix = V.transformStringToMatrix(transform);
1632
+ var decomposedMatrix = V.decomposeMatrix(matrix);
1633
+
1634
+ // Extract `translate`, `scale`, `rotate` from matrix
1635
+ translate = [decomposedMatrix.translateX, decomposedMatrix.translateY];
1636
+ scale = [decomposedMatrix.scaleX, decomposedMatrix.scaleY];
1637
+ rotate = [decomposedMatrix.rotation];
1638
+
1639
+ // Rewrite `transform` string in `translate scale rotate` format
1640
+ var transformations = [];
1641
+ if (translate[0] !== 0 || translate[1] !== 0) {
1642
+ transformations.push('translate(' + translate + ')');
1643
+ }
1644
+ if (scale[0] !== 1 || scale[1] !== 1) {
1645
+ transformations.push('scale(' + scale + ')');
1646
+ }
1647
+ if (rotate[0] !== 0) {
1648
+ transformations.push('rotate(' + rotate + ')');
1649
+ }
1650
+ transform = transformations.join(' ');
1651
+
1652
+ } else {
1653
+
1654
+ // Extract `translate`, `rotate`, `scale` functions from `transform` string
1655
+ // Note: We only detect the first match of each (if any)
1656
+ // `match()` returns value of capturing group as `[1]`
1657
+ const translateMatch = transform.match(V.transformTranslateRegex);
1658
+ if (translateMatch) {
1659
+ translate = translateMatch[1].split(separator);
1660
+ }
1661
+ const rotateMatch = transform.match(V.transformRotateRegex);
1662
+ if (rotateMatch) {
1663
+ rotate = rotateMatch[1].split(separator);
1664
+ }
1665
+ const scaleMatch = transform.match(V.transformScaleRegex);
1666
+ if (scaleMatch) {
1667
+ scale = scaleMatch[1].split(separator);
1668
+ }
1669
+ }
1670
+ }
1671
+
1672
+ var sx = (scale && scale[0]) ? parseFloat(scale[0]) : 1;
1673
+
1674
+ return {
1675
+ value: transform,
1676
+ translate: {
1677
+ tx: (translate && translate[0]) ? parseInt(translate[0], 10) : 0,
1678
+ ty: (translate && translate[1]) ? parseInt(translate[1], 10) : 0
1679
+ },
1680
+ rotate: {
1681
+ angle: (rotate && rotate[0]) ? parseInt(rotate[0], 10) : 0,
1682
+ cx: (rotate && rotate[1]) ? parseInt(rotate[1], 10) : undefined,
1683
+ cy: (rotate && rotate[2]) ? parseInt(rotate[2], 10) : undefined
1684
+ },
1685
+ scale: {
1686
+ sx: sx,
1687
+ sy: (scale && scale[1]) ? parseFloat(scale[1]) : sx
1688
+ }
1689
+ };
1690
+ };
1691
+
1692
+ V.deltaTransformPoint = function(matrix, point) {
1693
+
1694
+ var dx = point.x * matrix.a + point.y * matrix.c + 0;
1695
+ var dy = point.x * matrix.b + point.y * matrix.d + 0;
1696
+ return { x: dx, y: dy };
1697
+ };
1698
+
1699
+ V.decomposeMatrix = function(matrix) {
1700
+
1701
+ // @see https://gist.github.com/2052247
1702
+
1703
+ // calculate delta transform point
1704
+ var px = V.deltaTransformPoint(matrix, { x: 0, y: 1 });
1705
+ var py = V.deltaTransformPoint(matrix, { x: 1, y: 0 });
1706
+
1707
+ // calculate skew
1708
+ var skewX = ((180 / PI) * atan2(px.y, px.x) - 90);
1709
+ var skewY = ((180 / PI) * atan2(py.y, py.x));
1710
+
1711
+ return {
1712
+
1713
+ translateX: matrix.e,
1714
+ translateY: matrix.f,
1715
+ scaleX: sqrt(matrix.a * matrix.a + matrix.b * matrix.b),
1716
+ scaleY: sqrt(matrix.c * matrix.c + matrix.d * matrix.d),
1717
+ skewX: skewX,
1718
+ skewY: skewY,
1719
+ rotation: skewX // rotation is the same as skew x
1720
+ };
1721
+ };
1722
+
1723
+ // Return the `scale` transformation from the following equation:
1724
+ // `translate(tx, ty) . rotate(angle) . scale(sx, sy) === matrix(a,b,c,d,e,f)`
1725
+ V.matrixToScale = function(matrix) {
1726
+
1727
+ var a, b, c, d;
1728
+ if (matrix) {
1729
+ a = V.isUndefined(matrix.a) ? 1 : matrix.a;
1730
+ d = V.isUndefined(matrix.d) ? 1 : matrix.d;
1731
+ b = matrix.b;
1732
+ c = matrix.c;
1733
+ } else {
1734
+ a = d = 1;
1735
+ }
1736
+ return {
1737
+ sx: b ? sqrt(a * a + b * b) : a,
1738
+ sy: c ? sqrt(c * c + d * d) : d
1739
+ };
1740
+ };
1741
+
1742
+ // Return the `rotate` transformation from the following equation:
1743
+ // `translate(tx, ty) . rotate(angle) . scale(sx, sy) === matrix(a,b,c,d,e,f)`
1744
+ V.matrixToRotate = function(matrix) {
1745
+
1746
+ var p = { x: 0, y: 1 };
1747
+ if (matrix) {
1748
+ p = V.deltaTransformPoint(matrix, p);
1749
+ }
1750
+
1751
+ return {
1752
+ angle: g.normalizeAngle(g.toDeg(atan2(p.y, p.x)) - 90)
1753
+ };
1754
+ };
1755
+
1756
+ // Return the `translate` transformation from the following equation:
1757
+ // `translate(tx, ty) . rotate(angle) . scale(sx, sy) === matrix(a,b,c,d,e,f)`
1758
+ V.matrixToTranslate = function(matrix) {
1759
+
1760
+ return {
1761
+ tx: (matrix && matrix.e) || 0,
1762
+ ty: (matrix && matrix.f) || 0
1763
+ };
1764
+ };
1765
+
1766
+ V.isV = function(object) {
1767
+
1768
+ return object instanceof V;
1769
+ };
1770
+
1771
+ // For backwards compatibility:
1772
+ V.isVElement = V.isV;
1773
+
1774
+ // Element implements `getBBox()`, `getCTM()` and `getScreenCTM()`
1775
+ // https://developer.mozilla.org/en-US/docs/Web/API/SVGGraphicsElement
1776
+ V.isSVGGraphicsElement = function(node) {
1777
+ if (!node) return false;
1778
+ node = V.toNode(node);
1779
+ // IE/Edge does not implement SVGGraphicsElement interface, thus check for `getScreenCTM` below
1780
+ return node instanceof SVGElement && typeof node.getScreenCTM === 'function';
1781
+ };
1782
+
1783
+ var svgDocument = V('svg').node;
1784
+
1785
+ V.createSVGMatrix = function(matrix) {
1786
+
1787
+ var svgMatrix = svgDocument.createSVGMatrix();
1788
+ for (var component in matrix) {
1789
+ svgMatrix[component] = matrix[component];
1790
+ }
1791
+
1792
+ return svgMatrix;
1793
+ };
1794
+
1795
+ V.createSVGTransform = function(matrix) {
1796
+
1797
+ if (!V.isUndefined(matrix)) {
1798
+
1799
+ if (!(matrix instanceof SVGMatrix)) {
1800
+ matrix = V.createSVGMatrix(matrix);
1801
+ }
1802
+
1803
+ return svgDocument.createSVGTransformFromMatrix(matrix);
1804
+ }
1805
+
1806
+ return svgDocument.createSVGTransform();
1807
+ };
1808
+
1809
+ V.createSVGPoint = function(x, y) {
1810
+
1811
+ var p = svgDocument.createSVGPoint();
1812
+ p.x = x;
1813
+ p.y = y;
1814
+ return p;
1815
+ };
1816
+
1817
+ V.transformRect = function(r, matrix) {
1818
+
1819
+ var p = svgDocument.createSVGPoint();
1820
+
1821
+ p.x = r.x;
1822
+ p.y = r.y;
1823
+ var corner1 = p.matrixTransform(matrix);
1824
+
1825
+ p.x = r.x + r.width;
1826
+ p.y = r.y;
1827
+ var corner2 = p.matrixTransform(matrix);
1828
+
1829
+ p.x = r.x + r.width;
1830
+ p.y = r.y + r.height;
1831
+ var corner3 = p.matrixTransform(matrix);
1832
+
1833
+ p.x = r.x;
1834
+ p.y = r.y + r.height;
1835
+ var corner4 = p.matrixTransform(matrix);
1836
+
1837
+ var minX = min(corner1.x, corner2.x, corner3.x, corner4.x);
1838
+ var maxX = max(corner1.x, corner2.x, corner3.x, corner4.x);
1839
+ var minY = min(corner1.y, corner2.y, corner3.y, corner4.y);
1840
+ var maxY = max(corner1.y, corner2.y, corner3.y, corner4.y);
1841
+
1842
+ return new g.Rect(minX, minY, maxX - minX, maxY - minY);
1843
+ };
1844
+
1845
+ V.transformPoint = function(p, matrix) {
1846
+
1847
+ return new g.Point(V.createSVGPoint(p.x, p.y).matrixTransform(matrix));
1848
+ };
1849
+
1850
+ V.transformLine = function(l, matrix) {
1851
+
1852
+ return new g.Line(
1853
+ V.transformPoint(l.start, matrix),
1854
+ V.transformPoint(l.end, matrix)
1855
+ );
1856
+ };
1857
+
1858
+ V.transformPolyline = function(p, matrix) {
1859
+
1860
+ var inPoints = (p instanceof g.Polyline) ? p.points : p;
1861
+ if (!V.isArray(inPoints)) inPoints = [];
1862
+ var outPoints = [];
1863
+ for (var i = 0, n = inPoints.length; i < n; i++) outPoints[i] = V.transformPoint(inPoints[i], matrix);
1864
+ return new g.Polyline(outPoints);
1865
+ };
1866
+
1867
+ // Convert a style represented as string (e.g. `'fill="blue"; stroke="red"'`) to
1868
+ // an object (`{ fill: 'blue', stroke: 'red' }`).
1869
+ V.styleToObject = function(styleString) {
1870
+ var ret = {};
1871
+ var styles = styleString.split(';');
1872
+ for (var i = 0; i < styles.length; i++) {
1873
+ var style = styles[i];
1874
+ var pair = style.split('=');
1875
+ ret[pair[0].trim()] = pair[1].trim();
1876
+ }
1877
+ return ret;
1878
+ };
1879
+
1880
+ // Inspired by d3.js https://github.com/mbostock/d3/blob/master/src/svg/arc.js
1881
+ V.createSlicePathData = function(innerRadius, outerRadius, startAngle, endAngle) {
1882
+
1883
+ var svgArcMax = 2 * PI - 1e-6;
1884
+ var r0 = innerRadius;
1885
+ var r1 = outerRadius;
1886
+ var a0 = startAngle;
1887
+ var a1 = endAngle;
1888
+ var da = (a1 < a0 && (da = a0, a0 = a1, a1 = da), a1 - a0);
1889
+ var df = da < PI ? '0' : '1';
1890
+ var c0 = cos(a0);
1891
+ var s0 = sin(a0);
1892
+ var c1 = cos(a1);
1893
+ var s1 = sin(a1);
1894
+
1895
+ return (da >= svgArcMax)
1896
+ ? (r0
1897
+ ? 'M0,' + r1
1898
+ + 'A' + r1 + ',' + r1 + ' 0 1,1 0,' + (-r1)
1899
+ + 'A' + r1 + ',' + r1 + ' 0 1,1 0,' + r1
1900
+ + 'M0,' + r0
1901
+ + 'A' + r0 + ',' + r0 + ' 0 1,0 0,' + (-r0)
1902
+ + 'A' + r0 + ',' + r0 + ' 0 1,0 0,' + r0
1903
+ + 'Z'
1904
+ : 'M0,' + r1
1905
+ + 'A' + r1 + ',' + r1 + ' 0 1,1 0,' + (-r1)
1906
+ + 'A' + r1 + ',' + r1 + ' 0 1,1 0,' + r1
1907
+ + 'Z')
1908
+ : (r0
1909
+ ? 'M' + r1 * c0 + ',' + r1 * s0
1910
+ + 'A' + r1 + ',' + r1 + ' 0 ' + df + ',1 ' + r1 * c1 + ',' + r1 * s1
1911
+ + 'L' + r0 * c1 + ',' + r0 * s1
1912
+ + 'A' + r0 + ',' + r0 + ' 0 ' + df + ',0 ' + r0 * c0 + ',' + r0 * s0
1913
+ + 'Z'
1914
+ : 'M' + r1 * c0 + ',' + r1 * s0
1915
+ + 'A' + r1 + ',' + r1 + ' 0 ' + df + ',1 ' + r1 * c1 + ',' + r1 * s1
1916
+ + 'L0,0'
1917
+ + 'Z');
1918
+ };
1919
+
1920
+ // Merge attributes from object `b` with attributes in object `a`.
1921
+ // Note that this modifies the object `a`.
1922
+ // Also important to note that attributes are merged but CSS classes are concatenated.
1923
+ V.mergeAttrs = function(a, b) {
1924
+
1925
+ for (var attr in b) {
1926
+
1927
+ if (attr === 'class') {
1928
+ // Concatenate classes.
1929
+ a[attr] = a[attr] ? a[attr] + ' ' + b[attr] : b[attr];
1930
+ } else if (attr === 'style') {
1931
+ // `style` attribute can be an object.
1932
+ if (V.isObject(a[attr]) && V.isObject(b[attr])) {
1933
+ // `style` stored in `a` is an object.
1934
+ a[attr] = V.mergeAttrs(a[attr], b[attr]);
1935
+ } else if (V.isObject(a[attr])) {
1936
+ // `style` in `a` is an object but it's a string in `b`.
1937
+ // Convert the style represented as a string to an object in `b`.
1938
+ a[attr] = V.mergeAttrs(a[attr], V.styleToObject(b[attr]));
1939
+ } else if (V.isObject(b[attr])) {
1940
+ // `style` in `a` is a string, in `b` it's an object.
1941
+ a[attr] = V.mergeAttrs(V.styleToObject(a[attr]), b[attr]);
1942
+ } else {
1943
+ // Both styles are strings.
1944
+ a[attr] = V.mergeAttrs(V.styleToObject(a[attr]), V.styleToObject(b[attr]));
1945
+ }
1946
+ } else {
1947
+ a[attr] = b[attr];
1948
+ }
1949
+ }
1950
+
1951
+ return a;
1952
+ };
1953
+
1954
+ V.annotateString = function(t, annotations, opt) {
1955
+
1956
+ annotations = annotations || [];
1957
+ opt = opt || {};
1958
+
1959
+ var offset = opt.offset || 0;
1960
+ var compacted = [];
1961
+ var batch;
1962
+ var ret = [];
1963
+ var item;
1964
+ var prev;
1965
+
1966
+ for (var i = 0; i < t.length; i++) {
1967
+
1968
+ item = ret[i] = t[i];
1969
+
1970
+ for (var j = 0; j < annotations.length; j++) {
1971
+
1972
+ var annotation = annotations[j];
1973
+ var start = annotation.start + offset;
1974
+ var end = annotation.end + offset;
1975
+
1976
+ if (i >= start && i < end) {
1977
+ // Annotation applies.
1978
+ if (V.isObject(item)) {
1979
+ // There is more than one annotation to be applied => Merge attributes.
1980
+ item.attrs = V.mergeAttrs(V.mergeAttrs({}, item.attrs), annotation.attrs);
1981
+ } else {
1982
+ item = ret[i] = { t: t[i], attrs: annotation.attrs };
1983
+ }
1984
+ if (opt.includeAnnotationIndices) {
1985
+ (item.annotations || (item.annotations = [])).push(j);
1986
+ }
1987
+ }
1988
+ }
1989
+
1990
+ prev = ret[i - 1];
1991
+
1992
+ if (!prev) {
1993
+
1994
+ batch = item;
1995
+
1996
+ } else if (V.isObject(item) && V.isObject(prev)) {
1997
+ // Both previous item and the current one are annotations. If the attributes
1998
+ // didn't change, merge the text.
1999
+ if (JSON.stringify(item.attrs) === JSON.stringify(prev.attrs)) {
2000
+ batch.t += item.t;
2001
+ } else {
2002
+ compacted.push(batch);
2003
+ batch = item;
2004
+ }
2005
+
2006
+ } else if (V.isObject(item)) {
2007
+ // Previous item was a string, current item is an annotation.
2008
+ compacted.push(batch);
2009
+ batch = item;
2010
+
2011
+ } else if (V.isObject(prev)) {
2012
+ // Previous item was an annotation, current item is a string.
2013
+ compacted.push(batch);
2014
+ batch = item;
2015
+
2016
+ } else {
2017
+ // Both previous and current item are strings.
2018
+ batch = (batch || '') + item;
2019
+ }
2020
+ }
2021
+
2022
+ if (batch) {
2023
+ compacted.push(batch);
2024
+ }
2025
+
2026
+ return compacted;
2027
+ };
2028
+
2029
+ V.findAnnotationsAtIndex = function(annotations, index) {
2030
+
2031
+ var found = [];
2032
+
2033
+ if (annotations) {
2034
+
2035
+ annotations.forEach(function(annotation) {
2036
+
2037
+ if (annotation.start < index && index <= annotation.end) {
2038
+ found.push(annotation);
2039
+ }
2040
+ });
2041
+ }
2042
+
2043
+ return found;
2044
+ };
2045
+
2046
+ V.findAnnotationsBetweenIndexes = function(annotations, start, end) {
2047
+
2048
+ var found = [];
2049
+
2050
+ if (annotations) {
2051
+
2052
+ annotations.forEach(function(annotation) {
2053
+
2054
+ if ((start >= annotation.start && start < annotation.end) || (end > annotation.start && end <= annotation.end) || (annotation.start >= start && annotation.end < end)) {
2055
+ found.push(annotation);
2056
+ }
2057
+ });
2058
+ }
2059
+
2060
+ return found;
2061
+ };
2062
+
2063
+ // Shift all the text annotations after character `index` by `offset` positions.
2064
+ V.shiftAnnotations = function(annotations, index, offset) {
2065
+
2066
+ if (annotations) {
2067
+
2068
+ annotations.forEach(function(annotation) {
2069
+
2070
+ if (annotation.start < index && annotation.end >= index) {
2071
+ annotation.end += offset;
2072
+ } else if (annotation.start >= index) {
2073
+ annotation.start += offset;
2074
+ annotation.end += offset;
2075
+ }
2076
+ });
2077
+ }
2078
+
2079
+ return annotations;
2080
+ };
2081
+
2082
+ V.convertLineToPathData = function(line) {
2083
+
2084
+ line = V(line);
2085
+ var d = [
2086
+ 'M', line.attr('x1'), line.attr('y1'),
2087
+ 'L', line.attr('x2'), line.attr('y2')
2088
+ ].join(' ');
2089
+ return d;
2090
+ };
2091
+
2092
+ V.convertPolygonToPathData = function(polygon) {
2093
+
2094
+ var points = V.getPointsFromSvgNode(polygon);
2095
+ if (points.length === 0) return null;
2096
+
2097
+ return V.svgPointsToPath(points) + ' Z';
2098
+ };
2099
+
2100
+ V.convertPolylineToPathData = function(polyline) {
2101
+
2102
+ var points = V.getPointsFromSvgNode(polyline);
2103
+ if (points.length === 0) return null;
2104
+
2105
+ return V.svgPointsToPath(points);
2106
+ };
2107
+
2108
+ V.svgPointsToPath = function(points) {
2109
+
2110
+ for (var i = 0, n = points.length; i < n; i++) {
2111
+ points[i] = points[i].x + ' ' + points[i].y;
2112
+ }
2113
+
2114
+ return 'M ' + points.join(' L');
2115
+ };
2116
+
2117
+ V.getPointsFromSvgNode = function(node) {
2118
+
2119
+ node = V.toNode(node);
2120
+ var points = [];
2121
+ var nodePoints = node.points;
2122
+ if (nodePoints) {
2123
+ for (var i = 0, n = nodePoints.numberOfItems; i < n; i++) {
2124
+ points.push(nodePoints.getItem(i));
2125
+ }
2126
+ }
2127
+
2128
+ return points;
2129
+ };
2130
+
2131
+ V.KAPPA = 0.551784;
2132
+
2133
+ V.convertCircleToPathData = function(circle) {
2134
+
2135
+ circle = V(circle);
2136
+ var cx = parseFloat(circle.attr('cx')) || 0;
2137
+ var cy = parseFloat(circle.attr('cy')) || 0;
2138
+ var r = parseFloat(circle.attr('r'));
2139
+ var cd = r * V.KAPPA; // Control distance.
2140
+
2141
+ var d = [
2142
+ 'M', cx, cy - r, // Move to the first point.
2143
+ 'C', cx + cd, cy - r, cx + r, cy - cd, cx + r, cy, // I. Quadrant.
2144
+ 'C', cx + r, cy + cd, cx + cd, cy + r, cx, cy + r, // II. Quadrant.
2145
+ 'C', cx - cd, cy + r, cx - r, cy + cd, cx - r, cy, // III. Quadrant.
2146
+ 'C', cx - r, cy - cd, cx - cd, cy - r, cx, cy - r, // IV. Quadrant.
2147
+ 'Z'
2148
+ ].join(' ');
2149
+ return d;
2150
+ };
2151
+
2152
+ V.convertEllipseToPathData = function(ellipse) {
2153
+
2154
+ ellipse = V(ellipse);
2155
+ var cx = parseFloat(ellipse.attr('cx')) || 0;
2156
+ var cy = parseFloat(ellipse.attr('cy')) || 0;
2157
+ var rx = parseFloat(ellipse.attr('rx'));
2158
+ var ry = parseFloat(ellipse.attr('ry')) || rx;
2159
+ var cdx = rx * V.KAPPA; // Control distance x.
2160
+ var cdy = ry * V.KAPPA; // Control distance y.
2161
+
2162
+ var d = [
2163
+ 'M', cx, cy - ry, // Move to the first point.
2164
+ 'C', cx + cdx, cy - ry, cx + rx, cy - cdy, cx + rx, cy, // I. Quadrant.
2165
+ 'C', cx + rx, cy + cdy, cx + cdx, cy + ry, cx, cy + ry, // II. Quadrant.
2166
+ 'C', cx - cdx, cy + ry, cx - rx, cy + cdy, cx - rx, cy, // III. Quadrant.
2167
+ 'C', cx - rx, cy - cdy, cx - cdx, cy - ry, cx, cy - ry, // IV. Quadrant.
2168
+ 'Z'
2169
+ ].join(' ');
2170
+ return d;
2171
+ };
2172
+
2173
+ V.convertRectToPathData = function(rect) {
2174
+
2175
+ rect = V(rect);
2176
+
2177
+ return V.rectToPath({
2178
+ x: parseFloat(rect.attr('x')) || 0,
2179
+ y: parseFloat(rect.attr('y')) || 0,
2180
+ width: parseFloat(rect.attr('width')) || 0,
2181
+ height: parseFloat(rect.attr('height')) || 0,
2182
+ rx: parseFloat(rect.attr('rx')) || 0,
2183
+ ry: parseFloat(rect.attr('ry')) || 0
2184
+ });
2185
+ };
2186
+
2187
+ // Convert a rectangle to SVG path commands. `r` is an object of the form:
2188
+ // `{ x: [number], y: [number], width: [number], height: [number], top-ry: [number], top-ry: [number], bottom-rx: [number], bottom-ry: [number] }`,
2189
+ // where `x, y, width, height` are the usual rectangle attributes and [top-/bottom-]rx/ry allows for
2190
+ // specifying radius of the rectangle for all its sides (as opposed to the built-in SVG rectangle
2191
+ // that has only `rx` and `ry` attributes).
2192
+ V.rectToPath = function(r) {
2193
+
2194
+ var d;
2195
+ var x = r.x;
2196
+ var y = r.y;
2197
+ var width = r.width;
2198
+ var height = r.height;
2199
+ var topRx = min(r.rx || r['top-rx'] || 0, width / 2);
2200
+ var bottomRx = min(r.rx || r['bottom-rx'] || 0, width / 2);
2201
+ var topRy = min(r.ry || r['top-ry'] || 0, height / 2);
2202
+ var bottomRy = min(r.ry || r['bottom-ry'] || 0, height / 2);
2203
+
2204
+ if (topRx || bottomRx || topRy || bottomRy) {
2205
+ d = [
2206
+ 'M', x, y + topRy,
2207
+ 'v', height - topRy - bottomRy,
2208
+ 'a', bottomRx, bottomRy, 0, 0, 0, bottomRx, bottomRy,
2209
+ 'h', width - 2 * bottomRx,
2210
+ 'a', bottomRx, bottomRy, 0, 0, 0, bottomRx, -bottomRy,
2211
+ 'v', -(height - bottomRy - topRy),
2212
+ 'a', topRx, topRy, 0, 0, 0, -topRx, -topRy,
2213
+ 'h', -(width - 2 * topRx),
2214
+ 'a', topRx, topRy, 0, 0, 0, -topRx, topRy,
2215
+ 'Z'
2216
+ ];
2217
+ } else {
2218
+ d = [
2219
+ 'M', x, y,
2220
+ 'H', x + width,
2221
+ 'V', y + height,
2222
+ 'H', x,
2223
+ 'V', y,
2224
+ 'Z'
2225
+ ];
2226
+ }
2227
+
2228
+ return d.join(' ');
2229
+ };
2230
+
2231
+ // Take a path data string
2232
+ // Return a normalized path data string
2233
+ // If data cannot be parsed, return 'M 0 0'
2234
+ // Highly inspired by Raphael Library (www.raphael.com)
2235
+ V.normalizePathData = (function() {
2236
+
2237
+ var spaces = '\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029';
2238
+ var pathCommand = new RegExp('([a-z])[' + spaces + ',]*((-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?[' + spaces + ']*,?[' + spaces + ']*)+)', 'ig');
2239
+ var pathValues = new RegExp('(-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?)[' + spaces + ']*,?[' + spaces + ']*', 'ig');
2240
+
2241
+ var math = Math;
2242
+ var PI = math.PI;
2243
+ var sin = math.sin;
2244
+ var cos = math.cos;
2245
+ var tan = math.tan;
2246
+ var asin = math.asin;
2247
+ var sqrt = math.sqrt;
2248
+ var abs = math.abs;
2249
+
2250
+ function q2c(x1, y1, ax, ay, x2, y2) {
2251
+
2252
+ var _13 = 1 / 3;
2253
+ var _23 = 2 / 3;
2254
+ return [(_13 * x1) + (_23 * ax), (_13 * y1) + (_23 * ay), (_13 * x2) + (_23 * ax), (_13 * y2) + (_23 * ay), x2, y2];
2255
+ }
2256
+
2257
+ function rotate(x, y, rad) {
2258
+
2259
+ var X = (x * cos(rad)) - (y * sin(rad));
2260
+ var Y = (x * sin(rad)) + (y * cos(rad));
2261
+ return { x: X, y: Y };
2262
+ }
2263
+
2264
+ function a2c(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
2265
+ // for more information of where this math came from visit:
2266
+ // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
2267
+ var _120 = (PI * 120) / 180;
2268
+ var rad = (PI / 180) * (+angle || 0);
2269
+ var res = [];
2270
+ var xy;
2271
+
2272
+ if (!recursive) {
2273
+ xy = rotate(x1, y1, -rad);
2274
+ x1 = xy.x;
2275
+ y1 = xy.y;
2276
+
2277
+ xy = rotate(x2, y2, -rad);
2278
+ x2 = xy.x;
2279
+ y2 = xy.y;
2280
+
2281
+ var x = (x1 - x2) / 2;
2282
+ var y = (y1 - y2) / 2;
2283
+ var h = ((x * x) / (rx * rx)) + ((y * y) / (ry * ry));
2284
+
2285
+ if (h > 1) {
2286
+ h = sqrt(h);
2287
+ rx = h * rx;
2288
+ ry = h * ry;
2289
+ }
2290
+
2291
+ var rx2 = rx * rx;
2292
+ var ry2 = ry * ry;
2293
+
2294
+ var k = ((large_arc_flag == sweep_flag) ? -1 : 1) * sqrt(abs(((rx2 * ry2) - (rx2 * y * y) - (ry2 * x * x)) / ((rx2 * y * y) + (ry2 * x * x))));
2295
+
2296
+ var cx = ((k * rx * y) / ry) + ((x1 + x2) / 2);
2297
+ var cy = ((k * -ry * x) / rx) + ((y1 + y2) / 2);
2298
+
2299
+ var f1 = asin(((y1 - cy) / ry).toFixed(9));
2300
+ var f2 = asin(((y2 - cy) / ry).toFixed(9));
2301
+
2302
+ f1 = ((x1 < cx) ? (PI - f1) : f1);
2303
+ f2 = ((x2 < cx) ? (PI - f2) : f2);
2304
+
2305
+ if (f1 < 0) f1 = (PI * 2) + f1;
2306
+ if (f2 < 0) f2 = (PI * 2) + f2;
2307
+
2308
+ if (sweep_flag && (f1 > f2)) f1 = f1 - (PI * 2);
2309
+ if (!sweep_flag && (f2 > f1)) f2 = f2 - (PI * 2);
2310
+
2311
+ } else {
2312
+ f1 = recursive[0];
2313
+ f2 = recursive[1];
2314
+ cx = recursive[2];
2315
+ cy = recursive[3];
2316
+ }
2317
+
2318
+ var df = f2 - f1;
2319
+ if (abs(df) > _120) {
2320
+ var f2old = f2;
2321
+ var x2old = x2;
2322
+ var y2old = y2;
2323
+ f2 = f1 + (_120 * ((sweep_flag && (f2 > f1)) ? 1 : -1));
2324
+ x2 = cx + (rx * cos(f2));
2325
+ y2 = cy + (ry * sin(f2));
2326
+ res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
2327
+ }
2328
+
2329
+ df = f2 - f1;
2330
+
2331
+ var c1 = cos(f1);
2332
+ var s1 = sin(f1);
2333
+ var c2 = cos(f2);
2334
+ var s2 = sin(f2);
2335
+ var t = tan(df / 4);
2336
+ var hx = (4 / 3) * (rx * t);
2337
+ var hy = (4 / 3) * (ry * t);
2338
+ var m1 = [x1, y1];
2339
+ var m2 = [x1 + (hx * s1), y1 - (hy * c1)];
2340
+ var m3 = [x2 + (hx * s2), y2 - (hy * c2)];
2341
+ var m4 = [x2, y2];
2342
+
2343
+ m2[0] = (2 * m1[0]) - m2[0];
2344
+ m2[1] = (2 * m1[1]) - m2[1];
2345
+
2346
+ if (recursive) {
2347
+ return [m2, m3, m4].concat(res);
2348
+ } else {
2349
+ res = [m2, m3, m4].concat(res).join().split(',');
2350
+ var newres = [];
2351
+ var ii = res.length;
2352
+ for (var i = 0; i < ii; i++) {
2353
+ newres[i] = (i % 2) ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
2354
+ }
2355
+ return newres;
2356
+ }
2357
+ }
2358
+
2359
+ function parsePathString(pathString) {
2360
+
2361
+ if (!pathString) return null;
2362
+
2363
+ var paramCounts = { a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0 };
2364
+ var data = [];
2365
+
2366
+ String(pathString).replace(pathCommand, function(a, b, c) {
2367
+
2368
+ var params = [];
2369
+ var name = b.toLowerCase();
2370
+ c.replace(pathValues, function(a, b) {
2371
+ if (b) params.push(+b);
2372
+ });
2373
+
2374
+ if ((name === 'm') && (params.length > 2)) {
2375
+ data.push([b].concat(params.splice(0, 2)));
2376
+ name = 'l';
2377
+ b = ((b === 'm') ? 'l' : 'L');
2378
+ }
2379
+
2380
+ while (params.length >= paramCounts[name]) {
2381
+ data.push([b].concat(params.splice(0, paramCounts[name])));
2382
+ if (!paramCounts[name]) break;
2383
+ }
2384
+ });
2385
+
2386
+ return data;
2387
+ }
2388
+
2389
+ function pathToAbsolute(pathArray) {
2390
+
2391
+ if (!Array.isArray(pathArray) || !Array.isArray(pathArray && pathArray[0])) { // rough assumption
2392
+ pathArray = parsePathString(pathArray);
2393
+ }
2394
+
2395
+ // if invalid string, return 'M 0 0'
2396
+ if (!pathArray || !pathArray.length) return [['M', 0, 0]];
2397
+
2398
+ var res = [];
2399
+ var x = 0;
2400
+ var y = 0;
2401
+ var mx = 0;
2402
+ var my = 0;
2403
+ var start = 0;
2404
+ var pa0;
2405
+
2406
+ var ii = pathArray.length;
2407
+ for (var i = start; i < ii; i++) {
2408
+
2409
+ var r = [];
2410
+ res.push(r);
2411
+
2412
+ var pa = pathArray[i];
2413
+ pa0 = pa[0];
2414
+
2415
+ if (pa0 != pa0.toUpperCase()) {
2416
+ r[0] = pa0.toUpperCase();
2417
+
2418
+ var jj;
2419
+ var j;
2420
+ switch (r[0]) {
2421
+ case 'A':
2422
+ r[1] = pa[1];
2423
+ r[2] = pa[2];
2424
+ r[3] = pa[3];
2425
+ r[4] = pa[4];
2426
+ r[5] = pa[5];
2427
+ r[6] = +pa[6] + x;
2428
+ r[7] = +pa[7] + y;
2429
+ break;
2430
+
2431
+ case 'V':
2432
+ r[1] = +pa[1] + y;
2433
+ break;
2434
+
2435
+ case 'H':
2436
+ r[1] = +pa[1] + x;
2437
+ break;
2438
+
2439
+ case 'M':
2440
+ mx = +pa[1] + x;
2441
+ my = +pa[2] + y;
2442
+
2443
+ jj = pa.length;
2444
+ for (j = 1; j < jj; j++) {
2445
+ r[j] = +pa[j] + ((j % 2) ? x : y);
2446
+ }
2447
+ break;
2448
+
2449
+ default:
2450
+ jj = pa.length;
2451
+ for (j = 1; j < jj; j++) {
2452
+ r[j] = +pa[j] + ((j % 2) ? x : y);
2453
+ }
2454
+ break;
2455
+ }
2456
+ } else {
2457
+ var kk = pa.length;
2458
+ for (var k = 0; k < kk; k++) {
2459
+ r[k] = pa[k];
2460
+ }
2461
+ }
2462
+
2463
+ switch (r[0]) {
2464
+ case 'Z':
2465
+ x = +mx;
2466
+ y = +my;
2467
+ break;
2468
+
2469
+ case 'H':
2470
+ x = r[1];
2471
+ break;
2472
+
2473
+ case 'V':
2474
+ y = r[1];
2475
+ break;
2476
+
2477
+ case 'M':
2478
+ mx = r[r.length - 2];
2479
+ my = r[r.length - 1];
2480
+ x = r[r.length - 2];
2481
+ y = r[r.length - 1];
2482
+ break;
2483
+
2484
+ default:
2485
+ x = r[r.length - 2];
2486
+ y = r[r.length - 1];
2487
+ break;
2488
+ }
2489
+ }
2490
+
2491
+ return res;
2492
+ }
2493
+
2494
+ function normalize(path) {
2495
+
2496
+ var p = pathToAbsolute(path);
2497
+ var attrs = { x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null };
2498
+
2499
+ function processPath(path, d, pcom) {
2500
+
2501
+ var nx, ny;
2502
+
2503
+ if (!path) return ['C', d.x, d.y, d.x, d.y, d.x, d.y];
2504
+
2505
+ if (!(path[0] in { T: 1, Q: 1 })) {
2506
+ d.qx = null;
2507
+ d.qy = null;
2508
+ }
2509
+
2510
+ switch (path[0]) {
2511
+ case 'M':
2512
+ d.X = path[1];
2513
+ d.Y = path[2];
2514
+ break;
2515
+
2516
+ case 'A':
2517
+ if (parseFloat(path[1]) === 0 || parseFloat(path[2]) === 0) {
2518
+ // https://www.w3.org/TR/SVG/paths.html#ArcOutOfRangeParameters
2519
+ // "If either rx or ry is 0, then this arc is treated as a
2520
+ // straight line segment (a "lineto") joining the endpoints."
2521
+ path = ['L', path[6], path[7]];
2522
+ } else {
2523
+ path = ['C'].concat(a2c.apply(0, [d.x, d.y].concat(path.slice(1))));
2524
+ }
2525
+ break;
2526
+
2527
+ case 'S':
2528
+ if (pcom === 'C' || pcom === 'S') { // In 'S' case we have to take into account, if the previous command is C/S.
2529
+ nx = (d.x * 2) - d.bx; // And reflect the previous
2530
+ ny = (d.y * 2) - d.by; // command's control point relative to the current point.
2531
+ } else { // or some else or nothing
2532
+ nx = d.x;
2533
+ ny = d.y;
2534
+ }
2535
+ path = ['C', nx, ny].concat(path.slice(1));
2536
+ break;
2537
+
2538
+ case 'T':
2539
+ if (pcom === 'Q' || pcom === 'T') { // In 'T' case we have to take into account, if the previous command is Q/T.
2540
+ d.qx = (d.x * 2) - d.qx; // And make a reflection similar
2541
+ d.qy = (d.y * 2) - d.qy; // to case 'S'.
2542
+ } else { // or something else or nothing
2543
+ d.qx = d.x;
2544
+ d.qy = d.y;
2545
+ }
2546
+ path = ['C'].concat(q2c(d.x, d.y, d.qx, d.qy, path[1], path[2]));
2547
+ break;
2548
+
2549
+ case 'Q':
2550
+ d.qx = path[1];
2551
+ d.qy = path[2];
2552
+ path = ['C'].concat(q2c(d.x, d.y, path[1], path[2], path[3], path[4]));
2553
+ break;
2554
+
2555
+ case 'H':
2556
+ path = ['L'].concat(path[1], d.y);
2557
+ break;
2558
+
2559
+ case 'V':
2560
+ path = ['L'].concat(d.x, path[1]);
2561
+ break;
2562
+
2563
+ case 'L':
2564
+ break;
2565
+
2566
+ case 'Z':
2567
+ break;
2568
+ }
2569
+
2570
+ return path;
2571
+ }
2572
+
2573
+ function fixArc(pp, i) {
2574
+
2575
+ if (pp[i].length > 7) {
2576
+
2577
+ pp[i].shift();
2578
+ var pi = pp[i];
2579
+
2580
+ while (pi.length) {
2581
+ pcoms[i] = 'A'; // if created multiple 'C's, their original seg is saved
2582
+ pp.splice(i++, 0, ['C'].concat(pi.splice(0, 6)));
2583
+ }
2584
+
2585
+ pp.splice(i, 1);
2586
+ ii = p.length;
2587
+ }
2588
+ }
2589
+
2590
+ var pcoms = []; // path commands of original path p
2591
+ var pfirst = ''; // temporary holder for original path command
2592
+ var pcom = ''; // holder for previous path command of original path
2593
+
2594
+ var ii = p.length;
2595
+ for (var i = 0; i < ii; i++) {
2596
+ if (p[i]) pfirst = p[i][0]; // save current path command
2597
+
2598
+ if (pfirst !== 'C') { // C is not saved yet, because it may be result of conversion
2599
+ pcoms[i] = pfirst; // Save current path command
2600
+ if (i > 0) pcom = pcoms[i - 1]; // Get previous path command pcom
2601
+ }
2602
+
2603
+ p[i] = processPath(p[i], attrs, pcom); // Previous path command is inputted to processPath
2604
+
2605
+ if (pcoms[i] !== 'A' && pfirst === 'C') pcoms[i] = 'C'; // 'A' is the only command
2606
+ // which may produce multiple 'C's
2607
+ // so we have to make sure that 'C' is also 'C' in original path
2608
+
2609
+ fixArc(p, i); // fixArc adds also the right amount of 'A's to pcoms
2610
+
2611
+ var seg = p[i];
2612
+ var seglen = seg.length;
2613
+
2614
+ attrs.x = seg[seglen - 2];
2615
+ attrs.y = seg[seglen - 1];
2616
+
2617
+ attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
2618
+ attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
2619
+ }
2620
+
2621
+ // make sure normalized path data string starts with an M segment
2622
+ if (!p[0][0] || p[0][0] !== 'M') {
2623
+ p.unshift(['M', 0, 0]);
2624
+ }
2625
+
2626
+ return p;
2627
+ }
2628
+
2629
+ return function(pathData) {
2630
+ return normalize(pathData).join(',').split(',').join(' ');
2631
+ };
2632
+ })();
2633
+
2634
+ V.namespace = ns;
2635
+
2636
+ V.g = g;
2637
+
2638
+ return V;
2639
+
2640
+ })();
2641
+
2642
+ export default V;