svg-path-simplify 0.4.1 → 0.4.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/CHANGELOG.md +9 -0
- package/dist/svg-path-simplify.esm.js +528 -165
- package/dist/svg-path-simplify.esm.min.js +2 -2
- package/dist/svg-path-simplify.js +528 -165
- package/dist/svg-path-simplify.min.js +2 -2
- package/dist/svg-path-simplify.pathdata.esm.js +40 -0
- package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
- package/index.html +79 -16
- package/package.json +5 -2
- package/src/pathSimplify-main.js +15 -4
- package/src/string_helpers.js +18 -0
- package/src/svgii/convert_units.js +8 -2
- package/src/svgii/pathData_convert.js +38 -0
- package/src/svgii/pathData_parse_els.js +135 -133
- package/src/svgii/svg-styles-to-attributes-const.js +1 -0
- package/src/svgii/svg_cleanup.js +328 -36
- package/src/svgii/svg_el_parse_style_props.js +29 -3
- package/tests/testSVG_shape.js +59 -0
- package/tests/testSVG_transform.js +61 -0
|
@@ -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,
|
|
@@ -86,6 +87,7 @@ export function convertPathData(pathData, {
|
|
|
86
87
|
} = {}) {
|
|
87
88
|
|
|
88
89
|
|
|
90
|
+
let pathDataAbs = []
|
|
89
91
|
|
|
90
92
|
// pathdata properties - test= true adds a manual test
|
|
91
93
|
if (testTypes) {
|
|
@@ -120,14 +122,39 @@ export function convertPathData(pathData, {
|
|
|
120
122
|
//console.log(toShorthands, toRelative, decimals);
|
|
121
123
|
if (hasQuadratics && quadraticToCubic) pathData = pathDataQuadraticToCubic(pathData);
|
|
122
124
|
|
|
125
|
+
if(toMixed) toRelative = true
|
|
123
126
|
|
|
124
127
|
// pre round - before relative conversion to minimize distortions
|
|
125
128
|
if (decimals > -1 && toRelative) pathData = roundPathData(pathData, decimals);
|
|
126
129
|
|
|
130
|
+
// clone absolute pathdata
|
|
131
|
+
if(toMixed){
|
|
132
|
+
pathDataAbs = JSON.parse(JSON.stringify(pathData))
|
|
133
|
+
}
|
|
127
134
|
|
|
128
135
|
if (toRelative) pathData = pathDataToRelative(pathData);
|
|
129
136
|
if (decimals > -1) pathData = roundPathData(pathData, decimals);
|
|
130
137
|
|
|
138
|
+
|
|
139
|
+
// choose most compact commands: relative or absolute
|
|
140
|
+
if(toMixed){
|
|
141
|
+
for(let i=0; i<pathData.length; i++){
|
|
142
|
+
let com = pathData[i]
|
|
143
|
+
let comA = pathDataAbs[i]
|
|
144
|
+
// compare Lengths
|
|
145
|
+
let comStr = [com.type, com.values.join(' ')].join('').replaceAll(' -', '-').replaceAll(' 0.', ' .')
|
|
146
|
+
let comStrA = [comA.type, comA.values.join(' ')].join('').replaceAll(' -', '-').replaceAll(' 0.', ' .')
|
|
147
|
+
|
|
148
|
+
let lenR = comStr.length;
|
|
149
|
+
let lenA = comStrA.length;
|
|
150
|
+
|
|
151
|
+
if(lenA<lenR){
|
|
152
|
+
//console.log('absolute is shorter', comStrA, comStr);
|
|
153
|
+
pathData[i] = pathDataAbs[i]
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
131
158
|
return pathData
|
|
132
159
|
}
|
|
133
160
|
|
|
@@ -138,6 +165,9 @@ export function convertPathData(pathData, {
|
|
|
138
165
|
*/
|
|
139
166
|
|
|
140
167
|
export function optimizeArcPathData(pathData = []) {
|
|
168
|
+
|
|
169
|
+
let remove =[]
|
|
170
|
+
|
|
141
171
|
pathData.forEach((com, i) => {
|
|
142
172
|
let { type, values } = com;
|
|
143
173
|
if (type === 'A') {
|
|
@@ -149,6 +179,12 @@ export function optimizeArcPathData(pathData = []) {
|
|
|
149
179
|
//largeArc=true
|
|
150
180
|
//let pMid = {x: Math.abs(x-x0), y:Math.abs(y-y0)}
|
|
151
181
|
|
|
182
|
+
if(rx===0 || ry===0){
|
|
183
|
+
pathData[i]= null
|
|
184
|
+
remove.push(i)
|
|
185
|
+
//console.log('!!!');
|
|
186
|
+
}
|
|
187
|
+
|
|
152
188
|
// rx and ry are large enough
|
|
153
189
|
if (rx >= 1 && (x === x0 || y === y0)) {
|
|
154
190
|
let diff = Math.abs(rx - ry) / rx;
|
|
@@ -171,6 +207,8 @@ export function optimizeArcPathData(pathData = []) {
|
|
|
171
207
|
}
|
|
172
208
|
}
|
|
173
209
|
})
|
|
210
|
+
|
|
211
|
+
if(remove.length) pathData = pathData.filter(Boolean)
|
|
174
212
|
return pathData;
|
|
175
213
|
}
|
|
176
214
|
|
|
@@ -17,137 +17,6 @@ import { attLookup } from './svg-styles-to-attributes-const.js';
|
|
|
17
17
|
import { qrDecomposeMatrix } from './transform_qr_decompose.js';
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
export function pathElToShape(el, {
|
|
21
|
-
convert_rects = false,
|
|
22
|
-
convert_ellipses = false,
|
|
23
|
-
convert_poly = false,
|
|
24
|
-
convert_lines = false
|
|
25
|
-
} = {}) {
|
|
26
|
-
|
|
27
|
-
//console.log('pathElToShape', convert_rects, convert_ellipses, convert_lines );
|
|
28
|
-
|
|
29
|
-
let pathData = parsePathDataNormalized(el.getAttribute('d'));
|
|
30
|
-
let coms = Array.from(new Set(pathData.map(com => com.type))).join('')
|
|
31
|
-
|
|
32
|
-
let hasArcs = (/[a]/gi).test(coms)
|
|
33
|
-
let hasBeziers = (/[csqt]/gi).test(coms)
|
|
34
|
-
let hasLines = (/[l]/gi).test(coms)
|
|
35
|
-
let isPoly = !(/[acqts]/gi).test(coms)
|
|
36
|
-
let closed = (/[z]/gi).test(coms)
|
|
37
|
-
let shape = null;
|
|
38
|
-
let type = null
|
|
39
|
-
|
|
40
|
-
let attributes = getElementAtts(el)
|
|
41
|
-
let attsNew = {}
|
|
42
|
-
let decimals = 7;
|
|
43
|
-
|
|
44
|
-
if (isPoly) {
|
|
45
|
-
|
|
46
|
-
// is line
|
|
47
|
-
if (pathData.length === 2 && convert_lines) {
|
|
48
|
-
type = 'line'
|
|
49
|
-
shape = document.createElementNS(svgNs, type)
|
|
50
|
-
let [x1, y1, x2, y2] = [...pathData[0].values, ...pathData[1].values].map(val => roundTo(val, decimals))
|
|
51
|
-
attsNew = { x1, y1, x2, y2 }
|
|
52
|
-
}
|
|
53
|
-
// polygon, polyline or rect
|
|
54
|
-
else {
|
|
55
|
-
|
|
56
|
-
let vertices = getPathDataVertices(pathData);
|
|
57
|
-
let bb = getPolyBBox(vertices)
|
|
58
|
-
let areaPoly = getPolygonArea(vertices, true)
|
|
59
|
-
let areaRect = bb.width * bb.height;
|
|
60
|
-
let areaDiff = Math.abs(1 - areaRect / areaPoly);
|
|
61
|
-
|
|
62
|
-
// is rect
|
|
63
|
-
if (convert_rects && areaDiff < 0.01) {
|
|
64
|
-
type = 'rect'
|
|
65
|
-
shape = document.createElementNS(svgNs, type)
|
|
66
|
-
let { x, y, width, height } = bb
|
|
67
|
-
attsNew = { x, y, width, height }
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
// polyline or polygon
|
|
71
|
-
else if(convert_poly) {
|
|
72
|
-
type = closed ? 'polygon' : 'polyline';
|
|
73
|
-
shape = document.createElementNS(svgNs, type)
|
|
74
|
-
let points = vertices.map(pt => { return [pt.x, pt.y] }).flat().map(val => roundTo(val, decimals)).join(' ')
|
|
75
|
-
attsNew = { points }
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
// circles or ellipses
|
|
80
|
-
else if (!hasLines && convert_ellipses) {
|
|
81
|
-
|
|
82
|
-
// try to convert cubics to arcs
|
|
83
|
-
if (!hasArcs && hasBeziers) {
|
|
84
|
-
pathData = pathDataCubicsToArc(pathData, { areaThreshold: 2.5 })
|
|
85
|
-
hasArcs = pathData.filter(com => com.type === 'A').length;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (hasArcs) {
|
|
90
|
-
let pathData2 = getPathDataVerbose(pathData, { addArcParams: true })
|
|
91
|
-
let arcComs = pathData2.filter(com => com.type === 'A')
|
|
92
|
-
|
|
93
|
-
let cxVals = new Set();
|
|
94
|
-
let cyVals = new Set();
|
|
95
|
-
let rxVals = new Set();
|
|
96
|
-
let ryVals = new Set();
|
|
97
|
-
|
|
98
|
-
if (arcComs.length > 1) {
|
|
99
|
-
//console.log('!!!arcComs', arcComs);
|
|
100
|
-
pathData2.forEach(com => {
|
|
101
|
-
if (com.type === 'A') {
|
|
102
|
-
//console.log('params', com, com.cx, com.cy, com.rx, com.ry);
|
|
103
|
-
cxVals.add(roundTo(com.cx, decimals))
|
|
104
|
-
cyVals.add(roundTo(com.cy, decimals))
|
|
105
|
-
rxVals.add(roundTo(com.rx, decimals))
|
|
106
|
-
ryVals.add(roundTo(com.ry, decimals))
|
|
107
|
-
}
|
|
108
|
-
})
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
cxVals = Array.from(cxVals)
|
|
112
|
-
cyVals = Array.from(cyVals)
|
|
113
|
-
rxVals = Array.from(rxVals)
|
|
114
|
-
ryVals = Array.from(ryVals)
|
|
115
|
-
|
|
116
|
-
if(cxVals.length===1 && cyVals.length===1 && rxVals.length===1 && ryVals.length===1){
|
|
117
|
-
let [rx, ry, cx, cy] = [rxVals[0], ryVals[0], cxVals[0], cyVals[0]]
|
|
118
|
-
type = rx===ry ? 'circle' : 'ellipse';
|
|
119
|
-
shape = document.createElementNS(svgNs, type)
|
|
120
|
-
attsNew = type==='circle' ? { r:rx, cx, cy } : {rx, ry, cx, cy}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// if el could be replaced
|
|
127
|
-
if (shape) {
|
|
128
|
-
let ignore = ['id', 'class']
|
|
129
|
-
|
|
130
|
-
// set shape attributes
|
|
131
|
-
for (let att in attsNew) {
|
|
132
|
-
shape.setAttribute(att, attsNew[att])
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// copy old attributes
|
|
136
|
-
for (let att in attributes) {
|
|
137
|
-
//console.log(attributes);
|
|
138
|
-
if (attLookup.atts[att].includes(type) || ignore.includes(att) || att.startsWith('data-')) {
|
|
139
|
-
shape.setAttribute(att, attributes[att])
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// replace
|
|
144
|
-
el = shape;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return el;
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
|
|
151
20
|
export function shapeElToPath(el, { width = 0,
|
|
152
21
|
height = 0,
|
|
153
22
|
convert_rects = false,
|
|
@@ -233,7 +102,6 @@ export function getPathDataFromEl(el, {
|
|
|
233
102
|
|
|
234
103
|
// convert relative and physical units to user-units
|
|
235
104
|
let atts = svgElUnitsToPixel(el, { width, height })
|
|
236
|
-
//console.log('atts', atts);
|
|
237
105
|
|
|
238
106
|
switch (type) {
|
|
239
107
|
case 'path':
|
|
@@ -339,4 +207,138 @@ export function getPathDataFromEl(el, {
|
|
|
339
207
|
|
|
340
208
|
return stringify ? stringifyPathData(pathData) : pathData;
|
|
341
209
|
|
|
342
|
-
};
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
export function pathElToShape(el, {
|
|
216
|
+
convert_rects = false,
|
|
217
|
+
convert_ellipses = false,
|
|
218
|
+
convert_poly = false,
|
|
219
|
+
convert_lines = false
|
|
220
|
+
} = {}) {
|
|
221
|
+
|
|
222
|
+
//console.log('pathElToShape', convert_rects, convert_ellipses, convert_lines );
|
|
223
|
+
|
|
224
|
+
let pathData = parsePathDataNormalized(el.getAttribute('d'));
|
|
225
|
+
let coms = Array.from(new Set(pathData.map(com => com.type))).join('')
|
|
226
|
+
|
|
227
|
+
let hasArcs = (/[a]/gi).test(coms)
|
|
228
|
+
let hasBeziers = (/[csqt]/gi).test(coms)
|
|
229
|
+
let hasLines = (/[l]/gi).test(coms)
|
|
230
|
+
let isPoly = !(/[acqts]/gi).test(coms)
|
|
231
|
+
let closed = (/[z]/gi).test(coms)
|
|
232
|
+
let shape = null;
|
|
233
|
+
let type = null
|
|
234
|
+
|
|
235
|
+
let attributes = getElementAtts(el)
|
|
236
|
+
let attsNew = {}
|
|
237
|
+
let decimals = 7;
|
|
238
|
+
|
|
239
|
+
if (isPoly) {
|
|
240
|
+
|
|
241
|
+
// is line
|
|
242
|
+
if (pathData.length === 2 && convert_lines) {
|
|
243
|
+
type = 'line'
|
|
244
|
+
shape = document.createElementNS(svgNs, type)
|
|
245
|
+
let [x1, y1, x2, y2] = [...pathData[0].values, ...pathData[1].values].map(val => roundTo(val, decimals))
|
|
246
|
+
attsNew = { x1, y1, x2, y2 }
|
|
247
|
+
}
|
|
248
|
+
// polygon, polyline or rect
|
|
249
|
+
else {
|
|
250
|
+
|
|
251
|
+
let vertices = getPathDataVertices(pathData);
|
|
252
|
+
let bb = getPolyBBox(vertices)
|
|
253
|
+
let areaPoly = getPolygonArea(vertices, true)
|
|
254
|
+
let areaRect = bb.width * bb.height;
|
|
255
|
+
let areaDiff = Math.abs(1 - areaRect / areaPoly);
|
|
256
|
+
|
|
257
|
+
// is rect
|
|
258
|
+
if (convert_rects && areaDiff < 0.01) {
|
|
259
|
+
type = 'rect'
|
|
260
|
+
shape = document.createElementNS(svgNs, type)
|
|
261
|
+
let { x, y, width, height } = bb
|
|
262
|
+
attsNew = { x, y, width, height }
|
|
263
|
+
|
|
264
|
+
}
|
|
265
|
+
// polyline or polygon
|
|
266
|
+
else if(convert_poly) {
|
|
267
|
+
type = closed ? 'polygon' : 'polyline';
|
|
268
|
+
shape = document.createElementNS(svgNs, type)
|
|
269
|
+
let points = vertices.map(pt => { return [pt.x, pt.y] }).flat().map(val => roundTo(val, decimals)).join(' ')
|
|
270
|
+
attsNew = { points }
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// circles or ellipses
|
|
275
|
+
else if (!hasLines && convert_ellipses) {
|
|
276
|
+
|
|
277
|
+
// try to convert cubics to arcs
|
|
278
|
+
if (!hasArcs && hasBeziers) {
|
|
279
|
+
pathData = pathDataCubicsToArc(pathData, { areaThreshold: 2.5 })
|
|
280
|
+
hasArcs = pathData.filter(com => com.type === 'A').length;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
if (hasArcs) {
|
|
285
|
+
let pathData2 = getPathDataVerbose(pathData, { addArcParams: true })
|
|
286
|
+
let arcComs = pathData2.filter(com => com.type === 'A')
|
|
287
|
+
|
|
288
|
+
let cxVals = new Set();
|
|
289
|
+
let cyVals = new Set();
|
|
290
|
+
let rxVals = new Set();
|
|
291
|
+
let ryVals = new Set();
|
|
292
|
+
|
|
293
|
+
if (arcComs.length > 1) {
|
|
294
|
+
//console.log('!!!arcComs', arcComs);
|
|
295
|
+
pathData2.forEach(com => {
|
|
296
|
+
if (com.type === 'A') {
|
|
297
|
+
//console.log('params', com, com.cx, com.cy, com.rx, com.ry);
|
|
298
|
+
cxVals.add(roundTo(com.cx, decimals))
|
|
299
|
+
cyVals.add(roundTo(com.cy, decimals))
|
|
300
|
+
rxVals.add(roundTo(com.rx, decimals))
|
|
301
|
+
ryVals.add(roundTo(com.ry, decimals))
|
|
302
|
+
}
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
cxVals = Array.from(cxVals)
|
|
307
|
+
cyVals = Array.from(cyVals)
|
|
308
|
+
rxVals = Array.from(rxVals)
|
|
309
|
+
ryVals = Array.from(ryVals)
|
|
310
|
+
|
|
311
|
+
if(cxVals.length===1 && cyVals.length===1 && rxVals.length===1 && ryVals.length===1){
|
|
312
|
+
let [rx, ry, cx, cy] = [rxVals[0], ryVals[0], cxVals[0], cyVals[0]]
|
|
313
|
+
type = rx===ry ? 'circle' : 'ellipse';
|
|
314
|
+
shape = document.createElementNS(svgNs, type)
|
|
315
|
+
attsNew = type==='circle' ? { r:rx, cx, cy } : {rx, ry, cx, cy}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
// if el could be replaced
|
|
322
|
+
if (shape) {
|
|
323
|
+
let ignore = ['id', 'class']
|
|
324
|
+
|
|
325
|
+
// set shape attributes
|
|
326
|
+
for (let att in attsNew) {
|
|
327
|
+
shape.setAttribute(att, attsNew[att])
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// copy old attributes
|
|
331
|
+
for (let att in attributes) {
|
|
332
|
+
//console.log(attributes);
|
|
333
|
+
if (attLookup.atts[att].includes(type) || ignore.includes(att) || att.startsWith('data-')) {
|
|
334
|
+
shape.setAttribute(att, attributes[att])
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// replace
|
|
339
|
+
el = shape;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return el;
|
|
343
|
+
|
|
344
|
+
}
|
|
@@ -19,6 +19,7 @@ export const transHorizontal = ['scaleX', 'translateX', 'skewX'];
|
|
|
19
19
|
export const transVertical = ['scaleY', 'translateY', 'skewY'];
|
|
20
20
|
|
|
21
21
|
export const colorProps = ['fill', 'stroke', 'stop-color'];
|
|
22
|
+
export const geometryProps = ['d', 'points', 'cx', 'cy', 'x1', 'x2', 'y1', 'y2', 'width', 'height', 'r', 'rx', 'ry', 'x', 'y'];
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
export const geometryEls = [
|