@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
package/src/g/path.mjs ADDED
@@ -0,0 +1,2260 @@
1
+ // Accepts path data string, array of segments, array of Curves and/or Lines, or a Polyline.
2
+ // Path created is not guaranteed to be a valid (serializable) path (might not start with an M).
3
+ import { Polyline } from './polyline.mjs';
4
+ import { Rect } from './rect.mjs';
5
+ import { Point } from './point.mjs';
6
+ import { Line } from './line.mjs';
7
+ import { Curve } from './curve.mjs';
8
+ import { types } from './types.mjs';
9
+ import { extend } from './extend.mjs';
10
+ export const Path = function(arg) {
11
+
12
+ if (!(this instanceof Path)) {
13
+ return new Path(arg);
14
+ }
15
+
16
+ if (typeof arg === 'string') { // create from a path data string
17
+ return new Path.parse(arg);
18
+ }
19
+
20
+ this.segments = [];
21
+
22
+ var i;
23
+ var n;
24
+
25
+ if (!arg) {
26
+ // don't do anything
27
+
28
+ } else if (Array.isArray(arg) && arg.length !== 0) { // if arg is a non-empty array
29
+ // flatten one level deep
30
+ // so we can chain arbitrary Path.createSegment results
31
+ arg = arg.reduce(function(acc, val) {
32
+ return acc.concat(val);
33
+ }, []);
34
+
35
+ n = arg.length;
36
+ if (arg[0].isSegment) { // create from an array of segments
37
+ for (i = 0; i < n; i++) {
38
+
39
+ var segment = arg[i];
40
+
41
+ this.appendSegment(segment);
42
+ }
43
+
44
+ } else { // create from an array of Curves and/or Lines
45
+ var previousObj = null;
46
+ for (i = 0; i < n; i++) {
47
+
48
+ var obj = arg[i];
49
+
50
+ if (!((obj instanceof Line) || (obj instanceof Curve))) {
51
+ throw new Error('Cannot construct a path segment from the provided object.');
52
+ }
53
+
54
+ if (i === 0) this.appendSegment(Path.createSegment('M', obj.start));
55
+
56
+ // if objects do not link up, moveto segments are inserted to cover the gaps
57
+ if (previousObj && !previousObj.end.equals(obj.start)) this.appendSegment(Path.createSegment('M', obj.start));
58
+
59
+ if (obj instanceof Line) {
60
+ this.appendSegment(Path.createSegment('L', obj.end));
61
+
62
+ } else if (obj instanceof Curve) {
63
+ this.appendSegment(Path.createSegment('C', obj.controlPoint1, obj.controlPoint2, obj.end));
64
+ }
65
+
66
+ previousObj = obj;
67
+ }
68
+ }
69
+
70
+ } else if (arg.isSegment) { // create from a single segment
71
+ this.appendSegment(arg);
72
+
73
+ } else if (arg instanceof Line) { // create from a single Line
74
+ this.appendSegment(Path.createSegment('M', arg.start));
75
+ this.appendSegment(Path.createSegment('L', arg.end));
76
+
77
+ } else if (arg instanceof Curve) { // create from a single Curve
78
+ this.appendSegment(Path.createSegment('M', arg.start));
79
+ this.appendSegment(Path.createSegment('C', arg.controlPoint1, arg.controlPoint2, arg.end));
80
+
81
+ } else if (arg instanceof Polyline) { // create from a Polyline
82
+ if (!(arg.points && (arg.points.length !== 0))) return; // if Polyline has no points, leave Path empty
83
+
84
+ n = arg.points.length;
85
+ for (i = 0; i < n; i++) {
86
+
87
+ var point = arg.points[i];
88
+
89
+ if (i === 0) this.appendSegment(Path.createSegment('M', point));
90
+ else this.appendSegment(Path.createSegment('L', point));
91
+ }
92
+
93
+ } else { // unknown object
94
+ throw new Error('Cannot construct a path from the provided object.');
95
+ }
96
+ };
97
+
98
+ // More permissive than V.normalizePathData and Path.prototype.serialize.
99
+ // Allows path data strings that do not start with a Moveto command (unlike SVG specification).
100
+ // Does not require spaces between elements; commas are allowed, separators may be omitted when unambiguous (e.g. 'ZM10,10', 'L1.6.8', 'M100-200').
101
+ // Allows for command argument chaining.
102
+ // Throws an error if wrong number of arguments is provided with a command.
103
+ // Throws an error if an unrecognized path command is provided (according to Path.segmentTypes). Only a subset of SVG commands is currently supported (L, C, M, Z).
104
+ Path.parse = function(pathData) {
105
+
106
+ if (!pathData) return new Path();
107
+
108
+ var path = new Path();
109
+
110
+ var commandRe = /(?:[a-zA-Z] *)(?:(?:-?\d+(?:\.\d+)?(?:e[-+]?\d+)? *,? *)|(?:-?\.\d+ *,? *))+|(?:[a-zA-Z] *)(?! |\d|-|\.)/g;
111
+ var commands = pathData.match(commandRe);
112
+
113
+ var numCommands = commands.length;
114
+ for (var i = 0; i < numCommands; i++) {
115
+
116
+ var command = commands[i];
117
+ var argRe = /(?:[a-zA-Z])|(?:(?:-?\d+(?:\.\d+)?(?:e[-+]?\d+)?))|(?:(?:-?\.\d+))/g;
118
+ var args = command.match(argRe);
119
+
120
+ var segment = Path.createSegment.apply(this, args); // args = [type, coordinate1, coordinate2...]
121
+ path.appendSegment(segment);
122
+ }
123
+
124
+ return path;
125
+ };
126
+
127
+ // Create a segment or an array of segments.
128
+ // Accepts unlimited points/coords arguments after `type`.
129
+ Path.createSegment = function(type) {
130
+
131
+ if (!type) throw new Error('Type must be provided.');
132
+
133
+ var segmentConstructor = Path.segmentTypes[type];
134
+ if (!segmentConstructor) throw new Error(type + ' is not a recognized path segment type.');
135
+
136
+ var args = [];
137
+ var n = arguments.length;
138
+ for (var i = 1; i < n; i++) { // do not add first element (`type`) to args array
139
+ args.push(arguments[i]);
140
+ }
141
+
142
+ return applyToNew(segmentConstructor, args);
143
+ };
144
+
145
+ Path.prototype = {
146
+
147
+ type: types.Path,
148
+
149
+ // Accepts one segment or an array of segments as argument.
150
+ // Throws an error if argument is not a segment or an array of segments.
151
+ appendSegment: function(arg) {
152
+
153
+ var segments = this.segments;
154
+ var numSegments = segments.length;
155
+ // works even if path has no segments
156
+
157
+ var currentSegment;
158
+
159
+ var previousSegment = ((numSegments !== 0) ? segments[numSegments - 1] : null); // if we are appending to an empty path, previousSegment is null
160
+ var nextSegment = null;
161
+
162
+ if (!Array.isArray(arg)) { // arg is a segment
163
+ if (!arg || !arg.isSegment) throw new Error('Segment required.');
164
+
165
+ currentSegment = this.prepareSegment(arg, previousSegment, nextSegment);
166
+ segments.push(currentSegment);
167
+
168
+ } else { // arg is an array of segments
169
+ // flatten one level deep
170
+ // so we can chain arbitrary Path.createSegment results
171
+ arg = arg.reduce(function(acc, val) {
172
+ return acc.concat(val);
173
+ }, []);
174
+
175
+ if (!arg[0].isSegment) throw new Error('Segments required.');
176
+
177
+ var n = arg.length;
178
+ for (var i = 0; i < n; i++) {
179
+
180
+ var currentArg = arg[i];
181
+ currentSegment = this.prepareSegment(currentArg, previousSegment, nextSegment);
182
+ segments.push(currentSegment);
183
+ previousSegment = currentSegment;
184
+ }
185
+ }
186
+ },
187
+
188
+ // Returns the bbox of the path.
189
+ // If path has no segments, returns null.
190
+ // If path has only invisible segments, returns bbox of the end point of last segment.
191
+ bbox: function() {
192
+
193
+ var segments = this.segments;
194
+ var numSegments = segments.length;
195
+ if (numSegments === 0) return null; // if segments is an empty array
196
+
197
+ var bbox;
198
+ for (var i = 0; i < numSegments; i++) {
199
+
200
+ var segment = segments[i];
201
+ if (segment.isVisible) {
202
+ var segmentBBox = segment.bbox();
203
+ bbox = bbox ? bbox.union(segmentBBox) : segmentBBox;
204
+ }
205
+ }
206
+
207
+ if (bbox) return bbox;
208
+
209
+ // if the path has only invisible elements, return end point of last segment
210
+ var lastSegment = segments[numSegments - 1];
211
+ return new Rect(lastSegment.end.x, lastSegment.end.y, 0, 0);
212
+ },
213
+
214
+ // Returns a new path that is a clone of this path.
215
+ clone: function() {
216
+
217
+ var segments = this.segments;
218
+ var numSegments = segments.length;
219
+ // works even if path has no segments
220
+
221
+ var path = new Path();
222
+ for (var i = 0; i < numSegments; i++) {
223
+
224
+ var segment = segments[i].clone();
225
+ path.appendSegment(segment);
226
+ }
227
+
228
+ return path;
229
+ },
230
+
231
+ closestPoint: function(p, opt) {
232
+
233
+ var t = this.closestPointT(p, opt);
234
+ if (!t) return null;
235
+
236
+ return this.pointAtT(t);
237
+ },
238
+
239
+ closestPointLength: function(p, opt) {
240
+
241
+ opt = opt || {};
242
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
243
+ var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
244
+ var localOpt = { precision: precision, segmentSubdivisions: segmentSubdivisions };
245
+
246
+ var t = this.closestPointT(p, localOpt);
247
+ if (!t) return 0;
248
+
249
+ return this.lengthAtT(t, localOpt);
250
+ },
251
+
252
+ closestPointNormalizedLength: function(p, opt) {
253
+
254
+ opt = opt || {};
255
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
256
+ var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
257
+ var localOpt = { precision: precision, segmentSubdivisions: segmentSubdivisions };
258
+
259
+ var cpLength = this.closestPointLength(p, localOpt);
260
+ if (cpLength === 0) return 0; // shortcut
261
+
262
+ var length = this.length(localOpt);
263
+ if (length === 0) return 0; // prevents division by zero
264
+
265
+ return cpLength / length;
266
+ },
267
+
268
+ // Private function.
269
+ closestPointT: function(p, opt) {
270
+
271
+ var segments = this.segments;
272
+ var numSegments = segments.length;
273
+ if (numSegments === 0) return null; // if segments is an empty array
274
+
275
+ opt = opt || {};
276
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
277
+ var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
278
+ // not using localOpt
279
+
280
+ var closestPointT;
281
+ var minSquaredDistance = Infinity;
282
+ for (var i = 0; i < numSegments; i++) {
283
+
284
+ var segment = segments[i];
285
+ var subdivisions = segmentSubdivisions[i];
286
+
287
+ if (segment.isVisible) {
288
+ var segmentClosestPointT = segment.closestPointT(p, {
289
+ precision: precision,
290
+ subdivisions: subdivisions
291
+ });
292
+ var segmentClosestPoint = segment.pointAtT(segmentClosestPointT);
293
+ var squaredDistance = (new Line(segmentClosestPoint, p)).squaredLength();
294
+
295
+ if (squaredDistance < minSquaredDistance) {
296
+ closestPointT = { segmentIndex: i, value: segmentClosestPointT };
297
+ minSquaredDistance = squaredDistance;
298
+ }
299
+ }
300
+ }
301
+
302
+ if (closestPointT) return closestPointT;
303
+
304
+ // if no visible segment, return end of last segment
305
+ return { segmentIndex: numSegments - 1, value: 1 };
306
+ },
307
+
308
+ closestPointTangent: function(p, opt) {
309
+
310
+ var segments = this.segments;
311
+ var numSegments = segments.length;
312
+ if (numSegments === 0) return null; // if segments is an empty array
313
+
314
+ opt = opt || {};
315
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
316
+ var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
317
+ // not using localOpt
318
+
319
+ var closestPointTangent;
320
+ var minSquaredDistance = Infinity;
321
+ for (var i = 0; i < numSegments; i++) {
322
+
323
+ var segment = segments[i];
324
+ var subdivisions = segmentSubdivisions[i];
325
+
326
+ if (segment.isDifferentiable()) {
327
+ var segmentClosestPointT = segment.closestPointT(p, {
328
+ precision: precision,
329
+ subdivisions: subdivisions
330
+ });
331
+ var segmentClosestPoint = segment.pointAtT(segmentClosestPointT);
332
+ var squaredDistance = (new Line(segmentClosestPoint, p)).squaredLength();
333
+
334
+ if (squaredDistance < minSquaredDistance) {
335
+ closestPointTangent = segment.tangentAtT(segmentClosestPointT);
336
+ minSquaredDistance = squaredDistance;
337
+ }
338
+ }
339
+ }
340
+
341
+ if (closestPointTangent) return closestPointTangent;
342
+
343
+ // if no valid segment, return null
344
+ return null;
345
+ },
346
+
347
+ // Returns `true` if the area surrounded by the path contains the point `p`.
348
+ // Implements the even-odd algorithm (self-intersections are "outside").
349
+ // Closes open paths (always imagines a final closing segment).
350
+ // Precision may be adjusted by passing an `opt` object.
351
+ containsPoint: function(p, opt) {
352
+
353
+ var polylines = this.toPolylines(opt);
354
+ if (!polylines) return false; // shortcut (this path has no polylines)
355
+
356
+ var numPolylines = polylines.length;
357
+
358
+ // how many component polylines does `p` lie within?
359
+ var numIntersections = 0;
360
+ for (var i = 0; i < numPolylines; i++) {
361
+ var polyline = polylines[i];
362
+ if (polyline.containsPoint(p)) {
363
+ // `p` lies within this polyline
364
+ numIntersections++;
365
+ }
366
+ }
367
+
368
+ // returns `true` for odd numbers of intersections (even-odd algorithm)
369
+ return ((numIntersections % 2) === 1);
370
+ },
371
+
372
+ // Divides the path into two at requested `ratio` between 0 and 1 with precision better than `opt.precision`; optionally using `opt.subdivisions` provided.
373
+ divideAt: function(ratio, opt) {
374
+
375
+ var segments = this.segments;
376
+ var numSegments = segments.length;
377
+ if (numSegments === 0) return null; // if segments is an empty array
378
+
379
+ if (ratio < 0) ratio = 0;
380
+ if (ratio > 1) ratio = 1;
381
+
382
+ opt = opt || {};
383
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
384
+ var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
385
+ var localOpt = { precision: precision, segmentSubdivisions: segmentSubdivisions };
386
+
387
+ var pathLength = this.length(localOpt);
388
+ var length = pathLength * ratio;
389
+
390
+ return this.divideAtLength(length, localOpt);
391
+ },
392
+
393
+ // Divides the path into two at requested `length` with precision better than requested `opt.precision`; optionally using `opt.subdivisions` provided.
394
+ divideAtLength: function(length, opt) {
395
+
396
+ var numSegments = this.segments.length;
397
+ if (numSegments === 0) return null; // if segments is an empty array
398
+
399
+ var fromStart = true;
400
+ if (length < 0) {
401
+ fromStart = false; // negative lengths mean start calculation from end point
402
+ length = -length; // absolute value
403
+ }
404
+
405
+ opt = opt || {};
406
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
407
+ var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
408
+ // not using localOpt
409
+
410
+ var i;
411
+ var segment;
412
+
413
+ // identify the segment to divide:
414
+
415
+ var l = 0; // length so far
416
+ var divided;
417
+ var dividedSegmentIndex;
418
+ var lastValidSegment; // visible AND differentiable
419
+ var lastValidSegmentIndex;
420
+ var t;
421
+ for (i = 0; i < numSegments; i++) {
422
+ var index = (fromStart ? i : (numSegments - 1 - i));
423
+
424
+ segment = this.getSegment(index);
425
+ var subdivisions = segmentSubdivisions[index];
426
+ var d = segment.length({ precision: precision, subdivisions: subdivisions });
427
+
428
+ if (segment.isDifferentiable()) { // segment is not just a point
429
+ lastValidSegment = segment;
430
+ lastValidSegmentIndex = index;
431
+
432
+ if (length <= (l + d)) {
433
+ dividedSegmentIndex = index;
434
+ divided = segment.divideAtLength(((fromStart ? 1 : -1) * (length - l)), {
435
+ precision: precision,
436
+ subdivisions: subdivisions
437
+ });
438
+ break;
439
+ }
440
+ }
441
+
442
+ l += d;
443
+ }
444
+
445
+ if (!lastValidSegment) { // no valid segment found
446
+ return null;
447
+ }
448
+
449
+ // else: the path contains at least one valid segment
450
+
451
+ if (!divided) { // the desired length is greater than the length of the path
452
+ dividedSegmentIndex = lastValidSegmentIndex;
453
+ t = (fromStart ? 1 : 0);
454
+ divided = lastValidSegment.divideAtT(t);
455
+ }
456
+
457
+ // create a copy of this path and replace the identified segment with its two divided parts:
458
+
459
+ var pathCopy = this.clone();
460
+ pathCopy.replaceSegment(dividedSegmentIndex, divided);
461
+
462
+ var divisionStartIndex = dividedSegmentIndex;
463
+ var divisionMidIndex = dividedSegmentIndex + 1;
464
+ var divisionEndIndex = dividedSegmentIndex + 2;
465
+
466
+ // do not insert the part if it looks like a point
467
+ if (!divided[0].isDifferentiable()) {
468
+ pathCopy.removeSegment(divisionStartIndex);
469
+ divisionMidIndex -= 1;
470
+ divisionEndIndex -= 1;
471
+ }
472
+
473
+ // insert a Moveto segment to ensure secondPath will be valid:
474
+ var movetoEnd = pathCopy.getSegment(divisionMidIndex).start;
475
+ pathCopy.insertSegment(divisionMidIndex, Path.createSegment('M', movetoEnd));
476
+ divisionEndIndex += 1;
477
+
478
+ // do not insert the part if it looks like a point
479
+ if (!divided[1].isDifferentiable()) {
480
+ pathCopy.removeSegment(divisionEndIndex - 1);
481
+ divisionEndIndex -= 1;
482
+ }
483
+
484
+ // ensure that Closepath segments in secondPath will be assigned correct subpathStartSegment:
485
+
486
+ var secondPathSegmentIndexConversion = divisionEndIndex - divisionStartIndex - 1;
487
+ for (i = divisionEndIndex; i < pathCopy.segments.length; i++) {
488
+
489
+ var originalSegment = this.getSegment(i - secondPathSegmentIndexConversion);
490
+ segment = pathCopy.getSegment(i);
491
+
492
+ if ((segment.type === 'Z') && !originalSegment.subpathStartSegment.end.equals(segment.subpathStartSegment.end)) {
493
+ // pathCopy segment's subpathStartSegment is different from original segment's one
494
+ // convert this Closepath segment to a Lineto and replace it in pathCopy
495
+ var convertedSegment = Path.createSegment('L', originalSegment.end);
496
+ pathCopy.replaceSegment(i, convertedSegment);
497
+ }
498
+ }
499
+
500
+ // distribute pathCopy segments into two paths and return those:
501
+
502
+ var firstPath = new Path(pathCopy.segments.slice(0, divisionMidIndex));
503
+ var secondPath = new Path(pathCopy.segments.slice(divisionMidIndex));
504
+
505
+ return [firstPath, secondPath];
506
+ },
507
+
508
+ // Checks whether two paths are exactly the same.
509
+ // If `p` is undefined or null, returns false.
510
+ equals: function(p) {
511
+
512
+ if (!p) return false;
513
+
514
+ var segments = this.segments;
515
+ var otherSegments = p.segments;
516
+
517
+ var numSegments = segments.length;
518
+ if (otherSegments.length !== numSegments) return false; // if the two paths have different number of segments, they cannot be equal
519
+
520
+ for (var i = 0; i < numSegments; i++) {
521
+
522
+ var segment = segments[i];
523
+ var otherSegment = otherSegments[i];
524
+
525
+ // as soon as an inequality is found in segments, return false
526
+ if ((segment.type !== otherSegment.type) || (!segment.equals(otherSegment))) return false;
527
+ }
528
+
529
+ // if no inequality found in segments, return true
530
+ return true;
531
+ },
532
+
533
+ // Accepts negative indices.
534
+ // Throws an error if path has no segments.
535
+ // Throws an error if index is out of range.
536
+ getSegment: function(index) {
537
+
538
+ var segments = this.segments;
539
+ var numSegments = segments.length;
540
+ if (numSegments === 0) throw new Error('Path has no segments.');
541
+
542
+ if (index < 0) index = numSegments + index; // convert negative indices to positive
543
+ if (index >= numSegments || index < 0) throw new Error('Index out of range.');
544
+
545
+ return segments[index];
546
+ },
547
+
548
+ // Returns an array of segment subdivisions, with precision better than requested `opt.precision`.
549
+ getSegmentSubdivisions: function(opt) {
550
+
551
+ var segments = this.segments;
552
+ var numSegments = segments.length;
553
+ // works even if path has no segments
554
+
555
+ opt = opt || {};
556
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
557
+ // not using opt.segmentSubdivisions
558
+ // not using localOpt
559
+
560
+ var segmentSubdivisions = [];
561
+ for (var i = 0; i < numSegments; i++) {
562
+
563
+ var segment = segments[i];
564
+ var subdivisions = segment.getSubdivisions({ precision: precision });
565
+ segmentSubdivisions.push(subdivisions);
566
+ }
567
+
568
+ return segmentSubdivisions;
569
+ },
570
+
571
+ // Returns an array of subpaths of this path.
572
+ // Invalid paths are validated first.
573
+ // Returns `[]` if path has no segments.
574
+ getSubpaths: function() {
575
+
576
+ const validatedPath = this.clone().validate();
577
+
578
+ const segments = validatedPath.segments;
579
+ const numSegments = segments.length;
580
+
581
+ const subpaths = [];
582
+ for (let i = 0; i < numSegments; i++) {
583
+
584
+ const segment = segments[i];
585
+ if (segment.isSubpathStart) {
586
+ // we encountered a subpath start segment
587
+ // create a new path for segment, and push it to list of subpaths
588
+ subpaths.push(new Path(segment));
589
+
590
+ } else {
591
+ // append current segment to the last subpath
592
+ subpaths[subpaths.length - 1].appendSegment(segment);
593
+ }
594
+ }
595
+
596
+ return subpaths;
597
+ },
598
+
599
+ // Insert `arg` at given `index`.
600
+ // `index = 0` means insert at the beginning.
601
+ // `index = segments.length` means insert at the end.
602
+ // Accepts negative indices, from `-1` to `-(segments.length + 1)`.
603
+ // Accepts one segment or an array of segments as argument.
604
+ // Throws an error if index is out of range.
605
+ // Throws an error if argument is not a segment or an array of segments.
606
+ insertSegment: function(index, arg) {
607
+
608
+ var segments = this.segments;
609
+ var numSegments = segments.length;
610
+ // works even if path has no segments
611
+
612
+ // note that these are incremented compared to getSegments()
613
+ // we can insert after last element (note that this changes the meaning of index -1)
614
+ if (index < 0) index = numSegments + index + 1; // convert negative indices to positive
615
+ if (index > numSegments || index < 0) throw new Error('Index out of range.');
616
+
617
+ var currentSegment;
618
+
619
+ var previousSegment = null;
620
+ var nextSegment = null;
621
+
622
+ if (numSegments !== 0) {
623
+ if (index >= 1) {
624
+ previousSegment = segments[index - 1];
625
+ nextSegment = previousSegment.nextSegment; // if we are inserting at end, nextSegment is null
626
+
627
+ } else { // if index === 0
628
+ // previousSegment is null
629
+ nextSegment = segments[0];
630
+ }
631
+ }
632
+
633
+ if (!Array.isArray(arg)) {
634
+ if (!arg || !arg.isSegment) throw new Error('Segment required.');
635
+
636
+ currentSegment = this.prepareSegment(arg, previousSegment, nextSegment);
637
+ segments.splice(index, 0, currentSegment);
638
+
639
+ } else {
640
+ // flatten one level deep
641
+ // so we can chain arbitrary Path.createSegment results
642
+ arg = arg.reduce(function(acc, val) {
643
+ return acc.concat(val);
644
+ }, []);
645
+
646
+ if (!arg[0].isSegment) throw new Error('Segments required.');
647
+
648
+ var n = arg.length;
649
+ for (var i = 0; i < n; i++) {
650
+
651
+ var currentArg = arg[i];
652
+ currentSegment = this.prepareSegment(currentArg, previousSegment, nextSegment);
653
+ segments.splice((index + i), 0, currentSegment); // incrementing index to insert subsequent segments after inserted segments
654
+ previousSegment = currentSegment;
655
+ }
656
+ }
657
+ },
658
+
659
+ intersectionWithLine: function(line, opt) {
660
+
661
+ var intersection = null;
662
+ var polylines = this.toPolylines(opt);
663
+ if (!polylines) return null;
664
+ for (var i = 0, n = polylines.length; i < n; i++) {
665
+ var polyline = polylines[i];
666
+ var polylineIntersection = line.intersect(polyline);
667
+ if (polylineIntersection) {
668
+ intersection || (intersection = []);
669
+ if (Array.isArray(polylineIntersection)) {
670
+ Array.prototype.push.apply(intersection, polylineIntersection);
671
+ } else {
672
+ intersection.push(polylineIntersection);
673
+ }
674
+ }
675
+ }
676
+
677
+ return intersection;
678
+ },
679
+
680
+ isDifferentiable: function() {
681
+
682
+ var segments = this.segments;
683
+ var numSegments = segments.length;
684
+
685
+ for (var i = 0; i < numSegments; i++) {
686
+
687
+ var segment = segments[i];
688
+ // as soon as a differentiable segment is found in segments, return true
689
+ if (segment.isDifferentiable()) return true;
690
+ }
691
+
692
+ // if no differentiable segment is found in segments, return false
693
+ return false;
694
+ },
695
+
696
+ // Checks whether current path segments are valid.
697
+ // Note that d is allowed to be empty - should disable rendering of the path.
698
+ isValid: function() {
699
+
700
+ var segments = this.segments;
701
+ var isValid = (segments.length === 0) || (segments[0].type === 'M'); // either empty or first segment is a Moveto
702
+ return isValid;
703
+ },
704
+
705
+ // Returns length of the path, with precision better than requested `opt.precision`; or using `opt.segmentSubdivisions` provided.
706
+ // If path has no segments, returns 0.
707
+ length: function(opt) {
708
+
709
+ var segments = this.segments;
710
+ var numSegments = segments.length;
711
+ if (numSegments === 0) return 0; // if segments is an empty array
712
+
713
+ opt = opt || {};
714
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; // opt.precision only used in getSegmentSubdivisions() call
715
+ var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
716
+ // not using localOpt
717
+
718
+ var length = 0;
719
+ for (var i = 0; i < numSegments; i++) {
720
+
721
+ var segment = segments[i];
722
+ var subdivisions = segmentSubdivisions[i];
723
+ length += segment.length({ subdivisions: subdivisions });
724
+ }
725
+
726
+ return length;
727
+ },
728
+
729
+ // Private function.
730
+ lengthAtT: function(t, opt) {
731
+
732
+ var segments = this.segments;
733
+ var numSegments = segments.length;
734
+ if (numSegments === 0) return 0; // if segments is an empty array
735
+
736
+ var segmentIndex = t.segmentIndex;
737
+ if (segmentIndex < 0) return 0; // regardless of t.value
738
+
739
+ var tValue = t.value;
740
+ if (segmentIndex >= numSegments) {
741
+ segmentIndex = numSegments - 1;
742
+ tValue = 1;
743
+ } else if (tValue < 0) tValue = 0;
744
+ else if (tValue > 1) tValue = 1;
745
+
746
+ opt = opt || {};
747
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
748
+ var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
749
+ // not using localOpt
750
+
751
+ var subdivisions;
752
+ var length = 0;
753
+ for (var i = 0; i < segmentIndex; i++) {
754
+
755
+ var segment = segments[i];
756
+ subdivisions = segmentSubdivisions[i];
757
+ length += segment.length({ precisison: precision, subdivisions: subdivisions });
758
+ }
759
+
760
+ segment = segments[segmentIndex];
761
+ subdivisions = segmentSubdivisions[segmentIndex];
762
+ length += segment.lengthAtT(tValue, { precisison: precision, subdivisions: subdivisions });
763
+
764
+ return length;
765
+ },
766
+
767
+ // Returns point at requested `ratio` between 0 and 1, with precision better than requested `opt.precision`; optionally using `opt.segmentSubdivisions` provided.
768
+ pointAt: function(ratio, opt) {
769
+
770
+ var segments = this.segments;
771
+ var numSegments = segments.length;
772
+ if (numSegments === 0) return null; // if segments is an empty array
773
+
774
+ if (ratio <= 0) return this.start.clone();
775
+ if (ratio >= 1) return this.end.clone();
776
+
777
+ opt = opt || {};
778
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
779
+ var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
780
+ var localOpt = { precision: precision, segmentSubdivisions: segmentSubdivisions };
781
+
782
+ var pathLength = this.length(localOpt);
783
+ var length = pathLength * ratio;
784
+
785
+ return this.pointAtLength(length, localOpt);
786
+ },
787
+
788
+ // Returns point at requested `length`, with precision better than requested `opt.precision`; optionally using `opt.segmentSubdivisions` provided.
789
+ // Accepts negative length.
790
+ pointAtLength: function(length, opt) {
791
+
792
+ var segments = this.segments;
793
+ var numSegments = segments.length;
794
+ if (numSegments === 0) return null; // if segments is an empty array
795
+
796
+ if (length === 0) return this.start.clone();
797
+
798
+ var fromStart = true;
799
+ if (length < 0) {
800
+ fromStart = false; // negative lengths mean start calculation from end point
801
+ length = -length; // absolute value
802
+ }
803
+
804
+ opt = opt || {};
805
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
806
+ var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
807
+ // not using localOpt
808
+
809
+ var lastVisibleSegment;
810
+ var l = 0; // length so far
811
+ for (var i = 0; i < numSegments; i++) {
812
+ var index = (fromStart ? i : (numSegments - 1 - i));
813
+
814
+ var segment = segments[index];
815
+ var subdivisions = segmentSubdivisions[index];
816
+ var d = segment.length({ precision: precision, subdivisions: subdivisions });
817
+
818
+ if (segment.isVisible) {
819
+ if (length <= (l + d)) {
820
+ return segment.pointAtLength(((fromStart ? 1 : -1) * (length - l)), {
821
+ precision: precision,
822
+ subdivisions: subdivisions
823
+ });
824
+ }
825
+
826
+ lastVisibleSegment = segment;
827
+ }
828
+
829
+ l += d;
830
+ }
831
+
832
+ // if length requested is higher than the length of the path, return last visible segment endpoint
833
+ if (lastVisibleSegment) return (fromStart ? lastVisibleSegment.end : lastVisibleSegment.start);
834
+
835
+ // if no visible segment, return last segment end point (no matter if fromStart or no)
836
+ var lastSegment = segments[numSegments - 1];
837
+ return lastSegment.end.clone();
838
+ },
839
+
840
+ // Private function.
841
+ pointAtT: function(t) {
842
+
843
+ var segments = this.segments;
844
+ var numSegments = segments.length;
845
+ if (numSegments === 0) return null; // if segments is an empty array
846
+
847
+ var segmentIndex = t.segmentIndex;
848
+ if (segmentIndex < 0) return segments[0].pointAtT(0);
849
+ if (segmentIndex >= numSegments) return segments[numSegments - 1].pointAtT(1);
850
+
851
+ var tValue = t.value;
852
+ if (tValue < 0) tValue = 0;
853
+ else if (tValue > 1) tValue = 1;
854
+
855
+ return segments[segmentIndex].pointAtT(tValue);
856
+ },
857
+
858
+ // Default precision
859
+ PRECISION: 3,
860
+
861
+ // Helper method for adding segments.
862
+ prepareSegment: function(segment, previousSegment, nextSegment) {
863
+
864
+ // insert after previous segment and before previous segment's next segment
865
+ segment.previousSegment = previousSegment;
866
+ segment.nextSegment = nextSegment;
867
+ if (previousSegment) previousSegment.nextSegment = segment;
868
+ if (nextSegment) nextSegment.previousSegment = segment;
869
+
870
+ var updateSubpathStart = segment;
871
+ if (segment.isSubpathStart) {
872
+ segment.subpathStartSegment = segment; // assign self as subpath start segment
873
+ updateSubpathStart = nextSegment; // start updating from next segment
874
+ }
875
+
876
+ // assign previous segment's subpath start (or self if it is a subpath start) to subsequent segments
877
+ if (updateSubpathStart) this.updateSubpathStartSegment(updateSubpathStart);
878
+
879
+ return segment;
880
+ },
881
+
882
+ // Remove the segment at `index`.
883
+ // Accepts negative indices, from `-1` to `-segments.length`.
884
+ // Throws an error if path has no segments.
885
+ // Throws an error if index is out of range.
886
+ removeSegment: function(index) {
887
+
888
+ var segments = this.segments;
889
+ var numSegments = segments.length;
890
+ if (numSegments === 0) throw new Error('Path has no segments.');
891
+
892
+ if (index < 0) index = numSegments + index; // convert negative indices to positive
893
+ if (index >= numSegments || index < 0) throw new Error('Index out of range.');
894
+
895
+ var removedSegment = segments.splice(index, 1)[0];
896
+ var previousSegment = removedSegment.previousSegment;
897
+ var nextSegment = removedSegment.nextSegment;
898
+
899
+ // link the previous and next segments together (if present)
900
+ if (previousSegment) previousSegment.nextSegment = nextSegment; // may be null
901
+ if (nextSegment) nextSegment.previousSegment = previousSegment; // may be null
902
+
903
+ // if removed segment used to start a subpath, update all subsequent segments until another subpath start segment is reached
904
+ if (removedSegment.isSubpathStart && nextSegment) this.updateSubpathStartSegment(nextSegment);
905
+ },
906
+
907
+ // Replace the segment at `index` with `arg`.
908
+ // Accepts negative indices, from `-1` to `-segments.length`.
909
+ // Accepts one segment or an array of segments as argument.
910
+ // Throws an error if path has no segments.
911
+ // Throws an error if index is out of range.
912
+ // Throws an error if argument is not a segment or an array of segments.
913
+ replaceSegment: function(index, arg) {
914
+
915
+ var segments = this.segments;
916
+ var numSegments = segments.length;
917
+ if (numSegments === 0) throw new Error('Path has no segments.');
918
+
919
+ if (index < 0) index = numSegments + index; // convert negative indices to positive
920
+ if (index >= numSegments || index < 0) throw new Error('Index out of range.');
921
+
922
+ var currentSegment;
923
+
924
+ var replacedSegment = segments[index];
925
+ var previousSegment = replacedSegment.previousSegment;
926
+ var nextSegment = replacedSegment.nextSegment;
927
+
928
+ var updateSubpathStart = replacedSegment.isSubpathStart; // boolean: is an update of subpath starts necessary?
929
+
930
+ if (!Array.isArray(arg)) {
931
+ if (!arg || !arg.isSegment) throw new Error('Segment required.');
932
+
933
+ currentSegment = this.prepareSegment(arg, previousSegment, nextSegment);
934
+ segments.splice(index, 1, currentSegment); // directly replace
935
+
936
+ if (updateSubpathStart && currentSegment.isSubpathStart) updateSubpathStart = false; // already updated by `prepareSegment`
937
+
938
+ } else {
939
+ // flatten one level deep
940
+ // so we can chain arbitrary Path.createSegment results
941
+ arg = arg.reduce(function(acc, val) {
942
+ return acc.concat(val);
943
+ }, []);
944
+
945
+ if (!arg[0].isSegment) throw new Error('Segments required.');
946
+
947
+ segments.splice(index, 1);
948
+
949
+ var n = arg.length;
950
+ for (var i = 0; i < n; i++) {
951
+
952
+ var currentArg = arg[i];
953
+ currentSegment = this.prepareSegment(currentArg, previousSegment, nextSegment);
954
+ segments.splice((index + i), 0, currentSegment); // incrementing index to insert subsequent segments after inserted segments
955
+ previousSegment = currentSegment;
956
+
957
+ if (updateSubpathStart && currentSegment.isSubpathStart) updateSubpathStart = false; // already updated by `prepareSegment`
958
+ }
959
+ }
960
+
961
+ // if replaced segment used to start a subpath and no new subpath start was added, update all subsequent segments until another subpath start segment is reached
962
+ if (updateSubpathStart && nextSegment) this.updateSubpathStartSegment(nextSegment);
963
+ },
964
+
965
+ round: function(precision) {
966
+
967
+ var segments = this.segments;
968
+ var numSegments = segments.length;
969
+
970
+ for (var i = 0; i < numSegments; i++) {
971
+
972
+ var segment = segments[i];
973
+ segment.round(precision);
974
+ }
975
+
976
+ return this;
977
+ },
978
+
979
+ scale: function(sx, sy, origin) {
980
+
981
+ var segments = this.segments;
982
+ var numSegments = segments.length;
983
+
984
+ for (var i = 0; i < numSegments; i++) {
985
+
986
+ var segment = segments[i];
987
+ segment.scale(sx, sy, origin);
988
+ }
989
+
990
+ return this;
991
+ },
992
+
993
+ segmentAt: function(ratio, opt) {
994
+
995
+ var index = this.segmentIndexAt(ratio, opt);
996
+ if (!index) return null;
997
+
998
+ return this.getSegment(index);
999
+ },
1000
+
1001
+ // Accepts negative length.
1002
+ segmentAtLength: function(length, opt) {
1003
+
1004
+ var index = this.segmentIndexAtLength(length, opt);
1005
+ if (!index) return null;
1006
+
1007
+ return this.getSegment(index);
1008
+ },
1009
+
1010
+ segmentIndexAt: function(ratio, opt) {
1011
+
1012
+ var segments = this.segments;
1013
+ var numSegments = segments.length;
1014
+ if (numSegments === 0) return null; // if segments is an empty array
1015
+
1016
+ if (ratio < 0) ratio = 0;
1017
+ if (ratio > 1) ratio = 1;
1018
+
1019
+ opt = opt || {};
1020
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
1021
+ var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
1022
+ var localOpt = { precision: precision, segmentSubdivisions: segmentSubdivisions };
1023
+
1024
+ var pathLength = this.length(localOpt);
1025
+ var length = pathLength * ratio;
1026
+
1027
+ return this.segmentIndexAtLength(length, localOpt);
1028
+ },
1029
+
1030
+ // Accepts negative length.
1031
+ segmentIndexAtLength: function(length, opt) {
1032
+
1033
+ var segments = this.segments;
1034
+ var numSegments = segments.length;
1035
+ if (numSegments === 0) return null; // if segments is an empty array
1036
+
1037
+ var fromStart = true;
1038
+ if (length < 0) {
1039
+ fromStart = false; // negative lengths mean start calculation from end point
1040
+ length = -length; // absolute value
1041
+ }
1042
+
1043
+ opt = opt || {};
1044
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
1045
+ var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
1046
+ // not using localOpt
1047
+
1048
+ var lastVisibleSegmentIndex = null;
1049
+ var l = 0; // length so far
1050
+ for (var i = 0; i < numSegments; i++) {
1051
+ var index = (fromStart ? i : (numSegments - 1 - i));
1052
+
1053
+ var segment = segments[index];
1054
+ var subdivisions = segmentSubdivisions[index];
1055
+ var d = segment.length({ precision: precision, subdivisions: subdivisions });
1056
+
1057
+ if (segment.isVisible) {
1058
+ if (length <= (l + d)) return index;
1059
+ lastVisibleSegmentIndex = index;
1060
+ }
1061
+
1062
+ l += d;
1063
+ }
1064
+
1065
+ // if length requested is higher than the length of the path, return last visible segment index
1066
+ // if no visible segment, return null
1067
+ return lastVisibleSegmentIndex;
1068
+ },
1069
+
1070
+ // Returns a string that can be used to reconstruct the path.
1071
+ // Additional error checking compared to toString (must start with M segment).
1072
+ serialize: function() {
1073
+
1074
+ if (!this.isValid()) throw new Error('Invalid path segments.');
1075
+
1076
+ return this.toString();
1077
+ },
1078
+
1079
+ // Returns tangent line at requested `ratio` between 0 and 1, with precision better than requested `opt.precision`; optionally using `opt.segmentSubdivisions` provided.
1080
+ tangentAt: function(ratio, opt) {
1081
+
1082
+ var segments = this.segments;
1083
+ var numSegments = segments.length;
1084
+ if (numSegments === 0) return null; // if segments is an empty array
1085
+
1086
+ if (ratio < 0) ratio = 0;
1087
+ if (ratio > 1) ratio = 1;
1088
+
1089
+ opt = opt || {};
1090
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
1091
+ var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
1092
+ var localOpt = { precision: precision, segmentSubdivisions: segmentSubdivisions };
1093
+
1094
+ var pathLength = this.length(localOpt);
1095
+ var length = pathLength * ratio;
1096
+
1097
+ return this.tangentAtLength(length, localOpt);
1098
+ },
1099
+
1100
+ // Returns tangent line at requested `length`, with precision better than requested `opt.precision`; optionally using `opt.segmentSubdivisions` provided.
1101
+ // Accepts negative length.
1102
+ tangentAtLength: function(length, opt) {
1103
+
1104
+ var segments = this.segments;
1105
+ var numSegments = segments.length;
1106
+ if (numSegments === 0) return null; // if segments is an empty array
1107
+
1108
+ var fromStart = true;
1109
+ if (length < 0) {
1110
+ fromStart = false; // negative lengths mean start calculation from end point
1111
+ length = -length; // absolute value
1112
+ }
1113
+
1114
+ opt = opt || {};
1115
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
1116
+ var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
1117
+ // not using localOpt
1118
+
1119
+ var lastValidSegment; // visible AND differentiable (with a tangent)
1120
+ var l = 0; // length so far
1121
+ for (var i = 0; i < numSegments; i++) {
1122
+ var index = (fromStart ? i : (numSegments - 1 - i));
1123
+
1124
+ var segment = segments[index];
1125
+ var subdivisions = segmentSubdivisions[index];
1126
+ var d = segment.length({ precision: precision, subdivisions: subdivisions });
1127
+
1128
+ if (segment.isDifferentiable()) {
1129
+ if (length <= (l + d)) {
1130
+ return segment.tangentAtLength(((fromStart ? 1 : -1) * (length - l)), {
1131
+ precision: precision,
1132
+ subdivisions: subdivisions
1133
+ });
1134
+ }
1135
+
1136
+ lastValidSegment = segment;
1137
+ }
1138
+
1139
+ l += d;
1140
+ }
1141
+
1142
+ // if length requested is higher than the length of the path, return tangent of endpoint of last valid segment
1143
+ if (lastValidSegment) {
1144
+ var t = (fromStart ? 1 : 0);
1145
+ return lastValidSegment.tangentAtT(t);
1146
+ }
1147
+
1148
+ // if no valid segment, return null
1149
+ return null;
1150
+ },
1151
+
1152
+ // Private function.
1153
+ tangentAtT: function(t) {
1154
+
1155
+ var segments = this.segments;
1156
+ var numSegments = segments.length;
1157
+ if (numSegments === 0) return null; // if segments is an empty array
1158
+
1159
+ var segmentIndex = t.segmentIndex;
1160
+ if (segmentIndex < 0) return segments[0].tangentAtT(0);
1161
+ if (segmentIndex >= numSegments) return segments[numSegments - 1].tangentAtT(1);
1162
+
1163
+ var tValue = t.value;
1164
+ if (tValue < 0) tValue = 0;
1165
+ else if (tValue > 1) tValue = 1;
1166
+
1167
+ return segments[segmentIndex].tangentAtT(tValue);
1168
+ },
1169
+
1170
+ toPoints: function(opt) {
1171
+
1172
+ var segments = this.segments;
1173
+ var numSegments = segments.length;
1174
+ if (numSegments === 0) return null; // if segments is an empty array
1175
+
1176
+ opt = opt || {};
1177
+ var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
1178
+ var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
1179
+
1180
+ var points = [];
1181
+ var partialPoints = [];
1182
+ for (var i = 0; i < numSegments; i++) {
1183
+ var segment = segments[i];
1184
+ if (segment.isVisible) {
1185
+ var currentSegmentSubdivisions = segmentSubdivisions[i];
1186
+ if (currentSegmentSubdivisions.length > 0) {
1187
+ var subdivisionPoints = currentSegmentSubdivisions.map(function(curve) {
1188
+ return curve.start;
1189
+ });
1190
+ Array.prototype.push.apply(partialPoints, subdivisionPoints);
1191
+ } else {
1192
+ partialPoints.push(segment.start);
1193
+ }
1194
+ } else if (partialPoints.length > 0) {
1195
+ partialPoints.push(segments[i - 1].end);
1196
+ points.push(partialPoints);
1197
+ partialPoints = [];
1198
+ }
1199
+ }
1200
+
1201
+ if (partialPoints.length > 0) {
1202
+ partialPoints.push(this.end);
1203
+ points.push(partialPoints);
1204
+ }
1205
+ return points;
1206
+ },
1207
+
1208
+ toPolylines: function(opt) {
1209
+
1210
+ var polylines = [];
1211
+ var points = this.toPoints(opt);
1212
+ if (!points) return null;
1213
+ for (var i = 0, n = points.length; i < n; i++) {
1214
+ polylines.push(new Polyline(points[i]));
1215
+ }
1216
+
1217
+ return polylines;
1218
+ },
1219
+
1220
+ toString: function() {
1221
+
1222
+ var segments = this.segments;
1223
+ var numSegments = segments.length;
1224
+
1225
+ var pathData = '';
1226
+ for (var i = 0; i < numSegments; i++) {
1227
+
1228
+ var segment = segments[i];
1229
+ pathData += segment.serialize() + ' ';
1230
+ }
1231
+
1232
+ return pathData.trim();
1233
+ },
1234
+
1235
+ translate: function(tx, ty) {
1236
+
1237
+ var segments = this.segments;
1238
+ var numSegments = segments.length;
1239
+
1240
+ for (var i = 0; i < numSegments; i++) {
1241
+
1242
+ var segment = segments[i];
1243
+ segment.translate(tx, ty);
1244
+ }
1245
+
1246
+ return this;
1247
+ },
1248
+
1249
+ // Helper method for updating subpath start of segments, starting with the one provided.
1250
+ updateSubpathStartSegment: function(segment) {
1251
+
1252
+ var previousSegment = segment.previousSegment; // may be null
1253
+ while (segment && !segment.isSubpathStart) {
1254
+
1255
+ // assign previous segment's subpath start segment to this segment
1256
+ if (previousSegment) segment.subpathStartSegment = previousSegment.subpathStartSegment; // may be null
1257
+ else segment.subpathStartSegment = null; // if segment had no previous segment, assign null - creates an invalid path!
1258
+
1259
+ previousSegment = segment;
1260
+ segment = segment.nextSegment; // move on to the segment after etc.
1261
+ }
1262
+ },
1263
+
1264
+ // If the path is not valid, insert M 0 0 at the beginning.
1265
+ // Path with no segments is considered valid, so nothing is inserted.
1266
+ validate: function() {
1267
+
1268
+ if (!this.isValid()) this.insertSegment(0, Path.createSegment('M', 0, 0));
1269
+ return this;
1270
+ }
1271
+ };
1272
+
1273
+ Object.defineProperty(Path.prototype, 'start', {
1274
+ // Getter for the first visible endpoint of the path.
1275
+
1276
+ configurable: true,
1277
+
1278
+ enumerable: true,
1279
+
1280
+ get: function() {
1281
+
1282
+ var segments = this.segments;
1283
+ var numSegments = segments.length;
1284
+ if (numSegments === 0) return null;
1285
+
1286
+ for (var i = 0; i < numSegments; i++) {
1287
+
1288
+ var segment = segments[i];
1289
+ if (segment.isVisible) return segment.start;
1290
+ }
1291
+
1292
+ // if no visible segment, return last segment end point
1293
+ return segments[numSegments - 1].end;
1294
+ }
1295
+ });
1296
+
1297
+ Object.defineProperty(Path.prototype, 'end', {
1298
+ // Getter for the last visible endpoint of the path.
1299
+
1300
+ configurable: true,
1301
+
1302
+ enumerable: true,
1303
+
1304
+ get: function() {
1305
+
1306
+ var segments = this.segments;
1307
+ var numSegments = segments.length;
1308
+ if (numSegments === 0) return null;
1309
+
1310
+ for (var i = numSegments - 1; i >= 0; i--) {
1311
+
1312
+ var segment = segments[i];
1313
+ if (segment.isVisible) return segment.end;
1314
+ }
1315
+
1316
+ // if no visible segment, return last segment end point
1317
+ return segments[numSegments - 1].end;
1318
+ }
1319
+ });
1320
+
1321
+
1322
+ // Local helper function.
1323
+ // Use an array of arguments to call a constructor (function called with `new`).
1324
+ // Adapted from https://stackoverflow.com/a/8843181/2263595
1325
+ // It is not necessary to use this function if the arguments can be passed separately (i.e. if the number of arguments is limited).
1326
+ // - If that is the case, use `new constructor(arg1, arg2)`, for example.
1327
+ // It is not necessary to use this function if the function that needs an array of arguments is not supposed to be used as a constructor.
1328
+ // - If that is the case, use `f.apply(thisArg, [arg1, arg2...])`, for example.
1329
+ function applyToNew(constructor, argsArray) {
1330
+ // The `new` keyword can only be applied to functions that take a limited number of arguments.
1331
+ // - We can fake that with .bind().
1332
+ // - It calls a function (`constructor`, here) with the arguments that were provided to it - effectively transforming an unlimited number of arguments into limited.
1333
+ // - So `new (constructor.bind(thisArg, arg1, arg2...))`
1334
+ // - `thisArg` can be anything (e.g. null) because `new` keyword resets context to the constructor object.
1335
+ // We need to pass in a variable number of arguments to the bind() call.
1336
+ // - We can use .apply().
1337
+ // - So `new (constructor.bind.apply(constructor, [thisArg, arg1, arg2...]))`
1338
+ // - `thisArg` can still be anything because `new` overwrites it.
1339
+ // Finally, to make sure that constructor.bind overwriting is not a problem, we switch to `Function.prototype.bind`.
1340
+ // - So, the final version is `new (Function.prototype.bind.apply(constructor, [thisArg, arg1, arg2...]))`
1341
+
1342
+ // The function expects `argsArray[0]` to be `thisArg`.
1343
+ // - This means that whatever is sent as the first element will be ignored.
1344
+ // - The constructor will only see arguments starting from argsArray[1].
1345
+ // - So, a new dummy element is inserted at the start of the array.
1346
+ argsArray.unshift(null);
1347
+
1348
+ return new (Function.prototype.bind.apply(constructor, argsArray));
1349
+ }
1350
+
1351
+ // Path segment interface:
1352
+ var segmentPrototype = {
1353
+
1354
+ // virtual
1355
+ bbox: function() {
1356
+
1357
+ throw new Error('Declaration missing for virtual function.');
1358
+ },
1359
+
1360
+ // virtual
1361
+ clone: function() {
1362
+
1363
+ throw new Error('Declaration missing for virtual function.');
1364
+ },
1365
+
1366
+ // virtual
1367
+ closestPoint: function() {
1368
+
1369
+ throw new Error('Declaration missing for virtual function.');
1370
+ },
1371
+
1372
+ // virtual
1373
+ closestPointLength: function() {
1374
+
1375
+ throw new Error('Declaration missing for virtual function.');
1376
+ },
1377
+
1378
+ // virtual
1379
+ closestPointNormalizedLength: function() {
1380
+
1381
+ throw new Error('Declaration missing for virtual function.');
1382
+ },
1383
+
1384
+ // Redirect calls to closestPointNormalizedLength() function if closestPointT() is not defined for segment.
1385
+ closestPointT: function(p) {
1386
+
1387
+ if (this.closestPointNormalizedLength) return this.closestPointNormalizedLength(p);
1388
+
1389
+ throw new Error('Neither closestPointT() nor closestPointNormalizedLength() function is implemented.');
1390
+ },
1391
+
1392
+ // virtual
1393
+ closestPointTangent: function() {
1394
+
1395
+ throw new Error('Declaration missing for virtual function.');
1396
+ },
1397
+
1398
+ // virtual
1399
+ divideAt: function() {
1400
+
1401
+ throw new Error('Declaration missing for virtual function.');
1402
+ },
1403
+
1404
+ // virtual
1405
+ divideAtLength: function() {
1406
+
1407
+ throw new Error('Declaration missing for virtual function.');
1408
+ },
1409
+
1410
+ // Redirect calls to divideAt() function if divideAtT() is not defined for segment.
1411
+ divideAtT: function(t) {
1412
+
1413
+ if (this.divideAt) return this.divideAt(t);
1414
+
1415
+ throw new Error('Neither divideAtT() nor divideAt() function is implemented.');
1416
+ },
1417
+
1418
+ // virtual
1419
+ equals: function() {
1420
+
1421
+ throw new Error('Declaration missing for virtual function.');
1422
+ },
1423
+
1424
+ // virtual
1425
+ getSubdivisions: function() {
1426
+
1427
+ throw new Error('Declaration missing for virtual function.');
1428
+ },
1429
+
1430
+ // virtual
1431
+ isDifferentiable: function() {
1432
+
1433
+ throw new Error('Declaration missing for virtual function.');
1434
+ },
1435
+
1436
+ isSegment: true,
1437
+
1438
+ isSubpathStart: false, // true for Moveto segments
1439
+
1440
+ isVisible: true, // false for Moveto segments
1441
+
1442
+ // virtual
1443
+ length: function() {
1444
+
1445
+ throw new Error('Declaration missing for virtual function.');
1446
+ },
1447
+
1448
+ // Return a fraction of result of length() function if lengthAtT() is not defined for segment.
1449
+ lengthAtT: function(t) {
1450
+
1451
+ if (t <= 0) return 0;
1452
+
1453
+ var length = this.length();
1454
+
1455
+ if (t >= 1) return length;
1456
+
1457
+ return length * t;
1458
+ },
1459
+
1460
+ nextSegment: null, // needed for subpath start segment updating
1461
+
1462
+ // virtual
1463
+ pointAt: function() {
1464
+
1465
+ throw new Error('Declaration missing for virtual function.');
1466
+ },
1467
+
1468
+ // virtual
1469
+ pointAtLength: function() {
1470
+
1471
+ throw new Error('Declaration missing for virtual function.');
1472
+ },
1473
+
1474
+ // Redirect calls to pointAt() function if pointAtT() is not defined for segment.
1475
+ pointAtT: function(t) {
1476
+
1477
+ if (this.pointAt) return this.pointAt(t);
1478
+
1479
+ throw new Error('Neither pointAtT() nor pointAt() function is implemented.');
1480
+ },
1481
+
1482
+ previousSegment: null, // needed to get segment start property
1483
+
1484
+ // virtual
1485
+ round: function() {
1486
+
1487
+ throw new Error('Declaration missing for virtual function.');
1488
+ },
1489
+
1490
+ subpathStartSegment: null, // needed to get Closepath segment end property
1491
+
1492
+ // virtual
1493
+ scale: function() {
1494
+
1495
+ throw new Error('Declaration missing for virtual function.');
1496
+ },
1497
+
1498
+ // virtual
1499
+ serialize: function() {
1500
+
1501
+ throw new Error('Declaration missing for virtual function.');
1502
+ },
1503
+
1504
+ // virtual
1505
+ tangentAt: function() {
1506
+
1507
+ throw new Error('Declaration missing for virtual function.');
1508
+ },
1509
+
1510
+ // virtual
1511
+ tangentAtLength: function() {
1512
+
1513
+ throw new Error('Declaration missing for virtual function.');
1514
+ },
1515
+
1516
+ // Redirect calls to tangentAt() function if tangentAtT() is not defined for segment.
1517
+ tangentAtT: function(t) {
1518
+
1519
+ if (this.tangentAt) return this.tangentAt(t);
1520
+
1521
+ throw new Error('Neither tangentAtT() nor tangentAt() function is implemented.');
1522
+ },
1523
+
1524
+ // virtual
1525
+ toString: function() {
1526
+
1527
+ throw new Error('Declaration missing for virtual function.');
1528
+ },
1529
+
1530
+ // virtual
1531
+ translate: function() {
1532
+
1533
+ throw new Error('Declaration missing for virtual function.');
1534
+ }
1535
+ };
1536
+
1537
+ // usually directly assigned
1538
+ // getter for Closepath
1539
+ Object.defineProperty(segmentPrototype, 'end', {
1540
+
1541
+ configurable: true,
1542
+
1543
+ enumerable: true,
1544
+
1545
+ writable: true
1546
+ });
1547
+
1548
+ // always a getter
1549
+ // always throws error for Moveto
1550
+ Object.defineProperty(segmentPrototype, 'start', {
1551
+ // get a reference to the end point of previous segment
1552
+
1553
+ configurable: true,
1554
+
1555
+ enumerable: true,
1556
+
1557
+ get: function() {
1558
+
1559
+ if (!this.previousSegment) throw new Error('Missing previous segment. (This segment cannot be the first segment of a path; OR segment has not yet been added to a path.)');
1560
+
1561
+ return this.previousSegment.end;
1562
+ }
1563
+ });
1564
+
1565
+ // virtual
1566
+ Object.defineProperty(segmentPrototype, 'type', {
1567
+
1568
+ configurable: true,
1569
+
1570
+ enumerable: true,
1571
+
1572
+ get: function() {
1573
+
1574
+ throw new Error('Bad segment declaration. No type specified.');
1575
+ }
1576
+ });
1577
+
1578
+ // Path segment implementations:
1579
+ var Lineto = function() {
1580
+
1581
+ var args = [];
1582
+ var n = arguments.length;
1583
+ for (var i = 0; i < n; i++) {
1584
+ args.push(arguments[i]);
1585
+ }
1586
+
1587
+ if (!(this instanceof Lineto)) { // switching context of `this` to Lineto when called without `new`
1588
+ return applyToNew(Lineto, args);
1589
+ }
1590
+
1591
+ if (n === 0) {
1592
+ throw new Error('Lineto constructor expects a line, 1 point, or 2 coordinates (none provided).');
1593
+ }
1594
+
1595
+ var outputArray;
1596
+
1597
+ if (args[0] instanceof Line) { // lines provided
1598
+ if (n === 1) {
1599
+ this.end = args[0].end.clone();
1600
+ return this;
1601
+
1602
+ } else {
1603
+ throw new Error('Lineto constructor expects a line, 1 point, or 2 coordinates (' + n + ' lines provided).');
1604
+ }
1605
+
1606
+ } else if (typeof args[0] === 'string' || typeof args[0] === 'number') { // coordinates provided
1607
+ if (n === 2) {
1608
+ this.end = new Point(+args[0], +args[1]);
1609
+ return this;
1610
+
1611
+ } else if (n < 2) {
1612
+ throw new Error('Lineto constructor expects a line, 1 point, or 2 coordinates (' + n + ' coordinates provided).');
1613
+
1614
+ } else { // this is a poly-line segment
1615
+ var segmentCoords;
1616
+ outputArray = [];
1617
+ for (i = 0; i < n; i += 2) { // coords come in groups of two
1618
+
1619
+ segmentCoords = args.slice(i, i + 2); // will send one coord if args.length not divisible by 2
1620
+ outputArray.push(applyToNew(Lineto, segmentCoords));
1621
+ }
1622
+ return outputArray;
1623
+ }
1624
+
1625
+ } else { // points provided (needs to be last to also cover plain objects with x and y)
1626
+ if (n === 1) {
1627
+ this.end = new Point(args[0]);
1628
+ return this;
1629
+
1630
+ } else { // this is a poly-line segment
1631
+ var segmentPoint;
1632
+ outputArray = [];
1633
+ for (i = 0; i < n; i += 1) {
1634
+
1635
+ segmentPoint = args[i];
1636
+ outputArray.push(new Lineto(segmentPoint));
1637
+ }
1638
+ return outputArray;
1639
+ }
1640
+ }
1641
+ };
1642
+
1643
+ var linetoPrototype = {
1644
+
1645
+ clone: function() {
1646
+
1647
+ return new Lineto(this.end);
1648
+ },
1649
+
1650
+ divideAt: function(ratio) {
1651
+
1652
+ var line = new Line(this.start, this.end);
1653
+ var divided = line.divideAt(ratio);
1654
+ return [
1655
+ new Lineto(divided[0]),
1656
+ new Lineto(divided[1])
1657
+ ];
1658
+ },
1659
+
1660
+ divideAtLength: function(length) {
1661
+
1662
+ var line = new Line(this.start, this.end);
1663
+ var divided = line.divideAtLength(length);
1664
+ return [
1665
+ new Lineto(divided[0]),
1666
+ new Lineto(divided[1])
1667
+ ];
1668
+ },
1669
+
1670
+ getSubdivisions: function() {
1671
+
1672
+ return [];
1673
+ },
1674
+
1675
+ isDifferentiable: function() {
1676
+
1677
+ if (!this.previousSegment) return false;
1678
+
1679
+ return !this.start.equals(this.end);
1680
+ },
1681
+
1682
+ round: function(precision) {
1683
+
1684
+ this.end.round(precision);
1685
+ return this;
1686
+ },
1687
+
1688
+ scale: function(sx, sy, origin) {
1689
+
1690
+ this.end.scale(sx, sy, origin);
1691
+ return this;
1692
+ },
1693
+
1694
+ serialize: function() {
1695
+
1696
+ var end = this.end;
1697
+ return this.type + ' ' + end.x + ' ' + end.y;
1698
+ },
1699
+
1700
+ toString: function() {
1701
+
1702
+ return this.type + ' ' + this.start + ' ' + this.end;
1703
+ },
1704
+
1705
+ translate: function(tx, ty) {
1706
+
1707
+ this.end.translate(tx, ty);
1708
+ return this;
1709
+ }
1710
+ };
1711
+
1712
+ Object.defineProperty(linetoPrototype, 'type', {
1713
+
1714
+ configurable: true,
1715
+
1716
+ enumerable: true,
1717
+
1718
+ value: 'L'
1719
+ });
1720
+
1721
+ Lineto.prototype = extend(segmentPrototype, Line.prototype, linetoPrototype);
1722
+
1723
+ var Curveto = function() {
1724
+
1725
+ var args = [];
1726
+ var n = arguments.length;
1727
+ for (var i = 0; i < n; i++) {
1728
+ args.push(arguments[i]);
1729
+ }
1730
+
1731
+ if (!(this instanceof Curveto)) { // switching context of `this` to Curveto when called without `new`
1732
+ return applyToNew(Curveto, args);
1733
+ }
1734
+
1735
+ if (n === 0) {
1736
+ throw new Error('Curveto constructor expects a curve, 3 points, or 6 coordinates (none provided).');
1737
+ }
1738
+
1739
+ var outputArray;
1740
+
1741
+ if (args[0] instanceof Curve) { // curves provided
1742
+ if (n === 1) {
1743
+ this.controlPoint1 = args[0].controlPoint1.clone();
1744
+ this.controlPoint2 = args[0].controlPoint2.clone();
1745
+ this.end = args[0].end.clone();
1746
+ return this;
1747
+
1748
+ } else {
1749
+ throw new Error('Curveto constructor expects a curve, 3 points, or 6 coordinates (' + n + ' curves provided).');
1750
+ }
1751
+
1752
+ } else if (typeof args[0] === 'string' || typeof args[0] === 'number') { // coordinates provided
1753
+ if (n === 6) {
1754
+ this.controlPoint1 = new Point(+args[0], +args[1]);
1755
+ this.controlPoint2 = new Point(+args[2], +args[3]);
1756
+ this.end = new Point(+args[4], +args[5]);
1757
+ return this;
1758
+
1759
+ } else if (n < 6) {
1760
+ throw new Error('Curveto constructor expects a curve, 3 points, or 6 coordinates (' + n + ' coordinates provided).');
1761
+
1762
+ } else { // this is a poly-bezier segment
1763
+ var segmentCoords;
1764
+ outputArray = [];
1765
+ for (i = 0; i < n; i += 6) { // coords come in groups of six
1766
+
1767
+ segmentCoords = args.slice(i, i + 6); // will send fewer than six coords if args.length not divisible by 6
1768
+ outputArray.push(applyToNew(Curveto, segmentCoords));
1769
+ }
1770
+ return outputArray;
1771
+ }
1772
+
1773
+ } else { // points provided (needs to be last to also cover plain objects with x and y)
1774
+ if (n === 3) {
1775
+ this.controlPoint1 = new Point(args[0]);
1776
+ this.controlPoint2 = new Point(args[1]);
1777
+ this.end = new Point(args[2]);
1778
+ return this;
1779
+
1780
+ } else if (n < 3) {
1781
+ throw new Error('Curveto constructor expects a curve, 3 points, or 6 coordinates (' + n + ' points provided).');
1782
+
1783
+ } else { // this is a poly-bezier segment
1784
+ var segmentPoints;
1785
+ outputArray = [];
1786
+ for (i = 0; i < n; i += 3) { // points come in groups of three
1787
+
1788
+ segmentPoints = args.slice(i, i + 3); // will send fewer than three points if args.length is not divisible by 3
1789
+ outputArray.push(applyToNew(Curveto, segmentPoints));
1790
+ }
1791
+ return outputArray;
1792
+ }
1793
+ }
1794
+ };
1795
+
1796
+ var curvetoPrototype = {
1797
+
1798
+ clone: function() {
1799
+
1800
+ return new Curveto(this.controlPoint1, this.controlPoint2, this.end);
1801
+ },
1802
+
1803
+ divideAt: function(ratio, opt) {
1804
+
1805
+ var curve = new Curve(this.start, this.controlPoint1, this.controlPoint2, this.end);
1806
+ var divided = curve.divideAt(ratio, opt);
1807
+ return [
1808
+ new Curveto(divided[0]),
1809
+ new Curveto(divided[1])
1810
+ ];
1811
+ },
1812
+
1813
+ divideAtLength: function(length, opt) {
1814
+
1815
+ var curve = new Curve(this.start, this.controlPoint1, this.controlPoint2, this.end);
1816
+ var divided = curve.divideAtLength(length, opt);
1817
+ return [
1818
+ new Curveto(divided[0]),
1819
+ new Curveto(divided[1])
1820
+ ];
1821
+ },
1822
+
1823
+ divideAtT: function(t) {
1824
+
1825
+ var curve = new Curve(this.start, this.controlPoint1, this.controlPoint2, this.end);
1826
+ var divided = curve.divideAtT(t);
1827
+ return [
1828
+ new Curveto(divided[0]),
1829
+ new Curveto(divided[1])
1830
+ ];
1831
+ },
1832
+
1833
+ isDifferentiable: function() {
1834
+
1835
+ if (!this.previousSegment) return false;
1836
+
1837
+ var start = this.start;
1838
+ var control1 = this.controlPoint1;
1839
+ var control2 = this.controlPoint2;
1840
+ var end = this.end;
1841
+
1842
+ return !(start.equals(control1) && control1.equals(control2) && control2.equals(end));
1843
+ },
1844
+
1845
+ round: function(precision) {
1846
+
1847
+ this.controlPoint1.round(precision);
1848
+ this.controlPoint2.round(precision);
1849
+ this.end.round(precision);
1850
+ return this;
1851
+ },
1852
+
1853
+ scale: function(sx, sy, origin) {
1854
+
1855
+ this.controlPoint1.scale(sx, sy, origin);
1856
+ this.controlPoint2.scale(sx, sy, origin);
1857
+ this.end.scale(sx, sy, origin);
1858
+ return this;
1859
+ },
1860
+
1861
+ serialize: function() {
1862
+
1863
+ var c1 = this.controlPoint1;
1864
+ var c2 = this.controlPoint2;
1865
+ var end = this.end;
1866
+ return this.type + ' ' + c1.x + ' ' + c1.y + ' ' + c2.x + ' ' + c2.y + ' ' + end.x + ' ' + end.y;
1867
+ },
1868
+
1869
+ toString: function() {
1870
+
1871
+ return this.type + ' ' + this.start + ' ' + this.controlPoint1 + ' ' + this.controlPoint2 + ' ' + this.end;
1872
+ },
1873
+
1874
+ translate: function(tx, ty) {
1875
+
1876
+ this.controlPoint1.translate(tx, ty);
1877
+ this.controlPoint2.translate(tx, ty);
1878
+ this.end.translate(tx, ty);
1879
+ return this;
1880
+ }
1881
+ };
1882
+
1883
+ Object.defineProperty(curvetoPrototype, 'type', {
1884
+
1885
+ configurable: true,
1886
+
1887
+ enumerable: true,
1888
+
1889
+ value: 'C'
1890
+ });
1891
+
1892
+ Curveto.prototype = extend(segmentPrototype, Curve.prototype, curvetoPrototype);
1893
+
1894
+ var Moveto = function() {
1895
+
1896
+ var args = [];
1897
+ var n = arguments.length;
1898
+ for (var i = 0; i < n; i++) {
1899
+ args.push(arguments[i]);
1900
+ }
1901
+
1902
+ if (!(this instanceof Moveto)) { // switching context of `this` to Moveto when called without `new`
1903
+ return applyToNew(Moveto, args);
1904
+ }
1905
+
1906
+ if (n === 0) {
1907
+ throw new Error('Moveto constructor expects a line, a curve, 1 point, or 2 coordinates (none provided).');
1908
+ }
1909
+
1910
+ var outputArray;
1911
+
1912
+ if (args[0] instanceof Line) { // lines provided
1913
+ if (n === 1) {
1914
+ this.end = args[0].end.clone();
1915
+ return this;
1916
+
1917
+ } else {
1918
+ throw new Error('Moveto constructor expects a line, a curve, 1 point, or 2 coordinates (' + n + ' lines provided).');
1919
+ }
1920
+
1921
+ } else if (args[0] instanceof Curve) { // curves provided
1922
+ if (n === 1) {
1923
+ this.end = args[0].end.clone();
1924
+ return this;
1925
+
1926
+ } else {
1927
+ throw new Error('Moveto constructor expects a line, a curve, 1 point, or 2 coordinates (' + n + ' curves provided).');
1928
+ }
1929
+
1930
+ } else if (typeof args[0] === 'string' || typeof args[0] === 'number') { // coordinates provided
1931
+ if (n === 2) {
1932
+ this.end = new Point(+args[0], +args[1]);
1933
+ return this;
1934
+
1935
+ } else if (n < 2) {
1936
+ throw new Error('Moveto constructor expects a line, a curve, 1 point, or 2 coordinates (' + n + ' coordinates provided).');
1937
+
1938
+ } else { // this is a moveto-with-subsequent-poly-line segment
1939
+ var segmentCoords;
1940
+ outputArray = [];
1941
+ for (i = 0; i < n; i += 2) { // coords come in groups of two
1942
+
1943
+ segmentCoords = args.slice(i, i + 2); // will send one coord if args.length not divisible by 2
1944
+ if (i === 0) outputArray.push(applyToNew(Moveto, segmentCoords));
1945
+ else outputArray.push(applyToNew(Lineto, segmentCoords));
1946
+ }
1947
+ return outputArray;
1948
+ }
1949
+
1950
+ } else { // points provided (needs to be last to also cover plain objects with x and y)
1951
+ if (n === 1) {
1952
+ this.end = new Point(args[0]);
1953
+ return this;
1954
+
1955
+ } else { // this is a moveto-with-subsequent-poly-line segment
1956
+ var segmentPoint;
1957
+ outputArray = [];
1958
+ for (i = 0; i < n; i += 1) { // points come one by one
1959
+
1960
+ segmentPoint = args[i];
1961
+ if (i === 0) outputArray.push(new Moveto(segmentPoint));
1962
+ else outputArray.push(new Lineto(segmentPoint));
1963
+ }
1964
+ return outputArray;
1965
+ }
1966
+ }
1967
+ };
1968
+
1969
+ var movetoPrototype = {
1970
+
1971
+ bbox: function() {
1972
+
1973
+ return null;
1974
+ },
1975
+
1976
+ clone: function() {
1977
+
1978
+ return new Moveto(this.end);
1979
+ },
1980
+
1981
+ closestPoint: function() {
1982
+
1983
+ return this.end.clone();
1984
+ },
1985
+
1986
+ closestPointNormalizedLength: function() {
1987
+
1988
+ return 0;
1989
+ },
1990
+
1991
+ closestPointLength: function() {
1992
+
1993
+ return 0;
1994
+ },
1995
+
1996
+ closestPointT: function() {
1997
+
1998
+ return 1;
1999
+ },
2000
+
2001
+ closestPointTangent: function() {
2002
+
2003
+ return null;
2004
+ },
2005
+
2006
+ divideAt: function() {
2007
+
2008
+ return [
2009
+ this.clone(),
2010
+ this.clone()
2011
+ ];
2012
+ },
2013
+
2014
+ divideAtLength: function() {
2015
+
2016
+ return [
2017
+ this.clone(),
2018
+ this.clone()
2019
+ ];
2020
+ },
2021
+
2022
+ equals: function(m) {
2023
+
2024
+ return this.end.equals(m.end);
2025
+ },
2026
+
2027
+ getSubdivisions: function() {
2028
+
2029
+ return [];
2030
+ },
2031
+
2032
+ isDifferentiable: function() {
2033
+
2034
+ return false;
2035
+ },
2036
+
2037
+ isSubpathStart: true,
2038
+
2039
+ isVisible: false,
2040
+
2041
+ length: function() {
2042
+
2043
+ return 0;
2044
+ },
2045
+
2046
+ lengthAtT: function() {
2047
+
2048
+ return 0;
2049
+ },
2050
+
2051
+ pointAt: function() {
2052
+
2053
+ return this.end.clone();
2054
+ },
2055
+
2056
+ pointAtLength: function() {
2057
+
2058
+ return this.end.clone();
2059
+ },
2060
+
2061
+ pointAtT: function() {
2062
+
2063
+ return this.end.clone();
2064
+ },
2065
+
2066
+ round: function(precision) {
2067
+
2068
+ this.end.round(precision);
2069
+ return this;
2070
+ },
2071
+
2072
+ scale: function(sx, sy, origin) {
2073
+
2074
+ this.end.scale(sx, sy, origin);
2075
+ return this;
2076
+ },
2077
+
2078
+ serialize: function() {
2079
+
2080
+ var end = this.end;
2081
+ return this.type + ' ' + end.x + ' ' + end.y;
2082
+ },
2083
+
2084
+ tangentAt: function() {
2085
+
2086
+ return null;
2087
+ },
2088
+
2089
+ tangentAtLength: function() {
2090
+
2091
+ return null;
2092
+ },
2093
+
2094
+ tangentAtT: function() {
2095
+
2096
+ return null;
2097
+ },
2098
+
2099
+ toString: function() {
2100
+
2101
+ return this.type + ' ' + this.end;
2102
+ },
2103
+
2104
+ translate: function(tx, ty) {
2105
+
2106
+ this.end.translate(tx, ty);
2107
+ return this;
2108
+ }
2109
+ };
2110
+
2111
+ Object.defineProperty(movetoPrototype, 'start', {
2112
+
2113
+ configurable: true,
2114
+
2115
+ enumerable: true,
2116
+
2117
+ get: function() {
2118
+
2119
+ throw new Error('Illegal access. Moveto segments should not need a start property.');
2120
+ }
2121
+ });
2122
+
2123
+ Object.defineProperty(movetoPrototype, 'type', {
2124
+
2125
+ configurable: true,
2126
+
2127
+ enumerable: true,
2128
+
2129
+ value: 'M'
2130
+ });
2131
+
2132
+ Moveto.prototype = extend(segmentPrototype, movetoPrototype); // does not inherit from any other geometry object
2133
+
2134
+ var Closepath = function() {
2135
+
2136
+ var args = [];
2137
+ var n = arguments.length;
2138
+ for (var i = 0; i < n; i++) {
2139
+ args.push(arguments[i]);
2140
+ }
2141
+
2142
+ if (!(this instanceof Closepath)) { // switching context of `this` to Closepath when called without `new`
2143
+ return applyToNew(Closepath, args);
2144
+ }
2145
+
2146
+ if (n > 0) {
2147
+ throw new Error('Closepath constructor expects no arguments.');
2148
+ }
2149
+
2150
+ return this;
2151
+ };
2152
+
2153
+ var closepathPrototype = {
2154
+
2155
+ clone: function() {
2156
+
2157
+ return new Closepath();
2158
+ },
2159
+
2160
+ divideAt: function(ratio) {
2161
+
2162
+ var line = new Line(this.start, this.end);
2163
+ var divided = line.divideAt(ratio);
2164
+ return [
2165
+ // if we didn't actually cut into the segment, first divided part can stay as Z
2166
+ (divided[1].isDifferentiable() ? new Lineto(divided[0]) : this.clone()),
2167
+ new Lineto(divided[1])
2168
+ ];
2169
+ },
2170
+
2171
+ divideAtLength: function(length) {
2172
+
2173
+ var line = new Line(this.start, this.end);
2174
+ var divided = line.divideAtLength(length);
2175
+ return [
2176
+ // if we didn't actually cut into the segment, first divided part can stay as Z
2177
+ (divided[1].isDifferentiable() ? new Lineto(divided[0]) : this.clone()),
2178
+ new Lineto(divided[1])
2179
+ ];
2180
+ },
2181
+
2182
+ getSubdivisions: function() {
2183
+
2184
+ return [];
2185
+ },
2186
+
2187
+ isDifferentiable: function() {
2188
+
2189
+ if (!this.previousSegment || !this.subpathStartSegment) return false;
2190
+
2191
+ return !this.start.equals(this.end);
2192
+ },
2193
+
2194
+ round: function() {
2195
+
2196
+ return this;
2197
+ },
2198
+
2199
+ scale: function() {
2200
+
2201
+ return this;
2202
+ },
2203
+
2204
+ serialize: function() {
2205
+
2206
+ return this.type;
2207
+ },
2208
+
2209
+ toString: function() {
2210
+
2211
+ return this.type + ' ' + this.start + ' ' + this.end;
2212
+ },
2213
+
2214
+ translate: function() {
2215
+
2216
+ return this;
2217
+ }
2218
+ };
2219
+
2220
+ Object.defineProperty(closepathPrototype, 'end', {
2221
+ // get a reference to the end point of subpath start segment
2222
+
2223
+ configurable: true,
2224
+
2225
+ enumerable: true,
2226
+
2227
+ get: function() {
2228
+
2229
+ if (!this.subpathStartSegment) throw new Error('Missing subpath start segment. (This segment needs a subpath start segment (e.g. Moveto); OR segment has not yet been added to a path.)');
2230
+
2231
+ return this.subpathStartSegment.end;
2232
+ }
2233
+ });
2234
+
2235
+ Object.defineProperty(closepathPrototype, 'type', {
2236
+
2237
+ configurable: true,
2238
+
2239
+ enumerable: true,
2240
+
2241
+ value: 'Z'
2242
+ });
2243
+
2244
+ Closepath.prototype = extend(segmentPrototype, Line.prototype, closepathPrototype);
2245
+
2246
+ var segmentTypes = Path.segmentTypes = {
2247
+ L: Lineto,
2248
+ C: Curveto,
2249
+ M: Moveto,
2250
+ Z: Closepath,
2251
+ z: Closepath
2252
+ };
2253
+
2254
+ Path.regexSupportedData = new RegExp('^[\\s\\d' + Object.keys(segmentTypes).join('') + ',.]*$');
2255
+
2256
+ Path.isDataSupported = function(data) {
2257
+
2258
+ if (typeof data !== 'string') return false;
2259
+ return this.regexSupportedData.test(data);
2260
+ };