svg-path-simplify 0.3.4 → 0.3.6

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.
Files changed (39) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.yml +28 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.yml +35 -0
  3. package/dist/svg-path-simplify.esm.js +4104 -3481
  4. package/dist/svg-path-simplify.esm.min.js +2 -8
  5. package/dist/svg-path-simplify.js +4105 -3480
  6. package/dist/svg-path-simplify.min.js +2 -8
  7. package/dist/svg-path-simplify.pathdata.esm.js +1090 -1039
  8. package/dist/svg-path-simplify.pathdata.esm.min.js +2 -8
  9. package/index.html +493 -116
  10. package/package.json +1 -1
  11. package/site.webmanifest +21 -0
  12. package/src/constants.js +3 -1
  13. package/src/index.js +7 -1
  14. package/src/pathData_simplify_cubic.js +1 -10
  15. package/src/pathSimplify-main.js +71 -28
  16. package/src/pathSimplify-only-pathdata.js +2 -2
  17. package/src/svg-getAttributes.js +13 -0
  18. package/src/svg_flatten_transforms.js +43 -0
  19. package/src/svg_getViewbox.js +23 -11
  20. package/src/svg_rootSVG.js +9 -0
  21. package/src/svgii/convert_colors.js +98 -0
  22. package/src/svgii/convert_units.js +144 -0
  23. package/src/svgii/geometry.js +24 -4
  24. package/src/svgii/geometry_bbox.js +2 -1
  25. package/src/svgii/geometry_bbox_element.js +46 -0
  26. package/src/svgii/pathData_analyze.js +1 -1
  27. package/src/svgii/pathData_convert.js +143 -29
  28. package/src/svgii/pathData_parse.js +2 -99
  29. package/src/svgii/pathData_parse_els.js +198 -125
  30. package/src/svgii/pathData_simplify_refineCorners.js +72 -43
  31. package/src/svgii/pathData_stringify.js +6 -5
  32. package/src/svgii/poly_normalize.js +21 -1
  33. package/src/svgii/rounding.js +36 -5
  34. package/src/svgii/svg-styles-getTransforms.js +43 -5
  35. package/src/svgii/svg-styles-to-attributes-const.js +8 -3
  36. package/src/svgii/svg-styles-to-attributes.js +106 -9
  37. package/src/svgii/svg_cleanup.js +291 -35
  38. package/src/svgii/svg_el_parse_style_props.js +423 -0
  39. package/src/svgii/stringify.js +0 -103
@@ -0,0 +1,423 @@
1
+ /**
2
+ * parse svg presentational attributes
3
+ * or CSS styles
4
+ */
5
+
6
+ import { rad2Deg } from "../constants";
7
+ import { getUnit, isNumericValue, normalizeUnits } from "./convert_units";
8
+ import { autoRound } from "./rounding";
9
+ import { attLookup, horizontalProps, strokeAtts, verticalProps } from "./svg-styles-to-attributes-const";
10
+
11
+ export function parseStylesProperties(el, {
12
+ removeNameSpaced = true,
13
+ autoRoundValues = true,
14
+ removeInvalid = true,
15
+ removeDefaults = true,
16
+ cleanUpStrokes = true,
17
+ exclude = [],
18
+ width = 0,
19
+ height = 0,
20
+ } = {}) {
21
+
22
+ let nodeName = el.nodeName.toLowerCase();
23
+ let attProps = getSvgPresentationAtts(el)
24
+ let cssProps = getSvgCssProps(el)
25
+
26
+ console.log('cssProps', cssProps);
27
+
28
+ /**
29
+ * merge props
30
+ * CSS has higher specificity
31
+ */
32
+ let props = {
33
+ ...attProps,
34
+ ...cssProps,
35
+ }
36
+
37
+ delete props['style'];
38
+ exclude.push('style')
39
+
40
+ let remove = ['style']
41
+
42
+ let transformsStandalone = ['scale', 'translate', 'rotate'];
43
+
44
+ //let testProp = normalizeUnits(0.5, {unit:'turn'})
45
+ //console.log('testProp', testProp);
46
+
47
+
48
+ /**
49
+ * remove invalid properties
50
+ * e.g font-family for <path>
51
+ */
52
+
53
+ if (removeInvalid || removeDefaults || removeNameSpaced) {
54
+ let propsFilteredObj = filterSvgElProps(nodeName, props, { removeDefaults, removeNameSpaced, exclude, cleanUpStrokes, include: transformsStandalone })
55
+ props = propsFilteredObj.propsFiltered
56
+ remove.push(...propsFilteredObj.remove)
57
+ }
58
+
59
+ //console.log('!!!props', props, remove);
60
+
61
+ // sanitized prop array
62
+ let propArr = []
63
+
64
+ for (let prop in props) {
65
+
66
+ let valueStr = props[prop];
67
+
68
+ // we parse the path data separately
69
+ if (prop === 'd') continue;
70
+
71
+ let item = { prop, values: [] }
72
+
73
+ if (prop === 'transform') {
74
+ //let regex = /(\w+)\(([^)]+)\)/g;
75
+ //let match;
76
+ let transArr = []
77
+
78
+ //split transform functions
79
+ let transFormFunctions = valueStr.split(/(\w+)\(([^)]+)\)/).map(val => val.trim()).filter(Boolean)
80
+ //console.log(transFormFunctions);
81
+
82
+ for (let i = 1; i < transFormFunctions.length; i += 2) {
83
+ let fn = transFormFunctions[i - 1];
84
+ let values = transFormFunctions[i].split(/,| /).filter(Boolean)
85
+ let transItem = { fn, values: [] }
86
+
87
+ for (let v = 0; v < values.length; v++) {
88
+ //let { value, unit } = parseValue(values[v])
89
+ let transValues = parseValue(values[v])
90
+ //console.log('!!!transValues', transValues);
91
+ transItem.values.push(...transValues)
92
+ //transItem.units.push(unit)
93
+ }
94
+ transArr.push(transItem)
95
+ }
96
+
97
+ //item.transforms = transArr;
98
+ if (transArr.length) {
99
+ propArr.push({ prop: 'transforms', values: transArr })
100
+ }
101
+ //console.log('transArr', transArr);
102
+ }
103
+ // other propa
104
+ else {
105
+ item.values = parseValue(valueStr);
106
+ //item[prop] = (valueStr);
107
+ }
108
+
109
+ if (item.values.length) {
110
+ propArr.push(item)
111
+ }
112
+
113
+ }
114
+
115
+ /**
116
+ * normalize values to
117
+ * user units
118
+ */
119
+
120
+ console.log('!!!propArr', propArr);
121
+
122
+ let propsNorm = {}
123
+ let transFormOrigin = []
124
+
125
+ for (let i = 0; i < propArr.length; i++) {
126
+ let item = propArr[i];
127
+ let { prop, values } = item;
128
+ //let itemN = {prop}
129
+ let valsNew = [], valX = 0, valY = 0, unitX = '', unitY = '';
130
+
131
+ if (prop !== 'transforms') {
132
+ //console.log('---prop', prop, values, width, height);
133
+
134
+ if (prop === 'transform-origin') {
135
+
136
+ values.forEach((item, i) => {
137
+ let val = item.value
138
+ if (val === 'left') values[i].value = 0;
139
+ else if (val === 'right') values[i].value = width;
140
+ else if (val === 'top') values[i].value = 0;
141
+ else if (val === 'bottom') values[i].value = height;
142
+ else if (val === 'center') values[i].value = '50%';
143
+ })
144
+
145
+ valX = values[0].value;
146
+ valY = values[1] ? values[1].value : valX;
147
+ unitX = values[0].unit;
148
+ unitY = values[1] ? values[1].unit : unitX;
149
+
150
+ // normalize units for matrix calculation
151
+ valX = normalizeUnits(valX, { unit: unitX, width, height, isHorizontal: true, autoRoundValues })
152
+ valY = normalizeUnits(valY, { unit: unitY, width, height, isVertical: true, autoRoundValues })
153
+
154
+ transFormOrigin.push(valX, valY)
155
+
156
+
157
+ } else {
158
+
159
+ for (let v = 0; v < values.length; v++) {
160
+ let val = values[v];
161
+
162
+ let unit = val.unit[v];
163
+ let valAbs = val.value;
164
+
165
+ let isHorizontal = horizontalProps.includes(prop)
166
+ let isVertical = verticalProps.includes(prop)
167
+
168
+ if (unit) {
169
+ valAbs = normalizeUnits(val.value, { unit, width, height, isHorizontal, isVertical, autoRoundValues })
170
+ //if (autoRoundValues) valAbs = autoRound(valAbs)
171
+ }
172
+
173
+ valsNew.push(valAbs)
174
+ }
175
+
176
+ }
177
+
178
+ //propsNorm[prop] = valsNew.length === 1 ? valsNew[0] : valsNew;
179
+ if (valsNew.length) propsNorm[prop] = valsNew;
180
+
181
+ } else {
182
+
183
+ let transforms = values || []
184
+ //console.log('transforms', transforms, prop, item);
185
+
186
+ let len = transforms.length
187
+ let transFormAll = []
188
+ for (let t = 0; len && t < len; t++) {
189
+ let { fn, values } = transforms[t];
190
+ let valsN = [], valX = 0, valY = 0, unitX = '', unitY = '', transformFunction = [];
191
+
192
+ // console.log('!!!values', values);
193
+ if (fn === 'scale' || fn === 'translate') {
194
+ valX = values[0].value;
195
+ valY = values[1] ? values[1].value : valX;
196
+ unitX = values[0].unit;
197
+ unitY = values[1] ? values[1].unit : unitX;
198
+
199
+ if (fn === 'scale') {
200
+ valX = unitX = '%' ? valX / 100 : valX
201
+ valY = unitY = '%' ? valY / 100 : valY
202
+
203
+ } else {
204
+ valX = normalizeUnits(valX, { unit: unitX, width, height, isHorizontal: true, autoRoundValues })
205
+ valY = normalizeUnits(valY, { unit: unitY, width, height, isVertical: true, autoRoundValues })
206
+
207
+ }
208
+ valsN.push(valX, valY)
209
+
210
+ transformFunction.push(`${fn}(${valsN.join(' ')})`)
211
+
212
+ }
213
+
214
+ // SVG rotations may contain a transform origin
215
+ if (fn === 'rotate') {
216
+
217
+ let angle = values[0].value;
218
+ let unit = values[0].unit;
219
+ angle = normalizeUnits(angle, { unit, autoRoundValues })
220
+ //if(unit==='rad') angle = angle*rad2Deg;
221
+ let rot = [`${fn}(${angle})`]
222
+
223
+ if (values.length === 3) {
224
+ //console.log('has pivot point');
225
+ let cx = values[1].value
226
+ let cy = values[2].value
227
+ rot = [`translate(${cx} ${cy})`, rot[0], `translate(${-cx} ${-cy})`]
228
+ }
229
+ //transFormAll.push(...rot)
230
+ transformFunction = rot
231
+
232
+ }
233
+
234
+ //transFormAll.push(`${fn}(${valsN.join(' ')})`)
235
+ transFormAll.push(...transformFunction)
236
+ //console.log('transFormAll', transFormAll);
237
+
238
+ }
239
+
240
+ propsNorm['transform'] = transFormAll
241
+
242
+ }
243
+
244
+
245
+ //console.log('transFormOrigin', transFormOrigin);
246
+
247
+ }
248
+
249
+
250
+ // append standalone transforms
251
+ let translate = propsNorm['translate'] !== undefined ? `translate(${propsNorm['translate'].join(' ')})` : null;
252
+ let scale = propsNorm['scale'] !== undefined ? `scale(${propsNorm['scale'].join(' ')})` : null;
253
+ let rotate = propsNorm['rotate'] !== undefined ? `rotate(${propsNorm['rotate'].join(' ')})` : null;
254
+
255
+ let standaloneTransforms = [translate, rotate, scale].filter(Boolean)
256
+ if (standaloneTransforms.length) {
257
+ remove.push('translate', 'scale', 'rotate')
258
+ propsNorm['transform'] = [...propsNorm['transform'], ...standaloneTransforms ]
259
+ }
260
+
261
+ console.log('standaloneTransforms', standaloneTransforms);
262
+ //if()
263
+
264
+
265
+ // replace transform-origin with translates
266
+ if (transFormOrigin.length && propsNorm['transform'] !== undefined) {
267
+ //console.log('transFormOrigin', transFormOrigin);
268
+ propsNorm['transform'] = [`translate(${transFormOrigin[0]} ${transFormOrigin[1]})`, ...propsNorm['transform'], `translate(${-transFormOrigin[0]} ${-transFormOrigin[1]})`]
269
+ }
270
+
271
+
272
+
273
+
274
+
275
+ console.log('!!!propsNorm', propsNorm);
276
+
277
+ //console.log('parseStylesProperties', props);
278
+
279
+
280
+
281
+
282
+ }
283
+
284
+ /**
285
+ * filter out nonsense
286
+ * presentation attributes or
287
+ * style properties not valid
288
+ * for element type
289
+ */
290
+ export function filterSvgElProps(elNodename = '', props = {}, {
291
+ removeInvalid = true,
292
+ removeDefaults = true,
293
+ allowDataAtts = true,
294
+ cleanUpStrokes = true,
295
+ include = ['id', 'class'],
296
+ exclude = [],
297
+ } = {}) {
298
+ let propsFiltered = {}
299
+ let remove = [];
300
+
301
+
302
+ let noStrokeColor = cleanUpStrokes ? (props['stroke'] === undefined) : false;
303
+
304
+ for (let prop in props) {
305
+ let value = props[prop];
306
+ //console.log(prop);
307
+
308
+ // filter out useless
309
+ let isValid = removeInvalid ?
310
+ (attLookup.atts[prop] ? attLookup.atts[prop].includes(elNodename) : false) :
311
+ false;
312
+
313
+ // allow data attributes
314
+ let isDataAtt = allowDataAtts ? prop.startsWith('data-') : false;
315
+
316
+ // filter out defaults
317
+ let isDefault = removeDefaults ?
318
+ (attLookup.defaults[prop] ? attLookup.defaults[prop] !== undefined && attLookup.defaults[prop].includes(value) : false) :
319
+ false;
320
+
321
+ if (isDataAtt || include.includes(prop)) isValid = true;
322
+ if (isDefault) isValid = false
323
+ if (exclude.length && exclude.includes(prop)) isValid = false;
324
+ if (noStrokeColor && strokeAtts.includes(prop)) isValid = false
325
+
326
+ if (isValid) {
327
+ propsFiltered[prop] = props[prop]
328
+ }
329
+ else {
330
+ remove.push(prop)
331
+ }
332
+ }
333
+
334
+
335
+ return { propsFiltered, remove }
336
+ }
337
+
338
+
339
+ export function parseValue(valStr = '') {
340
+ let valArr = valStr.split(/,| /);
341
+
342
+ for (let i = 0; i < valArr.length; i++) {
343
+
344
+ let valStr = valArr[i];
345
+ let val = { value: null, unit: '', numeric: false }
346
+ let isNumeric = isNumericValue(valStr);
347
+ if (!isNumeric) {
348
+ val.value = valStr
349
+ }
350
+ else if (isNumeric) {
351
+ let unit = getUnit(valStr)
352
+ let valNum = parseFloat(valStr)
353
+ val.value = valNum;
354
+ val.unit = unit;
355
+ val.numeric = true
356
+ }
357
+ valArr[i] = val;
358
+ }
359
+
360
+ return valArr;
361
+ }
362
+
363
+
364
+
365
+ export function getSvgCssProps(el) {
366
+ let styleAtt = el.getAttribute('style')
367
+ let props = styleAtt ? parseInlineCss(styleAtt) : {}
368
+ return props
369
+ }
370
+
371
+ export function getSvgPresentationAtts(el) {
372
+ let props = {}
373
+ let atts = [...el.attributes].map((att) => att.name);
374
+ let l = atts.length;
375
+ if (!l) return props;
376
+
377
+ for (let i = 0; i < l; i++) {
378
+ let att = atts[i];
379
+ let value = el.getAttribute(att);
380
+
381
+ // test invalid transform functions
382
+ if (att === 'transform') {
383
+ let transformSan = [];
384
+ let transFormFunctions = value.split(/(\w+)\(([^)]+)\)/).map(val => val.trim()).filter(Boolean)
385
+ //console.log('!!transFormFunctions', el.nodeName, transFormFunctions);
386
+ for (let i = 1; i < transFormFunctions.length; i += 2) {
387
+ let prop = transFormFunctions[i - 1];
388
+ let val = transFormFunctions[i];
389
+ let units = val.split(/,| /).map(val => getUnit(val.trim())).filter(Boolean)
390
+
391
+ // remove invalid transform function
392
+ if (!units.length) {
393
+ transformSan.push(`${prop}(${val})`)
394
+ }
395
+ }
396
+ value = transformSan.join(' ');
397
+ }
398
+
399
+ props[att] = value.trim()
400
+ }
401
+
402
+ //console.log('!!!svg props', props, 'remove', remove);
403
+ return props;
404
+ }
405
+
406
+
407
+ function parseInlineCss(styleAtt = '') {
408
+
409
+ let props = {}
410
+ if (!styleAtt) return props;
411
+
412
+ let styleArr = styleAtt.split(';').filter(Boolean).map(prop => prop.trim());
413
+ let l = styleArr.length
414
+ if (!l) return props;
415
+
416
+ for (let i = 0; l && i < l; i++) {
417
+ let style = styleArr[i]
418
+ let [prop, value] = style.split(':').filter(Boolean)
419
+ props[prop] = value;
420
+ }
421
+
422
+ return props
423
+ }
@@ -1,103 +0,0 @@
1
- /**
2
- * Serialize pathData array to a minified "d" attribute string.
3
- */
4
- export function pathDataToD(pathData, optimize = 1) {
5
-
6
- let beautify = optimize>1;
7
- let minify = beautify ? false : true;
8
-
9
- // Convert first "M" to "m" if followed by "l" (when minified)
10
- if (pathData[1].type === "l" && minify) {
11
- pathData[0].type = "m";
12
- }
13
-
14
- let d = '';
15
- if(beautify) {
16
- d = `${pathData[0].type} ${pathData[0].values.join(" ")}\n`;
17
- }else{
18
- d = `${pathData[0].type}${pathData[0].values.join(" ")}`;
19
- }
20
-
21
-
22
- for (let i = 1, len = pathData.length; i < len; i++) {
23
- let com0 = pathData[i - 1];
24
- let com = pathData[i];
25
- let { type, values } = com;
26
-
27
- // Minify Arc commands (A/a) – actually sucks!
28
- if (minify && (type === 'A' || type === 'a')) {
29
- values = [
30
- values[0], values[1], values[2],
31
- `${values[3]}${values[4]}${values[5]}`,
32
- values[6]
33
- ];
34
- }
35
-
36
- // Omit type for repeated commands
37
- type = (com0.type === com.type && com.type.toLowerCase() !== 'm' && minify)
38
- ? " "
39
- : (
40
- (com0.type === "m" && com.type === "l") ||
41
- (com0.type === "M" && com.type === "l") ||
42
- (com0.type === "M" && com.type === "L")
43
- ) && minify
44
- ? " "
45
- : com.type;
46
-
47
-
48
- // concatenate subsequent floating point values
49
- if (minify) {
50
-
51
- //console.log(optimize, beautify, minify);
52
-
53
- let valsString = '';
54
- let prevWasFloat = false;
55
-
56
- for (let v = 0, l = values.length; v < l; v++) {
57
- let val = values[v];
58
- let valStr = val.toString();
59
- let isFloat = valStr.includes('.');
60
- let isSmallFloat = isFloat && Math.abs(val) < 1;
61
-
62
-
63
- // Remove leading zero from small floats *only* if the previous was also a float
64
- if (isSmallFloat && prevWasFloat) {
65
- valStr = valStr.replace(/^0\./, '.');
66
- }
67
-
68
- // Add space unless this is the first value OR previous was a small float
69
- if (v > 0 && !(prevWasFloat && isSmallFloat)) {
70
- valsString += ' ';
71
- }
72
- //console.log(isSmallFloat, prevWasFloat, valStr);
73
-
74
- valsString += valStr
75
- //.replace(/-0./g, '-.').replace(/ -./g, '-.')
76
- prevWasFloat = isSmallFloat;
77
- }
78
-
79
- //console.log('minify', valsString);
80
- d += `${type}${valsString}`;
81
-
82
- }
83
- // regular non-minified output
84
- else{
85
- if(beautify) {
86
- d += `${type} ${values.join(' ')}\n`;
87
- }else{
88
- d += `${type}${values.join(' ')}`;
89
- }
90
- }
91
- }
92
-
93
- if (minify) {
94
- d = d
95
- .replace(/ 0\./g, " .") // Space before small decimals
96
- .replace(/ -/g, "-") // Remove space before negatives
97
- .replace(/-0\./g, "-.") // Remove leading zero from negative decimals
98
- .replace(/Z/g, "z"); // Convert uppercase 'Z' to lowercase
99
- }
100
-
101
-
102
- return d;
103
- }