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,449 @@
|
|
|
1
|
+
import { pointAtT, svgArcToCenterParam, getBezierExtremeT } from "./geometry";
|
|
2
|
+
import { renderPoint, renderPath } from "./visualize";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* split segments into chunks to
|
|
7
|
+
* prevent simplification across
|
|
8
|
+
* extremes, corners or direction changes
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export function getPathDataPlusChunks(pathDataPlus = [], debug = false) {
|
|
12
|
+
|
|
13
|
+
// loop sub paths
|
|
14
|
+
for (let s = 0, l = pathDataPlus.length; s < l; s++) {
|
|
15
|
+
let sub = pathDataPlus[s];
|
|
16
|
+
let pathDataSub = sub.pathData;
|
|
17
|
+
pathDataPlus[s].chunks = [[pathDataSub[0]], []];
|
|
18
|
+
|
|
19
|
+
let pathDataChunks = [[pathDataSub[0]], []];
|
|
20
|
+
let ind = 1
|
|
21
|
+
|
|
22
|
+
let wasExtreme = false
|
|
23
|
+
let wasCorner = false
|
|
24
|
+
let wasClosePath = false;
|
|
25
|
+
let prevType = 'M';
|
|
26
|
+
let typeChange = false;
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
for (let i = 1, len = pathDataSub.length; i < len; i++) {
|
|
30
|
+
let com = pathDataSub[i]
|
|
31
|
+
|
|
32
|
+
let { extreme, corner, directionChange } = com;
|
|
33
|
+
typeChange = prevType !== com.type;
|
|
34
|
+
let split = directionChange || wasExtreme || wasCorner || wasClosePath || typeChange;
|
|
35
|
+
//let split = wasExtreme
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
// new chunk
|
|
39
|
+
if (split) {
|
|
40
|
+
/*
|
|
41
|
+
if(directionChange){
|
|
42
|
+
renderPoint(svg1, com.p0 , 'red')
|
|
43
|
+
}
|
|
44
|
+
if(wasExtreme){
|
|
45
|
+
renderPoint(svg1, com.p0 , 'blue')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if(wasCorner){
|
|
49
|
+
renderPoint(svg1, com.p0 , 'magenta')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if(wasClosePath){
|
|
53
|
+
renderPoint(svg1, com.p0 , 'red')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if(typeChange && com.type==='Q' && prevType==='M'){
|
|
57
|
+
console.log('typechange', pathDataSub[i], pathDataSub[i-1]);
|
|
58
|
+
renderPoint(svg1, com.p0 , 'purple')
|
|
59
|
+
}
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
//let orphanedC = pathDataChunks[ind].length===1 && i<len-1 && wasExtreme
|
|
63
|
+
//orphanedC=false
|
|
64
|
+
//console.log('orphanedC', i, len, orphanedC, pathDataChunks[ind].length);
|
|
65
|
+
|
|
66
|
+
if (pathDataChunks[ind].length) {
|
|
67
|
+
pathDataChunks.push([]);
|
|
68
|
+
ind++
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
wasExtreme = extreme
|
|
73
|
+
wasCorner = corner;
|
|
74
|
+
wasClosePath = com.type.toLowerCase() === 'z'
|
|
75
|
+
prevType = com.type
|
|
76
|
+
//pathDataPlus[s].chunks[ind].push(com);
|
|
77
|
+
pathDataChunks[ind].push(com)
|
|
78
|
+
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
// debug rendering
|
|
83
|
+
if (debug) {
|
|
84
|
+
|
|
85
|
+
//console.log('show chunks', pathDataChunks);
|
|
86
|
+
pathDataChunks.forEach((ch, i) => {
|
|
87
|
+
let stroke = i % 2 === 0 ? 'green' : 'orange';
|
|
88
|
+
if(i===pathDataChunks.length-2){
|
|
89
|
+
stroke = 'magenta'
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let M = ch[0].p0;
|
|
93
|
+
if (M) {
|
|
94
|
+
//renderPoint(svg1, M, 'green', '1%')
|
|
95
|
+
let d = `M ${M.x} ${M.y}`
|
|
96
|
+
|
|
97
|
+
ch.forEach(com => {
|
|
98
|
+
//console.log(com);
|
|
99
|
+
d += `${com.type} ${com.values.join(' ')}`
|
|
100
|
+
//let pt = com.p;
|
|
101
|
+
//renderPoint(svg1, pt, 'cyan')
|
|
102
|
+
})
|
|
103
|
+
//console.log(d);
|
|
104
|
+
renderPath(svg1, d, stroke, '0.5%', '0.5')
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// add to pathdataPlus object
|
|
111
|
+
pathDataPlus[s].chunks = pathDataChunks
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
//console.log(pathDataPlus);
|
|
116
|
+
return pathDataPlus
|
|
117
|
+
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* split compound paths into
|
|
125
|
+
* sub path data array
|
|
126
|
+
*/
|
|
127
|
+
export function splitSubpaths(pathData) {
|
|
128
|
+
|
|
129
|
+
let subPathArr = [];
|
|
130
|
+
|
|
131
|
+
//split segments after M command
|
|
132
|
+
|
|
133
|
+
try{
|
|
134
|
+
let subPathIndices = pathData.map((com, i) => (com.type.toLowerCase() === 'm' ? i : -1)).filter(i => i !== -1);
|
|
135
|
+
|
|
136
|
+
}catch{
|
|
137
|
+
console.log('catch', pathData);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
let subPathIndices = pathData.map((com, i) => (com.type.toLowerCase() === 'm' ? i : -1)).filter(i => i !== -1);
|
|
142
|
+
//let subPathIndices = pathData.map((com, i) => (com.type === 'M' ? i : -1)).filter(i => i !== -1);
|
|
143
|
+
|
|
144
|
+
// no compound path
|
|
145
|
+
if (subPathIndices.length === 1) {
|
|
146
|
+
return [pathData]
|
|
147
|
+
}
|
|
148
|
+
subPathIndices.forEach((index, i) => {
|
|
149
|
+
subPathArr.push(pathData.slice(index, subPathIndices[i + 1]));
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return subPathArr;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* calculate split command points
|
|
159
|
+
* for single t value
|
|
160
|
+
*/
|
|
161
|
+
export function splitCommand(points, t) {
|
|
162
|
+
|
|
163
|
+
let seg1 = [];
|
|
164
|
+
let seg2 = [];
|
|
165
|
+
|
|
166
|
+
let p0 = points[0];
|
|
167
|
+
let cp1 = points[1];
|
|
168
|
+
let cp2 = points[points.length - 2];
|
|
169
|
+
let p = points[points.length - 1];
|
|
170
|
+
let m0,m1,m2,m3,m4, p2
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
// cubic
|
|
174
|
+
if (points.length === 4) {
|
|
175
|
+
m0 = pointAtT([p0, cp1], t);
|
|
176
|
+
m1 = pointAtT([cp1, cp2], t);
|
|
177
|
+
m2 = pointAtT([cp2, p], t);
|
|
178
|
+
m3 = pointAtT([m0, m1], t);
|
|
179
|
+
m4 = pointAtT([m1, m2], t);
|
|
180
|
+
|
|
181
|
+
// split end point
|
|
182
|
+
p2 = pointAtT([m3, m4], t);
|
|
183
|
+
|
|
184
|
+
// 1. segment
|
|
185
|
+
seg1.push(
|
|
186
|
+
{ x: p0.x, y: p0.y },
|
|
187
|
+
{ x: m0.x, y: m0.y },
|
|
188
|
+
{ x: m3.x, y: m3.y },
|
|
189
|
+
{ x: p2.x, y: p2.y },
|
|
190
|
+
)
|
|
191
|
+
// 2. segment
|
|
192
|
+
seg2.push(
|
|
193
|
+
{ x: p2.x, y: p2.y },
|
|
194
|
+
{ x: m4.x, y: m4.y },
|
|
195
|
+
{ x: m2.x, y: m2.y },
|
|
196
|
+
{ x: p.x, y: p.y },
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// quadratic
|
|
201
|
+
else if (points.length === 3) {
|
|
202
|
+
m1 = pointAtT([p0, cp1], t);
|
|
203
|
+
m2 = pointAtT([cp1, p], t);
|
|
204
|
+
p2 = pointAtT([m1, m2], t);
|
|
205
|
+
|
|
206
|
+
// 1. segment
|
|
207
|
+
seg1.push(
|
|
208
|
+
{ x: p0.x, y: p0.y },
|
|
209
|
+
{ x: m1.x, y: m1.y },
|
|
210
|
+
{ x: p2.x, y: p2.y },
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
// 1. segment
|
|
214
|
+
seg2.push(
|
|
215
|
+
{ x: p2.x, y: p2.y },
|
|
216
|
+
{ x: m2.x, y: m2.y },
|
|
217
|
+
{ x: p.x, y: p.y },
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// lineto
|
|
222
|
+
else if (points.length === 2) {
|
|
223
|
+
m1 = pointAtT([p0, p], t);
|
|
224
|
+
|
|
225
|
+
// 1. segment
|
|
226
|
+
seg1.push(
|
|
227
|
+
{ x: p0.x, y: p0.y },
|
|
228
|
+
{ x: m1.x, y: m1.y },
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
// 1. segment
|
|
232
|
+
seg2.push(
|
|
233
|
+
{ x: m1.x, y: m1.y },
|
|
234
|
+
{ x: p.x, y: p.y },
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
return [seg1, seg2];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* calculate command extremes
|
|
243
|
+
*/
|
|
244
|
+
|
|
245
|
+
export function addExtemesToCommand(p0, values, tMin=0, tMax=1) {
|
|
246
|
+
|
|
247
|
+
let pathDataNew = [];
|
|
248
|
+
|
|
249
|
+
let type = values.length === 6 ? 'C' : 'Q'
|
|
250
|
+
let cp1 = { x: values[0], y: values[1] }
|
|
251
|
+
let cp2 = type === 'C' ? { x: values[2], y: values[3] } : cp1
|
|
252
|
+
let p = { x: values[4], y: values[5] }
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
// get inner bbox
|
|
256
|
+
let xMax = Math.max(p.x, p0.x)
|
|
257
|
+
let xMin = Math.min(p.x, p0.x)
|
|
258
|
+
let yMax = Math.max(p.y, p0.y)
|
|
259
|
+
let yMin = Math.min(p.y, p0.y)
|
|
260
|
+
|
|
261
|
+
let extremeCount = 0;
|
|
262
|
+
|
|
263
|
+
//has extreme - split
|
|
264
|
+
if (
|
|
265
|
+
cp1.x < xMin ||
|
|
266
|
+
cp1.x > xMax ||
|
|
267
|
+
cp1.y < yMin ||
|
|
268
|
+
cp1.y > yMax ||
|
|
269
|
+
cp2.x < xMin ||
|
|
270
|
+
cp2.x > xMax ||
|
|
271
|
+
cp2.y < yMin ||
|
|
272
|
+
cp2.y > yMax
|
|
273
|
+
|
|
274
|
+
) {
|
|
275
|
+
let pts = type === 'C' ? [p0, cp1, cp2, p] : [p0, cp1, p];
|
|
276
|
+
let tArr = getBezierExtremeT(pts).sort();
|
|
277
|
+
|
|
278
|
+
// avoid t split too close to start or end
|
|
279
|
+
tArr = tArr.filter(t=>t>tMin && t<tMax)
|
|
280
|
+
|
|
281
|
+
if(tArr.length){
|
|
282
|
+
let commandsSplit = splitCommandAtTValues(p0, values, tArr)
|
|
283
|
+
pathDataNew.push(...commandsSplit)
|
|
284
|
+
extremeCount += commandsSplit.length;
|
|
285
|
+
}else{
|
|
286
|
+
//console.log('no extreme: ', tArr);
|
|
287
|
+
pathDataNew.push({ type: type, values: values })
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
}
|
|
291
|
+
// no extremes
|
|
292
|
+
else {
|
|
293
|
+
pathDataNew.push({ type: type, values: values })
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return { pathData: pathDataNew, count: extremeCount };
|
|
297
|
+
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
export function addExtremePoints(pathData, tMin=0, tMax=1) {
|
|
303
|
+
let pathDataNew = [pathData[0]];
|
|
304
|
+
// previous on path point
|
|
305
|
+
let p0 = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
306
|
+
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
307
|
+
let len = pathData.length;
|
|
308
|
+
|
|
309
|
+
for (let c = 1; len && c < len; c++) {
|
|
310
|
+
let com = pathData[c];
|
|
311
|
+
//let comPrev = pathData[c - 1];
|
|
312
|
+
//let comN = pathData[c + 1] ? pathData[c + 1] : '';
|
|
313
|
+
let { type, values } = com;
|
|
314
|
+
let valsL = values.slice(-2);
|
|
315
|
+
let p = { x: valsL[0], y: valsL[1] };
|
|
316
|
+
|
|
317
|
+
if (type !== 'C' && type !== 'Q') {
|
|
318
|
+
pathDataNew.push(com)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
else {
|
|
322
|
+
// add extremes
|
|
323
|
+
if (type === 'C' || type === 'Q') {
|
|
324
|
+
let comExt = addExtemesToCommand(p0, values, tMin, tMax).pathData;
|
|
325
|
+
//console.log('comExt', comExt);
|
|
326
|
+
pathDataNew.push(...comExt )
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
p0 = { x: valsL[0], y: valsL[1] };
|
|
331
|
+
|
|
332
|
+
if (type.toLowerCase() === "z") {
|
|
333
|
+
p0 = M;
|
|
334
|
+
} else if (type === "M") {
|
|
335
|
+
M = { x: valsL[0], y: valsL[1] };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
//console.log(pathData.length, pathDataNew.length)
|
|
340
|
+
return pathDataNew;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* split commands multiple times
|
|
347
|
+
* based on command points
|
|
348
|
+
* and t array
|
|
349
|
+
*/
|
|
350
|
+
export function splitCommandAtTValues(p0, values, tArr, returnCommand = true) {
|
|
351
|
+
let segmentPoints = [];
|
|
352
|
+
|
|
353
|
+
if (!tArr.length) {
|
|
354
|
+
return false
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
let valuesL = values.length;
|
|
358
|
+
let p = { x: values[valuesL - 2], y: values[valuesL - 1] };
|
|
359
|
+
let type, cp1, cp2, points;
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
if (values.length === 2) {
|
|
363
|
+
type = 'L'
|
|
364
|
+
points = [p0, p]
|
|
365
|
+
}
|
|
366
|
+
else if (values.length === 4) {
|
|
367
|
+
type = 'Q'
|
|
368
|
+
cp1 = { x: values[0], y: values[1] };
|
|
369
|
+
points = [p0, cp1, p]
|
|
370
|
+
}
|
|
371
|
+
else if (values.length === 6) {
|
|
372
|
+
type = 'C'
|
|
373
|
+
cp1 = { x: values[0], y: values[1] };
|
|
374
|
+
cp2 = { x: values[2], y: values[3] };
|
|
375
|
+
points = [p0, cp1, cp2, p]
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
if (tArr.length) {
|
|
381
|
+
// single t
|
|
382
|
+
if (tArr.length === 1) {
|
|
383
|
+
let segs = splitCommand(points, tArr[0]);
|
|
384
|
+
let points1 = segs[0]
|
|
385
|
+
let points2 = segs[1]
|
|
386
|
+
segmentPoints.push(points1, points2)
|
|
387
|
+
//return segmentPoints;
|
|
388
|
+
} else {
|
|
389
|
+
|
|
390
|
+
// 1st segment
|
|
391
|
+
let t1 = tArr[0];
|
|
392
|
+
let seg0 = splitCommand(points, t1);
|
|
393
|
+
let points0 = seg0[0];
|
|
394
|
+
segmentPoints.push(points0)
|
|
395
|
+
points = seg0[1];
|
|
396
|
+
|
|
397
|
+
//console.log('tarr', tArr);
|
|
398
|
+
|
|
399
|
+
for (let i = 1; i < tArr.length; i++) {
|
|
400
|
+
t1 = tArr[i - 1]
|
|
401
|
+
let t2 = tArr[i]
|
|
402
|
+
|
|
403
|
+
// new t value for 2nd segment
|
|
404
|
+
let t2_1 = (t2 - t1) / (1 - t1)
|
|
405
|
+
let segs2 = splitCommand(points, t2_1);
|
|
406
|
+
segmentPoints.push(segs2[0])
|
|
407
|
+
|
|
408
|
+
if (i === tArr.length - 1) {
|
|
409
|
+
segmentPoints.push(segs2[segs2.length - 1])
|
|
410
|
+
}
|
|
411
|
+
// take 2nd segment for next splitting
|
|
412
|
+
points = segs2[1];
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (returnCommand) {
|
|
418
|
+
|
|
419
|
+
let pathData = [];
|
|
420
|
+
let com, values;
|
|
421
|
+
|
|
422
|
+
segmentPoints.forEach(seg => {
|
|
423
|
+
com = { type: '', values: [] };
|
|
424
|
+
seg.shift();
|
|
425
|
+
values = seg.map(val => { return Object.values(val) }).flat()
|
|
426
|
+
com.values = values;
|
|
427
|
+
|
|
428
|
+
// cubic
|
|
429
|
+
if (seg.length === 3) {
|
|
430
|
+
com.type = 'C';
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// quadratic
|
|
434
|
+
else if (seg.length === 2) {
|
|
435
|
+
com.type = 'Q';
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// lineto
|
|
439
|
+
else if (seg.length === 1) {
|
|
440
|
+
com.type = 'L';
|
|
441
|
+
}
|
|
442
|
+
pathData.push(com)
|
|
443
|
+
})
|
|
444
|
+
return pathData;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return segmentPoints;
|
|
448
|
+
}
|
|
449
|
+
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* serialize pathData array to
|
|
4
|
+
* d attribute string
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export function pathDataToD(pathData, optimize = 0) {
|
|
8
|
+
|
|
9
|
+
optimize = parseFloat(optimize)
|
|
10
|
+
|
|
11
|
+
let beautify = optimize > 1;
|
|
12
|
+
let minify = beautify || optimize ? false : true;
|
|
13
|
+
|
|
14
|
+
// Convert first "M" to "m" if followed by "l" (when minified)
|
|
15
|
+
if (pathData[1].type === "l" && minify) {
|
|
16
|
+
pathData[0].type = "m";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let d = '';
|
|
20
|
+
let suff = beautify ? `\n` : ' ';
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if (minify) {
|
|
24
|
+
d = `${pathData[0].type} ${pathData[0].values.join(" ")}`;
|
|
25
|
+
} else {
|
|
26
|
+
d = `${pathData[0].type} ${pathData[0].values.join(" ")}${suff}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
for (let i = 1, len = pathData.length; i < len; i++) {
|
|
30
|
+
let com0 = pathData[i - 1];
|
|
31
|
+
let com = pathData[i];
|
|
32
|
+
let { type, values } = com;
|
|
33
|
+
|
|
34
|
+
// Minify Arc commands (A/a) – actually sucks!
|
|
35
|
+
if (minify && (type === 'A' || type === 'a')) {
|
|
36
|
+
values = [
|
|
37
|
+
values[0], values[1], values[2],
|
|
38
|
+
`${values[3]}${values[4]}${values[5]}`,
|
|
39
|
+
values[6]
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Omit type for repeated commands
|
|
44
|
+
type = (com0.type === com.type && com.type.toLowerCase() !== 'm' && minify)
|
|
45
|
+
? " "
|
|
46
|
+
: (
|
|
47
|
+
(com0.type === "m" && com.type === "l") ||
|
|
48
|
+
(com0.type === "M" && com.type === "l") ||
|
|
49
|
+
(com0.type === "M" && com.type === "L")
|
|
50
|
+
) && minify
|
|
51
|
+
? " "
|
|
52
|
+
: com.type;
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
// concatenate subsequent floating point values
|
|
56
|
+
if (minify) {
|
|
57
|
+
|
|
58
|
+
//console.log(optimize, beautify, minify);
|
|
59
|
+
|
|
60
|
+
let valsString = '';
|
|
61
|
+
let prevWasFloat = false;
|
|
62
|
+
|
|
63
|
+
for (let v = 0, l = values.length; v < l; v++) {
|
|
64
|
+
let val = values[v];
|
|
65
|
+
let valStr = val.toString();
|
|
66
|
+
let isFloat = valStr.includes('.');
|
|
67
|
+
let isSmallFloat = isFloat && Math.abs(val) < 1;
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
// Remove leading zero from small floats *only* if the previous was also a float
|
|
71
|
+
if (isSmallFloat && prevWasFloat) {
|
|
72
|
+
valStr = valStr.replace(/^0\./, '.');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Add space unless this is the first value OR previous was a small float
|
|
76
|
+
if (v > 0 && !(prevWasFloat && isSmallFloat)) {
|
|
77
|
+
valsString += ' ';
|
|
78
|
+
}
|
|
79
|
+
//console.log(isSmallFloat, prevWasFloat, valStr);
|
|
80
|
+
|
|
81
|
+
valsString += valStr
|
|
82
|
+
//.replace(/-0./g, '-.').replace(/ -./g, '-.')
|
|
83
|
+
prevWasFloat = isSmallFloat;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
//console.log('minify', valsString);
|
|
87
|
+
d += `${type}${valsString}`;
|
|
88
|
+
|
|
89
|
+
}
|
|
90
|
+
// regular non-minified output
|
|
91
|
+
else {
|
|
92
|
+
d += `${type} ${values.join(' ')}${suff}`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (minify) {
|
|
97
|
+
d = d
|
|
98
|
+
.replace(/ 0\./g, " .") // Space before small decimals
|
|
99
|
+
.replace(/ -/g, "-") // Remove space before negatives
|
|
100
|
+
.replace(/-0\./g, "-.") // Remove leading zero from negative decimals
|
|
101
|
+
.replace(/Z/g, "z"); // Convert uppercase 'Z' to lowercase
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return d;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
export function pathDataToD_0(pathData, decimals = -1, minify = false) {
|
|
109
|
+
// implicit l command
|
|
110
|
+
if (pathData[1].type === "l" && minify) {
|
|
111
|
+
pathData[0].type = "m";
|
|
112
|
+
}
|
|
113
|
+
let d = `${pathData[0].type}${pathData[0].values.join(" ")}`;
|
|
114
|
+
|
|
115
|
+
for (let i = 1; i < pathData.length; i++) {
|
|
116
|
+
let com0 = pathData[i - 1];
|
|
117
|
+
let com = pathData[i];
|
|
118
|
+
|
|
119
|
+
let type = (com0.type === com.type && minify) ?
|
|
120
|
+
" " : (
|
|
121
|
+
(com0.type === "m" && com.type === "l") ||
|
|
122
|
+
(com0.type === "M" && com.type === "l") ||
|
|
123
|
+
(com0.type === "M" && com.type === "L")
|
|
124
|
+
) && minify ?
|
|
125
|
+
" " : com.type;
|
|
126
|
+
|
|
127
|
+
// round
|
|
128
|
+
if (com.values.length && decimals > -1) {
|
|
129
|
+
com.values = com.values.map(val => { return +val.toFixed(decimals) })
|
|
130
|
+
}
|
|
131
|
+
d += `${type}${com.values.join(" ")}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
if (minify) {
|
|
136
|
+
d = d
|
|
137
|
+
.replaceAll(" 0.", " .")
|
|
138
|
+
.replaceAll(" -", "-")
|
|
139
|
+
.replaceAll("-0.", "-.")
|
|
140
|
+
.replace(/\s+([mlcsqtahvz])/gi, "$1")
|
|
141
|
+
.replaceAll("Z", "z");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return d;
|
|
145
|
+
}
|
|
146
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { pointAtT } from "./geometry";
|
|
2
|
+
import { getPolyBBox } from "./geometry_bbox";
|
|
3
|
+
import { addDimensionData, analyzePathData } from "./pathData_analyze";
|
|
4
|
+
import { addExtremePoints } from "./pathData_split";
|
|
5
|
+
|
|
6
|
+
export function pathDataToPolyPlus(pathDataArr = [], addExtremes=true) {
|
|
7
|
+
|
|
8
|
+
// check if splitting sub paths is required
|
|
9
|
+
pathDataArr = Object.hasOwnProperty(pathDataArr[0].type) ? splitSubpaths(pathDataArr) : pathDataArr;
|
|
10
|
+
|
|
11
|
+
let dimMin = Infinity;
|
|
12
|
+
let dimMax = 0;
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* add extremes to beziers
|
|
17
|
+
* to reproduce the shape better
|
|
18
|
+
*/
|
|
19
|
+
if(addExtremes){
|
|
20
|
+
pathDataArr.forEach((pathData, i) => {
|
|
21
|
+
|
|
22
|
+
//pathDataArr[i] = addExtremePoints(pathData)
|
|
23
|
+
let pathDataE = addExtremePoints(pathData)
|
|
24
|
+
|
|
25
|
+
//pathDataArr[i] = analyzePathData(pathDataE).pathData
|
|
26
|
+
pathDataArr[i] = addDimensionData(pathDataE)
|
|
27
|
+
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* approximate min and max segment sizes
|
|
34
|
+
* for segment splitting
|
|
35
|
+
*/
|
|
36
|
+
pathDataArr.forEach(pathData => {
|
|
37
|
+
|
|
38
|
+
let dimArr = pathData.filter(com => com.dimA).sort((a, b) => a.dimA - b.DimA)
|
|
39
|
+
let dimMinL = dimArr[0].dimA
|
|
40
|
+
let dimMaxL = dimArr[dimArr.length - 1].dimA
|
|
41
|
+
//console.log('dimArr', dimArr, dimMaxL);
|
|
42
|
+
if (dimMinL && dimMinL < dimMin) dimMin = dimMinL;
|
|
43
|
+
if (dimMaxL && dimMaxL > dimMax) dimMax = dimMaxL;
|
|
44
|
+
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
//console.log(dimMin, dimMax);
|
|
48
|
+
|
|
49
|
+
// find split point based on smallest point distance
|
|
50
|
+
dimMin = (dimMin * 2 + dimMax) / 2 * 0.5
|
|
51
|
+
|
|
52
|
+
// collect vertices
|
|
53
|
+
let polyArr = [];
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
pathDataArr.forEach(pathData => {
|
|
57
|
+
|
|
58
|
+
let poly = [pathData[0].p0];
|
|
59
|
+
|
|
60
|
+
pathData.forEach(com => {
|
|
61
|
+
|
|
62
|
+
let { type, values, dimA = null, p0, p, cp1 = null, cp2 = null } = com;
|
|
63
|
+
let split = type === 'C' && dimA ? Math.ceil(dimA / dimMin) : 0;
|
|
64
|
+
|
|
65
|
+
if (type === 'C' && split) {
|
|
66
|
+
|
|
67
|
+
let splitT = 1 / split;
|
|
68
|
+
|
|
69
|
+
for (let i = 1; i < split; i++) {
|
|
70
|
+
|
|
71
|
+
let t = splitT * i;
|
|
72
|
+
let ptI = pointAtT([p0, cp1, cp2, p], t)
|
|
73
|
+
poly.push(ptI)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
}
|
|
77
|
+
poly.push(p)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
polyArr.push(poly)
|
|
81
|
+
|
|
82
|
+
/*
|
|
83
|
+
let bb = getPolyBBox(poly);
|
|
84
|
+
let dimAv = (bb.width+bb.height)/2
|
|
85
|
+
console.log('dimAv', dimAv);
|
|
86
|
+
*/
|
|
87
|
+
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
return polyArr
|
|
91
|
+
|
|
92
|
+
}
|