svg-path-simplify 0.4.3 → 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 (39) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +1 -0
  3. package/dist/svg-path-simplify.esm.js +1610 -495
  4. package/dist/svg-path-simplify.esm.min.js +2 -2
  5. package/dist/svg-path-simplify.js +1611 -494
  6. package/dist/svg-path-simplify.min.js +2 -2
  7. package/dist/svg-path-simplify.pathdata.esm.js +893 -456
  8. package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
  9. package/dist/svg-path-simplify.poly.cjs +9 -8
  10. package/index.html +58 -17
  11. package/package.json +1 -1
  12. package/src/constants.js +4 -0
  13. package/src/detect_input.js +47 -29
  14. package/src/index.js +8 -0
  15. package/src/pathData_simplify_cubic.js +26 -16
  16. package/src/pathData_simplify_revertToquadratics.js +0 -1
  17. package/src/pathSimplify-main.js +75 -20
  18. package/src/pathSimplify-only-pathdata.js +7 -2
  19. package/src/pathSimplify-presets.js +15 -4
  20. package/src/svg-getAttributes.js +4 -2
  21. package/src/svgii/convert_units.js +1 -1
  22. package/src/svgii/geometry.js +140 -2
  23. package/src/svgii/geometry_bbox_element.js +1 -1
  24. package/src/svgii/geometry_deduceRadius.js +116 -27
  25. package/src/svgii/geometry_length.js +17 -1
  26. package/src/svgii/pathData_analyze.js +18 -0
  27. package/src/svgii/pathData_convert.js +188 -88
  28. package/src/svgii/pathData_fix_directions.js +10 -18
  29. package/src/svgii/pathData_reorder.js +122 -16
  30. package/src/svgii/pathData_simplify_refineCorners.js +130 -35
  31. package/src/svgii/pathData_simplify_refine_round.js +420 -0
  32. package/src/svgii/rounding.js +79 -78
  33. package/src/svgii/svg_cleanup.js +68 -20
  34. package/src/svgii/svg_cleanup_convertPathLength.js +22 -15
  35. package/src/svgii/svg_cleanup_remove_els_and_atts.js +6 -1
  36. package/src/svgii/svg_el_parse_style_props.js +13 -10
  37. package/src/svgii/svg_validate.js +220 -0
  38. package/tests/testSVG.js +14 -1
  39. package/src/svgii/pathData_refine_round.js +0 -222
@@ -17,7 +17,7 @@ import { qrDecomposeMatrix } from "./transform_qr_decompose";
17
17
  import { svgNs } from "../constants";
18
18
  import { toCamelCase, toShortStr } from "../string_helpers";
19
19
  import { getElementLength } from "./svg_getElementLength";
20
- import { removeHiddenSvgEls, removeSvgAtts, removeSvgChildAtts, removeSvgEls } from "./svg_cleanup_remove_els_and_atts";
20
+ import { removeAtts, removeHiddenSvgEls, removeSvgAtts, removeSvgChildAtts, removeSvgEls } from "./svg_cleanup_remove_els_and_atts";
21
21
  import { cleanupSVGAttributes, removeElAtts } from "./svg_cleanup_general_svg_atts";
22
22
  import { convertPathLengthAtt } from "./svg_cleanup_convertPathLength";
23
23
  import { removeGroupProps, ungroupElements } from "./svg_cleanup_ungroup";
@@ -54,7 +54,10 @@ export function cleanUpSVG(svgMarkup, {
54
54
  cleanupSVGAtts = true,
55
55
  removeNameSpaced = true,
56
56
  removeNameSpacedAtts = true,
57
+
58
+ // unit conversions
57
59
  convertPathLength = false,
60
+ toAbsoluteUnits = false,
58
61
 
59
62
  // meta
60
63
  allowMeta = false,
@@ -82,10 +85,10 @@ export function cleanUpSVG(svgMarkup, {
82
85
 
83
86
 
84
87
  // resolve dependencies
85
- if (unGroup || convertTransforms || minifyRgbColors || attributesToGroup)
86
- stylesToAttributes = true;
88
+ if (unGroup || convertTransforms || minifyRgbColors || attributesToGroup)
89
+ stylesToAttributes = true;
87
90
 
88
- if(stylesToAttributes) cleanUpStrokes = true;
91
+ if (stylesToAttributes) cleanUpStrokes = true;
89
92
 
90
93
  // replace namespaced refs
91
94
  if (fixHref) svgMarkup = svgMarkup.replaceAll("xlink:href=", "href=");
@@ -136,7 +139,7 @@ export function cleanUpSVG(svgMarkup, {
136
139
  removeClassNames,
137
140
  minifyRgbColors,
138
141
  stylesheetProps: {},
139
- exclude:[]
142
+ exclude: []
140
143
  }
141
144
 
142
145
  // root svg inline style properties
@@ -275,9 +278,16 @@ export function cleanUpSVG(svgMarkup, {
275
278
  if (stylePropsSVG['class']) delete stylePropsSVG['class']
276
279
  if (stylePropsSVG['id']) delete stylePropsSVG['id']
277
280
 
281
+ // add svg props
282
+ inheritedProps = {
283
+ ...stylePropsSVG,
284
+ ...inheritedProps,
285
+ };
286
+
287
+ //console.log('inheritedProps', inheritedProps);
288
+
278
289
  // merge with svg props
279
290
  styleProps = {
280
- ...stylePropsSVG,
281
291
  ...inheritedProps,
282
292
  ...styleProps
283
293
  }
@@ -287,7 +297,6 @@ export function cleanUpSVG(svgMarkup, {
287
297
  addTransFormProps(styleProps, transFormInherited);
288
298
  //console.log('transFormInherited', transFormInherited);
289
299
  //console.log('styleProps', styleProps);
290
-
291
300
  remove = [...new Set([...remove, ...styleProps.remove])];
292
301
 
293
302
 
@@ -325,6 +334,46 @@ export function cleanUpSVG(svgMarkup, {
325
334
  // general cleanup
326
335
  if (cleanupSVGAtts) cleanupSVGAttributes(svg, { removeIds, removeClassNames, removeDimensions, stylesToAttributes, allowMeta, allowAriaAtts, allowDataAtts });
327
336
 
337
+ // all relative units to absolute
338
+ if (toAbsoluteUnits) {
339
+ normalizeTransforms = true;
340
+ //stylesToAttributes = true
341
+ //console.log(name, styleProps);
342
+
343
+ /**
344
+ * apply consolidated
345
+ * element attributes
346
+ * remove non-supported element props
347
+ */
348
+ stylePropsFiltered = filterSvgElProps(name, styleProps,
349
+ { removeDefaults: true, cleanUpStrokes, allowMeta, allowAriaAtts, allowDataAtts, removeIds, inheritedProps });
350
+
351
+
352
+ for (let prop in stylePropsFiltered.propsFiltered) {
353
+ let values = styleProps[prop]
354
+ let val = values.length ? values.join(' ') : values[0]
355
+ el.setAttribute(prop, val)
356
+ }
357
+
358
+ //console.log('inheritedProps', inheritedProps);
359
+ //console.log('current props', stylePropsFiltered.propsFiltered);
360
+
361
+ let removeAttsEl = [...new Set([...remove, ...stylePropsFiltered.remove])];
362
+
363
+ // check if same value is in inherited
364
+ for (let prop in stylePropsFiltered.propsFiltered) {
365
+ let valInh = inheritedProps[prop] || [];
366
+ let val = stylePropsFiltered.propsFiltered[prop] || [];
367
+ if (valInh.join() === val.join()) {
368
+ removeAttsEl.push(prop)
369
+ }
370
+ }
371
+
372
+ // remove obsolete/inherited
373
+ removeAtts(el, removeAttsEl)
374
+
375
+ }
376
+
328
377
 
329
378
 
330
379
  if (stylesToAttributes) {
@@ -345,12 +394,9 @@ export function cleanUpSVG(svgMarkup, {
345
394
  * remove non-supported element props
346
395
  */
347
396
  stylePropsFiltered = filterSvgElProps(name, styleProps,
348
- { removeDefaults: true, cleanUpStrokes, allowMeta, allowAriaAtts, allowDataAtts, removeIds });
397
+ { removeDefaults: true, cleanUpStrokes, allowMeta, allowAriaAtts, allowDataAtts, removeIds, inheritedProps });
349
398
 
350
- //remove = [...remove, ...stylePropsFiltered.remove];
351
399
  remove = [...new Set([...remove, ...stylePropsFiltered.remove])];
352
- //console.log('el remove', name, remove);
353
- //console.log('!!stylePropsFiltered', name, stylePropsFiltered);
354
400
 
355
401
  for (let prop in stylePropsFiltered.propsFiltered) {
356
402
  let values = styleProps[prop]
@@ -363,14 +409,14 @@ export function cleanUpSVG(svgMarkup, {
363
409
  * remove obsolete
364
410
  * attributes
365
411
  */
366
- //removeSvgChildAtts(svg, remove)
412
+ removeAtts(el, remove)
367
413
 
414
+ /*
368
415
  for (let i = 0; i < remove.length; i++) {
369
416
  let att = remove[i];
370
- //if (att === 'style') continue
371
- //console.log('--remove att', remove, att, name);
372
417
  el.removeAttribute(att)
373
418
  }
419
+ */
374
420
 
375
421
 
376
422
  } // endof style processing
@@ -460,15 +506,15 @@ export function cleanUpSVG(svgMarkup, {
460
506
  let values = stylePropsFiltered[prop]
461
507
  let val = values.length ? values.join(' ') : values[0]
462
508
 
463
- if(prop!=='class' && prop!=='id'){
509
+ if (prop !== 'class' && prop !== 'id') {
464
510
 
465
511
  let propShort = toShortStr(prop)
466
512
  let valShort = toShortStr(val)
467
513
  let propStr = `${propShort}-${valShort}`;
468
-
514
+
469
515
  // store in node property
470
516
  if (!el.styleSet) el.styleSet = new Set()
471
- if(propStr) el.styleSet.add(propStr)
517
+ if (propStr) el.styleSet.add(propStr)
472
518
  }
473
519
  }
474
520
 
@@ -524,7 +570,7 @@ export function cleanUpSVG(svgMarkup, {
524
570
 
525
571
 
526
572
  function removeEmptyClassAtts(svg) {
527
- let emptyClassEls = svg.querySelectorAll('[class=""');
573
+ let emptyClassEls = svg.querySelectorAll('[class=""]');
528
574
  emptyClassEls.forEach(el => {
529
575
  el.removeAttribute('class')
530
576
  })
@@ -538,7 +584,7 @@ function sharedAttributesToGroup(svg) {
538
584
 
539
585
  let els = svg.querySelectorAll(renderedEls.join(', '))
540
586
  let len = els.length;
541
- if(len===1) return;
587
+ if (len === 1) return;
542
588
 
543
589
  let el0 = els[0] || null
544
590
  let stylePrev = el0.styleSet !== undefined ? [...el0.styleSet].join('_') : ''
@@ -623,7 +669,7 @@ function sharedAttributesToGroup(svg) {
623
669
 
624
670
 
625
671
  // create new group
626
- if (!groupEl || groups.length>1) {
672
+ if (!groupEl || groups.length > 1) {
627
673
  //console.log('new group');
628
674
  groupEl = document.createElementNS(svgNs, 'g')
629
675
  child0.parentNode.insertBefore(groupEl, child0)
@@ -719,7 +765,9 @@ function removeOffCanvasEls(svg, { x = 0, y = 0, width = 0, height = 0 } = {}) {
719
765
  bb0.bottom = y + height
720
766
 
721
767
  els.forEach(el => {
768
+ //console.log(el);
722
769
  let bb = getElBBox(el)
770
+ //console.log('!!bb', bb);
723
771
  let outside = bb.right < bb0.x || bb.bottom < bb0.y || bb.x > bb0.right || bb.y > bb.bottom
724
772
  if (outside) el.remove();
725
773
  })
@@ -1,29 +1,36 @@
1
+ import { scaleProps } from "./svg_cleanup_normalize_transforms";
2
+ import { getElementLength } from "./svg_getElementLength";
3
+
1
4
  export function convertPathLengthAtt(el, {
2
5
  styleProps = {}
3
- }={}) {
6
+ } = {}) {
7
+
8
+ let pathLength = styleProps['pathLength'];
9
+
10
+ if (pathLength) {
4
11
 
5
- let pathLength = el.getAttribute('pathLength') ? +el.getAttribute('pathLength') : 0;
6
- //let strokeDasharray
7
- if (pathLength && (styleProps['stroke-dasharray'] || styleProps['stroke-dashoffset'])) {
8
- let elLength = getElementLength(el, {
9
- pathLength,
10
- props: styleProps
11
- })
12
+ //let strokeDasharray
13
+ if ((styleProps['stroke-dasharray'] || styleProps['stroke-dashoffset'])) {
14
+ let elLength = getElementLength(el, {
15
+ pathLength,
16
+ props: styleProps
17
+ })
12
18
 
13
- let scale = elLength / pathLength
14
- //scale = 1/scale
19
+ let scale = elLength / pathLength
20
+ styleProps = scaleProps(styleProps, { props: ['stroke-dasharray', 'stroke-dashoffset'], scale })
15
21
 
16
- styleProps = scaleProps(styleProps, { props: ['stroke-dasharray', 'stroke-dashoffset'], scale })
17
- let [strokeDasharrayN = [], strokeDashoffsetN = []] = [styleProps['stroke-dasharray'], styleProps['stroke-dashoffset']]
18
- //if(strokeDasharrayN.length) el.setAttribute('stroke-dasharray', strokeDasharrayN.map(val=>roundTo(val, 3)).join(' '))
19
- //if(strokeDashoffsetN.length) el.setAttribute('stroke-dashoffset', strokeDashoffsetN.map(val=>roundTo(val, 3)).join(' '))
22
+ // set absolute
23
+ if (styleProps['stroke-dasharray']) el.setAttribute('stroke-dasharray', styleProps['stroke-dasharray'].join(' '))
24
+ if (styleProps['stroke-dashoffset']) el.setAttribute('stroke-dashoffset', styleProps['stroke-dashoffset'][0])
25
+
26
+ }
20
27
 
21
28
  // tag for removal
22
29
  delete styleProps['pathLength'];
23
30
  styleProps.remove.push('pathLength')
24
31
  el.removeAttribute('pathLength')
25
32
 
26
- //console.log(name, styleProps, 'pathLength', pathLength, elLength, scale, 'strokeDasharrayN', strokeDasharrayN);
33
+
27
34
  }
28
35
 
29
36
  return styleProps;
@@ -51,11 +51,16 @@ export function removeSvgEls(svg, {
51
51
  */
52
52
 
53
53
  export function removeSvgAtts(svg, remove = []) {
54
+ removeAtts(svg, remove)
55
+ }
56
+
57
+ export function removeAtts(el, remove = []) {
54
58
  remove.forEach(att => {
55
- svg.removeAttribute(att);
59
+ el.removeAttribute(att);
56
60
  })
57
61
  }
58
62
 
63
+
59
64
  export function removeSvgChildAtts(svg, remove = []) {
60
65
  if (remove.length) {
61
66
  let selector = remove.map(att => { return `[${att}]` }).join(', ')
@@ -18,8 +18,8 @@ export function parseStylesProperties(el, {
18
18
  autoRoundValues = false,
19
19
  minifyRgbColors = false,
20
20
  removeInvalid = true,
21
- allowDataAtts=true,
22
- allowAriaAtts=true,
21
+ allowDataAtts = true,
22
+ allowAriaAtts = true,
23
23
  removeDefaults = true,
24
24
  cleanUpStrokes = true,
25
25
  normalizeTransforms = true,
@@ -94,7 +94,7 @@ export function parseStylesProperties(el, {
94
94
  */
95
95
 
96
96
  if (removeInvalid || removeDefaults || removeNameSpaced) {
97
- let propsFilteredObj = filterSvgElProps(nodeName, props, {allowDataAtts, allowAriaAtts, removeIds, removeClassNames, removeDefaults, removeNameSpaced, exclude, cleanUpStrokes, include: [...transformsStandalone, ...include], cleanUpStrokes: false })
97
+ let propsFilteredObj = filterSvgElProps(nodeName, props, { allowDataAtts, allowAriaAtts, removeIds, removeClassNames, removeDefaults, removeNameSpaced, exclude, cleanUpStrokes, include: [...transformsStandalone, ...include], cleanUpStrokes: false })
98
98
  props = propsFilteredObj.propsFiltered
99
99
  remove.push(...propsFilteredObj.remove)
100
100
  //console.log(propsFilteredObj.remove, allowDataAtts, allowAriaAtts);
@@ -196,10 +196,11 @@ export function parseStylesProperties(el, {
196
196
 
197
197
  if (prop !== 'transforms') {
198
198
 
199
- if (cleanUpStrokes && (prop === 'stroke-dasharray' || prop === 'stroke-dashoffset')) {
199
+ //cleanUpStrokes &&
200
+ if ((prop === 'stroke-dasharray' || prop === 'stroke-dashoffset')) {
200
201
  normalizedDiagonal = true
201
202
  for (let i = 0; i < values.length; i++) {
202
- let val = normalizeUnits(values[i].value, { unit: values[i].unit, width, height, normalizedDiagonal, fontSize })
203
+ let val = normalizeUnits(values[i].value, { unit: values[i].unit, width, height, normalizedDiagonal, fontSize, autoRoundValues })
203
204
  valsNew.push(val)
204
205
  }
205
206
  }
@@ -244,15 +245,12 @@ export function parseStylesProperties(el, {
244
245
  if (prop === 'scale' && unit === '%') {
245
246
  valAbs = valAbs * 0.01;
246
247
  } else {
247
- if (prop === 'r') normalizedDiagonal = true;
248
+ if (prop === 'r' && width!==height) normalizedDiagonal = true;
248
249
  valAbs = normalizeUnits(val.value, { unit, width, height, isHorizontal, isVertical, normalizedDiagonal, fontSize })
249
250
 
250
251
  if (autoRoundValues && isNumeric) {
251
252
  valAbs = autoRound(valAbs)
252
- //valAbs = roundTo(valAbs, 3)
253
253
  }
254
-
255
- //console.log('norm', prop, valAbs, 'val', val, unit, isHorizontal, isVertical, width, height, 'isNumeric', isNumeric);
256
254
  }
257
255
  }
258
256
  valsNew.push(valAbs)
@@ -466,6 +464,7 @@ export function filterSvgElProps(elNodename = '', props = {}, {
466
464
  removeIds = false,
467
465
  removeClassNames = false,
468
466
  exclude = [],
467
+ inheritedProps = null,
469
468
  } = {}) {
470
469
  let propsFiltered = {}
471
470
  let remove = [];
@@ -474,6 +473,7 @@ export function filterSvgElProps(elNodename = '', props = {}, {
474
473
  if (!removeClassNames) include.push('class')
475
474
  //console.log('???include', 'removeIds', removeIds, include);
476
475
 
476
+
477
477
  // allow defaults for nested
478
478
  //removeDefaults = false;
479
479
 
@@ -490,6 +490,7 @@ export function filterSvgElProps(elNodename = '', props = {}, {
490
490
  (attLookup.atts[prop] ? attLookup.atts[prop].includes(elNodename) : false) :
491
491
  false;
492
492
 
493
+
493
494
  // remove null transforms
494
495
  if (prop === 'transform' && value === 'matrix(1 0 0 1 0 0)') isValid = false;
495
496
 
@@ -498,7 +499,7 @@ export function filterSvgElProps(elNodename = '', props = {}, {
498
499
  let isMeta = prop === 'title'
499
500
  let isAria = prop.startsWith('aria-')
500
501
 
501
- if( (allowDataAtts && isDataAtt) || (allowAriaAtts && isAria) || (allowMeta && isMeta ) ) continue
502
+ if ((allowDataAtts && isDataAtt) || (allowAriaAtts && isAria) || (allowMeta && isMeta)) continue
502
503
 
503
504
  // filter out defaults
504
505
  let isDefault = removeDefaults ?
@@ -509,6 +510,8 @@ export function filterSvgElProps(elNodename = '', props = {}, {
509
510
 
510
511
  if (isDefault || isDataAtt || isMeta || isAria || isFutileStroke) isValid = false
511
512
  if (include.includes(prop)) isValid = true;
513
+ if (exclude.includes(prop)) isValid = false
514
+
512
515
 
513
516
  if (isValid) {
514
517
  propsFiltered[prop] = props[prop]
@@ -0,0 +1,220 @@
1
+
2
+
3
+ export function validateSVG(markup, allowed = {}) {
4
+ allowed = {
5
+ ...{
6
+ //useEls: 10,
7
+ //hasPrologue: false,
8
+ //hasXmlns: true,
9
+ useElsNested: 5000,
10
+ hasScripts: false,
11
+ hasEntity: false,
12
+ fileSizeKB: 10000,
13
+ isSymbolSprite: false,
14
+ isSvgFont: false
15
+ },
16
+ ...allowed
17
+ };
18
+
19
+
20
+ let fileReport = analyzeSVG(markup, allowed);
21
+ let isValid = true;
22
+ let log = [];
23
+
24
+ if (!fileReport.hasEls) {
25
+ log.push("no elements");
26
+ isValid = false;
27
+ }
28
+
29
+ if (Object.keys(fileReport).length) {
30
+ if (fileReport.isBillionLaugh === true) {
31
+ log.push(`suspicious: might contain billion laugh attack`);
32
+ isValid = false;
33
+ }
34
+
35
+ for (let key in allowed) {
36
+ let val = allowed[key];
37
+ let valRep = fileReport[key];
38
+ if (typeof val === "number" && valRep > val) {
39
+ log.push(`allowed "${key}" exceeded: ${valRep} / ${val} `);
40
+ isValid = false;
41
+ }
42
+ if (valRep === true && val === false) {
43
+ log.push(`not allowed: "${key}" `);
44
+ isValid = false;
45
+ }
46
+ }
47
+ } else {
48
+ isValid = false;
49
+ }
50
+
51
+ /*
52
+ if (!isValid) {
53
+ log = ["SVG not valid"].concat(log);
54
+ //console.log(log.join("\n"));
55
+ if (Object.keys(fileReport).length) {
56
+ console.warn(fileReport);
57
+ }
58
+ }
59
+ */
60
+
61
+ return { isValid, log, fileReport };
62
+ }
63
+
64
+ function analyzeSVG(markup, allowed = {}) {
65
+ markup = markup.trim();
66
+ let doc, svg;
67
+ let fileSizeKB = +(markup.length / 1024).toFixed(3);
68
+
69
+ let fileReport = {
70
+ totalEls: 1,
71
+ hasEls: true,
72
+ hasDefs: false,
73
+ geometryEls: [],
74
+ useEls: 0,
75
+ useElsNested: 0,
76
+ nonsensePaths: 0,
77
+ isSuspicious: false,
78
+ isBillionLaugh: false,
79
+ hasScripts: false,
80
+ hasPrologue: false,
81
+ hasEntity: false,
82
+ isPathData:false,
83
+ fileSizeKB,
84
+ hasXmlns: markup.includes("http://www.w3.org/2000/svg"),
85
+ isSymbolSprite: false,
86
+ isSvgFont: markup.includes("<glyph>")
87
+ };
88
+
89
+
90
+ let maxNested = allowed.useElsNested ? allowed.useElsNested : 2000;
91
+
92
+ /**
93
+ * analyze nestes use references
94
+ */
95
+ const countUseRefs = (useEls, maxNested = 2000) => {
96
+ let nestedCount = 0;
97
+ //stop loop if number of nested use references is exceeded
98
+ for (let i = 0; i < useEls.length && nestedCount < maxNested; i++) {
99
+ let use = useEls[i];
100
+ let refId = use.getAttribute("xlink:href")
101
+ ? use.getAttribute("xlink:href")
102
+ : use.getAttribute("href");
103
+ refId = refId ? refId.replace("#", "") : "";
104
+
105
+ //normalize href attributes to facilitate JS selection
106
+ use.setAttribute("href", "#" + refId);
107
+
108
+ let refEl = svg.getElementById(refId);
109
+ let nestedUse = refEl.querySelectorAll("use");
110
+ let nestedUseLength = nestedUse.length;
111
+ nestedCount += nestedUseLength;
112
+
113
+ // query nested use references
114
+ for (let n = 0; n < nestedUse.length && nestedCount < maxNested; n++) {
115
+ let nested = nestedUse[n];
116
+ let id1 = nested.getAttribute("href").replace("#", "");
117
+ let refEl1 = svg.getElementById(id1);
118
+ let nestedUse1 = refEl1.querySelectorAll("use");
119
+ nestedCount += nestedUse1.length;
120
+ }
121
+ }
122
+ fileReport.useElsNested = nestedCount;
123
+ return nestedCount;
124
+ };
125
+
126
+ /**
127
+ * check on raw text level
128
+ */
129
+ let hasPrologue = /\<\?xml.+\?\>|\<\!DOCTYPE.+]\>/g.test(markup);
130
+ let hasEntity = /\<\!ENTITY/gi.test(markup);
131
+ let hasScripts = /\<script/gi.test(markup) ? true : false;
132
+ let hasUse = /\<use/gi.test(markup) ? true : false;
133
+ let hasEls = /[\<path|\<polygon|\<polyline|\<rect|\<circle|\<ellipse|\<line|\<text|\<foreignObject]/gi.test(markup);
134
+ let hasDefs = /[\<filter|\<linearGradient|\<radialGradient|\<pattern|\<animate|\<animateMotion|\<animateTransform|\<clipPath|\<mask|\<symbol|\<marker]/gi.test(markup);
135
+
136
+ let isPathData = (markup.startsWith('M') || markup.startsWith('m')) && !/[\<svg|\<\/svg]/gi.test(markup);
137
+ fileReport.isPathData = isPathData;
138
+
139
+ // seems OK
140
+ if (!hasEntity && !hasUse && !hasScripts && (hasEls || hasDefs) && fileSizeKB < allowed.fileSizeKB) {
141
+ fileReport.hasEls = hasEls
142
+ fileReport.hasDefs = hasDefs
143
+ //console.log('Looks OK!', fileReport, allowed);
144
+ return fileReport
145
+ }
146
+
147
+
148
+ // Contains xml entity definition: highly suspicious - stop parsing!
149
+ if (allowed.hasEntity === false && hasEntity) {
150
+ fileReport.hasEntity = true;
151
+ //return fileReport;
152
+ }
153
+
154
+ /**
155
+ * sanitizing for parsing:
156
+ * remove xml prologue and comments
157
+ */
158
+ markup = markup
159
+ .replace(/\<\?xml.+\?\>|\<\!DOCTYPE.+]\>/g, "")
160
+ .replace(/(<!--.*?-->)|(<!--[\S\s]+?-->)|(<!--[\S\s]*?$)/g, "");
161
+
162
+ /**
163
+ * Try to parse svg:
164
+ * invalid svg will return false via "catch"
165
+ */
166
+ try {
167
+ //doc = new DOMParser().parseFromString(markup, "image/svg+xml");
168
+ doc = new DOMParser().parseFromString(markup, "text/html");
169
+ svg = doc.querySelector("svg");
170
+
171
+ // paths containing only a M command
172
+ let nonsensePaths = svg.querySelectorAll('path[d="M0,0"], path[d="M0 0"]').length;
173
+ let useEls = svg.querySelectorAll("use").length;
174
+
175
+
176
+ // create analyzing object
177
+ fileReport.totalEls = svg.querySelectorAll("*").length;
178
+ fileReport.geometryEls = svg.querySelectorAll(
179
+ "path, rect, circle, ellipse, polygon, polyline, line"
180
+ ).length
181
+
182
+ fileReport.hasScripts = hasScripts
183
+ fileReport.useEls = useEls;
184
+ fileReport.nonsensePaths = nonsensePaths;
185
+ fileReport.isSuspicious = false;
186
+ fileReport.isBillionLaugh = false;
187
+ fileReport.hasXmlns = svg.getAttribute("xmlns")
188
+ ? svg.getAttribute("xmlns") === "http://www.w3.org/2000/svg"
189
+ ? true
190
+ : false
191
+ : false;
192
+ fileReport.isSymbolSprite =
193
+ svg.querySelectorAll("symbol").length &&
194
+ svg.querySelectorAll("use").length === 0
195
+ ? true
196
+ : false;
197
+ fileReport.isSvgFont = svg.querySelectorAll("glyph").length ? true : false;
198
+
199
+ let totalEls = fileReport.totalEls;
200
+ let totalUseEls = fileReport.useEls;
201
+ let usePercentage = (100 / totalEls) * totalUseEls;
202
+
203
+ // if percentage of use elements is higher than 75% - suspicious
204
+ if (usePercentage > 75) {
205
+ fileReport.isSuspicious = true;
206
+
207
+ // check nested use references
208
+ let nestedCount = countUseRefs(svg.querySelectorAll("use"), maxNested);
209
+ if (nestedCount >= maxNested) {
210
+ fileReport.isBillionLaugh = true;
211
+ }
212
+ }
213
+
214
+ return fileReport;
215
+ } catch {
216
+ // svg file has malformed markup
217
+ console.warn("svg could not be parsed");
218
+ return false;
219
+ }
220
+ }
package/tests/testSVG.js CHANGED
@@ -19,8 +19,21 @@ let svgMarkup =
19
19
  </g>
20
20
  </svg>`
21
21
 
22
+
23
+ /*
24
+ let document = new DOMParser().parseFromString(svgMarkup, 'image/svg+xml');
25
+ let svg = document.querySelector('svg');
26
+ let path = svg.querySelector('path');
27
+ let els = svg.querySelectorAll('path')
28
+ let d = path.getAttribute('d').substring(0, 10)
29
+ console.log(d);
30
+ */
31
+
32
+
33
+
34
+
22
35
  // try to simplify
23
- let svgOpt = svgPathSimplify(svgMarkup, {scaleTo:24});
36
+ let svgOpt = svgPathSimplify(svgMarkup, {preset:'high'});
24
37
 
25
38
  // simplified pathData
26
39
  console.log(svgOpt)