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.
@@ -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 = [