svg-path-simplify 0.4.3 → 0.4.5
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 +21 -0
- package/README.md +2 -1
- package/dist/svg-path-simplify.esm.js +1670 -509
- package/dist/svg-path-simplify.esm.min.js +2 -2
- package/dist/svg-path-simplify.js +1671 -508
- package/dist/svg-path-simplify.min.js +2 -2
- package/dist/svg-path-simplify.pathdata.esm.js +936 -463
- package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
- package/dist/svg-path-simplify.poly.cjs +9 -8
- package/index.html +60 -20
- package/package.json +1 -1
- package/src/constants.js +4 -0
- package/src/detect_input.js +47 -29
- package/src/index.js +8 -0
- package/src/pathData_simplify_cubic.js +46 -18
- package/src/pathData_simplify_revertToquadratics.js +0 -1
- package/src/pathSimplify-main.js +81 -20
- package/src/pathSimplify-only-pathdata.js +7 -2
- package/src/pathSimplify-presets.js +14 -4
- package/src/svg-getAttributes.js +5 -3
- package/src/svgii/convert_units.js +1 -1
- package/src/svgii/geometry.js +140 -2
- package/src/svgii/geometry_bbox_element.js +1 -1
- package/src/svgii/geometry_deduceRadius.js +116 -27
- package/src/svgii/geometry_length.js +18 -2
- package/src/svgii/pathData_analyze.js +18 -0
- package/src/svgii/pathData_convert.js +188 -88
- package/src/svgii/pathData_fix_directions.js +10 -18
- package/src/svgii/pathData_reorder.js +123 -16
- package/src/svgii/pathData_simplify_refineCorners.js +130 -35
- package/src/svgii/pathData_simplify_refine_round.js +420 -0
- package/src/svgii/poly_normalize.js +9 -8
- package/src/svgii/rounding.js +112 -80
- package/src/svgii/svg_cleanup.js +75 -22
- package/src/svgii/svg_cleanup_convertPathLength.js +27 -15
- package/src/svgii/svg_cleanup_normalize_transforms.js +1 -1
- package/src/svgii/svg_cleanup_remove_els_and_atts.js +6 -1
- package/src/svgii/svg_el_parse_style_props.js +13 -10
- package/src/svgii/svg_validate.js +220 -0
- package/tests/testSVG.js +14 -1
- package/src/svgii/pathData_refine_round.js +0 -222
package/src/svgii/geometry.js
CHANGED
|
@@ -321,6 +321,7 @@ export function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false, retu
|
|
|
321
321
|
let t1 = 1 - t;
|
|
322
322
|
|
|
323
323
|
// cubic beziers
|
|
324
|
+
/*
|
|
324
325
|
if (isCubic) {
|
|
325
326
|
pt = {
|
|
326
327
|
x:
|
|
@@ -336,11 +337,30 @@ export function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false, retu
|
|
|
336
337
|
};
|
|
337
338
|
|
|
338
339
|
}
|
|
340
|
+
*/
|
|
341
|
+
|
|
342
|
+
if (isCubic) {
|
|
343
|
+
pt = {
|
|
344
|
+
x:
|
|
345
|
+
t1 * t1 * t1 * p0.x +
|
|
346
|
+
3 * t1 * t1 * t * cp1.x +
|
|
347
|
+
3 * t1 * t * t * cp2.x +
|
|
348
|
+
t * t * t * p.x,
|
|
349
|
+
y:
|
|
350
|
+
t1 * t1 * t1 * p0.y +
|
|
351
|
+
3 * t1 * t1 * t * cp1.y +
|
|
352
|
+
3 * t1 * t * t * cp2.y +
|
|
353
|
+
t * t * t * p.y,
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
|
|
339
359
|
// quadratic beziers
|
|
340
360
|
else {
|
|
341
361
|
pt = {
|
|
342
|
-
x: t1 * t1 * p0.x + 2 * t1 * t * cp1.x + t
|
|
343
|
-
y: t1 * t1 * p0.y + 2 * t1 * t * cp1.y + t
|
|
362
|
+
x: t1 * t1 * p0.x + 2 * t1 * t * cp1.x + t * t * p.x,
|
|
363
|
+
y: t1 * t1 * p0.y + 2 * t1 * t * cp1.y + t * t * p.y,
|
|
344
364
|
};
|
|
345
365
|
}
|
|
346
366
|
|
|
@@ -963,6 +983,124 @@ export function getBezierExtremeT(pts, { addExtremes = true, addSemiExtremes = f
|
|
|
963
983
|
* https://stackoverflow.com/questions/87734/#75031511
|
|
964
984
|
* See also: https://github.com/foo123/Geometrize
|
|
965
985
|
*/
|
|
986
|
+
|
|
987
|
+
export function getArcExtemesParam({
|
|
988
|
+
cx=0, cy=0, rx=0, ry=0,
|
|
989
|
+
p=null,
|
|
990
|
+
p0=null,
|
|
991
|
+
endAngle=0,
|
|
992
|
+
deltaAngle=0,
|
|
993
|
+
|
|
994
|
+
|
|
995
|
+
}={}) {
|
|
996
|
+
// compute point on ellipse from angle around ellipse (theta)
|
|
997
|
+
const arc = (theta, cx, cy, rx, ry, alpha) => {
|
|
998
|
+
// theta is angle in radians around arc
|
|
999
|
+
// alpha is angle of rotation of ellipse in radians
|
|
1000
|
+
var cos = Math.cos(alpha),
|
|
1001
|
+
sin = Math.sin(alpha),
|
|
1002
|
+
x = rx * Math.cos(theta),
|
|
1003
|
+
y = ry * Math.sin(theta);
|
|
1004
|
+
|
|
1005
|
+
return {
|
|
1006
|
+
x: cx + cos * x - sin * y,
|
|
1007
|
+
y: cy + sin * x + cos * y
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
//parametrize arcto data
|
|
1012
|
+
//let arcData = svgArcToCenterParam(p0.x, p0.y, values[0], values[1], values[2], values[3], values[4], values[5], values[6]);
|
|
1013
|
+
//let { rx, ry, cx, cy, endAngle, deltaAngle } = arcData;
|
|
1014
|
+
|
|
1015
|
+
// arc rotation
|
|
1016
|
+
let deg = values[2];
|
|
1017
|
+
|
|
1018
|
+
// final on path point
|
|
1019
|
+
//let p = { x: values[5], y: values[6] }
|
|
1020
|
+
|
|
1021
|
+
// collect extreme points – add end point
|
|
1022
|
+
let extremes = [p]
|
|
1023
|
+
|
|
1024
|
+
// rotation to radians
|
|
1025
|
+
let alpha = deg * Math.PI / 180;
|
|
1026
|
+
let tan = Math.tan(alpha),
|
|
1027
|
+
p1, p2, p3, p4, theta;
|
|
1028
|
+
|
|
1029
|
+
/**
|
|
1030
|
+
* find min/max from zeroes of directional derivative along x and y
|
|
1031
|
+
* along x axis
|
|
1032
|
+
*/
|
|
1033
|
+
theta = Math.atan2(-ry * tan, rx);
|
|
1034
|
+
|
|
1035
|
+
let angle1 = theta;
|
|
1036
|
+
let angle2 = theta + Math.PI;
|
|
1037
|
+
let angle3 = Math.atan2(ry, rx * tan);
|
|
1038
|
+
let angle4 = angle3 + Math.PI;
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
// inner bounding box
|
|
1042
|
+
let xArr = [p0.x, p.x]
|
|
1043
|
+
let yArr = [p0.y, p.y]
|
|
1044
|
+
let xMin = Math.min(...xArr)
|
|
1045
|
+
let xMax = Math.max(...xArr)
|
|
1046
|
+
let yMin = Math.min(...yArr)
|
|
1047
|
+
let yMax = Math.max(...yArr)
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
// on path point close after start
|
|
1051
|
+
let angleAfterStart = endAngle - deltaAngle * 0.001
|
|
1052
|
+
let pP2 = arc(angleAfterStart, cx, cy, rx, ry, alpha);
|
|
1053
|
+
|
|
1054
|
+
// on path point close before end
|
|
1055
|
+
let angleBeforeEnd = endAngle - deltaAngle * 0.999
|
|
1056
|
+
let pP3 = arc(angleBeforeEnd, cx, cy, rx, ry, alpha);
|
|
1057
|
+
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* expected extremes
|
|
1061
|
+
* if leaving inner bounding box
|
|
1062
|
+
* (between segment start and end point)
|
|
1063
|
+
* otherwise exclude elliptic extreme points
|
|
1064
|
+
*/
|
|
1065
|
+
|
|
1066
|
+
// right
|
|
1067
|
+
if (pP2.x > xMax || pP3.x > xMax) {
|
|
1068
|
+
// get point for this theta
|
|
1069
|
+
p1 = arc(angle1, cx, cy, rx, ry, alpha);
|
|
1070
|
+
extremes.push(p1)
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// left
|
|
1074
|
+
if (pP2.x < xMin || pP3.x < xMin) {
|
|
1075
|
+
// get anti-symmetric point
|
|
1076
|
+
p2 = arc(angle2, cx, cy, rx, ry, alpha);
|
|
1077
|
+
extremes.push(p2)
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// top
|
|
1081
|
+
if (pP2.y < yMin || pP3.y < yMin) {
|
|
1082
|
+
// get anti-symmetric point
|
|
1083
|
+
p4 = arc(angle4, cx, cy, rx, ry, alpha);
|
|
1084
|
+
extremes.push(p4)
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// bottom
|
|
1088
|
+
if (pP2.y > yMax || pP3.y > yMax) {
|
|
1089
|
+
// get point for this theta
|
|
1090
|
+
p3 = arc(angle3, cx, cy, rx, ry, alpha);
|
|
1091
|
+
extremes.push(p3)
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
return extremes;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
|
|
1098
|
+
|
|
1099
|
+
|
|
1100
|
+
|
|
1101
|
+
|
|
1102
|
+
|
|
1103
|
+
|
|
966
1104
|
export function getArcExtemes(p0, values) {
|
|
967
1105
|
// compute point on ellipse from angle around ellipse (theta)
|
|
968
1106
|
const arc = (theta, cx, cy, rx, ry, alpha) => {
|
|
@@ -1,42 +1,92 @@
|
|
|
1
|
-
import { getDeltaAngle, getDistance } from "./geometry";
|
|
1
|
+
import { checkLineIntersection, getDeltaAngle, getDistance, getDistManhattan, getSquareDistance } from "./geometry";
|
|
2
|
+
import { renderPoint } from "./visualize";
|
|
3
|
+
//import { arcToBezierResolved } from "./pathData_convert";
|
|
4
|
+
//import { renderPoint, renderPoly } from "./visualize";
|
|
2
5
|
|
|
3
|
-
export function getArcFromPoly(pts) {
|
|
6
|
+
export function getArcFromPoly(pts, precise = false) {
|
|
4
7
|
if (pts.length < 3) return false
|
|
5
8
|
|
|
6
9
|
// Pick 3 well-spaced points
|
|
7
|
-
let
|
|
8
|
-
let
|
|
9
|
-
let
|
|
10
|
+
let len = pts.length
|
|
11
|
+
let idx1 = Math.floor(len * 0.333)
|
|
12
|
+
let idx2 = Math.floor(len * 0.666)
|
|
13
|
+
let idx3 = Math.floor(len * 0.5)
|
|
10
14
|
|
|
11
|
-
let x1 = p1.x, y1 = p1.y;
|
|
12
|
-
let x2 = p2.x, y2 = p2.y;
|
|
13
|
-
let x3 = p3.x, y3 = p3.y;
|
|
14
15
|
|
|
15
|
-
let
|
|
16
|
-
let
|
|
17
|
-
let
|
|
18
|
-
let d = y1 - y3;
|
|
16
|
+
let p1 = pts[0];
|
|
17
|
+
let p2 = pts[idx3];
|
|
18
|
+
let p3 = pts[len - 1];
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
let
|
|
20
|
+
// Radius (use start point)
|
|
21
|
+
let pts1 = [p1, p2, p3];
|
|
22
|
+
let centroid = getPolyArcCentroid(pts1);
|
|
22
23
|
|
|
23
|
-
let
|
|
24
|
+
let r = 0, deltaAngle = 0, startAngle = 0, endAngle = 0, angleData = {};
|
|
24
25
|
|
|
25
|
-
if (Math.abs(det) < 1e-10) {
|
|
26
|
-
console.warn("Points are collinear or numerically unstable");
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
26
|
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
// check if radii are consistent
|
|
28
|
+
if (precise) {
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* check multiple centroids
|
|
33
|
+
* if the polyline can be expressed as
|
|
34
|
+
* an arc - all centroids should be close
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
if (len > 3) {
|
|
38
|
+
let centroid1 = getPolyArcCentroid([p1, pts[idx1], p3]);
|
|
39
|
+
let centroid2 = getPolyArcCentroid([p1, pts[idx2], p3]);
|
|
40
|
+
|
|
41
|
+
if (!centroid1 || !centroid2) return false;
|
|
42
|
+
|
|
43
|
+
//let dist0 = getDistManhattan(p1, p3)
|
|
44
|
+
let dist0 = getDistManhattan(centroid, p2)
|
|
45
|
+
let dist1 = getDistManhattan(centroid, centroid1)
|
|
46
|
+
let dist2 = getDistManhattan(centroid, centroid2)
|
|
47
|
+
let errorCentroid = (dist1 + dist2)
|
|
48
|
+
|
|
49
|
+
// centroids diverging too much
|
|
50
|
+
if (errorCentroid > dist0 * 0.05) {
|
|
51
|
+
//renderPoint(markers, centroid, 'magenta')
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let rSqMid = getSquareDistance(centroid, p2);
|
|
58
|
+
|
|
59
|
+
//check if radii are close enough
|
|
60
|
+
for (let i = 0; i < len; i++) {
|
|
61
|
+
let pt = pts[i]
|
|
62
|
+
let rSq = getSquareDistance(centroid, pt);
|
|
63
|
+
let error = Math.abs(rSqMid - rSq) / rSqMid
|
|
64
|
+
|
|
65
|
+
if (error > 0.0025) {
|
|
66
|
+
/*
|
|
67
|
+
console.log('error', error, len, idx1, idx2, idx3);
|
|
68
|
+
renderPoint(markers, centroid, 'orange')
|
|
69
|
+
renderPoint(markers, p1, 'green')
|
|
70
|
+
renderPoint(markers, p2)
|
|
71
|
+
renderPoint(markers, p3, 'purple')
|
|
72
|
+
*/
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// calculate proper radius
|
|
78
|
+
r = Math.sqrt(rSqMid);
|
|
79
|
+
angleData = getDeltaAngle(centroid, p1, p3);
|
|
80
|
+
({ deltaAngle, startAngle, endAngle } = angleData);
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
} else {
|
|
84
|
+
r = getDistance(centroid, p1);
|
|
85
|
+
angleData = getDeltaAngle(centroid, p1, p3);
|
|
86
|
+
({ deltaAngle, startAngle, endAngle } = angleData);
|
|
87
|
+
}
|
|
34
88
|
|
|
35
|
-
// Radius (use start point)
|
|
36
|
-
let r = getDistance(centroid, p1);
|
|
37
89
|
|
|
38
|
-
let angleData = getDeltaAngle(centroid, p1, p3)
|
|
39
|
-
let {deltaAngle, startAngle, endAngle} = angleData;
|
|
40
90
|
|
|
41
91
|
return {
|
|
42
92
|
centroid,
|
|
@@ -48,3 +98,42 @@ export function getArcFromPoly(pts) {
|
|
|
48
98
|
}
|
|
49
99
|
|
|
50
100
|
|
|
101
|
+
|
|
102
|
+
export function getPolyArcCentroid(pts = []) {
|
|
103
|
+
|
|
104
|
+
pts = pts.filter(pt => pt !== undefined);
|
|
105
|
+
if (pts.length < 3) return false
|
|
106
|
+
//console.log(pts);
|
|
107
|
+
|
|
108
|
+
let p1 = pts[0];
|
|
109
|
+
let p2 = pts[Math.floor(pts.length / 2)];
|
|
110
|
+
let p3 = pts[pts.length - 1];
|
|
111
|
+
|
|
112
|
+
let x1 = p1.x, y1 = p1.y;
|
|
113
|
+
let x2 = p2.x, y2 = p2.y;
|
|
114
|
+
let x3 = p3.x, y3 = p3.y;
|
|
115
|
+
|
|
116
|
+
let a = x1 - x2;
|
|
117
|
+
let b = y1 - y2;
|
|
118
|
+
let c = x1 - x3;
|
|
119
|
+
let d = y1 - y3;
|
|
120
|
+
|
|
121
|
+
let e = ((x1 * x1 - x2 * x2) + (y1 * y1 - y2 * y2)) / 2;
|
|
122
|
+
let f = ((x1 * x1 - x3 * x3) + (y1 * y1 - y3 * y3)) / 2;
|
|
123
|
+
|
|
124
|
+
let det = a * d - b * c;
|
|
125
|
+
|
|
126
|
+
// colinear points
|
|
127
|
+
if (Math.abs(det) < 1e-10) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// find center of arc
|
|
132
|
+
let cx = (d * e - b * f) / det;
|
|
133
|
+
let cy = (-c * e + a * f) / det;
|
|
134
|
+
let centroid = { x: cx, y: cy };
|
|
135
|
+
return centroid
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getDistance } from "./geometry";
|
|
1
|
+
import { getDistance, getDistManhattan } from "./geometry";
|
|
2
2
|
|
|
3
3
|
// Legendre Gauss weight and abscissa values
|
|
4
4
|
export const waArr_global = [];
|
|
@@ -111,7 +111,7 @@ export function getLength(pts, {
|
|
|
111
111
|
|
|
112
112
|
// LG weight/abscissae generator
|
|
113
113
|
export function getLegendreGaussValues(n, x1 = -1, x2 = 1) {
|
|
114
|
-
console.log('add new LG', n);
|
|
114
|
+
//console.log('add new LG', n);
|
|
115
115
|
|
|
116
116
|
let waArr = []
|
|
117
117
|
let z1, z, xm, xl, pp, p3, p2, p1;
|
|
@@ -176,6 +176,22 @@ export function getPolygonLength(pts=[], isPoly=false){
|
|
|
176
176
|
return len
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
export function getPolygonLengthManhattan(pts=[], isPoly=false){
|
|
180
|
+
|
|
181
|
+
let len = 0;
|
|
182
|
+
let l=pts.length;
|
|
183
|
+
|
|
184
|
+
for(let i=1; i<l; i++){
|
|
185
|
+
let p1 = pts[i-1]
|
|
186
|
+
let p2 = pts[i]
|
|
187
|
+
len += getDistManhattan(p1, p2)
|
|
188
|
+
}
|
|
189
|
+
if(isPoly){
|
|
190
|
+
len += getDistManhattan(pts[l-1], pts[0])
|
|
191
|
+
}
|
|
192
|
+
return len
|
|
193
|
+
}
|
|
194
|
+
|
|
179
195
|
|
|
180
196
|
/**
|
|
181
197
|
* Ramanujan approximation
|
|
@@ -67,6 +67,7 @@ export function analyzePathData(pathData = [], {
|
|
|
67
67
|
let { type, values, p0, p, cp1 = null, cp2 = null, squareDist = 0, cptArea = 0, dimA = 0 } = com;
|
|
68
68
|
|
|
69
69
|
//next command
|
|
70
|
+
let comPrev = pathData[c-2];
|
|
70
71
|
let comN = pathData[c] || null;
|
|
71
72
|
|
|
72
73
|
|
|
@@ -93,6 +94,7 @@ export function analyzePathData(pathData = [], {
|
|
|
93
94
|
|
|
94
95
|
// bezier types
|
|
95
96
|
let isBezier = type === 'Q' || type === 'C';
|
|
97
|
+
let isArc = type === 'A';
|
|
96
98
|
let isBezierN = comN && (comN.type === 'Q' || comN.type === 'C');
|
|
97
99
|
|
|
98
100
|
|
|
@@ -149,6 +151,22 @@ export function analyzePathData(pathData = [], {
|
|
|
149
151
|
}
|
|
150
152
|
}
|
|
151
153
|
|
|
154
|
+
// check extremes introduce by small arcs
|
|
155
|
+
else if(isArc && comN && ((comPrev.type==='C' || comPrev.type==='Q') || (comN.type==='C' || comN.type==='Q')) ){
|
|
156
|
+
let distN = comN ? comN.dimA : 0
|
|
157
|
+
let isShort = com.dimA < (comPrev.dimA + distN) * 0.1;
|
|
158
|
+
let smallRadius = com.values[0] === com.values[1] && (com.values[0] < 1)
|
|
159
|
+
|
|
160
|
+
if(isShort && smallRadius){
|
|
161
|
+
let bb = getPolyBBox([comPrev.p0, comN.p])
|
|
162
|
+
if(p.x>bb.right || p.x<bb.x || p.y<bb.y || p.y>bb.bottom){
|
|
163
|
+
hasExtremes = true;
|
|
164
|
+
//renderPoint(markers, p)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
}
|
|
169
|
+
|
|
152
170
|
if (hasExtremes) com.extreme = true
|
|
153
171
|
|
|
154
172
|
// Corners and semi extremes
|