svg-path-simplify 0.4.1 → 0.4.3
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/CHANGELOG.md +19 -0
- package/README.md +6 -4
- package/dist/svg-path-simplify.esm.js +2450 -888
- package/dist/svg-path-simplify.esm.min.js +2 -2
- package/dist/svg-path-simplify.js +2450 -888
- package/dist/svg-path-simplify.min.js +2 -2
- package/dist/svg-path-simplify.pathdata.esm.js +167 -85
- package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
- package/docs/privacy-webapp.md +24 -0
- package/index.html +333 -132
- package/package.json +5 -2
- package/src/css_parse.js +317 -0
- package/src/detect_input.js +34 -4
- package/src/pathData_simplify_harmonize_cpts.js +77 -1
- package/src/pathSimplify-main.js +246 -262
- package/src/pathSimplify-presets.js +243 -0
- package/src/poly-fit-curve-schneider.js +14 -7
- package/src/simplify_poly_RC.js +102 -0
- package/src/simplify_poly_RDP.js +109 -1
- package/src/simplify_poly_radial_distance.js +3 -3
- package/src/string_helpers.js +144 -0
- package/src/svgii/convert_units.js +8 -2
- package/src/svgii/geometry.js +182 -3
- package/src/svgii/geometry_length.js +237 -0
- package/src/svgii/pathData_convert.js +43 -1
- package/src/svgii/pathData_fix_directions.js +6 -0
- package/src/svgii/pathData_fromPoly.js +3 -3
- package/src/svgii/pathData_getLength.js +86 -0
- package/src/svgii/pathData_parse.js +2 -0
- package/src/svgii/pathData_parse_els.js +189 -189
- package/src/svgii/pathData_split_to_groups.js +168 -0
- package/src/svgii/pathData_stringify.js +26 -64
- package/src/svgii/pathData_toPolygon.js +3 -4
- package/src/svgii/poly_analyze.js +61 -0
- package/src/svgii/poly_normalize.js +11 -2
- package/src/svgii/poly_to_pathdata.js +85 -24
- package/src/svgii/rounding.js +8 -7
- package/src/svgii/svg-styles-to-attributes-const.js +1 -0
- package/src/svgii/svg_cleanup.js +467 -421
- package/src/svgii/svg_cleanup_convertPathLength.js +32 -0
- package/src/svgii/svg_cleanup_general_svg_atts.js +97 -0
- package/src/svgii/svg_cleanup_normalize_transforms.js +83 -0
- package/src/svgii/svg_cleanup_remove_els_and_atts.js +72 -0
- package/src/svgii/svg_cleanup_ungroup.js +36 -0
- package/src/svgii/svg_el_parse_style_props.js +76 -28
- package/src/svgii/svg_getElementLength.js +67 -0
- package/tests/testSVG_shape.js +59 -0
- package/tests/testSVG_transform.js +61 -0
package/src/svgii/geometry.js
CHANGED
|
@@ -3,7 +3,8 @@ import {abs, acos, asin, atan, atan2, ceil, cos, exp, floor,
|
|
|
3
3
|
log, max, min, pow, random, round, sin, sqrt, tan, PI} from '/.constants.js';
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { rad2Deg, root2 } from "../constants";
|
|
6
|
+
import { deg2rad, rad2Deg, root2 } from "../constants";
|
|
7
|
+
//import { roundTo } from "./rounding";
|
|
7
8
|
//import { getPolyBBox } from "./geometry_bbox";
|
|
8
9
|
import { renderPoint } from "./visualize";
|
|
9
10
|
|
|
@@ -23,6 +24,11 @@ export function getAngle(p1, p2, normalize = false) {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
26
32
|
export function getDeltaAngle2(centerPoint, startPoint, endPoint, largeArc = false) {
|
|
27
33
|
|
|
28
34
|
const normalizeAngle = (angle) => {
|
|
@@ -370,9 +376,40 @@ export function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false, retu
|
|
|
370
376
|
|
|
371
377
|
|
|
372
378
|
|
|
379
|
+
|
|
373
380
|
/**
|
|
374
381
|
* get vertices from path command final on-path points
|
|
375
382
|
*/
|
|
383
|
+
|
|
384
|
+
export function getPathDataVertices(pathData=[], includeCpts = false, decimals = -1) {
|
|
385
|
+
let polyPoints = [];
|
|
386
|
+
//console.log(pathData);
|
|
387
|
+
|
|
388
|
+
pathData.forEach((com) => {
|
|
389
|
+
let { type, values } = com;
|
|
390
|
+
|
|
391
|
+
// get final on path point from last 2 values
|
|
392
|
+
if (values.length) {
|
|
393
|
+
|
|
394
|
+
// round
|
|
395
|
+
if (decimals > -1) values = values.map(val => +val.toFixed(decimals))
|
|
396
|
+
|
|
397
|
+
if (includeCpts) {
|
|
398
|
+
|
|
399
|
+
for (let i = 1; i < values.length; i += 2) {
|
|
400
|
+
polyPoints.push({ x: values[i - 1], y: values[i] });
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
} else {
|
|
404
|
+
polyPoints.push({ x: values[values.length - 2], y: values[values.length - 1] });
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
return polyPoints;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/*
|
|
376
413
|
export function getPathDataVertices(pathData) {
|
|
377
414
|
let polyPoints = [];
|
|
378
415
|
let p0 = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
@@ -389,6 +426,7 @@ export function getPathDataVertices(pathData) {
|
|
|
389
426
|
});
|
|
390
427
|
return polyPoints;
|
|
391
428
|
};
|
|
429
|
+
*/
|
|
392
430
|
|
|
393
431
|
|
|
394
432
|
|
|
@@ -396,7 +434,148 @@ export function getPathDataVertices(pathData) {
|
|
|
396
434
|
* based on @cuixiping;
|
|
397
435
|
* https://stackoverflow.com/questions/9017100/calculate-center-of-svg-arc/12329083#12329083
|
|
398
436
|
*/
|
|
399
|
-
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
export function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2, y2, normalize = true
|
|
440
|
+
) {
|
|
441
|
+
|
|
442
|
+
// helper for angle calculation
|
|
443
|
+
const getAngle = (cx, cy, x, y, normalize = true) => {
|
|
444
|
+
let angle = Math.atan2(y - cy, x - cx);
|
|
445
|
+
if (normalize && angle < 0) angle += Math.PI * 2
|
|
446
|
+
return angle
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
// make sure rx, ry are positive
|
|
450
|
+
rx = Math.abs(rx);
|
|
451
|
+
ry = Math.abs(ry);
|
|
452
|
+
|
|
453
|
+
// normalize xAxis rotation
|
|
454
|
+
xAxisRotation = rx === ry ? 0 : (xAxisRotation < 0 && normalize ? xAxisRotation + 360 : xAxisRotation);
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
// create data object
|
|
458
|
+
let arcData = {
|
|
459
|
+
cx: 0,
|
|
460
|
+
cy: 0,
|
|
461
|
+
// rx/ry values may be deceptive in arc commands
|
|
462
|
+
rx: rx,
|
|
463
|
+
ry: ry,
|
|
464
|
+
startAngle: 0,
|
|
465
|
+
endAngle: 0,
|
|
466
|
+
deltaAngle: 0,
|
|
467
|
+
clockwise: sweep,
|
|
468
|
+
// copy explicit arc properties
|
|
469
|
+
xAxisRotation,
|
|
470
|
+
largeArc,
|
|
471
|
+
sweep
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
if (rx == 0 || ry == 0) {
|
|
476
|
+
// invalid arguments
|
|
477
|
+
throw Error("rx and ry can not be 0");
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* if rx===ry x-axis rotation is ignored
|
|
483
|
+
* otherwise convert degrees to radians
|
|
484
|
+
*/
|
|
485
|
+
let phi = rx === ry ? 0 : xAxisRotation * deg2rad;
|
|
486
|
+
let cx, cy
|
|
487
|
+
|
|
488
|
+
let s_phi = !phi ? 0 : Math.sin(phi);
|
|
489
|
+
let c_phi = !phi ? 1 : Math.cos(phi);
|
|
490
|
+
|
|
491
|
+
let hd_x = (x1 - x2) / 2;
|
|
492
|
+
let hd_y = (y1 - y2) / 2;
|
|
493
|
+
let hs_x = (x1 + x2) / 2;
|
|
494
|
+
let hs_y = (y1 + y2) / 2;
|
|
495
|
+
|
|
496
|
+
// F6.5.1
|
|
497
|
+
let x1_ = !phi ? hd_x : c_phi * hd_x + s_phi * hd_y;
|
|
498
|
+
let y1_ = !phi ? hd_y : c_phi * hd_y - s_phi * hd_x;
|
|
499
|
+
|
|
500
|
+
// F.6.6 Correction of out-of-range radii
|
|
501
|
+
// Step 3: Ensure radii are large enough
|
|
502
|
+
let lambda = (x1_ * x1_) / (rx * rx) + (y1_ * y1_) / (ry * ry);
|
|
503
|
+
if (lambda > 1) {
|
|
504
|
+
let lambdaRoot = Math.sqrt(lambda);
|
|
505
|
+
rx = rx * lambdaRoot;
|
|
506
|
+
ry = ry * lambdaRoot;
|
|
507
|
+
|
|
508
|
+
// save real rx/ry
|
|
509
|
+
arcData.rx = rx;
|
|
510
|
+
arcData.ry = ry;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
let rxry = rx * ry;
|
|
514
|
+
let rxy1_ = rx * y1_;
|
|
515
|
+
let ryx1_ = ry * x1_;
|
|
516
|
+
let sum_of_sq = rxy1_ ** 2 + ryx1_ ** 2; // sum of square
|
|
517
|
+
if (!sum_of_sq) {
|
|
518
|
+
//console.log('error:', rx, ry, rxy1_, ryx1_);
|
|
519
|
+
throw Error("start point can not be same as end point");
|
|
520
|
+
}
|
|
521
|
+
let coe = Math.sqrt(Math.abs((rxry * rxry - sum_of_sq) / sum_of_sq));
|
|
522
|
+
if (largeArc === sweep) {
|
|
523
|
+
coe = -coe;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// F6.5.2
|
|
527
|
+
let cx_ = (coe * rxy1_) / ry;
|
|
528
|
+
let cy_ = (-coe * ryx1_) / rx;
|
|
529
|
+
|
|
530
|
+
/** F6.5.3
|
|
531
|
+
* center point of ellipse
|
|
532
|
+
*/
|
|
533
|
+
cx = !phi ? hs_x + cx_ : c_phi * cx_ - s_phi * cy_ + hs_x;
|
|
534
|
+
cy = !phi ? hs_y + cy_ : s_phi * cx_ + c_phi * cy_ + hs_y;
|
|
535
|
+
arcData.cy = cy;
|
|
536
|
+
arcData.cx = cx;
|
|
537
|
+
|
|
538
|
+
/** F6.5.5
|
|
539
|
+
* calculate angles between center point and
|
|
540
|
+
* commands starting and final on path point
|
|
541
|
+
*/
|
|
542
|
+
let startAngle = getAngle(cx, cy, x1, y1, normalize);
|
|
543
|
+
let endAngle = getAngle(cx, cy, x2, y2, normalize);
|
|
544
|
+
|
|
545
|
+
// adjust end angle
|
|
546
|
+
|
|
547
|
+
// Adjust angles based on sweep direction
|
|
548
|
+
if (sweep) {
|
|
549
|
+
// Clockwise
|
|
550
|
+
if (endAngle < startAngle) {
|
|
551
|
+
endAngle += Math.PI * 2;
|
|
552
|
+
}
|
|
553
|
+
} else {
|
|
554
|
+
// Counterclockwise
|
|
555
|
+
if (endAngle > startAngle) {
|
|
556
|
+
endAngle -= Math.PI * 2;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
let deltaAngle = endAngle - startAngle;
|
|
561
|
+
|
|
562
|
+
// The rest of your code remains the same
|
|
563
|
+
arcData.startAngle = startAngle;
|
|
564
|
+
arcData.startAngle_deg = startAngle * rad2Deg;
|
|
565
|
+
arcData.endAngle = endAngle;
|
|
566
|
+
arcData.endAngle_deg = endAngle * rad2Deg;
|
|
567
|
+
arcData.deltaAngle = deltaAngle;
|
|
568
|
+
arcData.deltaAngle_deg = deltaAngle * rad2Deg;
|
|
569
|
+
|
|
570
|
+
//console.log('arc', arcData);
|
|
571
|
+
return arcData;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
export function svgArcToCenterParam_back(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2, y2) {
|
|
400
579
|
|
|
401
580
|
// helper for angle calculation
|
|
402
581
|
const getAngle = (cx, cy, x, y) => {
|
|
@@ -1292,7 +1471,7 @@ export function getDistance(p1, p2, isArray = false) {
|
|
|
1292
1471
|
let dy = isArray ? p2[1] - p1[1] : (p2.y - p1.y);
|
|
1293
1472
|
|
|
1294
1473
|
//console.log('dx', dx, dy, p1, p2);
|
|
1295
|
-
return sqrt(dx * dx + dy * dy);
|
|
1474
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
1296
1475
|
}
|
|
1297
1476
|
|
|
1298
1477
|
// just an alias
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { getDistance } from "./geometry";
|
|
2
|
+
|
|
3
|
+
// Legendre Gauss weight and abscissa values
|
|
4
|
+
export const waArr_global = [];
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export function getLength(pts, {
|
|
9
|
+
t = 1,
|
|
10
|
+
waArr = []
|
|
11
|
+
} = {}) {
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
const cubicBezierLength = (p0, cp1, cp2, p, t = 0, wa = []) => {
|
|
15
|
+
if (t === 0) {
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
t = t > 1 ? 1 : t < 0 ? 0 : t;
|
|
20
|
+
let t2 = t / 2;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* set higher legendre gauss weight abscissae values
|
|
24
|
+
* by more accurate weight/abscissae lookups
|
|
25
|
+
* https://pomax.github.io/bezierinfo/legendre-gauss.html
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
let sum = 0;
|
|
30
|
+
|
|
31
|
+
let x0 = p0.x, y0 = p0.y, cp1x = cp1.x, cp1y = cp1.y, cp2x = cp2.x, cp2y = cp2.y, px = p.x, py = p.y;
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
for (let i = 0, len = wa.length; i < len; i++) {
|
|
35
|
+
// weight and abscissae
|
|
36
|
+
let [w, a] = [wa[i][0], wa[i][1]];
|
|
37
|
+
let ct1_t = t2 * a;
|
|
38
|
+
let ct0 = -ct1_t + t2;
|
|
39
|
+
|
|
40
|
+
let xbase0 = base3(ct0, x0, cp1x, cp2x, px)
|
|
41
|
+
let ybase0 = base3(ct0, y0, cp1y, cp2y, py)
|
|
42
|
+
|
|
43
|
+
let comb0 = xbase0 * xbase0 + ybase0 * ybase0;
|
|
44
|
+
|
|
45
|
+
sum += w * Math.sqrt(comb0)
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
return t2 * sum;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
const quadraticBezierLength = (p0, cp1, p, t, checkFlat = false) => {
|
|
53
|
+
if (t === 0) {
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
// is flat/linear – treat as line
|
|
57
|
+
if (checkFlat) {
|
|
58
|
+
let l1 = getDistance(p0, cp1) + getDistance(cp1, p);
|
|
59
|
+
let l2 = getDistance(p0, p);
|
|
60
|
+
if (l1 === l2) {
|
|
61
|
+
return l2;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let a, b, c, d, e, e1, d1, v1x, v1y;
|
|
66
|
+
v1x = cp1.x * 2;
|
|
67
|
+
v1y = cp1.y * 2;
|
|
68
|
+
d = p0.x - v1x + p.x;
|
|
69
|
+
d1 = p0.y - v1y + p.y;
|
|
70
|
+
e = v1x - 2 * p0.x;
|
|
71
|
+
e1 = v1y - 2 * p0.y;
|
|
72
|
+
a = 4 * (d * d + d1 * d1);
|
|
73
|
+
b = 4 * (d * e + d1 * e1);
|
|
74
|
+
c = e * e + e1 * e1;
|
|
75
|
+
|
|
76
|
+
const bt = b / (2 * a),
|
|
77
|
+
ct = c / a,
|
|
78
|
+
ut = t + bt,
|
|
79
|
+
//k = ct - bt ** 2;
|
|
80
|
+
k = ct - bt * bt;
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
(Math.sqrt(a) / 2) *
|
|
84
|
+
(ut * Math.sqrt(ut * ut + k) -
|
|
85
|
+
bt * Math.sqrt(bt * bt + k) +
|
|
86
|
+
k *
|
|
87
|
+
Math.log((ut + Math.sqrt(ut * ut + k)) / (bt + Math.sqrt(bt * bt + k))))
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
let length
|
|
93
|
+
if (pts.length === 4) {
|
|
94
|
+
length = cubicBezierLength(pts[0], pts[1], pts[2], pts[3], t, waArr)
|
|
95
|
+
|
|
96
|
+
}
|
|
97
|
+
else if (pts.length === 3) {
|
|
98
|
+
length = quadraticBezierLength(pts[0], pts[1], pts[2], t)
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
length = getDistance(pts[0], pts[1])
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return length;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
// LG weight/abscissae generator
|
|
113
|
+
export function getLegendreGaussValues(n, x1 = -1, x2 = 1) {
|
|
114
|
+
console.log('add new LG', n);
|
|
115
|
+
|
|
116
|
+
let waArr = []
|
|
117
|
+
let z1, z, xm, xl, pp, p3, p2, p1;
|
|
118
|
+
const m = (n + 1) >> 1;
|
|
119
|
+
xm = 0.5 * (x2 + x1);
|
|
120
|
+
xl = 0.5 * (x2 - x1);
|
|
121
|
+
|
|
122
|
+
for (let i = m - 1; i >= 0; i--) {
|
|
123
|
+
z = Math.cos((Math.PI * (i + 0.75)) / (n + 0.5));
|
|
124
|
+
do {
|
|
125
|
+
p1 = 1;
|
|
126
|
+
p2 = 0;
|
|
127
|
+
for (let j = 0; j < n; j++) {
|
|
128
|
+
//Loop up the recurrence relation to get the Legendre polynomial evaluated at z.
|
|
129
|
+
p3 = p2;
|
|
130
|
+
p2 = p1;
|
|
131
|
+
p1 = ((2 * j + 1) * z * p2 - j * p3) / (j + 1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
pp = (n * (z * p1 - p2)) / (z * z - 1);
|
|
135
|
+
z1 = z;
|
|
136
|
+
z = z1 - p1 / pp; //Newton’s method
|
|
137
|
+
|
|
138
|
+
} while (Math.abs(z - z1) > 1.0e-14);
|
|
139
|
+
|
|
140
|
+
let weight = (2 * xl) / ((1 - z * z) * pp * pp);
|
|
141
|
+
let abscissa = xm + xl * z;
|
|
142
|
+
|
|
143
|
+
waArr.push(
|
|
144
|
+
[weight, -abscissa],
|
|
145
|
+
[weight, abscissa],
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return waArr;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
export function base3(t, p1, p2, p3, p4) {
|
|
157
|
+
let t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
|
|
158
|
+
t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
|
|
159
|
+
return t * t2 - 3 * p1 + 3 * p2;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
export function getPolygonLength(pts=[], isPoly=false){
|
|
164
|
+
|
|
165
|
+
let len = 0;
|
|
166
|
+
let l=pts.length;
|
|
167
|
+
|
|
168
|
+
for(let i=1; i<l; i++){
|
|
169
|
+
let p1 = pts[i-1]
|
|
170
|
+
let p2 = pts[i]
|
|
171
|
+
len += getDistance(p1, p2)
|
|
172
|
+
}
|
|
173
|
+
if(isPoly){
|
|
174
|
+
len += getDistance(pts[l-1], pts[0])
|
|
175
|
+
}
|
|
176
|
+
return len
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Ramanujan approximation
|
|
182
|
+
* based on: https://www.mathsisfun.com/geometry/ellipse-perimeter.html#tool
|
|
183
|
+
*/
|
|
184
|
+
export function getEllipseLength(rx=0, ry=0) {
|
|
185
|
+
// is circle
|
|
186
|
+
if (rx === ry) {
|
|
187
|
+
//console.log('is circle')
|
|
188
|
+
return 2 * Math.PI * rx;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let c=rx+ry
|
|
192
|
+
let d = (rx - ry) / c;
|
|
193
|
+
let h = d*d
|
|
194
|
+
|
|
195
|
+
let totalLength = Math.PI * c * (1 + 3 * h / (10 + Math.sqrt(4 - 3 * h) ));
|
|
196
|
+
return totalLength;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* ellipse helpers
|
|
203
|
+
* approximate ellipse length
|
|
204
|
+
* by Legendre-Gauss
|
|
205
|
+
*/
|
|
206
|
+
|
|
207
|
+
export function getCircleArcLength(r = 0, deltaAngle = 0) {
|
|
208
|
+
if(r===0) {
|
|
209
|
+
console.warn('Radius must be positive');
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
let len = 2 * Math.PI * r * (1 / 360 * Math.abs(deltaAngle * 180 / Math.PI))
|
|
213
|
+
return len
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function getEllipseLengthLG(rx, ry, startAngle, endAngle, wa = []) {
|
|
217
|
+
|
|
218
|
+
// Transform [-1, 1] interval to [startAngle, endAngle]
|
|
219
|
+
let halfInterval = (endAngle - startAngle) * 0.5;
|
|
220
|
+
let midpoint = (endAngle + startAngle) * 0.5;
|
|
221
|
+
|
|
222
|
+
// Arc length integral approximation
|
|
223
|
+
let arcLength = 0;
|
|
224
|
+
for (let i = 0; i < wa.length; i++) {
|
|
225
|
+
let [weight, abscissae] = wa[i];
|
|
226
|
+
let theta = midpoint + halfInterval * abscissae;
|
|
227
|
+
|
|
228
|
+
let a = rx * Math.sin(theta);
|
|
229
|
+
let b = ry * Math.cos(theta);
|
|
230
|
+
let integrand = Math.sqrt(
|
|
231
|
+
a * a + b * b
|
|
232
|
+
);
|
|
233
|
+
arcLength += weight * integrand;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return Math.abs(halfInterval * arcLength)
|
|
237
|
+
}
|
|
@@ -69,6 +69,7 @@ export function convertPathData(pathData, {
|
|
|
69
69
|
toShorthands = true,
|
|
70
70
|
toLonghands = false,
|
|
71
71
|
toRelative = true,
|
|
72
|
+
toMixed = false,
|
|
72
73
|
toAbsolute = false,
|
|
73
74
|
decimals = 3,
|
|
74
75
|
arcToCubic = false,
|
|
@@ -79,6 +80,7 @@ export function convertPathData(pathData, {
|
|
|
79
80
|
hasShorthands = true,
|
|
80
81
|
hasQuadratics = true,
|
|
81
82
|
hasArcs = true,
|
|
83
|
+
isPoly = false,
|
|
82
84
|
optimizeArcs = true,
|
|
83
85
|
testTypes = false
|
|
84
86
|
|
|
@@ -86,6 +88,7 @@ export function convertPathData(pathData, {
|
|
|
86
88
|
} = {}) {
|
|
87
89
|
|
|
88
90
|
|
|
91
|
+
let pathDataAbs = []
|
|
89
92
|
|
|
90
93
|
// pathdata properties - test= true adds a manual test
|
|
91
94
|
if (testTypes) {
|
|
@@ -101,7 +104,9 @@ export function convertPathData(pathData, {
|
|
|
101
104
|
|
|
102
105
|
// some params exclude each other
|
|
103
106
|
toRelative = toAbsolute ? false : toRelative;
|
|
104
|
-
|
|
107
|
+
//toAbsolute = !toRelative ? true : toAbsolute;
|
|
108
|
+
toShorthands = toLonghands ? false : toShorthands;
|
|
109
|
+
//toLonghands = !toShorthands ? true : toLonghands;
|
|
105
110
|
|
|
106
111
|
|
|
107
112
|
if (toAbsolute) pathData = pathDataToAbsolute(pathData);
|
|
@@ -115,19 +120,45 @@ export function convertPathData(pathData, {
|
|
|
115
120
|
//if(decimals>-1 && decimals<2) pathData = roundPathData(pathData, decimals);
|
|
116
121
|
if (toShorthands) pathData = pathDataToShorthands(pathData);
|
|
117
122
|
|
|
123
|
+
//console.log('hasArcs', hasArcs, arcToCubic, pathData);
|
|
118
124
|
if (hasArcs && arcToCubic) pathData = pathDataArcsToCubics(pathData)
|
|
119
125
|
|
|
120
126
|
//console.log(toShorthands, toRelative, decimals);
|
|
121
127
|
if (hasQuadratics && quadraticToCubic) pathData = pathDataQuadraticToCubic(pathData);
|
|
122
128
|
|
|
129
|
+
if(toMixed) toRelative = true
|
|
123
130
|
|
|
124
131
|
// pre round - before relative conversion to minimize distortions
|
|
125
132
|
if (decimals > -1 && toRelative) pathData = roundPathData(pathData, decimals);
|
|
126
133
|
|
|
134
|
+
// clone absolute pathdata
|
|
135
|
+
if(toMixed){
|
|
136
|
+
pathDataAbs = JSON.parse(JSON.stringify(pathData))
|
|
137
|
+
}
|
|
127
138
|
|
|
128
139
|
if (toRelative) pathData = pathDataToRelative(pathData);
|
|
129
140
|
if (decimals > -1) pathData = roundPathData(pathData, decimals);
|
|
130
141
|
|
|
142
|
+
|
|
143
|
+
// choose most compact commands: relative or absolute
|
|
144
|
+
if(toMixed){
|
|
145
|
+
for(let i=0; i<pathData.length; i++){
|
|
146
|
+
let com = pathData[i]
|
|
147
|
+
let comA = pathDataAbs[i]
|
|
148
|
+
// compare Lengths
|
|
149
|
+
let comStr = [com.type, com.values.join(' ')].join('').replaceAll(' -', '-').replaceAll(' 0.', ' .')
|
|
150
|
+
let comStrA = [comA.type, comA.values.join(' ')].join('').replaceAll(' -', '-').replaceAll(' 0.', ' .')
|
|
151
|
+
|
|
152
|
+
let lenR = comStr.length;
|
|
153
|
+
let lenA = comStrA.length;
|
|
154
|
+
|
|
155
|
+
if(lenA<lenR){
|
|
156
|
+
//console.log('absolute is shorter', comStrA, comStr);
|
|
157
|
+
pathData[i] = pathDataAbs[i]
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
131
162
|
return pathData
|
|
132
163
|
}
|
|
133
164
|
|
|
@@ -138,6 +169,9 @@ export function convertPathData(pathData, {
|
|
|
138
169
|
*/
|
|
139
170
|
|
|
140
171
|
export function optimizeArcPathData(pathData = []) {
|
|
172
|
+
|
|
173
|
+
let remove =[]
|
|
174
|
+
|
|
141
175
|
pathData.forEach((com, i) => {
|
|
142
176
|
let { type, values } = com;
|
|
143
177
|
if (type === 'A') {
|
|
@@ -149,6 +183,12 @@ export function optimizeArcPathData(pathData = []) {
|
|
|
149
183
|
//largeArc=true
|
|
150
184
|
//let pMid = {x: Math.abs(x-x0), y:Math.abs(y-y0)}
|
|
151
185
|
|
|
186
|
+
if(rx===0 || ry===0){
|
|
187
|
+
pathData[i]= null
|
|
188
|
+
remove.push(i)
|
|
189
|
+
//console.log('!!!');
|
|
190
|
+
}
|
|
191
|
+
|
|
152
192
|
// rx and ry are large enough
|
|
153
193
|
if (rx >= 1 && (x === x0 || y === y0)) {
|
|
154
194
|
let diff = Math.abs(rx - ry) / rx;
|
|
@@ -171,6 +211,8 @@ export function optimizeArcPathData(pathData = []) {
|
|
|
171
211
|
}
|
|
172
212
|
}
|
|
173
213
|
})
|
|
214
|
+
|
|
215
|
+
if(remove.length) pathData = pathData.filter(Boolean)
|
|
174
216
|
return pathData;
|
|
175
217
|
}
|
|
176
218
|
|
|
@@ -10,6 +10,7 @@ import { getPolygonArea } from "./geometry_area";
|
|
|
10
10
|
import { getPolyBBox } from "./geometry_bbox";
|
|
11
11
|
import { reversePathData } from "./pathData_reverse";
|
|
12
12
|
import { getPathDataPolyPrecise } from "./pathData_toPolygon";
|
|
13
|
+
import { renderPoint, renderPoly } from "./visualize";
|
|
13
14
|
|
|
14
15
|
export function fixPathDataDirections(pathDataArr = [], toClockwise = false) {
|
|
15
16
|
|
|
@@ -42,6 +43,7 @@ export function fixPathDataDirections(pathDataArr = [], toClockwise = false) {
|
|
|
42
43
|
let ptMid = { x: bb.left + bb.width / 2, y: bb.top + bb.height / 2 }
|
|
43
44
|
let inPoly = isPointInPolygon(ptMid, prev.pts, bb0)
|
|
44
45
|
|
|
46
|
+
|
|
45
47
|
if (inPoly) {
|
|
46
48
|
polys[j].inter += 1
|
|
47
49
|
poly.includedIn.push(i)
|
|
@@ -61,9 +63,12 @@ export function fixPathDataDirections(pathDataArr = [], toClockwise = false) {
|
|
|
61
63
|
if (!includedIn.length && cw && !toClockwise
|
|
62
64
|
|| !includedIn.length && !cw && toClockwise
|
|
63
65
|
) {
|
|
66
|
+
//console.log('reverse outer');
|
|
67
|
+
|
|
64
68
|
pathDataArr[i].pathData = reversePathData(pathDataArr[i].pathData);
|
|
65
69
|
polys[i].cw = polys[i].cw ? false : true
|
|
66
70
|
cw = polys[i].cw
|
|
71
|
+
|
|
67
72
|
}
|
|
68
73
|
|
|
69
74
|
// reverse inner sub paths
|
|
@@ -72,6 +77,7 @@ export function fixPathDataDirections(pathDataArr = [], toClockwise = false) {
|
|
|
72
77
|
let child = polys[ind];
|
|
73
78
|
|
|
74
79
|
if (child.cw === cw) {
|
|
80
|
+
//console.log('reverse', child.cw, cw);
|
|
75
81
|
pathDataArr[ind].pathData = reversePathData(pathDataArr[ind].pathData);
|
|
76
82
|
polys[ind].cw = polys[ind].cw ? false : true
|
|
77
83
|
}
|
|
@@ -3,17 +3,17 @@ export function pathDataFromPoly(pts, closed = true) {
|
|
|
3
3
|
let pathData = []
|
|
4
4
|
let subPath = []
|
|
5
5
|
|
|
6
|
-
|
|
7
6
|
// complex polygon
|
|
8
7
|
if (Array.isArray(pts[0])) {
|
|
9
|
-
pts.forEach(sub => {
|
|
8
|
+
pts.forEach(sub => {
|
|
10
9
|
subPath = [
|
|
11
10
|
{ type: 'M', values: [sub[0].x, sub[0].y] },
|
|
12
11
|
...sub.slice(1).map(pt => { return { type: 'L', values: [pt.x, pt.y] } })
|
|
13
12
|
];
|
|
13
|
+
if (closed) subPath.push({ type: 'Z', values: [] })
|
|
14
14
|
pathData.push(...subPath)
|
|
15
15
|
})
|
|
16
|
-
}else{
|
|
16
|
+
} else {
|
|
17
17
|
pathData = [
|
|
18
18
|
{ type: 'M', values: [pts[0].x, pts[0].y] },
|
|
19
19
|
...pts.slice(1).map(pt => { return { type: 'L', values: [pt.x, pt.y] } })
|