svg-path-simplify 0.4.2 → 0.4.4

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 (61) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +7 -4
  3. package/dist/svg-path-simplify.esm.js +3593 -1279
  4. package/dist/svg-path-simplify.esm.min.js +2 -2
  5. package/dist/svg-path-simplify.js +3594 -1278
  6. package/dist/svg-path-simplify.min.js +2 -2
  7. package/dist/svg-path-simplify.pathdata.esm.js +1017 -538
  8. package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
  9. package/dist/svg-path-simplify.poly.cjs +9 -8
  10. package/docs/privacy-webapp.md +24 -0
  11. package/index.html +331 -152
  12. package/package.json +1 -1
  13. package/src/constants.js +4 -0
  14. package/src/css_parse.js +317 -0
  15. package/src/detect_input.js +76 -28
  16. package/src/index.js +8 -0
  17. package/src/pathData_simplify_cubic.js +26 -16
  18. package/src/pathData_simplify_harmonize_cpts.js +77 -1
  19. package/src/pathData_simplify_revertToquadratics.js +0 -1
  20. package/src/pathSimplify-main.js +304 -276
  21. package/src/pathSimplify-only-pathdata.js +7 -2
  22. package/src/pathSimplify-presets.js +254 -0
  23. package/src/poly-fit-curve-schneider.js +14 -7
  24. package/src/simplify_poly_RC.js +102 -0
  25. package/src/simplify_poly_RDP.js +109 -1
  26. package/src/simplify_poly_radial_distance.js +3 -3
  27. package/src/string_helpers.js +130 -4
  28. package/src/svg-getAttributes.js +4 -2
  29. package/src/svgii/convert_units.js +1 -1
  30. package/src/svgii/geometry.js +322 -5
  31. package/src/svgii/geometry_bbox_element.js +1 -1
  32. package/src/svgii/geometry_deduceRadius.js +116 -27
  33. package/src/svgii/geometry_length.js +253 -0
  34. package/src/svgii/pathData_analyze.js +18 -0
  35. package/src/svgii/pathData_convert.js +193 -89
  36. package/src/svgii/pathData_fix_directions.js +12 -14
  37. package/src/svgii/pathData_fromPoly.js +3 -3
  38. package/src/svgii/pathData_getLength.js +86 -0
  39. package/src/svgii/pathData_parse.js +2 -0
  40. package/src/svgii/pathData_parse_els.js +66 -68
  41. package/src/svgii/pathData_reorder.js +122 -16
  42. package/src/svgii/pathData_simplify_refineCorners.js +130 -35
  43. package/src/svgii/pathData_simplify_refine_round.js +420 -0
  44. package/src/svgii/pathData_split_to_groups.js +168 -0
  45. package/src/svgii/pathData_stringify.js +26 -64
  46. package/src/svgii/pathData_toPolygon.js +3 -4
  47. package/src/svgii/poly_analyze.js +61 -0
  48. package/src/svgii/poly_normalize.js +11 -2
  49. package/src/svgii/poly_to_pathdata.js +85 -24
  50. package/src/svgii/rounding.js +80 -78
  51. package/src/svgii/svg_cleanup.js +421 -619
  52. package/src/svgii/svg_cleanup_convertPathLength.js +39 -0
  53. package/src/svgii/svg_cleanup_general_svg_atts.js +97 -0
  54. package/src/svgii/svg_cleanup_normalize_transforms.js +83 -0
  55. package/src/svgii/svg_cleanup_remove_els_and_atts.js +77 -0
  56. package/src/svgii/svg_cleanup_ungroup.js +36 -0
  57. package/src/svgii/svg_el_parse_style_props.js +72 -47
  58. package/src/svgii/svg_getElementLength.js +67 -0
  59. package/src/svgii/svg_validate.js +220 -0
  60. package/tests/testSVG.js +14 -1
  61. package/src/svgii/pathData_refine_round.js +0 -222
@@ -1,8 +1,8 @@
1
1
  import { detectInputType } from './detect_input';
2
2
  import { simplifyPathDataCubic } from './pathData_simplify_cubic';
3
- import { getDistManhattan, getDistance, getPathDataVertices, interpolate, pointAtT } from './svgii/geometry';
3
+ import { getDistManhattan, getDistance, getPathDataVertices, getSquareDistance, interpolate, pointAtT, reducePoints, svgArcToCenterParam, toParametricAngle } from './svgii/geometry';
4
4
  import { getPolyBBox } from './svgii/geometry_bbox';
5
- import { analyzePathData } from './svgii/pathData_analyze';
5
+ import { analyzePathData, getPathDataVerbose } from './svgii/pathData_analyze';
6
6
  import { normalizePathData, parsePathDataNormalized, convertPathData } from './svgii/pathData_convert';
7
7
  import { shapeElToPath } from './svgii/pathData_parse_els';
8
8
  import { pathDataRemoveColinear } from './svgii/pathData_remove_collinear';
@@ -12,164 +12,97 @@ import { optimizeClosePath, pathDataToTopLeft } from './svgii/pathData_reorder';
12
12
  import { reversePathData } from './svgii/pathData_reverse';
13
13
  import { addExtremePoints, splitSubpaths } from './svgii/pathData_split';
14
14
  import { pathDataToD } from './svgii/pathData_stringify';
15
- import { detectAccuracy, roundPathData } from './svgii/rounding';
15
+ import { detectAccuracy, roundPathData, roundTo } from './svgii/rounding';
16
16
  import { refineAdjacentExtremes } from './svgii/pathData_simplify_refineExtremes';
17
- import { cleanUpSVG, removeEmptySVGEls, stringifySVG } from './svgii/svg_cleanup';
17
+ import { cleanUpSVG, removeEmptySVGEls } from './svgii/svg_cleanup';
18
18
  import { refineRoundedCorners } from './svgii/pathData_simplify_refineCorners';
19
- import { refineRoundSegments } from './svgii/pathData_refine_round';
19
+ import { refineRoundSegments, simplifyAdjacentRound } from './svgii/pathData_simplify_refine_round';
20
20
  import { refineClosingCommand } from './svgii/pathData_remove_short';
21
21
  import { scalePathData } from './svgii/pathData_transform_scale';
22
22
  import { getViewBox } from './svg_getViewbox';
23
23
  import { pathDataRevertCubicToQuadratic } from './pathData_simplify_revertToquadratics';
24
24
  import { pathDataCubicsToArc } from './pathData_simplify_cubicsToArcs';
25
25
  import { harmonizeCubicCpts } from './pathData_simplify_harmonize_cpts';
26
- import { pathDataToPolygon } from './svgii/pathData_toPolygon';
26
+ import { pathDataToPolygonOpt } from './svgii/pathData_toPolygon';
27
27
  import { pathDataLineToCubic } from './svgii/pathData_line_to_cubic';
28
28
  import { fixPathDataDirections } from './svgii/pathData_fix_directions';
29
29
  import { simplifyPolyChunks, getCurvePathData, simplifyPolygonToPathData } from './svgii/poly_to_pathdata';
30
30
  import { pathDataFromPoly } from './svgii/pathData_fromPoly';
31
- import { normalizePoly } from './svgii/poly_normalize';
31
+ import { normalizePoly, polyPtsToArray } from './svgii/poly_normalize';
32
32
  import { simplifyPolyRD } from './simplify_poly_radial_distance';
33
- import { simplifyPolyRDP } from './simplify_poly_RDP';
33
+ import { simplifyPolyRDP, simplifyPolyRDP__, simplifyRDP_rel } from './simplify_poly_RDP';
34
+ import { getEllipseLengthLG, getLegendreGaussValues, getLength, waArr_global } from './svgii/geometry_length';
35
+ import { deg2rad, dummySVG } from './constants';
36
+ import { getPathDataLength } from './svgii/pathData_getLength';
37
+ import { stringifySVG } from './string_helpers';
38
+ import { presetSettings, settingsDefaults } from './pathSimplify-presets';
39
+ import { splitCompundGroups } from './svgii/pathData_split_to_groups';
34
40
  //import { getPolyChunks } from "./svgii/poly_analyze_get_chunks";
35
41
 
36
42
 
37
43
  //import { installDOMPolyfills } from './dom-polyfill';
38
44
 
39
- export function svgPathSimplify(input = '', {
45
+ export function svgPathSimplify(input = '', settings = {}) {
40
46
 
41
- // return svg markup or object
42
- getObject = false,
47
+ let preset = settings['preset'] !== undefined && settings['preset'] ? settings['preset'] : null;
48
+ let defaults = preset && presetSettings[preset] !== undefined ? presetSettings[preset] : presetSettings['default'];
43
49
 
44
- toAbsolute = false,
45
- toRelative = true,
46
- toMixed = false,
47
- toShorthands = true,
48
- toLonghands = false,
49
-
50
- //optimize = 0,
51
-
52
- // not necessary unless you need cubics only
53
- quadraticToCubic = true,
54
-
55
- // mostly a fallback if arc calculations fail
56
- arcToCubic = false,
57
- cubicToArc = false,
58
-
59
-
60
- simplifyBezier = true,
61
- optimizeOrder = true,
62
- autoClose = false,
63
- removeZeroLength = true,
64
- refineClosing = true,
65
- removeColinear = true,
66
- flatBezierToLinetos = true,
67
- revertToQuadratics = true,
68
-
69
- refineExtremes = true,
70
- simplifyCorners = false,
71
- removeDimensions = false,
72
- removeIds = false,
73
- removeClassNames = false,
74
- omitNamespace = false,
75
-
76
- fixDirections = false,
77
-
78
- keepExtremes = true,
79
- keepCorners = true,
80
- extrapolateDominant = true,
81
- keepInflections = false,
82
- addExtremes = false,
83
- addSemiExtremes = false,
84
-
85
- toPolygon = false,
86
- smoothPoly = false,
87
- polyFormat = 'points',
88
- precisionPoly = 1,
89
-
90
- simplifyRD = 1,
91
- simplifyRDP = 1,
92
-
93
- harmonizeCpts = false,
94
-
95
- removeOrphanSubpaths = false,
96
- simplifyRound = false,
97
-
98
- //svg scaling
99
- scale = 1,
100
- scaleTo = 0,
101
- crop = false,
102
- alignToOrigin = false,
103
-
104
- // flatten transforms
105
- convertTransforms = false,
106
-
107
-
108
-
109
- //svg path optimizations
110
- decimals = 3,
111
- autoAccuracy = true,
112
-
113
- // experimental
114
- //roundSub = false,
115
-
116
- minifyD = 0,
117
- tolerance = 1,
118
- reversePath = false,
119
-
120
- //svg cleanup options
121
- minifyRgbColors = true,
122
- removePrologue = true,
123
- removeHidden = true,
124
- removeUnused = true,
125
- cleanupDefs = true,
126
- cleanupClip = true,
127
- cleanupSVGAtts = true,
128
-
129
- stylesToAttributes = false,
130
- fixHref = false,
131
- legacyHref = false,
132
- removeNameSpaced = true,
133
-
134
- //meta
135
- allowMeta = false,
136
- allowDataAtts = true,
137
- allowAriaAtts = true,
138
50
 
51
+ // merge settings
52
+ settings = {
53
+ ...defaults,
54
+ ...settings
55
+ }
139
56
 
140
- attributesToGroup = false,
141
- removeOffCanvas = false,
142
- unGroup = false,
143
- mergePaths = false,
144
57
 
145
- // shape conversions
146
- shapesToPaths = false,
58
+ let { getObject = false, removeComments, removeOffCanvas, unGroup, mergePaths, removeElements, removeDimensions, removeIds, removeClassNames, omitNamespace, cleanUpStrokes, addViewBox, addDimensions, removePrologue, removeHidden, removeUnused, cleanupDefs, cleanupClip, cleanupSVGAtts, removeNameSpaced, removeNameSpacedAtts, attributesToGroup, minifyRgbColors, stylesToAttributes, fixHref, legacyHref, allowMeta, allowDataAtts, allowAriaAtts, removeSVGAttributes, removeElAttributes, shapesToPaths, shapeConvert, convertShapes, simplifyBezier, optimizeOrder, autoClose, removeZeroLength, refineClosing, removeColinear, flatBezierToLinetos, revertToQuadratics, refineExtremes, simplifyCorners, fixDirections, keepExtremes, keepCorners, keepInflections, addExtremes, reversePath, toAbsolute, toRelative, toMixed, toShorthands, toLonghands, quadraticToCubic, arcToCubic, cubicToArc, lineToCubic, decimals, autoAccuracy, minifyD, tolerance, toPolygon, smoothPoly, polyFormat, precisionPoly, simplifyRD, simplifyRDP, harmonizeCpts, removeOrphanSubpaths, simplifyRound, simplifyQuadraticCorners, scale, scaleTo, crop, alignToOrigin, convertTransforms, keepSmaller, splitCompound, convertPathLength, toAbsoluteUnits } = settings;
147
59
 
60
+ //toAbsolute = !toRelative;
148
61
 
149
- //toPaths || toShapes
150
- shapeConvert = 0,
151
- convert_rects = false,
152
- convert_ellipses = false,
153
- convert_poly = false,
154
- convert_lines = false,
62
+ // clamp tolerance and scale
63
+ tolerance = Math.max(0.1, tolerance);
64
+ scale = Math.max(0.001, scale)
65
+ if (fixDirections) keepSmaller = false;
66
+ if (scale !== 1 || scaleTo || crop || alignToOrigin) {
67
+ convertTransforms = true;
68
+ settings.convertTransforms = true
69
+ }
155
70
 
156
71
 
72
+ /**
73
+ * intercept
74
+ * invalid inputs
75
+ */
157
76
 
158
- lineToCubic = false,
159
- cleanUpStrokes = true,
160
- addViewBox = false,
161
- addDimensions = false,
77
+ let inputDetection = detectInputType(input);
78
+ let { inputType, log } = inputDetection
79
+
80
+ // invalid file
81
+ if (inputType === 'invalid' || input === dummySVG) {
82
+ // return dummy SVG to continue processing
83
+ //input = dummySVG;
84
+ //inputType = 'invalid';
85
+
86
+ //console.warn(`Input is not valid!\n ${log}`);
87
+ //console.log(input);
88
+ //return false
89
+
90
+ let report = {
91
+ original: 0,
92
+ new: 0,
93
+ saved: 0,
94
+ svgSize:0,
95
+ svgSizeOpt:0,
96
+ compression:0,
97
+ decimals:0,
98
+ invalid:true
99
+ }
162
100
 
163
- removeComments = true,
101
+ return { svg: dummySVG, d: '', polys: [], report, pathDataPlusArr: [], pathDataPlusArr_global: [], inputType: 'invalid', dOriginal: '' };
164
102
 
165
- } = {}) {
103
+ }
166
104
 
167
105
 
168
- // clamp tolerance and scale
169
- tolerance = Math.max(0.1, tolerance);
170
- scale = Math.max(0.001, scale)
171
-
172
- let inputType = detectInputType(input);
173
106
  let svg = '';
174
107
  let svgSize = 0;
175
108
  let svgSizeOpt = 0;
@@ -181,9 +114,12 @@ export function svgPathSimplify(input = '', {
181
114
 
182
115
  // pathdata superset array - containing additional data
183
116
  let pathDataPlusArr_global = []
184
- let paths = []
185
- let polys = []
117
+ let paths = [];
118
+ let isPoly = false;
119
+ let polys = [];
120
+ let poly = [];
186
121
  let dStr = '';
122
+ let dOriginal = '';
187
123
 
188
124
  /**
189
125
  * normalize input
@@ -207,29 +143,66 @@ export function svgPathSimplify(input = '', {
207
143
  arcToCubic = toPolygon ? true : arcToCubic;
208
144
  autoClose = false;
209
145
  let accuracyArr = []
210
-
146
+ //console.log(inputType);
147
+
148
+ // validate point JSON
149
+ if (inputType === 'json') {
150
+ let pts = [];
151
+ let needsQuotes = /([{,]\s*)(x|y)(\s*:)/.test(input)
152
+ if (needsQuotes) input = input.replaceAll('x:', '"x":').replaceAll('y:', '"y":')
153
+
154
+ try {
155
+ pts = JSON.parse(input)
156
+ } catch {
157
+ console.warn('No valid JSON');
158
+ }
159
+ if (pts.length) {
160
+ inputType = 'polyArray'
161
+ input = normalizePoly(pts);
162
+ isPoly = true;
163
+ }
164
+ }
211
165
 
212
166
  //console.log('inputType', inputType);
213
167
 
168
+
214
169
  // single path or polys
215
- if (inputType !== 'svgMarkup') {
170
+ if (inputType !== 'svgMarkup' && inputType !== 'symbol') {
216
171
  if (inputType === 'pathDataString') {
217
172
  d = input
218
173
  } else if (inputType === 'polyString') {
219
- d = 'M' + input
174
+ splitCompound = false;
175
+ isPoly = true;
176
+ poly = normalizePoly(input)
177
+ d = pathDataFromPoly(poly, closed)
178
+ //console.log(poly);
179
+
220
180
  }
221
181
 
222
182
  else if (inputType === 'polyArray' || inputType === 'polyObjectArray' || inputType === 'polyComplexArray' || inputType === 'polyComplexObjectArray') {
183
+ splitCompound = false;
223
184
 
224
185
  // normalize poly input to object array
225
- let poly = normalizePoly(input)
186
+ poly = normalizePoly(input)
226
187
 
227
188
  // convert to pathdata
228
- d = pathDataFromPoly(poly)
189
+ let closed = true;
190
+
191
+ isPoly = true;
192
+ //polys.push(poly)
229
193
 
230
194
  // calculate size
195
+ d = pathDataFromPoly(poly, closed)
231
196
  dStr = d.map(com => { return `${com.type} ${com.values.join(' ')}` }).join(' ');
197
+ dOriginal = dStr;
232
198
  svgSize = dStr.length;
199
+
200
+ /*
201
+ d=''
202
+ dOriginal = '';
203
+ svgSize = input.length;
204
+ */
205
+
233
206
  }
234
207
 
235
208
  else if (inputType === 'pathData') {
@@ -238,6 +211,12 @@ export function svgPathSimplify(input = '', {
238
211
  // stringify to compare lengths
239
212
  dStr = Array.from(d).map(com => { return `${com.type} ${com.values.join(' ')}` }).join(' ');
240
213
  svgSize = dStr.length;
214
+ isPoly = false;
215
+
216
+ }
217
+ // not valid - set dummy path data
218
+ else {
219
+ d = 'M0 0 h0'
241
220
  }
242
221
 
243
222
  paths.push({ d, el: null })
@@ -246,40 +225,45 @@ export function svgPathSimplify(input = '', {
246
225
  // mode:1 – process complete svg DOM
247
226
  else {
248
227
 
228
+
229
+ // convert symbol temporarily to SVG
230
+ if (inputType === 'symbol') {
231
+ input = input.replaceAll('<symbol', '<svg').replaceAll('</symbol', '</svg')
232
+ // ids are mandatory for symbols
233
+ removeIds = false
234
+ removeDimensions = true
235
+ }
236
+
249
237
  // convert all shapes to paths
250
238
  if (shapesToPaths) {
251
239
  shapeConvert = 'toPaths'
252
- convert_rects = true
253
- convert_ellipses = true
254
- convert_poly = true
255
- convert_lines = true
240
+ convertShapes = ['rect', 'polygon', 'polyline', 'line', 'circle', 'ellipse']
256
241
  }
257
242
 
258
243
  //console.log('shapesToPaths', shapesToPaths, 'shapeConvert', shapeConvert, convert_rects, convert_ellipses, convert_poly);
259
244
 
260
- //sanitize
261
- let svgPropObject = cleanUpSVG(input, {
262
- removeIds, removeClassNames, removeDimensions, cleanupSVGAtts, cleanUpStrokes, removeHidden, removeUnused, removeNameSpaced, stylesToAttributes, removePrologue, fixHref, mergePaths, convertTransforms, legacyHref, cleanupDefs, cleanupClip, addViewBox, removeOffCanvas, addDimensions,
263
- shapeConvert, convert_rects, convert_ellipses, convert_poly, convert_lines, minifyRgbColors, unGroup, convertTransforms,
264
- allowMeta, allowDataAtts, allowAriaAtts, allowMeta, attributesToGroup
265
- }
266
- );
245
+ // sanitize SVG - clone/decouple settings
246
+ let svgPropObject = cleanUpSVG(input, JSON.parse(JSON.stringify(settings)));
247
+
248
+ //console.log('settings', settings);
249
+ //console.log('svgPropObject', svgPropObject);
267
250
 
268
251
  let { svgElProps } = svgPropObject
269
252
  svg = svgPropObject.svg;
270
-
253
+ //console.log(svgPropObject);
271
254
 
272
255
 
273
256
  // collect paths
274
257
  let pathEls = svg.querySelectorAll('path')
275
258
  //let pathEls2 = svg.getElementsByTagName('path')
276
- //console.log(pathEls2);
259
+ //console.log(pathEls);
277
260
 
278
261
  pathEls.forEach((path, i) => {
279
- paths.push({ d: path.getAttribute('d'), el: path, idx: i })
262
+ let d = path.getAttribute('d');
263
+ //console.log(d, path.nodeName, path.id);
264
+ paths.push({ d, el: path, idx: i })
280
265
  })
281
266
 
282
-
283
267
  // get viewBox/dimensions
284
268
  viewBox = getViewBox(svg, decimals)
285
269
 
@@ -296,14 +280,13 @@ export function svgPathSimplify(input = '', {
296
280
  let pathOptions = {
297
281
  toRelative,
298
282
  toMixed,
299
- toAbsolute,
300
- toLonghands,
301
283
  toShorthands,
302
284
  decimals,
303
285
  }
286
+ //console.log('pathOptions', pathOptions);
304
287
 
305
- // combinded path data for SVGs with mergePaths enabled
306
- let pathData_merged = [];
288
+ let comCount = 0
289
+ let comCountS = 0
307
290
 
308
291
 
309
292
  for (let i = 0, l = paths.length; l && i < l; i++) {
@@ -312,10 +295,14 @@ export function svgPathSimplify(input = '', {
312
295
  let path = paths[i];
313
296
  let { d, el } = path;
314
297
  let dN = ''
298
+ let isPoly = false;
315
299
 
316
-
300
+ // if polygon we already heave absolute coordinates
301
+ //let isPolyPath = !mode && isPoly && Array.isArray(d)
302
+ //let pathData = !isPolyPath ? parsePathDataNormalized(d, { quadraticToCubic, arcToCubic }) : d;
317
303
  let pathData = parsePathDataNormalized(d, { quadraticToCubic, arcToCubic });
318
304
  //console.log('!!!pathData', pathData, arcToCubic);
305
+ //console.log(isPoly);
319
306
 
320
307
  // get polygon bbox
321
308
  let bb_poly = smoothPoly || toPolygon ? getPolyBBox(getPathDataVertices(pathData)) : null
@@ -352,9 +339,10 @@ export function svgPathSimplify(input = '', {
352
339
  }
353
340
 
354
341
  // count commands for evaluation
355
- let comCount = pathData.length
342
+ comCount += pathData.length
343
+
344
+ if (!isPoly && removeOrphanSubpaths) pathData = removeOrphanedM(pathData);
356
345
 
357
- if (removeOrphanSubpaths) pathData = removeOrphanedM(pathData);
358
346
 
359
347
 
360
348
  /**
@@ -362,7 +350,6 @@ export function svgPathSimplify(input = '', {
362
350
  */
363
351
  let subPathArr = splitSubpaths(pathData);
364
352
  let lenSub = subPathArr.length;
365
-
366
353
  //console.log('subPathArr', subPathArr);
367
354
 
368
355
 
@@ -372,27 +359,29 @@ export function svgPathSimplify(input = '', {
372
359
  //let { pathData, bb } = subPathArr[i];
373
360
  let pathDataSub = subPathArr[i];
374
361
  let poly = []
375
-
376
362
  let coms = Array.from(new Set(pathDataSub.map(com => com.type))).join('')
377
- let isPoly = !(/[acqts]/gi).test(coms)
378
- let closed = (/[z]/gi).test(coms)
363
+ isPoly = !(/[acqts]/gi).test(coms)
364
+ let closed = isPoly ? true : false;
379
365
 
380
- if (isPoly) {
366
+ if (isPoly && !mode) {
381
367
 
382
368
  poly = getPathDataVertices(pathDataSub);
383
- //console.log(poly);
369
+ let bb = getPolyBBox(reducePoints(poly, 64))
370
+ //console.log(poly, bb);
384
371
 
385
372
  // simplify polygon
386
373
  if (simplifyRD > 0) {
387
- poly = simplifyPolyRD(poly, { quality: simplifyRD + 'px' })
374
+ poly = simplifyPolyRD(poly, { quality: simplifyRD, width: bb.width, height: bb.height })
388
375
  }
389
376
 
390
377
  if (simplifyRDP > 0) {
391
- poly = simplifyPolyRDP(poly, { quality: simplifyRDP + 'px' })
378
+ poly = simplifyPolyRDP(poly, { quality: simplifyRDP, width: bb.width, height: bb.height })
379
+ //poly = simplifyRDP_rel(poly, simplifyRDP, bb.width, bb.height)
392
380
  }
393
381
 
382
+ toPolygon = false;
394
383
  pathDataSub = pathDataFromPoly(poly, closed)
395
-
384
+ //pathDataSub[0].bb = bb
396
385
  }
397
386
 
398
387
 
@@ -405,33 +394,25 @@ export function svgPathSimplify(input = '', {
405
394
  smoothPoly = false;
406
395
  harmonizeCpts = false;
407
396
 
408
- //pathDataSub = normalizePathData(pathDataSub, {arcToCubic:true})
409
- //console.log(pathDataSub);
410
-
411
- let pathDataSubPlus = analyzePathData(pathDataSub)
412
- let { bb, pathData } = pathDataSubPlus;
413
- pathDataSub = pathData;
414
-
397
+ pathDataSub = getPathDataVerbose(pathDataSub);
415
398
 
416
- let polyData = pathDataToPolygon(pathDataSub, {
399
+ let polyData = pathDataToPolygonOpt(pathDataSub, {
417
400
  precisionPoly,
418
401
  autoAccuracy,
419
- polyFormat,
420
- decimals,
402
+ //polyFormat,
403
+ //decimals,
421
404
  simplifyRD,
422
405
  simplifyRDP
423
406
  })
424
407
 
425
-
426
-
427
- //poly.push(polyData.poly)
428
- polys.push(polyData.poly)
408
+ //console.log('toPolygon');
409
+ //polys.push(polyData.poly)
429
410
  pathDataSub = polyData.pathData
411
+ isPoly = true;
430
412
 
431
413
  }
432
414
 
433
415
 
434
-
435
416
  /**
436
417
  * poly to beziers via
437
418
  * Philip J. Schneider's
@@ -459,21 +440,22 @@ export function svgPathSimplify(input = '', {
459
440
  simplifyRD,
460
441
  simplifyRDP,
461
442
  }
443
+
444
+ //console.log('smooth');
462
445
  pathDataSub = simplifyPolygonToPathData(poly, optionsPoly)
446
+ // flag as non poly as we're smoothing to curves
447
+ //isPoly = false
463
448
  }
464
449
  }
465
450
 
466
451
 
467
-
468
452
  // harmonize cpts
469
453
  // if (harmonizeCpts) pathDataSub = harmonizeCubicCpts(pathDataSub)
470
454
 
471
-
472
455
  // remove zero length linetos
473
456
  if (removeColinear || removeZeroLength) pathDataSub = removeZeroLengthLinetos(pathDataSub)
474
457
 
475
458
 
476
-
477
459
  // sort to top left
478
460
  if (optimizeOrder) pathDataSub = pathDataToTopLeft(pathDataSub);
479
461
 
@@ -482,8 +464,8 @@ export function svgPathSimplify(input = '', {
482
464
  if (removeColinear) pathDataSub = pathDataRemoveColinear(pathDataSub, { tolerance, flatBezierToLinetos: false });
483
465
 
484
466
  let tMin = 0, tMax = 1;
485
- if (addExtremes || addSemiExtremes) pathDataSub = addExtremePoints(pathDataSub,
486
- { tMin, tMax, addExtremes, addSemiExtremes, angles: [30] })
467
+ if (addExtremes) pathDataSub = addExtremePoints(pathDataSub,
468
+ { tMin, tMax, addExtremes, angles: [30] })
487
469
 
488
470
 
489
471
 
@@ -492,23 +474,38 @@ export function svgPathSimplify(input = '', {
492
474
  pathDataSub = reversePathData(pathDataSub)
493
475
  }
494
476
 
477
+ // analyze pathdata to add info about significant properties such as extremes, corners
478
+ let pathDataPlus = { bb: {}, dimA: 0, pathData: [] }
479
+
480
+ if (!isPoly) {
481
+ pathDataPlus = analyzePathData(pathDataSub);
482
+ }
483
+ // we skip detailed analysis for native polygons
484
+ else {
485
+ if (!poly.length) {
486
+ let pathDataCubic = convertPathData(JSON.parse(JSON.stringify(pathDataSub)), { toLonghands: true, toAbsolute: true, arcToCubic: true, testTypes: true })
487
+ pathDataPlus.bb = getPolyBBox(getPathDataVertices(pathDataCubic))
488
+ }
489
+ pathDataPlus.dimA = pathDataPlus.bb.width + pathDataPlus.bb.height;
490
+ pathDataPlus.pathData = getPathDataVerbose(pathDataSub, {
491
+ addSquareLength: false,
492
+ addArea: false,
493
+ addAverageDim: false
494
+ })
495
+ }
495
496
 
496
- // analyze pathdata to add info about signicant properties such as extremes, corners
497
- let pathDataPlus = analyzePathData(pathDataSub, {
498
- detectSemiExtremes: addSemiExtremes,
499
- });
500
497
 
501
498
 
502
499
  // simplify beziers
503
500
  let { pathData, bb, dimA } = pathDataPlus;
501
+
504
502
  xArr.push(bb.x, bb.x + bb.width)
505
503
  yArr.push(bb.y, bb.y + bb.height)
506
504
 
507
505
 
508
506
  if (refineClosing) pathData = refineClosingCommand(pathData, { threshold: dimA * 0.001 })
509
507
 
510
-
511
- pathData = simplifyBezier ? simplifyPathDataCubic(pathData, { simplifyBezier, keepInflections, keepExtremes, keepCorners, extrapolateDominant, revertToQuadratics, tolerance }) : pathData;
508
+ pathData = simplifyBezier ? simplifyPathDataCubic(pathData, { simplifyBezier, keepInflections, keepExtremes, keepCorners, revertToQuadratics, tolerance }) : pathData;
512
509
 
513
510
 
514
511
  // refine extremes
@@ -533,12 +530,14 @@ export function svgPathSimplify(input = '', {
533
530
  //pathData = removeZeroLengthLinetos(pathData);
534
531
 
535
532
  let threshold = (bb.width + bb.height) * 0.1
536
- pathData = refineRoundedCorners(pathData, { threshold, tolerance })
533
+ pathData = refineRoundedCorners(pathData, { threshold, tolerance, simplifyQuadraticCorners })
537
534
  }
538
535
 
539
536
  // refine round segment sequences
540
- if (simplifyRound) pathData = refineRoundSegments(pathData);
541
-
537
+ if (simplifyRound) {
538
+ pathData = refineRoundSegments(pathData);
539
+ pathData = simplifyAdjacentRound(pathData);
540
+ }
542
541
 
543
542
  // simplify to quadratics
544
543
  if (revertToQuadratics) pathData = pathDataRevertCubicToQuadratic(pathData, tolerance);
@@ -555,10 +554,6 @@ export function svgPathSimplify(input = '', {
555
554
  //let decimalsSub = Math.max(2, detectAccuracy(pathData));
556
555
  let decimalsSub = detectAccuracy(pathData);
557
556
  accuracyArr.push(decimalsSub);
558
- //let decimalsSub = detectAccuracy(pathData);
559
-
560
- // pre round sub path
561
- //if(roundSub) pathData = roundPathData(pathData, decimalsSub);
562
557
 
563
558
  }
564
559
 
@@ -567,6 +562,7 @@ export function svgPathSimplify(input = '', {
567
562
 
568
563
  } // end sup paths
569
564
 
565
+
570
566
  // sort subpaths to top left
571
567
  let xMin = Math.min(...xArr)
572
568
  let yMin = Math.min(...yArr)
@@ -580,23 +576,42 @@ export function svgPathSimplify(input = '', {
580
576
  //console.log(i, pathDataPlusArr);
581
577
 
582
578
 
579
+ // fix path directions - before reordering
580
+ if (fixDirections) {
581
+ pathDataPlusArr = fixPathDataDirections(pathDataPlusArr);
582
+ }
583
+
583
584
 
584
585
  // prefer top to bottom priority for portrait aspect ratios
585
586
  if (optimizeOrder) {
587
+ /*
586
588
  pathDataPlusArr = isPortrait ? pathDataPlusArr.sort((a, b) => a.bb.y - b.bb.y || a.bb.x - b.bb.x) : pathDataPlusArr.sort((a, b) => a.bb.x - b.bb.x || a.bb.y - b.bb.y)
587
- }
589
+ */
590
+
591
+ // add missin bbox
592
+ pathDataPlusArr.forEach(p => {
593
+ if (p.bb.x === undefined) {
594
+ p.bb = getPolyBBox(getPathDataVertices(p.pathData))
595
+ }
596
+ })
597
+
598
+ try {
599
+ pathDataPlusArr = pathDataPlusArr.sort((a, b) => +a.bb.x.toFixed(2) - (+b.bb.x.toFixed(2)) || a.bb.y - b.bb.y);
600
+ //console.log(pathDataPlusArr);
601
+
602
+ } catch {
603
+ }
588
604
 
589
605
 
590
- // fix path directions
591
- if (fixDirections) {
592
- pathDataPlusArr = fixPathDataDirections(pathDataPlusArr);
593
606
  }
594
607
 
595
608
 
596
609
 
610
+
597
611
  // flatten compound paths
598
612
  pathData = [];
599
613
 
614
+
600
615
  // add to global array - including multiple path elements
601
616
  pathDataPlusArr_global.push(pathDataPlusArr);
602
617
 
@@ -614,99 +629,88 @@ export function svgPathSimplify(input = '', {
614
629
  pathOptions.decimals = decimals
615
630
  }
616
631
 
632
+ // add simplified poly - if not populated by toPoly conversion
633
+ if (isPoly) {
634
+ //console.log('5. isPoly', isPoly);
635
+
636
+ pathDataPlusArr.forEach(sub => {
637
+ let poly = getPathDataVertices(sub.pathData, false, decimals)
638
+ if (polyFormat === 'array') {
639
+ poly = polyPtsToArray(poly)
640
+ }
641
+ polys.push(poly)
642
+ })
617
643
 
618
- // collect for merged svg paths
619
- mergePaths = false
620
- if (el && mergePaths) {
621
- pathData_merged.push(...pathData)
622
644
  }
623
- // single output
624
- else {
625
645
 
626
- // clone pathdata
627
- //pathData = pathData.map(com => ({ type: com.type, values: [...com.values] }));
628
- pathData = JSON.parse(JSON.stringify(pathData));
629
646
 
630
- // optimize path data
631
- pathData = convertPathData(pathData, pathOptions)
647
+ // split into sub paths - returns svg with multiple paths
648
+ if (splitCompound && !mode && pathDataPlusArr.length > 1) {
649
+ let pathDataSplit = splitCompundGroups(pathDataPlusArr, { toRelative, toShorthands, decimals, addDimensions });
650
+ svg = new DOMParser().parseFromString(pathDataSplit.svg, 'image/svg+xml').querySelector('svg');
651
+ // switch output type
652
+ mode = 1;
653
+ inputType = 'splitPath'
654
+ }
632
655
 
633
- // remove zero-length segments introduced by rounding
634
- if (removeZeroLength) pathData = removeZeroLengthLinetos(pathData);
635
656
 
636
- // realign path to zero origin
637
- if (alignToOrigin) {
657
+ // clone pathdata
658
+ pathData = JSON.parse(JSON.stringify(pathData));
638
659
 
639
- pathData[0].values[0] = (pathData[0].values[0] - bb_global.x).toFixed(decimals)
640
- pathData[0].values[1] = (pathData[0].values[1] - bb_global.y).toFixed(decimals)
660
+ // optimize path data
661
+ pathData = convertPathData(pathData, pathOptions)
641
662
 
642
- bb_global.x = 0
643
- bb_global.y = 0
644
- }
663
+ // remove zero-length segments introduced by rounding
664
+ if (removeZeroLength) pathData = removeZeroLengthLinetos(pathData);
645
665
 
666
+ // realign path to zero origin
667
+ if (alignToOrigin) {
646
668
 
647
- // compare command count
648
- let comCountS = pathData.length
669
+ pathData[0].values[0] = roundTo((pathData[0].values[0] - bb_global.x), decimals)
670
+ pathData[0].values[1] = roundTo((pathData[0].values[1] - bb_global.y), decimals)
649
671
 
650
- let dOpt = pathDataToD(pathData, minifyD)
651
- //svgSizeOpt = new Blob([dOpt]).size;
652
- svgSizeOpt = dOpt.length
672
+ bb_global.x = 0
673
+ bb_global.y = 0
674
+ }
653
675
 
654
- compression = +(100 / svgSize * (svgSizeOpt)).toFixed(2)
655
676
 
656
- path.d = dOpt
657
- path.report = {
658
- original: comCount,
659
- new: comCountS,
660
- saved: comCount - comCountS,
661
- compression,
662
- decimals,
663
- //success: comCountS < comCount
664
- }
677
+ // compare command count
678
+ comCountS += pathData.length
665
679
 
666
- //console.log('el', el);
667
- // apply new path for svgs
668
- if (el) {
669
- el.setAttribute('d', dOpt)
670
- }
680
+ let dOpt = pathDataToD(pathData, minifyD)
681
+ //svgSizeOpt = new Blob([dOpt]).size;
682
+ svgSizeOpt = dOpt.length
683
+
684
+ compression = +(100 / svgSize * (svgSizeOpt)).toFixed(2)
685
+
686
+ path.d = dOpt
687
+ path.report = {
688
+ original: comCount,
689
+ new: comCountS,
690
+ saved: comCount - comCountS,
691
+ compression,
692
+ decimals,
693
+ //success: comCountS < comCount
694
+ }
695
+
696
+ // apply new path for svgs
697
+ if (el) {
698
+ el.setAttribute('d', dOpt)
671
699
  }
672
700
 
701
+
673
702
  } // end path array
674
703
 
675
704
  /**
676
705
  * stringify new SVG
677
706
  */
678
- if (mode) {
679
-
680
- //console.log(pathData_merged);
681
- if (pathData_merged.length) {
682
-
683
- // optimize path data
684
- let pathData = convertPathData(pathData_merged, pathOptions)
685
-
686
- // remove zero-length segments introduced by rounding
687
- pathData = removeZeroLengthLinetos(pathData);
688
-
689
- let dOpt = pathDataToD(pathData, minifyD)
707
+ if (mode || inputType === 'symbol') {
690
708
 
691
- // apply new path for svgs
692
- paths[0].el.setAttribute('d', dOpt)
693
-
694
- // remove other paths
695
- for (let i = 1; i < paths.length; i++) {
696
- let pathEl = paths[i].el
697
- if (pathEl) pathEl.remove()
698
- }
699
-
700
- // remove empty groups e.g groups
701
- removeEmptySVGEls(svg);
702
- }
709
+ //console.log('process', inputType);
703
710
 
704
711
  // adjust viewBox and width for scale
705
712
  if (scale) {
706
-
707
713
  let { x, y, width, height, w, h, hasViewBox, hasWidth, hasHeight, widthUnit, heightUnit } = viewBox;
708
- //console.log('bb_global', bb_global);
709
-
710
714
  if (crop) {
711
715
  x = bb_global.x
712
716
  y = bb_global.y
@@ -717,14 +721,14 @@ export function svgPathSimplify(input = '', {
717
721
  }
718
722
 
719
723
  if (hasViewBox) {
720
- svg.setAttribute('viewBox', [x, y, width, height].map(val => +(val * scale).toFixed(decimals)).join(' '))
724
+ svg.setAttribute('viewBox', [x, y, width, height].map(val => roundTo(val * scale, decimals)).join(' '))
721
725
  }
722
726
  if (hasWidth) {
723
- svg.setAttribute('width', +(w * scale).toFixed(decimals) + widthUnit)
727
+ svg.setAttribute('width', roundTo(w * scale, decimals) + widthUnit)
724
728
  }
725
729
 
726
730
  if (hasHeight) {
727
- svg.setAttribute('height', +(h * scale).toFixed(decimals) + heightUnit)
731
+ svg.setAttribute('height', roundTo(h * scale, decimals) + heightUnit)
728
732
  }
729
733
  }
730
734
 
@@ -737,9 +741,12 @@ export function svgPathSimplify(input = '', {
737
741
  })
738
742
  }
739
743
 
744
+ //console.log(svg);
745
+ if (removeSVGAttributes.includes('xmlns')) omitNamespace = true;
740
746
 
741
- svg = stringifySVG(svg, { omitNamespace, removeComments });
742
747
 
748
+ svg = stringifySVG(svg, { omitNamespace, removeComments, format: minifyD });
749
+ //console.log('!!!svg', svg);
743
750
 
744
751
  //svgSizeOpt = new Blob([svg]).size
745
752
  svgSizeOpt = svg.length;
@@ -749,13 +756,25 @@ export function svgPathSimplify(input = '', {
749
756
  svgSize = +(svgSize / 1024).toFixed(3)
750
757
  svgSizeOpt = +(svgSizeOpt / 1024).toFixed(3)
751
758
 
759
+
752
760
  report = {
761
+ original: comCount,
762
+ new: comCountS,
763
+ saved: comCount - comCountS,
753
764
  svgSize,
754
765
  svgSizeOpt,
755
766
  compression,
756
- decimals
767
+ decimals,
768
+ }
769
+
770
+ if (keepSmaller && svgSize < svgSizeOpt && !splitCompound) {
771
+ //console.log('Original is smaller!');
772
+ svg = input
773
+ report.node = 'Original is smaller!'
757
774
  }
758
775
 
776
+
777
+
759
778
  } else {
760
779
  ({ d, report } = paths[0]);
761
780
  }
@@ -764,7 +783,16 @@ export function svgPathSimplify(input = '', {
764
783
  polys = polys[0]
765
784
  }
766
785
 
767
- return !getObject ? (d ? d : svg) : { svg, d, polys, report, pathDataPlusArr: pathDataPlusArr_global, inputType };
786
+
787
+ //console.log('---simplify', input);
788
+ //console.log('5. svg', svg);
789
+
790
+ if (polyFormat === 'string' && polys.length) {
791
+ polys = polys.flat().map(pt => `${pt.x},${pt.y}`).join(' ')
792
+ }
793
+
794
+
795
+ return !getObject ? (d ? d : svg) : { svg, d, polys, report, pathDataPlusArr: pathDataPlusArr_global, inputType, dOriginal };
768
796
 
769
797
  }
770
798