svg-path-simplify 0.4.5 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +24 -14
  3. package/dist/svg-path-simplify.esm.js +1620 -1216
  4. package/dist/svg-path-simplify.esm.min.js +2 -2
  5. package/dist/svg-path-simplify.js +1620 -1215
  6. package/dist/svg-path-simplify.min.js +2 -2
  7. package/dist/svg-path-simplify.pathdata.esm.js +298 -665
  8. package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
  9. package/dist/svg-path-simplify.poly.cjs +8 -9
  10. package/drawing.svg +62 -0
  11. package/index.html +11 -5
  12. package/package.json +1 -1
  13. package/src/index.js +3 -1
  14. package/src/pathData_get_intersections.js +777 -0
  15. package/src/pathData_offset.js +201 -0
  16. package/src/pathData_simplify_cubic.js +14 -34
  17. package/src/pathData_simplify_cubic_extrapolate.js +147 -8
  18. package/src/pathData_simplify_cubicsToArcs.js +65 -21
  19. package/src/pathSimplify-main.js +259 -60
  20. package/src/pathSimplify-presets.js +1 -0
  21. package/src/poly-fit-curve-schneider.js +171 -132
  22. package/src/poly-fit-curve-schneider_check_bulge.js +131 -0
  23. package/src/poly-offset.js +133 -0
  24. package/src/simplify_poly_RC.js +29 -7
  25. package/src/svgii/geometry.js +130 -27
  26. package/src/svgii/geometry_bbox.js +1 -1
  27. package/src/svgii/pathData_analyze.js +6 -28
  28. package/src/svgii/pathData_convert.js +70 -15
  29. package/src/svgii/pathData_remove_collinear.js +32 -36
  30. package/src/svgii/pathData_simplify_refineExtremes.js +1 -3
  31. package/src/svgii/pathData_stringify.js +132 -6
  32. package/src/svgii/pathData_toPolygon.js +33 -23
  33. package/src/svgii/poly_analyze.js +272 -125
  34. package/src/svgii/poly_analyze_cleanup.js +311 -0
  35. package/src/svgii/poly_analyze_getTangents.js +125 -0
  36. package/src/svgii/poly_analyze_get_chunks.js +3 -1
  37. package/src/svgii/poly_normalize.js +12 -0
  38. package/src/svgii/poly_to_pathdata.js +477 -8
  39. package/src/svgii/rounding.js +29 -39
  40. package/src/svgii/svg_cleanup.js +42 -54
  41. package/src/svgii/svg_cleanup_normalize_transforms.js +1 -1
  42. package/src/svgii/visualize.js +33 -2
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import { harmonizeCubicCpts, harmonizeCubicCptsThird } from "./pathData_simplify_harmonize_cpts";
12
+ import { adjustTangentAngle, areaDeviationTooLarge } from "./poly-fit-curve-schneider_check_bulge";
12
13
  import { checkLineIntersection, getAngle, getDistance, getSquareDistance, pointAtT, reducePoints, rotatePoint } from "./svgii/geometry";
13
14
  import { getPolygonArea } from "./svgii/geometry_area";
14
15
  import { detectRegularPolygon, getPolyCentroid } from "./svgii/poly_analyze";
@@ -30,6 +31,8 @@ export function fitCurveSchneider(pts, {
30
31
  keepCorners = true
31
32
  } = {}) {
32
33
 
34
+ //console.log('fitCurveSchneider');
35
+
33
36
  if (pts.length === 1) {
34
37
  return [];
35
38
  }
@@ -47,112 +50,215 @@ export function fitCurveSchneider(pts, {
47
50
  //keepCorners = false
48
51
 
49
52
  let len = pts.length;
53
+ let cp1, cp2;
54
+
55
+
56
+ //adjust 1st and last angle
57
+ let p0 = { x: pts[0].x, y: pts[0].y }
58
+ let p1 = { x: pts[1].x, y: pts[1].y }
59
+ cp1 = p1
60
+
61
+ let p2 = pts[2] ? { x: pts[2].x, y: pts[2].y } : null
62
+
63
+ let pL = { x: pts[len - 1].x, y: pts[len - 1].y }
64
+ let pL1 = { x: pts[len - 2].x, y: pts[len - 2].y }
65
+ cp2 = pL1
66
+ let pL2 = pts[len - 3] ? { x: pts[len - 3].x, y: pts[len - 3].y } : null
67
+
68
+
69
+ // adjust start angles
70
+ if (p2) {
71
+ cp1 = adjustTangentAngle(cp1, p0, p1, p2)
72
+ }
73
+
74
+ if (pL2) {
75
+ cp2 = adjustTangentAngle(cp2, pL, pL1, pL2)
76
+ }
77
+
78
+
79
+ let leftTangent = createTangent(cp1, pts[0]);
80
+ let rightTangent = createTangent(cp2, pts[len - 1]);
81
+
82
+
83
+ /*
50
84
  let leftTangent = createTangent(pts[1], pts[0]);
51
85
  let rightTangent = createTangent(pts[len - 2], pts[len - 1]);
52
- let beziers = fitCubic(pts, leftTangent, rightTangent, maxError, keepCorners);
86
+ */
87
+
88
+
89
+ //let beziers = fitCubic(pts, leftTangent, rightTangent, maxError, keepCorners);
90
+ let beziers = fitCubicIterative(pts, leftTangent, rightTangent, maxError, keepCorners);
91
+ //console.log('beziers', JSON.parse(JSON.stringify(beziers)));
92
+
53
93
 
54
94
  // create pathdata
55
95
  let pathData = bezierPtsToPathData(beziers)
56
- let cp1, cp2;
57
96
 
58
97
  adjustCpts = false
98
+ // adjustCpts = true;
59
99
  //harmonize = false;
60
-
61
- adjustCpts = true;
62
100
  //harmonize = true;
63
101
 
64
102
 
65
- if (adjustCpts) {
66
103
 
67
- //console.log('refine cpts');
104
+ //console.log('pathData schneider', pathData);
105
+ return pathData
106
+ }
107
+
108
+
109
+ /**
110
+ * Fit a Bezier curve to a (sub)set of digitized pts.
111
+ * Your code should not call this function directly. Use fitCurve instead.
112
+ * control-point-1, control-point-2, second-point] and pts are [x, y]
113
+ */
114
+
68
115
 
69
- let len2 = pathData.length;
70
- let com1 = pathData[0]
116
+ function fitCubicIterative(pts = [], leftTangent = { x: 0, y: 0 }, rightTangent = { x: 0, y: 0 }, error = 1) {
71
117
 
72
- // last cubic segment
73
- let com2 = pathData[len2 - 1]
118
+ //error = 0.01
119
+ //console.log('error', error);
120
+ //console.log('fitCubicIterative');
74
121
 
75
- //adjust 1st and last angle
76
- let p0 = { x: pts[0].x, y: pts[0].y }
77
- let p1 = { x: pts[1].x, y: pts[1].y }
78
- let p2 = pts[2] ? { x: pts[2].x, y: pts[2].y } : null
122
+ let beziers = [];
79
123
 
80
- if (p2) {
81
- cp1 = { x: com1.values[0], y: com1.values[1] }
82
- cp1 = adjustTangentAngle(cp1, p0, p1, p2)
83
- com1.values[0] = cp1.x
84
- com1.values[1] = cp1.y
85
- }
124
+ // stack holds segments to process
125
+ let stack = [{
126
+ pts,
127
+ leftTangent,
128
+ rightTangent
129
+ }];
86
130
 
87
- let pL = { x: pts[len - 1].x, y: pts[len - 1].y }
88
- let pL1 = { x: pts[len - 2].x, y: pts[len - 2].y }
89
- let pL2 = pts[len - 3] ? { x: pts[len - 3][0], y: pts[len - 3][1] } : null
131
+ while (stack.length > 0) {
90
132
 
91
- if (pL2) {
92
- cp2 = { x: com2.values[2], y: com2.values[3] }
93
- cp2 = adjustTangentAngle(cp2, pL, pL1, pL2)
94
- com2.values[2] = cp2.x
95
- com2.values[3] = cp2.y
96
- }
133
+ let current = stack.pop();
134
+
135
+ let pts = current.pts;
136
+ let leftTangent = current.leftTangent;
137
+ let rightTangent = current.rightTangent;
138
+
139
+ let MaxIterations = 20;
140
+ let bezCurve = [];
141
+
142
+ // === same logic as before ===
97
143
 
144
+ let u = chordLengthParameterize(pts);
145
+ let _generateAndReport = generateAndReport(pts, u, u, leftTangent, rightTangent);
146
+
147
+ bezCurve = _generateAndReport[0];
148
+ let maxError = _generateAndReport[1];
149
+ let splitPoint = _generateAndReport[2];
150
+
151
+ // BULGE CHECK
98
152
  /*
99
- // harmonize too tight tangents
100
- let harmonize = true;
101
- harmonize = false;
102
- if (harmonize) {
103
- pathData = harmonizeCubicCptsThird([{ type: 'M', values: [pts[0].x, pts[0].y] },
104
- ...pathData])
105
- pathData.shift()
153
+ let checkBulge = areaDeviationTooLarge(pts, bezCurve);
154
+ let { isBulged, bezierNew, ptsN } = checkBulge;
155
+ isBulged = false
156
+
157
+ if (isBulged) {
158
+ beziers.push(bezierNew);
159
+ // skip this segment entirely (acts like your "early exit")
160
+ continue;
106
161
  }
107
162
  */
108
163
 
164
+ // ACCEPT
165
+ if (maxError === 0 || maxError < error) {
166
+ beziers.push(bezCurve);
167
+ continue;
168
+ }
169
+
170
+ // ITERATIVE REFINEMENT
171
+ if (maxError < error * error) {
172
+
173
+ let uPrime = u;
174
+ let prevErr = maxError;
175
+ let prevSplit = splitPoint;
176
+
177
+ for (let i = 0; i < MaxIterations; i++) {
178
+
179
+ uPrime = reparameterize(bezCurve, pts, uPrime);
180
+
181
+ let _generateAndReport2 = generateAndReport(pts, u, uPrime, leftTangent, rightTangent);
182
+
183
+ bezCurve = _generateAndReport2[0];
184
+ maxError = _generateAndReport2[1];
185
+ splitPoint = _generateAndReport2[2];
186
+
187
+ if (maxError < error) {
188
+ beziers.push(bezCurve);
189
+ break;
190
+ }
191
+
192
+ else if (splitPoint === prevSplit) {
193
+ let errChange = maxError / prevErr;
194
+ if (errChange > .9999 && errChange < 1.0001) {
195
+ break;
196
+ }
197
+ }
198
+
199
+ prevErr = maxError;
200
+ prevSplit = splitPoint;
201
+ }
202
+
203
+ if (maxError < error) continue;
204
+ }
205
+
206
+ // SPLIT (instead of recursion → push onto stack)
207
+
208
+ let centerVector = subtract(pts[splitPoint - 1], pts[splitPoint + 1]);
209
+
210
+ if (centerVector.x === 0 && centerVector.y === 0) {
211
+ centerVector = subtract(pts[splitPoint - 1], pts[splitPoint]);
212
+ centerVector = { x: -centerVector.y, y: centerVector.x };
213
+ }
214
+
215
+ let toCenterTangent = normalize(centerVector);
216
+ let fromCenterTangent = mulItems(toCenterTangent, -1);
217
+
218
+ // push RIGHT first, so LEFT is processed first (stack = LIFO)
219
+ stack.push({
220
+ pts: pts.slice(splitPoint),
221
+ leftTangent: fromCenterTangent,
222
+ rightTangent: rightTangent
223
+ });
224
+
225
+ stack.push({
226
+ pts: pts.slice(0, splitPoint + 1),
227
+ leftTangent: leftTangent,
228
+ rightTangent: toCenterTangent
229
+ });
109
230
  }
110
231
 
111
- //console.log('pathData schneider', pathData);
112
- return pathData
232
+ //console.log(beziers);
233
+ return beziers;
113
234
  }
114
235
 
115
236
 
116
- /**
117
- * Fit a Bezier curve to a (sub)set of digitized pts.
118
- * Your code should not call this function directly. Use fitCurve instead.
119
- *control-point-1, control-point-2, second-point] and pts are [x, y]
120
- */
121
- function fitCubic(pts, leftTangent, rightTangent, error, keepCorners = false) {
122
237
 
238
+ function fitCubic(pts = [], leftTangent = { x: 0, y: 0 }, rightTangent = { x: 0, y: 0 }, error = 1, keepCorners = false) {
123
239
 
124
240
  //Max times to try iterating (to find an acceptable curve)
125
241
  let MaxIterations = 20;
126
- let bezCurve;
127
-
128
- /*
129
- //Use heuristic if region only has two pts in it
130
- if (pts.length === 2) {
131
- let dist = getDistance(pts[0], pts[1], false) * 0.333;
132
- bezCurve = [pts[0], addArrays(pts[0], mulItems(leftTangent, dist)), addArrays(pts[1], mulItems(rightTangent, dist)), pts[1]];
133
- return [bezCurve];
134
- }
135
- */
242
+ let bezCurve = [];
243
+ let beziers = [];
136
244
 
137
245
 
138
246
  //Parameterize pts, and attempt to fit curve
139
247
  let u = chordLengthParameterize(pts);
140
248
  let _generateAndReport = generateAndReport(pts, u, u, leftTangent, rightTangent);
249
+ //console.log(leftTangent, u, error);
141
250
 
142
251
  bezCurve = _generateAndReport[0];
143
-
144
252
  let maxError = _generateAndReport[1];
145
253
  let splitPoint = _generateAndReport[2];
146
254
 
147
255
 
148
- // check if curve is bulged
149
- if (keepCorners) {
150
- let checkBulge = areaDeviationTooLarge(pts, bezCurve);
151
- let { isBulged, bezierNew } = checkBulge;
152
- if (isBulged) {
153
- // return cubic corner segment
154
- bezCurve = bezierNew
155
- }
256
+ // check if curve is bulged - quit
257
+ let checkBulge = areaDeviationTooLarge(pts, bezCurve);
258
+ let { isBulged, bezierNew } = checkBulge;
259
+ if (isBulged) {
260
+ beziers.push(bezierNew)
261
+ return beziers
156
262
  }
157
263
 
158
264
 
@@ -197,7 +303,6 @@ function fitCubic(pts, leftTangent, rightTangent, error, keepCorners = false) {
197
303
  }
198
304
 
199
305
  //Fitting failed -- split at max error point and fit recursively
200
- let beziers = [];
201
306
  let centerVector = subtract(pts[splitPoint - 1], pts[splitPoint + 1]);
202
307
 
203
308
  if (centerVector.x === 0 && centerVector.y === 0) {
@@ -211,9 +316,11 @@ function fitCubic(pts, leftTangent, rightTangent, error, keepCorners = false) {
211
316
  //To and from need to point in opposite directions:
212
317
  let fromCenterTangent = mulItems(toCenterTangent, -1);
213
318
 
319
+ /*
214
320
  if (pts.length === 3) {
215
321
  //splitPoint--
216
322
  }
323
+ */
217
324
 
218
325
  beziers.push(
219
326
  ...fitCubic(pts.slice(0, splitPoint + 1), leftTangent, toCenterTangent, error, keepCorners),
@@ -487,66 +594,6 @@ function find_t(param, t_distMap, B_parts) {
487
594
  }
488
595
 
489
596
 
490
- function areaDeviationTooLarge(pts, bezCurve) {
491
-
492
- let split = 4;
493
- let step = 1 / split;
494
- let l = pts.length
495
-
496
- // create polygon from curve candidate
497
- let poly = [bezCurve[0]];
498
- let pt;
499
-
500
- for (let i = 1; i < split; i++) {
501
- let t = step * i
502
- pt = pointAtT(bezCurve, t)
503
- poly.push(pt);
504
- }
505
-
506
- poly.push(bezCurve[bezCurve.length - 1]);
507
-
508
- // Original area
509
- let polyArea = getPolygonArea(pts, false)
510
-
511
- // flat line
512
- if (!polyArea && pts.length === 2) {
513
- polyArea = getSquareDistance(pts[0], pts[1]) * 0.01
514
- }
515
-
516
- let curveArea = getPolygonArea(poly, false);
517
- let rat = curveArea / polyArea;
518
-
519
- let isBulged = rat > 1.1
520
- //|| rat < 0.9
521
- // isBulged = rat > 2
522
- let bezierNew = bezCurve
523
- if (isBulged) {
524
- let ptMid = pts[Math.floor(l * 0.5)]
525
- let p = pts[l - 1];
526
- /*
527
- let cp1 = pointAtT([pts[0], ptMid], 0.666);
528
- let cp2 = pointAtT([p, ptMid], 0.666);
529
- //let ptMid2 = pts[Math.floor(pts.length*0.666)]
530
- bezierNew = [pts[0], cp1, cp2, p]
531
- //bezierNew = [pts[0], ptMid, ptMid, p]
532
-
533
- //renderPoint(markers, cp1, 'cyan')
534
- //renderPoint(markers, cp2, 'cyan')
535
- //console.log('bezCurve', bezCurve, pts);
536
-
537
- cp1 = pointAtT([bezCurve[0], bezCurve[3]], 0.333);
538
- cp2 = pointAtT([bezCurve[0], bezCurve[3]], 0.666);
539
- bezCurve = [bezCurve[0], cp1, cp2, bezCurve[3]]
540
- */
541
-
542
- //console.log('!!!bulged');
543
-
544
- bezCurve = [pts[0], ptMid, ptMid, p]
545
- bezierNew = bezCurve
546
- }
547
-
548
- return { isBulged, bezierNew };
549
- }
550
597
 
551
598
 
552
599
  /**
@@ -630,14 +677,6 @@ function bezierQprime(bez, u, second = false) {
630
677
  }
631
678
 
632
679
 
633
- function adjustTangentAngle(cp, p0, p1, p2) {
634
- let ang1 = getAngle(p0, p1)
635
- let ang2 = getAngle(p0, p2)
636
- let angDiff = (ang2 - ang1)
637
- cp = rotatePoint(cp, p0.x, p0.y, -angDiff)
638
- return cp
639
- }
640
-
641
680
 
642
681
 
643
682
  // convert to pathdata
@@ -0,0 +1,131 @@
1
+ import { getAngle, getBezierExtremeT, pointAtT, rotatePoint } from "./svgii/geometry";
2
+ import { getPolygonArea } from "./svgii/geometry_area";
3
+ import { pathDataToD } from "./svgii/pathData_stringify";
4
+ import { renderPath, renderPoint, renderPoly } from "./svgii/visualize";
5
+
6
+
7
+ export function checkExtremesInCurve(pts = [], bezCurve) {
8
+
9
+ let split = 4;
10
+ let l = pts.length
11
+
12
+ // create polygon from curve candidate
13
+ let pt;
14
+ let ptsN = pts;
15
+ let bezierNew = bezCurve
16
+
17
+ /**
18
+ * check extremes
19
+ */
20
+ let [p0, cp1, cp2, p] = bezCurve;
21
+ let tEx = getBezierExtremeT([p0, cp1, cp2, p])
22
+ if (tEx.length) {
23
+ //console.log('bezCurve', 'isBulged', isBulged, bezCurve, 'tEx', tEx);
24
+
25
+ tEx = tEx.filter(t => t !== 0 && t !== 1)[0]
26
+ if (tEx) {
27
+
28
+ let pEx = pointAtT(bezCurve, tEx, false, true)
29
+ let { x, y } = pEx
30
+ //console.log('pEx', pEx, pts);
31
+
32
+ let seg1_p0 = p0;
33
+ let seg1_cpt1 = pEx.cpts[0];
34
+ let seg1_cpt2 = pEx.cpts[2];
35
+ let seg1_p = { x, y };
36
+
37
+
38
+ let seg2_p0 = p0;
39
+ let seg2_cpt1 = pEx.cpts[3];
40
+ let seg2_cpt2 = pEx.cpts[1];
41
+ let seg2_p = p;
42
+
43
+ let pathD = [
44
+ { type: 'M', values: [seg1_p0.x, seg1_p0.y] },
45
+ { type: 'C', values: [seg1_cpt1.x, seg1_cpt1.y, seg1_cpt2.x, seg1_cpt2.y, seg1_p.x, seg1_p.y] },
46
+ { type: 'C', values: [seg2_cpt1.x, seg2_cpt1.y, seg2_cpt2.x, seg2_cpt2.y, seg2_p.x, seg2_p.y] },
47
+ ];
48
+
49
+
50
+
51
+ bezierNew = [seg1_p0, seg1_cpt1, seg1_cpt2, seg1_p];
52
+ //isBulged = true
53
+
54
+ }
55
+ }
56
+
57
+
58
+ return { isBulged, bezierNew, ptsN };
59
+ }
60
+
61
+
62
+
63
+ export function areaDeviationTooLarge(pts = [], bezCurve) {
64
+
65
+ let split = 4;
66
+ let step = 1 / split;
67
+ let l = pts.length
68
+
69
+ // create polygon from curve candidate
70
+ let poly = [bezCurve[0]];
71
+ let pt;
72
+ let ptsN = pts;
73
+
74
+ for (let i = 1; i < split; i++) {
75
+ let t = step * i
76
+ pt = pointAtT(bezCurve, t)
77
+ //renderPoint(markers, pt, 'red')
78
+ poly.push(pt);
79
+ }
80
+
81
+ poly.push(bezCurve[bezCurve.length - 1]);
82
+
83
+ // Original area
84
+ let polyArea = getPolygonArea(pts, true)
85
+
86
+ // flat line
87
+
88
+ if (!polyArea && pts.length === 2) {
89
+ polyArea = 0.0000001
90
+ }
91
+
92
+ //renderPoint(markers, pts[0], 'green')
93
+ //renderPoint(markers, pts[1], 'cyan')
94
+
95
+ let curveArea = getPolygonArea(poly, true);
96
+ //let rat = curveArea / polyArea;
97
+ let areaDiff = Math.abs(polyArea - curveArea)
98
+
99
+ let rat = areaDiff / polyArea;
100
+ //console.log('rat', rat, curveArea, polyArea);
101
+
102
+ let isBulged = rat > 1
103
+ let bezierNew = bezCurve
104
+
105
+
106
+
107
+ if (isBulged) {
108
+ //renderPoint(markers, pts[0], 'green')
109
+ renderPoint(markers, pts[1], 'magenta')
110
+ bezierNew = [pts[0], pts[0], pts[1], pts[1]]
111
+ //return { isBulged, bezierNew };
112
+ }
113
+
114
+ return { isBulged, bezierNew, ptsN };
115
+ }
116
+
117
+
118
+
119
+ export function adjustTangentAngle(cp, p0, p1, p2) {
120
+ let ang1 = getAngle(p0, p1)
121
+ let ang2 = getAngle(p0, p2)
122
+ let angDiff = (ang2 - ang1)
123
+ //let ang3 =
124
+ let f = 0.666
125
+ f = 1
126
+ //f=0
127
+ //f=0.75
128
+ //console.log(angDiff);
129
+ cp = rotatePoint(cp, p0.x, p0.y, -angDiff * f)
130
+ return cp
131
+ }
@@ -0,0 +1,133 @@
1
+ import { checkLineIntersection } from "./svgii/geometry";
2
+
3
+ export function getParallelPoly(pts=[], strokeWidth = 1, sweep = 1, closed=false) {
4
+ let l = pts.length;
5
+
6
+ let polyTop_lines = [];
7
+ let polyBottom_lines = [];
8
+ let polyTop = [];
9
+ let polyBottom = [];
10
+
11
+ for (let i = 1; i < l; i++) {
12
+ let p1 = pts[i - 1];
13
+ let p2 = pts[i];
14
+ let parralels = getParallels(p1, p2, strokeWidth);
15
+ let { top, bottom } = parralels;
16
+ polyTop_lines.push(top);
17
+ polyBottom_lines.push(bottom);
18
+ }
19
+
20
+ // find line intersections
21
+ l = polyTop_lines.length;
22
+
23
+ // add 1st points
24
+ polyTop.push(polyTop_lines[0][0]);
25
+ polyBottom.push(polyBottom_lines[0][0]);
26
+
27
+ for (let i = 1; i < l; i++) {
28
+ let l1_t = polyTop_lines[i - 1];
29
+ let l2_t = polyTop_lines[i];
30
+ let ptI_t = checkLineIntersection(
31
+ l1_t[0],
32
+ l1_t[1],
33
+ l2_t[1],
34
+ l2_t[0],
35
+ false,
36
+ true
37
+ );
38
+ let ptN_t = ptI_t ? ptI_t : l1_t[1];
39
+
40
+ let l1_b = polyBottom_lines[i - 1];
41
+ let l2_b = polyBottom_lines[i];
42
+ let ptI_b = checkLineIntersection(
43
+ l1_b[0],
44
+ l1_b[1],
45
+ l2_b[1],
46
+ l2_b[0],
47
+ false,
48
+ true
49
+ );
50
+ let ptN_b = ptI_b ? ptI_b : l1_t[1];
51
+
52
+ polyTop.push(ptN_t);
53
+ polyBottom.push(ptN_b);
54
+ }
55
+
56
+ // add last pts
57
+ polyTop.push(polyTop_lines[l - 1][1]);
58
+ polyBottom.push(polyBottom_lines[l - 1][1]);
59
+
60
+ if(closed){
61
+ let l=polyTop.length;
62
+ let ptI_1 =checkLineIntersection(polyTop[1],polyTop[0], polyTop[l-2],polyTop[l-1], false, true )
63
+ if(ptI_1){
64
+ //renderPoint(svg, ptI_1)
65
+ polyTop[0] = ptI_1
66
+ polyTop[l-1] = ptI_1
67
+ }
68
+ let ptI_2 =checkLineIntersection(polyBottom[1],polyBottom[0], polyBottom[l-2],polyBottom[l-1], false, true )
69
+
70
+ if(ptI_2){
71
+ //renderPoint(svg, ptI_2, 'green')
72
+ polyBottom[0] = ptI_2
73
+ polyBottom[l-1] = ptI_2
74
+ }
75
+
76
+
77
+
78
+ }
79
+
80
+ return sweep
81
+ ? { polyTop: polyBottom, polyBottom: polyTop }
82
+ : { polyTop, polyBottom };
83
+ }
84
+
85
+ export function getParallels(p1, p2, strokeWidth = 1) {
86
+ let distance = strokeWidth * 0.5;
87
+ // Get direction vector
88
+ let dx = p2.x - p1.x;
89
+ let dy = p2.y - p1.y;
90
+
91
+ // Calculate normalized perpendicular vector (rotated 90°)
92
+ let length = Math.hypot(dx, dy);
93
+ let perpX = -dy / length; // Left/up perpendicular
94
+ let perpY = dx / length;
95
+
96
+ // Scale by distance once
97
+ let offsetX = perpX * distance;
98
+ let offsetY = perpY * distance;
99
+
100
+ // Return both parallels in one go
101
+
102
+ let bottom = [
103
+ { x: p1.x - offsetX, y: p1.y - offsetY },
104
+ { x: p2.x - offsetX, y: p2.y - offsetY }
105
+ ];
106
+
107
+ let top = [
108
+ { x: p1.x + offsetX, y: p1.y + offsetY },
109
+ { x: p2.x + offsetX, y: p2.y + offsetY }
110
+ ];
111
+
112
+ //renderPoint(svg, top[0])
113
+ //renderPoint(svg, bottom[0], 'blue')
114
+
115
+ return { top, bottom };
116
+ }
117
+
118
+ function getExtendedPoint(p1, p2, distance) {
119
+ // Get direction vector
120
+ let dx = p2.x - p1.x;
121
+ let dy = p2.y - p1.y;
122
+ let length = Math.hypot(dx, dy);
123
+
124
+ // Normalize direction
125
+ let ndx = dx / length;
126
+ let ndy = dy / length;
127
+
128
+ // Extend along the line direction
129
+ return {
130
+ x: p2.x + ndx * distance,
131
+ y: p2.y + ndy * distance
132
+ };
133
+ }