@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.
- package/LICENSE +376 -0
- package/README.md +49 -0
- package/dist/geometry.js +6486 -0
- package/dist/geometry.min.js +8 -0
- package/dist/joint.d.ts +5536 -0
- package/dist/joint.js +39629 -0
- package/dist/joint.min.js +8 -0
- package/dist/joint.nowrap.js +39626 -0
- package/dist/joint.nowrap.min.js +8 -0
- package/dist/vectorizer.js +9135 -0
- package/dist/vectorizer.min.js +8 -0
- package/dist/version.mjs +3 -0
- package/index.js +3 -0
- package/joint.mjs +27 -0
- package/package.json +192 -0
- package/src/V/annotation.mjs +0 -0
- package/src/V/index.mjs +2642 -0
- package/src/anchors/index.mjs +123 -0
- package/src/config/index.mjs +12 -0
- package/src/connectionPoints/index.mjs +202 -0
- package/src/connectionStrategies/index.mjs +73 -0
- package/src/connectors/curve.mjs +553 -0
- package/src/connectors/index.mjs +6 -0
- package/src/connectors/jumpover.mjs +452 -0
- package/src/connectors/normal.mjs +12 -0
- package/src/connectors/rounded.mjs +17 -0
- package/src/connectors/smooth.mjs +44 -0
- package/src/connectors/straight.mjs +110 -0
- package/src/dia/Cell.mjs +945 -0
- package/src/dia/CellView.mjs +1316 -0
- package/src/dia/Element.mjs +519 -0
- package/src/dia/ElementView.mjs +859 -0
- package/src/dia/Graph.mjs +1112 -0
- package/src/dia/HighlighterView.mjs +319 -0
- package/src/dia/Link.mjs +565 -0
- package/src/dia/LinkView.mjs +2207 -0
- package/src/dia/Paper.mjs +3171 -0
- package/src/dia/PaperLayer.mjs +75 -0
- package/src/dia/ToolView.mjs +69 -0
- package/src/dia/ToolsView.mjs +128 -0
- package/src/dia/attributes/calc.mjs +128 -0
- package/src/dia/attributes/connection.mjs +75 -0
- package/src/dia/attributes/defs.mjs +76 -0
- package/src/dia/attributes/eval.mjs +64 -0
- package/src/dia/attributes/index.mjs +69 -0
- package/src/dia/attributes/legacy.mjs +148 -0
- package/src/dia/attributes/offset.mjs +53 -0
- package/src/dia/attributes/props.mjs +30 -0
- package/src/dia/attributes/shape.mjs +92 -0
- package/src/dia/attributes/text.mjs +180 -0
- package/src/dia/index.mjs +13 -0
- package/src/dia/layers/GridLayer.mjs +176 -0
- package/src/dia/ports.mjs +874 -0
- package/src/elementTools/Control.mjs +153 -0
- package/src/elementTools/HoverConnect.mjs +37 -0
- package/src/elementTools/index.mjs +5 -0
- package/src/env/index.mjs +43 -0
- package/src/g/bezier.mjs +175 -0
- package/src/g/curve.mjs +956 -0
- package/src/g/ellipse.mjs +245 -0
- package/src/g/extend.mjs +64 -0
- package/src/g/geometry.helpers.mjs +58 -0
- package/src/g/index.mjs +17 -0
- package/src/g/intersection.mjs +511 -0
- package/src/g/line.bearing.mjs +30 -0
- package/src/g/line.length.mjs +5 -0
- package/src/g/line.mjs +356 -0
- package/src/g/line.squaredLength.mjs +10 -0
- package/src/g/path.mjs +2260 -0
- package/src/g/point.mjs +375 -0
- package/src/g/points.mjs +247 -0
- package/src/g/polygon.mjs +51 -0
- package/src/g/polyline.mjs +523 -0
- package/src/g/rect.mjs +556 -0
- package/src/g/types.mjs +10 -0
- package/src/highlighters/addClass.mjs +27 -0
- package/src/highlighters/index.mjs +5 -0
- package/src/highlighters/list.mjs +111 -0
- package/src/highlighters/mask.mjs +220 -0
- package/src/highlighters/opacity.mjs +17 -0
- package/src/highlighters/stroke.mjs +100 -0
- package/src/layout/index.mjs +4 -0
- package/src/layout/ports/port.mjs +188 -0
- package/src/layout/ports/portLabel.mjs +224 -0
- package/src/linkAnchors/index.mjs +76 -0
- package/src/linkTools/Anchor.mjs +235 -0
- package/src/linkTools/Arrowhead.mjs +103 -0
- package/src/linkTools/Boundary.mjs +48 -0
- package/src/linkTools/Button.mjs +121 -0
- package/src/linkTools/Connect.mjs +85 -0
- package/src/linkTools/HoverConnect.mjs +161 -0
- package/src/linkTools/Segments.mjs +393 -0
- package/src/linkTools/Vertices.mjs +253 -0
- package/src/linkTools/helpers.mjs +33 -0
- package/src/linkTools/index.mjs +8 -0
- package/src/mvc/Collection.mjs +560 -0
- package/src/mvc/Data.mjs +46 -0
- package/src/mvc/Dom/Dom.mjs +587 -0
- package/src/mvc/Dom/Event.mjs +130 -0
- package/src/mvc/Dom/animations.mjs +122 -0
- package/src/mvc/Dom/events.mjs +69 -0
- package/src/mvc/Dom/index.mjs +13 -0
- package/src/mvc/Dom/methods.mjs +392 -0
- package/src/mvc/Dom/props.mjs +77 -0
- package/src/mvc/Dom/vars.mjs +5 -0
- package/src/mvc/Events.mjs +337 -0
- package/src/mvc/Listener.mjs +33 -0
- package/src/mvc/Model.mjs +239 -0
- package/src/mvc/View.mjs +323 -0
- package/src/mvc/ViewBase.mjs +182 -0
- package/src/mvc/index.mjs +9 -0
- package/src/mvc/mvcUtils.mjs +90 -0
- package/src/polyfills/array.js +4 -0
- package/src/polyfills/base64.js +68 -0
- package/src/polyfills/index.mjs +5 -0
- package/src/polyfills/number.js +3 -0
- package/src/polyfills/string.js +3 -0
- package/src/polyfills/typedArray.js +47 -0
- package/src/routers/index.mjs +6 -0
- package/src/routers/manhattan.mjs +856 -0
- package/src/routers/metro.mjs +91 -0
- package/src/routers/normal.mjs +6 -0
- package/src/routers/oneSide.mjs +60 -0
- package/src/routers/orthogonal.mjs +323 -0
- package/src/routers/rightAngle.mjs +1056 -0
- package/src/shapes/index.mjs +3 -0
- package/src/shapes/standard.mjs +755 -0
- package/src/util/cloneCells.mjs +67 -0
- package/src/util/getRectPoint.mjs +65 -0
- package/src/util/index.mjs +5 -0
- package/src/util/svgTagTemplate.mjs +110 -0
- package/src/util/util.mjs +1754 -0
- package/src/util/utilHelpers.mjs +2402 -0
- package/src/util/wrappers.mjs +56 -0
- package/types/geometry.d.ts +815 -0
- package/types/index.d.ts +53 -0
- package/types/joint.d.ts +4391 -0
- package/types/joint.head.d.ts +12 -0
- 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
|
+
};
|