svg-path-simplify 0.0.1 → 0.0.2
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/README.md +28 -1
- package/dist/svg-path-simplify.esm.js +4040 -0
- package/dist/svg-path-simplify.esm.min.js +1 -0
- package/dist/svg-path-simplify.js +4065 -0
- package/dist/svg-path-simplify.min.js +1 -0
- package/dist/svg-path-simplify.node.js +4062 -0
- package/dist/svg-path-simplify.node.min.js +1 -0
- package/index.html +222 -0
- package/package.json +2 -2
- package/src/constants.js +4 -0
- package/src/index.js +18 -3
- package/src/pathData_simplify_cubic.js +324 -0
- package/src/pathData_simplify_cubic_arr.js +50 -0
- package/src/pathData_simplify_cubic_extrapolate.js +220 -0
- package/src/pathSimplify-main.js +294 -0
- package/src/svgii/...parse.js +402 -0
- package/src/svgii/geometry.js +1096 -0
- package/src/svgii/geometry_area.js +265 -0
- package/src/svgii/geometry_bbox.js +223 -0
- package/src/svgii/pathData_analyze.js +896 -0
- package/src/svgii/pathData_convert.js +1180 -0
- package/src/svgii/pathData_parse.js +487 -0
- package/src/svgii/pathData_remove_collinear.js +85 -0
- package/src/svgii/pathData_remove_zerolength.js +28 -0
- package/src/svgii/pathData_reorder.js +204 -0
- package/src/svgii/pathData_reverse.js +124 -0
- package/src/svgii/pathData_scale.js +42 -0
- package/src/svgii/pathData_split.js +449 -0
- package/src/svgii/pathData_stringify.js +146 -0
- package/src/svgii/pathData_toPolygon.js +92 -0
- package/src/svgii/pathdata_cleanup.js +363 -0
- package/src/svgii/poly_analyze.js +172 -0
- package/src/svgii/poly_to_pathdata.js +185 -0
- package/src/svgii/rounding.js +154 -0
- package/src/svgii/simplify.js +248 -0
- package/src/svgii/simplify_bezier.js +470 -0
- package/src/svgii/simplify_linetos.js +93 -0
- package/src/svgii/simplify_polygon.js +135 -0
- package/src/svgii/stringify.js +103 -0
- package/src/svgii/svg_cleanup.js +80 -0
- package/src/svgii/visualize.js +317 -0
- package/LICENSE +0 -21
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { getDistance, getSquareDistance, checkLineIntersection, pointAtT, getDistAv } from "./svgii/geometry";
|
|
2
|
+
import { getBezierArea, getPolygonArea } from "./svgii/geometry_area";
|
|
3
|
+
import { renderPoint } from "./svgii/visualize";
|
|
4
|
+
|
|
5
|
+
export function combineCubicArray(commands = [], threshold = 0.05) {
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
let [comF, comM, comE] = commands;
|
|
9
|
+
|
|
10
|
+
commands.forEach(com => {
|
|
11
|
+
let area = getBezierArea([com.p0, com.cp1, com.cp2, com.p], true)
|
|
12
|
+
com.area = area
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
// sort by area
|
|
16
|
+
let commandsFiltered = JSON.parse(JSON.stringify(commands)).sort((a,b)=>b.area-a.area )
|
|
17
|
+
|
|
18
|
+
let comL = commandsFiltered[0]
|
|
19
|
+
|
|
20
|
+
// find largest segment
|
|
21
|
+
//let area2 = getBezierArea(comF)
|
|
22
|
+
|
|
23
|
+
console.log('area1', commands, commandsFiltered);
|
|
24
|
+
|
|
25
|
+
// largest segment
|
|
26
|
+
comM = commandsFiltered[0]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
//let segLargest =
|
|
30
|
+
let pt_mid = pointAtT([comM.p0, comM.cp1, comM.cp2, comM.p], 0.5, false, true)
|
|
31
|
+
|
|
32
|
+
let seg1_cp2 = pt_mid.cpts[2]
|
|
33
|
+
let seg2_cp1 = pt_mid.cpts[3]
|
|
34
|
+
renderPoint(markers, seg1_cp2, 'orange')
|
|
35
|
+
renderPoint(markers, seg2_cp1, 'cyan')
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
console.log('comM', pt_mid);
|
|
39
|
+
|
|
40
|
+
renderPoint(markers, comF.p0, 'green')
|
|
41
|
+
renderPoint(markers, comL.p, 'purple')
|
|
42
|
+
//renderPoint(markers, comE.cp2, 'orange')
|
|
43
|
+
|
|
44
|
+
renderPoint(markers, pt_mid, 'magenta')
|
|
45
|
+
//let ptI_1 = checkLineIntersection(comF)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
return commands;
|
|
49
|
+
}
|
|
50
|
+
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { checkLineIntersection, getAngle, getDistAv, getSquareDistance, interpolate, pointAtT, rotatePoint } from "./svgii/geometry";
|
|
2
|
+
import { getPathArea } from "./svgii/geometry_area";
|
|
3
|
+
import { pathDataToD } from "./svgii/pathData_stringify";
|
|
4
|
+
import { renderPath, renderPoint } from "./svgii/visualize";
|
|
5
|
+
|
|
6
|
+
export function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1) {
|
|
7
|
+
|
|
8
|
+
// cubic Bézier derivative
|
|
9
|
+
const cubicDerivative = (p0, p1, p2, p3, t) => {
|
|
10
|
+
let mt = 1 - t;
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
x:
|
|
14
|
+
3 * mt * mt * (p1.x - p0.x) +
|
|
15
|
+
6 * mt * t * (p2.x - p1.x) +
|
|
16
|
+
3 * t * t * (p3.x - p2.x),
|
|
17
|
+
y:
|
|
18
|
+
3 * mt * mt * (p1.y - p0.y) +
|
|
19
|
+
6 * mt * t * (p2.y - p1.y) +
|
|
20
|
+
3 * t * t * (p3.y - p2.y)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
// if combining fails return original commands
|
|
27
|
+
let commands = [com1, com2]
|
|
28
|
+
|
|
29
|
+
// detect dominant
|
|
30
|
+
let dist1 = getSquareDistance(com1.p0, com1.p)
|
|
31
|
+
let dist2 = getSquareDistance(com2.p0, com2.p)
|
|
32
|
+
let reverse = dist1 > dist2;
|
|
33
|
+
|
|
34
|
+
//let ang1 = getAngle(com1.p0, com1.cp1)
|
|
35
|
+
//let ang2 = getAngle(com2.p, com1.cp2)
|
|
36
|
+
|
|
37
|
+
// backup original commands
|
|
38
|
+
let com1_o = JSON.parse(JSON.stringify(com1))
|
|
39
|
+
let com2_o = JSON.parse(JSON.stringify(com2))
|
|
40
|
+
|
|
41
|
+
let ptI = checkLineIntersection(com1_o.p0, com1_o.cp1, com2_o.p, com2_o.cp2, false)
|
|
42
|
+
|
|
43
|
+
if (!ptI) {
|
|
44
|
+
//renderPoint(markers, com1.p, 'purple')
|
|
45
|
+
return commands
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if (reverse) {
|
|
50
|
+
let com2_R = {
|
|
51
|
+
p0: { x: com1.p.x, y: com1.p.y },
|
|
52
|
+
cp1: { x: com1.cp2.x, y: com1.cp2.y },
|
|
53
|
+
cp2: { x: com1.cp1.x, y: com1.cp1.y },
|
|
54
|
+
p: { x: com1.p0.x, y: com1.p0.y },
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let com1_R = {
|
|
58
|
+
p0: { x: com2.p.x, y: com2.p.y },
|
|
59
|
+
cp1: { x: com2.cp2.x, y: com2.cp2.y },
|
|
60
|
+
cp2: { x: com2.cp1.x, y: com2.cp1.y },
|
|
61
|
+
p: { x: com2.p0.x, y: com2.p0.y },
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
com1 = com1_R;
|
|
65
|
+
com2 = com2_R;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
let add = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
|
|
70
|
+
let sub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y });
|
|
71
|
+
let mul = (a, s) => ({ x: a.x * s, y: a.y * s });
|
|
72
|
+
let dot = (a, b) => a.x * b.x + a.y * b.y;
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
// estimate extrapolation parameter t0
|
|
76
|
+
|
|
77
|
+
let B0 = com2.p0;
|
|
78
|
+
let D0 = cubicDerivative(
|
|
79
|
+
com2.p0,
|
|
80
|
+
com2.cp1,
|
|
81
|
+
com2.cp2,
|
|
82
|
+
com2.p,
|
|
83
|
+
0
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
let v = sub(com1.p0, B0);
|
|
87
|
+
|
|
88
|
+
// first-order projection onto tangent
|
|
89
|
+
let t0 = dot(v, D0) / dot(D0, D0);
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
// refine with one Newton iteration (optional but cheap)
|
|
93
|
+
let P = pointAtT([com2.p0, com2.cp1, com2.cp2, com2.p], t0);
|
|
94
|
+
let dP = cubicDerivative(com2.p0, com2.cp1, com2.cp2, com2.p, t0);
|
|
95
|
+
let r = sub(P, com1.p0);
|
|
96
|
+
|
|
97
|
+
//let t0_2 = t0 - dot(r, dP) / dot(dP, dP);
|
|
98
|
+
//console.log(t0, t0_2);
|
|
99
|
+
|
|
100
|
+
t0 -= dot(r, dP) / dot(dP, dP);
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
// construct merged cubic over [t0, 1]
|
|
104
|
+
|
|
105
|
+
let Q0 = pointAtT([com2.p0, com2.cp1, com2.cp2, com2.p], t0);
|
|
106
|
+
let Q3 = com2.p;
|
|
107
|
+
|
|
108
|
+
let d0 = cubicDerivative(com2.p0, com2.cp1, com2.cp2, com2.p, t0);
|
|
109
|
+
let d1 = cubicDerivative(com2.p0, com2.cp1, com2.cp2, com2.p, 1);
|
|
110
|
+
|
|
111
|
+
let scale = 1 - t0;
|
|
112
|
+
|
|
113
|
+
let Q1 = add(Q0, mul(d0, scale / 3));
|
|
114
|
+
let Q2 = sub(Q3, mul(d1, scale / 3));
|
|
115
|
+
|
|
116
|
+
let result = {
|
|
117
|
+
p0: Q0,
|
|
118
|
+
cp1: Q1,
|
|
119
|
+
cp2: Q2,
|
|
120
|
+
p: Q3,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
if (reverse) {
|
|
125
|
+
result = {
|
|
126
|
+
p0: Q3,
|
|
127
|
+
cp1: Q2,
|
|
128
|
+
cp2: Q1,
|
|
129
|
+
p: Q0,
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
let ptM = pointAtT([result.p0, result.cp1, result.cp2, result.p], 0.5, false, true)
|
|
135
|
+
let seg1_cp2 = ptM.cpts[2]
|
|
136
|
+
//let seg2_cp1 = ptM.cpts[3]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
let ptI_1 = checkLineIntersection(ptM, seg1_cp2, result.p0, ptI, false)
|
|
140
|
+
let ptI_2 = checkLineIntersection(ptM, seg1_cp2, result.p, ptI, false)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
let cp1_2 = interpolate(result.p0, ptI_1, 1.333)
|
|
144
|
+
let cp2_2 = interpolate(result.p, ptI_2, 1.333)
|
|
145
|
+
|
|
146
|
+
// test self intersections and exit
|
|
147
|
+
let cp_intersection = checkLineIntersection(com1_o.p0, cp1_2, com2_o.p, cp2_2, true )
|
|
148
|
+
if(cp_intersection){
|
|
149
|
+
//renderPoint(markers, cp_intersection )
|
|
150
|
+
return commands;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
result.cp1 = cp1_2
|
|
155
|
+
result.cp2 = cp2_2
|
|
156
|
+
|
|
157
|
+
// check distances
|
|
158
|
+
|
|
159
|
+
let dist3 = getDistAv(com1_o.p0, result.p0)
|
|
160
|
+
let dist4 = getDistAv(com2_o.p, result.p)
|
|
161
|
+
let dist5 = (dist3 + dist4)
|
|
162
|
+
|
|
163
|
+
// use original points
|
|
164
|
+
result.p0 = com1_o.p0
|
|
165
|
+
result.p = com2_o.p
|
|
166
|
+
result.extreme = com2_o.extreme
|
|
167
|
+
result.corner = com2_o.corner
|
|
168
|
+
result.dimA = com2_o.dimA
|
|
169
|
+
result.directionChange = com2_o.directionChange
|
|
170
|
+
result.values = [result.cp1.x, result.cp1.y, result.cp2.x, result.cp2.y, result.p.x, result.p.y]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
// check if completely off
|
|
175
|
+
if (dist5 < maxDist) {
|
|
176
|
+
|
|
177
|
+
// compare combined with original area
|
|
178
|
+
let pathData0 = [
|
|
179
|
+
{ type: 'M', values: [com1_o.p0.x, com1_o.p0.y] },
|
|
180
|
+
{ type: 'C', values: [com1_o.cp1.x, com1_o.cp1.y, com1_o.cp2.x, com1_o.cp2.y, com1_o.p.x, com1_o.p.y] },
|
|
181
|
+
{ type: 'C', values: [com2_o.cp1.x, com2_o.cp1.y, com2_o.cp2.x, com2_o.cp2.y, com2_o.p.x, com2_o.p.y] },
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
let area0 = getPathArea(pathData0)
|
|
185
|
+
let pathDataN = [
|
|
186
|
+
{ type: 'M', values: [result.p0.x, result.p0.y] },
|
|
187
|
+
{ type: 'C', values: [result.cp1.x, result.cp1.y, result.cp2.x, result.cp2.y, result.p.x, result.p.y] },
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
let areaN = getPathArea(pathDataN)
|
|
191
|
+
let areaDiff = Math.abs(areaN / area0 - 1)
|
|
192
|
+
|
|
193
|
+
result.error = areaDiff * 10 * tolerance;
|
|
194
|
+
//result.error = areaDiff + dist5;
|
|
195
|
+
|
|
196
|
+
let d = pathDataToD(pathDataN)
|
|
197
|
+
|
|
198
|
+
// success
|
|
199
|
+
if (areaDiff < 0.01) {
|
|
200
|
+
commands = [result];
|
|
201
|
+
//renderPath(markers, d, 'orange')
|
|
202
|
+
//console.log('areaDiff', areaDiff);
|
|
203
|
+
|
|
204
|
+
} else {
|
|
205
|
+
// renderPath(markers, d, 'red')
|
|
206
|
+
// console.log('areaDiff', areaDiff);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
//renderPath(markers, d, 'orange')
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
//console.log(commands);
|
|
216
|
+
|
|
217
|
+
return commands
|
|
218
|
+
|
|
219
|
+
}
|
|
220
|
+
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { combineCubicPairs } from './pathData_simplify_cubic';
|
|
2
|
+
import { getPathDataVertices, pointAtT } from './svgii/geometry';
|
|
3
|
+
import { getPolyBBox } from './svgii/geometry_bbox';
|
|
4
|
+
import { analyzePathData, analyzePathData2 } from './svgii/pathData_analyze';
|
|
5
|
+
import { combineArcs, convertPathData, cubicCommandToArc, revertCubicQuadratic } from './svgii/pathData_convert';
|
|
6
|
+
import { parsePathDataNormalized } from './svgii/pathData_parse';
|
|
7
|
+
import { pathDataRemoveColinear } from './svgii/pathData_remove_collinear';
|
|
8
|
+
import { removeZeroLengthLinetos } from './svgii/pathData_remove_zerolength';
|
|
9
|
+
import { pathDataToTopLeft } from './svgii/pathData_reorder';
|
|
10
|
+
import { reversePathData } from './svgii/pathData_reverse';
|
|
11
|
+
import { addExtremePoints, splitSubpaths } from './svgii/pathData_split';
|
|
12
|
+
import { pathDataToD } from './svgii/pathData_stringify';
|
|
13
|
+
import { pathDataToPolyPlus } from './svgii/pathData_toPolygon';
|
|
14
|
+
import { analyzePoly } from './svgii/poly_analyze';
|
|
15
|
+
import { getCurvePathData } from './svgii/poly_to_pathdata';
|
|
16
|
+
import { detectAccuracy } from './svgii/rounding';
|
|
17
|
+
import { renderPoint } from './svgii/visualize';
|
|
18
|
+
|
|
19
|
+
export function svgPathSimplify(d = '', {
|
|
20
|
+
toAbsolute = true,
|
|
21
|
+
toRelative = true,
|
|
22
|
+
toShorthands = true,
|
|
23
|
+
decimals = 3,
|
|
24
|
+
//optimize = 0,
|
|
25
|
+
|
|
26
|
+
// not necessary unless you need cubics only
|
|
27
|
+
quadraticToCubic = true,
|
|
28
|
+
|
|
29
|
+
// mostly a fallback if arc calculations fail
|
|
30
|
+
arcToCubic = false,
|
|
31
|
+
cubicToArc = false,
|
|
32
|
+
|
|
33
|
+
// arc to cubic precision - adds more segments for better precision
|
|
34
|
+
arcAccuracy = 4,
|
|
35
|
+
keepExtremes = true,
|
|
36
|
+
keepCorners = true,
|
|
37
|
+
keepInflections = true,
|
|
38
|
+
extrapolateDominant = false,
|
|
39
|
+
addExtremes = false,
|
|
40
|
+
optimizeOrder = true,
|
|
41
|
+
removeColinear = true,
|
|
42
|
+
simplifyBezier = true,
|
|
43
|
+
autoAccuracy = true,
|
|
44
|
+
flatBezierToLinetos = true,
|
|
45
|
+
revertToQuadratics = true,
|
|
46
|
+
minifyD = 0,
|
|
47
|
+
tolerance = 1,
|
|
48
|
+
reverse = false
|
|
49
|
+
} = {}) {
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
let pathDataO = parsePathDataNormalized(d, { quadraticToCubic, toAbsolute, arcToCubic });
|
|
53
|
+
|
|
54
|
+
// create clone for fallback
|
|
55
|
+
let pathData = JSON.parse(JSON.stringify(pathDataO));
|
|
56
|
+
|
|
57
|
+
// count commands for evaluation
|
|
58
|
+
let comCount = pathDataO.length
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* get sub paths
|
|
62
|
+
*/
|
|
63
|
+
let subPathArr = splitSubpaths(pathData);
|
|
64
|
+
|
|
65
|
+
// cleaned up pathData
|
|
66
|
+
let pathDataArrN = [];
|
|
67
|
+
|
|
68
|
+
for (let i = 0, l = subPathArr.length; i < l; i++) {
|
|
69
|
+
|
|
70
|
+
//let { pathData, bb } = subPathArr[i];
|
|
71
|
+
let pathDataSub = subPathArr[i];
|
|
72
|
+
|
|
73
|
+
// try simplification in reversed order
|
|
74
|
+
if (reverse) pathDataSub = reversePathData(pathDataSub);
|
|
75
|
+
|
|
76
|
+
// remove zero length linetos
|
|
77
|
+
if (removeColinear) pathDataSub = removeZeroLengthLinetos(pathDataSub)
|
|
78
|
+
|
|
79
|
+
// add extremes
|
|
80
|
+
//let tMin=0.2, tMax=0.8;
|
|
81
|
+
let tMin = 0, tMax = 1;
|
|
82
|
+
if (addExtremes) pathDataSub = addExtremePoints(pathDataSub, tMin, tMax)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
// sort to top left
|
|
86
|
+
if (optimizeOrder) pathDataSub = pathDataToTopLeft(pathDataSub);
|
|
87
|
+
|
|
88
|
+
// remove colinear/flat
|
|
89
|
+
if (removeColinear) pathDataSub = pathDataRemoveColinear(pathDataSub, tolerance, flatBezierToLinetos);
|
|
90
|
+
|
|
91
|
+
// analyze pathdata to add info about signicant properties such as extremes, corners
|
|
92
|
+
let pathDataPlus = analyzePathData(pathDataSub);
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
// simplify beziers
|
|
96
|
+
let { pathData, bb, dimA } = pathDataPlus;
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
let pathDataN = pathData;
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
pathDataN = simplifyBezier ? simplifyPathData(pathDataN, { simplifyBezier, keepInflections, keepExtremes, keepCorners, extrapolateDominant, revertToQuadratics, tolerance, reverse }) : pathDataN;
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
// cubic to arcs
|
|
112
|
+
if(cubicToArc){
|
|
113
|
+
|
|
114
|
+
let thresh = 3;
|
|
115
|
+
|
|
116
|
+
pathDataN.forEach((com, c) => {
|
|
117
|
+
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
118
|
+
if (type === 'C') {
|
|
119
|
+
//console.log(com);
|
|
120
|
+
let comA = cubicCommandToArc(p0, cp1, cp2, p, thresh)
|
|
121
|
+
if(comA.isArc) pathDataN[c] = comA.com;
|
|
122
|
+
//if (comQ.type === 'Q') pathDataN[c] = comQ
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
// combine adjacent cubics
|
|
127
|
+
pathDataN = combineArcs(pathDataN)
|
|
128
|
+
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
// simplify to quadratics
|
|
133
|
+
if (revertToQuadratics) {
|
|
134
|
+
pathDataN.forEach((com, c) => {
|
|
135
|
+
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
136
|
+
if (type === 'C') {
|
|
137
|
+
//console.log(com);
|
|
138
|
+
let comQ = revertCubicQuadratic(p0, cp1, cp2, p)
|
|
139
|
+
if (comQ.type === 'Q') pathDataN[c] = comQ
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
// update
|
|
148
|
+
pathDataArrN.push(pathDataN)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
// merge pathdata
|
|
153
|
+
let pathDataFlat = pathDataArrN.flat();
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* detect accuracy
|
|
157
|
+
*/
|
|
158
|
+
if (autoAccuracy) {
|
|
159
|
+
decimals = detectAccuracy(pathDataFlat)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
// compare command count
|
|
165
|
+
let comCountS = pathDataFlat.length
|
|
166
|
+
|
|
167
|
+
// optimize
|
|
168
|
+
let pathOptions = {
|
|
169
|
+
toRelative,
|
|
170
|
+
toShorthands,
|
|
171
|
+
decimals,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
// optimize path data
|
|
176
|
+
pathData = convertPathData(pathDataFlat, pathOptions)
|
|
177
|
+
let dOpt = pathDataToD(pathData, minifyD)
|
|
178
|
+
|
|
179
|
+
let report = {
|
|
180
|
+
original: comCount,
|
|
181
|
+
new: comCountS,
|
|
182
|
+
saved: comCount - comCountS,
|
|
183
|
+
decimals,
|
|
184
|
+
success: comCountS < comCount
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return { pathData, d: dOpt, report };
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
function simplifyPathData(pathData, {
|
|
195
|
+
keepExtremes = true,
|
|
196
|
+
keepInflections = true,
|
|
197
|
+
keepCorners = true,
|
|
198
|
+
extrapolateDominant = true,
|
|
199
|
+
tolerance = 1,
|
|
200
|
+
reverse = false
|
|
201
|
+
} = {}) {
|
|
202
|
+
|
|
203
|
+
let pathDataN = [pathData[0]];
|
|
204
|
+
|
|
205
|
+
for (let i = 2, l = pathData.length; l && i <= l; i++) {
|
|
206
|
+
let com = pathData[i - 1];
|
|
207
|
+
let comN = i < l ? pathData[i] : null;
|
|
208
|
+
let typeN = comN?.type || null;
|
|
209
|
+
//let isCornerN = comN?.corner || null;
|
|
210
|
+
//let isExtremeN = comN?.extreme || null;
|
|
211
|
+
let isDirChange = com?.directionChange || null;
|
|
212
|
+
let isDirChangeN = comN?.directionChange || null;
|
|
213
|
+
|
|
214
|
+
let { type, values, p0, p, cp1 = null, cp2 = null, extreme = false, corner = false, dimA = 0 } = com;
|
|
215
|
+
|
|
216
|
+
// count simplifications
|
|
217
|
+
let success = 0;
|
|
218
|
+
|
|
219
|
+
// next is also cubic
|
|
220
|
+
if (type === 'C' && typeN === 'C') {
|
|
221
|
+
|
|
222
|
+
// cannot be combined as crossing extremes or corners
|
|
223
|
+
if (
|
|
224
|
+
(keepInflections && isDirChangeN) ||
|
|
225
|
+
(keepCorners && corner) ||
|
|
226
|
+
(!isDirChange && keepExtremes && extreme)
|
|
227
|
+
) {
|
|
228
|
+
//renderPoint(markers, p, 'red', '1%')
|
|
229
|
+
pathDataN.push(com)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// try simplification
|
|
233
|
+
else {
|
|
234
|
+
//renderPoint(markers, p, 'magenta', '1%')
|
|
235
|
+
let combined = combineCubicPairs(com, comN, extrapolateDominant, tolerance)
|
|
236
|
+
let error = 0;
|
|
237
|
+
|
|
238
|
+
// combining successful! try next segment
|
|
239
|
+
if (combined.length === 1) {
|
|
240
|
+
com = combined[0]
|
|
241
|
+
let offset = 1;
|
|
242
|
+
error += com.error;
|
|
243
|
+
//console.log('!error', error);
|
|
244
|
+
|
|
245
|
+
// find next candidates
|
|
246
|
+
for (let n = i + 1; error < tolerance && n < l; n++) {
|
|
247
|
+
let comN = pathData[n]
|
|
248
|
+
if (comN.type !== 'C' ||
|
|
249
|
+
(
|
|
250
|
+
(keepInflections && comN.directionChange) ||
|
|
251
|
+
(keepCorners && com.corner) ||
|
|
252
|
+
(keepExtremes && com.extreme)
|
|
253
|
+
)
|
|
254
|
+
) {
|
|
255
|
+
break
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let combined = combineCubicPairs(com, comN, extrapolateDominant, tolerance)
|
|
259
|
+
if (combined.length === 1) {
|
|
260
|
+
offset++
|
|
261
|
+
}
|
|
262
|
+
com = combined[0]
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
//com.opt = true
|
|
266
|
+
pathDataN.push(com)
|
|
267
|
+
|
|
268
|
+
if (i < l) {
|
|
269
|
+
i += offset
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
} else {
|
|
273
|
+
pathDataN.push(com)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
} // end of bezier command
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
// other commands
|
|
281
|
+
else {
|
|
282
|
+
pathDataN.push(com)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
} // end command loop
|
|
286
|
+
|
|
287
|
+
// reverse back
|
|
288
|
+
if (reverse) pathDataN = reversePathData(pathDataN)
|
|
289
|
+
|
|
290
|
+
return pathDataN
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
|