svg-path-simplify 0.3.0 → 0.3.4

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.
@@ -9,90 +9,59 @@
9
9
  */
10
10
 
11
11
  import { harmonizeCubicCpts, harmonizeCubicCptsThird } from "./pathData_simplify_harmonize_cpts";
12
- import { getAngle, getDistance, pointAtT, rotatePoint } from "./svgii/geometry";
13
- import { renderPoint } from "./svgii/visualize";
12
+ import { getAngle, getDistance, getSquareDistance, pointAtT, rotatePoint } from "./svgii/geometry";
13
+ import { getPolygonArea } from "./svgii/geometry_area";
14
+ import { renderPath, renderPoint } from "./svgii/visualize";
14
15
 
15
16
 
16
- let polyPtsToArray = (pts) => {
17
- return Array.from(pts).map(pt => [pt.x, pt.y])
18
- }
19
-
20
- // convert to pathdata
21
- let bezierPtsToPathData = (beziers) => {
22
- //let pathData = [{ type: 'M', values: [beziers[0][0][0], beziers[0][0][1]] }];
23
- let pathData = [];
24
-
25
- beziers.forEach(bez => {
26
- let cp1 = bez[1]
27
- let cp2 = bez[2]
28
- let p = bez[3]
29
- let com = { type: 'C', values: [cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1]] }
30
- pathData.push(com)
31
- })
32
-
33
- return pathData
34
- }
35
-
36
17
 
37
18
 
38
19
  /**
39
20
  * Fit one or more Bezier curves to a set of pts.
40
21
  *
41
22
  */
42
- export function fitCurveN(pts, maxError, adjustCpts = true, harmonize= true) {
43
-
44
- if (!Array.isArray(pts) || (pts[0].x !== undefined)) {
45
-
46
- if (pts[0].x !== undefined) {
47
- pts = polyPtsToArray(pts)
48
- } else {
49
- throw Error("Not a valid point array");
50
- }
51
- }
52
-
53
- //console.log(pts);
54
-
55
- // Remove duplicate pts
56
- pts = pts.filter(function (point, i) {
57
- return i === 0 || !point.every((val, j) => {
58
- return val === pts[i - 1][j];
59
- });
60
- });
23
+ export function fitCurveSchneider(pts, {
24
+ maxError = 0,
25
+ adjustCpts = true,
26
+ harmonize = true,
27
+ keepCorners = true
28
+ } = {}) {
61
29
 
62
30
  if (pts.length === 1) {
63
- //return [{ type: 'L', values: [pts[0][0], pts[0][1]] }];
64
31
  return [];
65
32
  }
66
33
 
67
-
68
34
  // single lineto
69
35
  if (pts.length === 2) {
70
36
  return [
71
- { type: 'L', values: [pts[0][0], pts[0][1]] },
72
- { type: 'L', values: [pts[1][0], pts[1][1]] }
37
+ { type: 'L', values: [pts[0].x, pts[0].y] },
38
+ { type: 'L', values: [pts[1].x, pts[1].y] }
73
39
  ]
74
40
  }
75
41
 
42
+ // prevent bulging
43
+ //keepCorners = true
44
+ //keepCorners = false
76
45
 
77
46
  let len = pts.length;
78
-
79
47
  let leftTangent = createTangent(pts[1], pts[0]);
80
48
  let rightTangent = createTangent(pts[len - 2], pts[len - 1]);
81
-
82
- let beziers = fitCubic(pts, leftTangent, rightTangent, maxError);
49
+ let beziers = fitCubic(pts, leftTangent, rightTangent, maxError, keepCorners);
83
50
 
84
51
  // create pathdata
85
52
  let pathData = bezierPtsToPathData(beziers)
86
53
 
87
54
 
88
55
 
89
- // adjustCpts -post
90
- //adjustCpts = false
91
- //harmonize= false;
92
-
93
56
  let cp1, cp2;
57
+
58
+ adjustCpts = false
59
+ harmonize = false;
60
+
94
61
  if (adjustCpts) {
95
62
 
63
+ console.log('refine cpts');
64
+
96
65
  let len2 = pathData.length;
97
66
  let com1 = pathData[0]
98
67
 
@@ -100,9 +69,9 @@ export function fitCurveN(pts, maxError, adjustCpts = true, harmonize= true) {
100
69
  let com2 = pathData[len2 - 1]
101
70
 
102
71
  //adjust 1st and last angle
103
- let p0 = { x: pts[0][0], y: pts[0][1] }
104
- let p1 = { x: pts[1][0], y: pts[1][1] }
105
- let p2 = pts[2] ? { x: pts[2][0], y: pts[2][1] } : null
72
+ let p0 = { x: pts[0].x, y: pts[0].y }
73
+ let p1 = { x: pts[1].x, y: pts[1].y }
74
+ let p2 = pts[2] ? { x: pts[2].x, y: pts[2].y } : null
106
75
 
107
76
  if (p2) {
108
77
  cp1 = { x: com1.values[0], y: com1.values[1] }
@@ -111,8 +80,8 @@ export function fitCurveN(pts, maxError, adjustCpts = true, harmonize= true) {
111
80
  com1.values[1] = cp1.y
112
81
  }
113
82
 
114
- let pL = { x: pts[len - 1][0], y: pts[len - 1][1] }
115
- let pL1 = { x: pts[len - 2][0], y: pts[len - 2][1] }
83
+ let pL = { x: pts[len - 1].x, y: pts[len - 1].y }
84
+ let pL1 = { x: pts[len - 2].x, y: pts[len - 2].y }
116
85
  let pL2 = pts[len - 3] ? { x: pts[len - 3][0], y: pts[len - 3][1] } : null
117
86
 
118
87
  if (pL2) {
@@ -124,13 +93,12 @@ export function fitCurveN(pts, maxError, adjustCpts = true, harmonize= true) {
124
93
 
125
94
  // harmonize too tight tangents
126
95
  //let harmonize= true;
127
- if(harmonize){
128
- pathData = harmonizeCubicCptsThird([{ type: 'M', values: [pts[0][0], pts[0][1]] },
96
+ if (harmonize) {
97
+ pathData = harmonizeCubicCptsThird([{ type: 'M', values: [pts[0].x, pts[0].y] },
129
98
  ...pathData])
130
99
  pathData.shift()
131
100
  }
132
101
 
133
-
134
102
  }
135
103
 
136
104
  //console.log('pathData schneider', pathData);
@@ -138,109 +106,54 @@ export function fitCurveN(pts, maxError, adjustCpts = true, harmonize= true) {
138
106
  }
139
107
 
140
108
 
141
- function adjustTangentAngle(cp, p0, p1, p2) {
142
- let ang1 = getAngle(p0, p1)
143
- let ang2 = getAngle(p0, p2)
144
- let angDiff = (ang2 - ang1)
145
- cp = rotatePoint(cp, p0.x, p0.y, -angDiff)
146
- return cp
147
- }
148
-
149
-
150
- /**
151
- * Use least-squares method to find Bezier control pts for region.
152
- */
153
- let generateBezier = (pts, parameters, leftTangent, rightTangent) => {
154
-
155
- //Bezier curve ctl pts
156
- let a, tmp, u, ux, firstPoint = pts[0], lastPoint = pts[pts.length - 1];
157
-
158
- let bezCurve = [firstPoint, null, null, lastPoint];
159
- let A = zeros_Xx2x2(parameters.length);
160
- let len = parameters.length;
161
-
162
- for (let i = 0; i < len; i++) {
163
- u = parameters[i];
164
- ux = 1 - u;
165
- a = A[i];
166
-
167
- a[0] = mulItems(leftTangent, 3 * u * (ux * ux));
168
- a[1] = mulItems(rightTangent, 3 * ux * (u * u));
169
- }
170
-
171
- //Create the C and X matrices
172
- let C = [[0, 0], [0, 0]];
173
- let X = [0, 0];
174
- let l = pts.length;
175
-
176
- for (let i = 0; i < l; i++) {
177
- u = parameters[i];
178
- a = A[i];
179
-
180
- C[0][0] += dot(a[0], a[0]);
181
- C[0][1] += dot(a[0], a[1]);
182
- C[1][0] += dot(a[0], a[1]);
183
- C[1][1] += dot(a[1], a[1]);
184
-
185
- tmp = subtract(pts[i], pointAtT([firstPoint, firstPoint, lastPoint, lastPoint], u));
186
-
187
- X[0] += dot(a[0], tmp);
188
- X[1] += dot(a[1], tmp);
189
- }
190
-
191
- //Compute the determinants of C and X
192
- let det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1];
193
- let det_C0_X = C[0][0] * X[1] - C[1][0] * X[0];
194
- let det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
195
-
196
- //Finally, derive alpha values
197
- let alpha_l = det_C0_C1 === 0 ? 0 : det_X_C1 / det_C0_C1;
198
- let alpha_r = det_C0_C1 === 0 ? 0 : det_C0_X / det_C0_C1;
199
- let segLength = getDistance(firstPoint, lastPoint, true);
200
- let epsilon = 1.0e-6 * segLength;
201
-
202
- if (alpha_l < epsilon || alpha_r < epsilon) {
203
- //Fall back on standard (probably inaccurate) formula, and subdivide further if needed.
204
- bezCurve[1] = addArrays(firstPoint, mulItems(leftTangent, segLength * 0.333));
205
- bezCurve[2] = addArrays(lastPoint, mulItems(rightTangent, segLength * 0.333));
206
- } else {
207
- // First and last control pts of the Bezier curve
208
- bezCurve[1] = addArrays(firstPoint, mulItems(leftTangent, alpha_l));
209
- bezCurve[2] = addArrays(lastPoint, mulItems(rightTangent, alpha_r));
210
- }
211
-
212
- return bezCurve;
213
- };
214
-
215
-
216
109
  /**
217
110
  * Fit a Bezier curve to a (sub)set of digitized pts.
218
- * Your code should not call this function directly. Use {@link fitCurve} instead.
111
+ * Your code should not call this function directly. Use fitCurve instead.
219
112
  *control-point-1, control-point-2, second-point] and pts are [x, y]
220
113
  */
221
- let fitCubic = (pts, leftTangent, rightTangent, error) => {
114
+ function fitCubic(pts, leftTangent, rightTangent, error, keepCorners = false) {
115
+
116
+
222
117
  //Max times to try iterating (to find an acceptable curve)
223
- let MaxIterations = 20;
118
+ let MaxIterations = 20;
224
119
  let bezCurve;
225
120
 
121
+ /*
226
122
  //Use heuristic if region only has two pts in it
227
123
  if (pts.length === 2) {
228
- let dist = getDistance(pts[0], pts[1], true) * 0.333;
124
+ let dist = getDistance(pts[0], pts[1], false) * 0.333;
229
125
  bezCurve = [pts[0], addArrays(pts[0], mulItems(leftTangent, dist)), addArrays(pts[1], mulItems(rightTangent, dist)), pts[1]];
230
126
  return [bezCurve];
231
127
  }
128
+ */
129
+
232
130
 
233
131
  //Parameterize pts, and attempt to fit curve
234
132
  let u = chordLengthParameterize(pts);
235
133
  let _generateAndReport = generateAndReport(pts, u, u, leftTangent, rightTangent);
236
134
 
237
135
  bezCurve = _generateAndReport[0];
136
+
238
137
  let maxError = _generateAndReport[1];
239
138
  let splitPoint = _generateAndReport[2];
240
139
 
140
+
141
+ // check if curve is bulged
142
+ if (keepCorners) {
143
+ let checkBulge = areaDeviationTooLarge(pts, bezCurve);
144
+ let { isBulged, bezierNew } = checkBulge;
145
+ if (isBulged) {
146
+ // return cubic corner segment
147
+ bezCurve = bezierNew
148
+ }
149
+ }
150
+
151
+
241
152
  if (maxError === 0 || maxError < error) {
242
153
  return [bezCurve];
243
154
  }
155
+
156
+
244
157
  //If error not too large, try some reparameterization and iteration
245
158
  if (maxError < error * error) {
246
159
 
@@ -280,28 +193,100 @@ let fitCubic = (pts, leftTangent, rightTangent, error) => {
280
193
  let beziers = [];
281
194
  let centerVector = subtract(pts[splitPoint - 1], pts[splitPoint + 1]);
282
195
 
283
- if (centerVector.every(function (val) {
284
- return val === 0;
285
- })) {
286
- //[x,y] -> [-y,x]: http://stackoverflow.com/a/4780141/1869660
196
+ if (centerVector.x === 0 && centerVector.y === 0) {
287
197
  centerVector = subtract(pts[splitPoint - 1], pts[splitPoint]);
288
- let _ref = [-centerVector[1], centerVector[0]];
289
- centerVector[0] = _ref[0];
290
- centerVector[1] = _ref[1];
198
+ let _ref = { x: -centerVector.y, y: centerVector.x };
199
+ centerVector.x = _ref.x;
200
+ centerVector.y = _ref.y;
291
201
  }
292
202
 
293
203
  let toCenterTangent = normalize(centerVector);
294
204
  //To and from need to point in opposite directions:
295
205
  let fromCenterTangent = mulItems(toCenterTangent, -1);
296
206
 
207
+ if (pts.length === 3) {
208
+ //splitPoint--
209
+ }
210
+
211
+ beziers.push(
212
+ ...fitCubic(pts.slice(0, splitPoint + 1), leftTangent, toCenterTangent, error, keepCorners),
213
+ ...fitCubic(pts.slice(splitPoint), fromCenterTangent, rightTangent, error, keepCorners)
214
+ );
215
+
216
+
297
217
 
298
- beziers = beziers.concat(fitCubic(pts.slice(0, splitPoint + 1), leftTangent, toCenterTangent, error));
299
- beziers = beziers.concat(fitCubic(pts.slice(splitPoint), fromCenterTangent, rightTangent, error));
300
218
  return beziers;
301
219
  };
302
220
 
303
221
 
304
- const generateAndReport = (pts, paramsOrig, paramsPrime, leftTangent, rightTangent) => {
222
+
223
+ /**
224
+ * Use least-squares method to find Bezier control pts for region.
225
+ */
226
+ function generateBezier(pts, parameters, leftTangent, rightTangent) {
227
+
228
+ //Bezier curve ctl pts
229
+ let a, tmp, u, ux, firstPoint = pts[0], lastPoint = pts[pts.length - 1];
230
+
231
+ let bezCurve = [firstPoint, null, null, lastPoint];
232
+ let A = zeros_Xx2x2(parameters.length);
233
+ let len = parameters.length;
234
+
235
+ for (let i = 0; i < len; i++) {
236
+ u = parameters[i];
237
+ ux = 1 - u;
238
+ a = A[i];
239
+
240
+ a[0] = mulItems(leftTangent, 3 * u * (ux * ux));
241
+ a[1] = mulItems(rightTangent, 3 * ux * (u * u));
242
+ }
243
+
244
+ //Create the C and X matrices
245
+ let C = [[0, 0], [0, 0]];
246
+ let X = [0, 0];
247
+ let l = pts.length;
248
+
249
+ for (let i = 0; i < l; i++) {
250
+ u = parameters[i];
251
+ a = A[i];
252
+
253
+ C[0][0] += dot(a[0], a[0]);
254
+ C[0][1] += dot(a[0], a[1]);
255
+ C[1][0] += dot(a[0], a[1]);
256
+ C[1][1] += dot(a[1], a[1]);
257
+
258
+ tmp = subtract(pts[i], pointAtT([firstPoint, firstPoint, lastPoint, lastPoint], u));
259
+
260
+ X[0] += dot(a[0], tmp);
261
+ X[1] += dot(a[1], tmp);
262
+ }
263
+
264
+ //Compute the determinants of C and X
265
+ let det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1];
266
+ let det_C0_X = C[0][0] * X[1] - C[1][0] * X[0];
267
+ let det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
268
+
269
+ //Finally, derive alpha values
270
+ let alpha_l = det_C0_C1 === 0 ? 0 : det_X_C1 / det_C0_C1;
271
+ let alpha_r = det_C0_C1 === 0 ? 0 : det_C0_X / det_C0_C1;
272
+ let segLength = getDistance(firstPoint, lastPoint, false);
273
+ let epsilon = 1.0e-6 * segLength;
274
+
275
+ if (alpha_l < epsilon || alpha_r < epsilon) {
276
+ //Fall back on standard (probably inaccurate) formula, and subdivide further if needed.
277
+ bezCurve[1] = addArrays(firstPoint, mulItems(leftTangent, segLength * 0.333));
278
+ bezCurve[2] = addArrays(lastPoint, mulItems(rightTangent, segLength * 0.333));
279
+ } else {
280
+ // First and last control pts of the Bezier curve
281
+ bezCurve[1] = addArrays(firstPoint, mulItems(leftTangent, alpha_l));
282
+ bezCurve[2] = addArrays(lastPoint, mulItems(rightTangent, alpha_r));
283
+ }
284
+
285
+ return bezCurve;
286
+ };
287
+
288
+
289
+ function generateAndReport(pts, paramsOrig, paramsPrime, leftTangent, rightTangent) {
305
290
  let bezCurve, maxError, splitPoint;
306
291
 
307
292
  bezCurve = generateBezier(pts, paramsPrime, leftTangent, rightTangent);
@@ -337,8 +322,8 @@ function newtonRaphsonRootFind(bez, point, u) {
337
322
  //let q = bezierQ(bez, u);
338
323
  let q = pointAtT(bez, u)
339
324
 
340
- let dx = q[0] - point[0];
341
- let dy = q[1] - point[1];
325
+ let dx = q.x - point.x;
326
+ let dy = q.y - point.y;
342
327
 
343
328
  // First derivative (tangent vector at u)
344
329
  let qp = bezierQprime(bez, u);
@@ -388,7 +373,7 @@ function chordLengthParameterize(pts) {
388
373
  for (let i = 0; i < l; i++) {
389
374
  p = pts[i];
390
375
  //currU = i ? prevU + length(subtract(p, p0)) : 0;
391
- currU = prevU + getDistance(p, p0, true);
376
+ currU = prevU + getDistance(p, p0, false);
392
377
  u.push(currU);
393
378
  prevU = currU;
394
379
 
@@ -420,16 +405,24 @@ function computeMaxError(pts, bez, parameters) {
420
405
 
421
406
  let t_distMap = mapTtoRelativeDistances(bez, 10);
422
407
  let l = pts.length
408
+ let ptOnPath = null
423
409
 
424
410
  for (i = 0; i < l; i++) {
425
411
  point = pts[i];
426
412
  //Find 't' for a point on the bez curve that's as close to 'point' as possible:
427
413
  t = find_t(parameters[i], t_distMap, 10);
428
414
 
415
+ ptOnPath = pointAtT(bez, t);
416
+ dist = getSquareDistance(ptOnPath, point)
417
+
418
+ /*
419
+ console.log('v', v);
429
420
  v = subtract(pointAtT(bez, t), point);
430
- dist = v[0] * v[0] + v[1] * v[1];
421
+ dist = v.x * v.x + v.y * v.y;
422
+ */
431
423
 
432
424
  if (dist > maxDist) {
425
+ //renderPoint(markers, ptOnPath)
433
426
  maxDist = dist;
434
427
  splitPoint = i;
435
428
  }
@@ -447,13 +440,13 @@ function mapTtoRelativeDistances(bez, B_parts) {
447
440
 
448
441
  for (let i = 1; i <= B_parts; i++) {
449
442
  B_t_curr = pointAtT(bez, i / B_parts);
450
- sumLen += getDistance(B_t_curr, B_t_prev, true);
443
+ sumLen += getDistance(B_t_curr, B_t_prev);
451
444
  B_t_dist.push(sumLen);
452
445
  B_t_prev = B_t_curr;
453
446
  }
454
447
 
455
448
  //Normalize B_length to the same interval as the parameter distances; 0 to 1:
456
- B_t_dist = B_t_dist.map(function (x) {
449
+ B_t_dist = B_t_dist.map((x) => {
457
450
  return x / sumLen;
458
451
  });
459
452
  return B_t_dist;
@@ -486,17 +479,80 @@ function find_t(param, t_distMap, B_parts) {
486
479
  return t;
487
480
  }
488
481
 
482
+
483
+ function areaDeviationTooLarge(pts, bezCurve) {
484
+
485
+ let split = 4;
486
+ let step = 1 / split;
487
+ let l = pts.length
488
+
489
+ // create polygon from curve candidate
490
+ let poly = [bezCurve[0]];
491
+ let pt;
492
+
493
+ for (let i = 1; i < split; i++) {
494
+ let t = step * i
495
+ pt = pointAtT(bezCurve, t)
496
+ poly.push(pt);
497
+ }
498
+
499
+ poly.push(bezCurve[bezCurve.length - 1]);
500
+
501
+ // Original area
502
+ let polyArea = getPolygonArea(pts, false)
503
+
504
+ // flat line
505
+ if (!polyArea && pts.length === 2) {
506
+ polyArea = getSquareDistance(pts[0], pts[1]) * 0.01
507
+ }
508
+
509
+ let curveArea = getPolygonArea(poly, false);
510
+ let rat = curveArea / polyArea;
511
+
512
+ let isBulged = rat > 1.1
513
+ //|| rat < 0.9
514
+ // isBulged = rat > 2
515
+ let bezierNew = bezCurve
516
+ if (isBulged) {
517
+ let ptMid = pts[Math.floor(l * 0.5)]
518
+ let p = pts[l - 1];
519
+ /*
520
+ let cp1 = pointAtT([pts[0], ptMid], 0.666);
521
+ let cp2 = pointAtT([p, ptMid], 0.666);
522
+ //let ptMid2 = pts[Math.floor(pts.length*0.666)]
523
+ bezierNew = [pts[0], cp1, cp2, p]
524
+ //bezierNew = [pts[0], ptMid, ptMid, p]
525
+
526
+ //renderPoint(markers, cp1, 'cyan')
527
+ //renderPoint(markers, cp2, 'cyan')
528
+ //console.log('bezCurve', bezCurve, pts);
529
+
530
+ cp1 = pointAtT([bezCurve[0], bezCurve[3]], 0.333);
531
+ cp2 = pointAtT([bezCurve[0], bezCurve[3]], 0.666);
532
+ bezCurve = [bezCurve[0], cp1, cp2, bezCurve[3]]
533
+ */
534
+
535
+ //console.log('!!!bulged');
536
+
537
+ bezCurve = [pts[0], ptMid, ptMid, p]
538
+ bezierNew = bezCurve
539
+ }
540
+
541
+ return { isBulged, bezierNew };
542
+ }
543
+
544
+
489
545
  /**
490
546
  * Creates a vector of length 1 which shows the direction from B to A
491
547
  */
492
548
  function createTangent(p1, p2) {
493
549
  // Returns unit vector pointing from B to A
494
- let dx = p1[0] - p2[0];
495
- let dy = p1[1] - p2[1];
550
+ let dx = p1.x - p2.x;
551
+ let dy = p1.y - p2.y;
496
552
  let length = Math.sqrt(dx * dx + dy * dy);
497
553
 
498
- if (length === 0) return [0, 0];
499
- return [dx / length, dy / length];
554
+ if (length === 0) return { x: 0, y: 0 };
555
+ return { x: dx / length, y: dy / length };
500
556
  }
501
557
 
502
558
 
@@ -506,26 +562,26 @@ function createTangent(p1, p2) {
506
562
 
507
563
  // Basic vector utilities (only what's absolutely necessary)
508
564
  function subtract(a, b) {
509
- return [a[0] - b[0], a[1] - b[1]];
565
+ return { x: a.x - b.x, y: a.y - b.y };
510
566
  }
511
567
 
512
568
  function addArrays(a, b) {
513
- return [a[0] + b[0], a[1] + b[1]];
569
+ return { x: a.x + b.x, y: a.y + b.y };
514
570
  }
515
571
 
516
572
 
517
573
  function mulItems(v, s) {
518
- return [v[0] * s, v[1] * s];
574
+ return { x: v.x * s, y: v.y * s };
519
575
  }
520
576
 
521
577
 
522
578
  function normalize(v) {
523
- let len = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
524
- return len === 0 ? [0, 0] : [v[0] / len, v[1] / len];
579
+ let len = Math.sqrt(v.x * v.x + v.y * v.y);
580
+ return len === 0 ? { x: 0, y: 0 } : { x: v.x / len, y: v.y / len };
525
581
  }
526
582
 
527
583
  function dot(a, b) {
528
- return a[0] * b[0] + a[1] * b[1];
584
+ return a.x * b.x + a.y * b.y;
529
585
  }
530
586
 
531
587
  function zeros_Xx2x2(x) {
@@ -547,24 +603,59 @@ function bezierQprime(bez, u, second = false) {
547
603
  let dx, dy;
548
604
 
549
605
  if (second) {
550
- dx = 6 * mt * (cp2[0] - 2 * cp1[0] + p0[0]) +
551
- 6 * t * (p1[0] - 2 * cp2[0] + cp1[0]);
606
+ dx = 6 * mt * (cp2.x - 2 * cp1.x + p0.x) +
607
+ 6 * t * (p1.x - 2 * cp2.x + cp1.x);
552
608
 
553
- dy = 6 * mt * (cp2[1] - 2 * cp1[1] + p0[1]) +
554
- 6 * t * (p1[1] - 2 * cp2[1] + cp1[1]);
609
+ dy = 6 * mt * (cp2.y - 2 * cp1.y + p0.y) +
610
+ 6 * t * (p1.y - 2 * cp2.y + cp1.y);
555
611
 
556
612
  } else {
557
- dx = 3 * mt2 * (cp1[0] - p0[0]) +
558
- 6 * mt * t * (cp2[0] - cp1[0]) +
559
- 3 * t2 * (p1[0] - cp2[0]);
613
+ dx = 3 * mt2 * (cp1.x - p0.x) +
614
+ 6 * mt * t * (cp2.x - cp1.x) +
615
+ 3 * t2 * (p1.x - cp2.x);
560
616
 
561
- dy = 3 * mt2 * (cp1[1] - p0[1]) +
562
- 6 * mt * t * (cp2[1] - cp1[1]) +
563
- 3 * t2 * (p1[1] - cp2[1]);
617
+ dy = 3 * mt2 * (cp1.y - p0.y) +
618
+ 6 * mt * t * (cp2.y - cp1.y) +
619
+ 3 * t2 * (p1.y - cp2.y);
564
620
  }
565
621
 
566
622
  return [dx, dy];
567
623
  }
568
624
 
569
625
 
626
+ function adjustTangentAngle(cp, p0, p1, p2) {
627
+ let ang1 = getAngle(p0, p1)
628
+ let ang2 = getAngle(p0, p2)
629
+ let angDiff = (ang2 - ang1)
630
+ cp = rotatePoint(cp, p0.x, p0.y, -angDiff)
631
+ return cp
632
+ }
633
+
634
+
635
+
636
+ // convert to pathdata
637
+ function bezierPtsToPathData(beziers = []) {
638
+ let pathData = [];
639
+ beziers.forEach(bez => {
640
+
641
+ let type = bez.length === 4 ? 'C' : (bez.length === 3 ? 'Q' : 'L')
642
+
643
+ let cp1 = type === 'C' || type === 'Q' ? bez[1] : null;
644
+ let cp2 = type === 'C' ? bez[2] : null;
645
+ let p = bez[bez.length - 1]
646
+
647
+ let values = type === 'C' ?
648
+ [cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y] :
649
+ (type === 'Q' ?
650
+ [cp1.x, cp1.y, p.x, p.y] :
651
+ [p.x, p.y]
652
+ )
653
+
654
+ let com = { type, values }
655
+ pathData.push(com)
656
+ })
657
+
658
+ return pathData
659
+ }
660
+
570
661