svg-toolbox 1.1.11 → 1.2.0

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 (181) hide show
  1. package/README.md +156 -118
  2. package/es/analyze/__tests__/colors.test.d.ts +1 -0
  3. package/es/analyze/__tests__/colors.test.js +44 -0
  4. package/es/analyze/__tests__/colors.test.js.map +1 -0
  5. package/es/analyze/__tests__/paths.test.d.ts +1 -0
  6. package/es/analyze/__tests__/paths.test.js +71 -0
  7. package/es/analyze/__tests__/paths.test.js.map +1 -0
  8. package/es/analyze/colors.d.ts +8 -0
  9. package/es/analyze/colors.js +55 -0
  10. package/es/analyze/colors.js.map +1 -0
  11. package/es/analyze/paths.d.ts +20 -0
  12. package/es/analyze/paths.js +92 -0
  13. package/es/analyze/paths.js.map +1 -0
  14. package/es/compare/__tests__/diff.test.d.ts +1 -0
  15. package/es/compare/__tests__/diff.test.js +64 -0
  16. package/es/compare/__tests__/diff.test.js.map +1 -0
  17. package/es/compare/diff.d.ts +18 -0
  18. package/es/compare/diff.js +70 -0
  19. package/es/compare/diff.js.map +1 -0
  20. package/es/convert/__tests__/base64.test.d.ts +1 -0
  21. package/es/convert/__tests__/base64.test.js +49 -0
  22. package/es/convert/__tests__/base64.test.js.map +1 -0
  23. package/es/convert/base64.d.ts +11 -0
  24. package/es/convert/base64.js +38 -0
  25. package/es/convert/base64.js.map +1 -0
  26. package/es/convert/image.d.ts +18 -0
  27. package/es/convert/image.js +63 -0
  28. package/es/convert/image.js.map +1 -0
  29. package/es/core/__tests__/dimensions.test.d.ts +1 -0
  30. package/es/core/__tests__/dimensions.test.js +40 -0
  31. package/es/core/__tests__/dimensions.test.js.map +1 -0
  32. package/es/core/__tests__/element.test.d.ts +1 -0
  33. package/es/core/__tests__/element.test.js +79 -0
  34. package/es/core/__tests__/element.test.js.map +1 -0
  35. package/es/core/dimensions.d.ts +8 -0
  36. package/es/core/dimensions.js +34 -0
  37. package/es/core/dimensions.js.map +1 -0
  38. package/es/core/element.d.ts +24 -0
  39. package/es/core/element.js +52 -0
  40. package/es/core/element.js.map +1 -0
  41. package/es/index.d.ts +15 -10
  42. package/es/index.js +21 -10
  43. package/es/index.js.map +1 -1
  44. package/es/optimize/__tests__/cleanup.test.d.ts +1 -0
  45. package/es/optimize/__tests__/cleanup.test.js +95 -0
  46. package/es/optimize/__tests__/cleanup.test.js.map +1 -0
  47. package/es/optimize/__tests__/path.test.d.ts +1 -0
  48. package/es/optimize/__tests__/path.test.js +41 -0
  49. package/es/optimize/__tests__/path.test.js.map +1 -0
  50. package/es/optimize/cleanup.d.ts +27 -0
  51. package/es/optimize/cleanup.js +100 -0
  52. package/es/optimize/cleanup.js.map +1 -0
  53. package/es/optimize/path.d.ts +7 -0
  54. package/es/optimize/path.js +58 -0
  55. package/es/optimize/path.js.map +1 -0
  56. package/es/types/index.d.ts +31 -0
  57. package/es/types/index.js +5 -0
  58. package/es/types/index.js.map +1 -0
  59. package/es/utils/__tests__/path-validation.test.d.ts +1 -0
  60. package/es/utils/__tests__/path-validation.test.js +64 -0
  61. package/es/utils/__tests__/path-validation.test.js.map +1 -0
  62. package/es/utils/__tests__/validation.test.d.ts +1 -0
  63. package/es/utils/__tests__/validation.test.js +42 -0
  64. package/es/utils/__tests__/validation.test.js.map +1 -0
  65. package/es/utils/dom.d.ts +23 -0
  66. package/es/utils/dom.js +25 -0
  67. package/es/utils/dom.js.map +1 -0
  68. package/es/utils/path-validation.d.ts +29 -0
  69. package/es/utils/path-validation.js +101 -0
  70. package/es/utils/path-validation.js.map +1 -0
  71. package/es/utils/validation.d.ts +11 -0
  72. package/es/utils/validation.js +30 -0
  73. package/es/utils/validation.js.map +1 -0
  74. package/lib/analyze/__tests__/colors.test.d.ts +1 -0
  75. package/lib/analyze/__tests__/colors.test.js +46 -0
  76. package/lib/analyze/__tests__/colors.test.js.map +1 -0
  77. package/lib/analyze/__tests__/paths.test.d.ts +1 -0
  78. package/lib/analyze/__tests__/paths.test.js +73 -0
  79. package/lib/analyze/__tests__/paths.test.js.map +1 -0
  80. package/lib/analyze/colors.d.ts +8 -0
  81. package/lib/analyze/colors.js +58 -0
  82. package/lib/analyze/colors.js.map +1 -0
  83. package/lib/analyze/paths.d.ts +20 -0
  84. package/lib/analyze/paths.js +97 -0
  85. package/lib/analyze/paths.js.map +1 -0
  86. package/lib/compare/__tests__/diff.test.d.ts +1 -0
  87. package/lib/compare/__tests__/diff.test.js +128 -0
  88. package/lib/compare/__tests__/diff.test.js.map +1 -0
  89. package/lib/compare/diff.d.ts +18 -0
  90. package/lib/compare/diff.js +134 -0
  91. package/lib/compare/diff.js.map +1 -0
  92. package/lib/convert/__tests__/base64.test.d.ts +1 -0
  93. package/lib/convert/__tests__/base64.test.js +51 -0
  94. package/lib/convert/__tests__/base64.test.js.map +1 -0
  95. package/lib/convert/base64.d.ts +11 -0
  96. package/lib/convert/base64.js +42 -0
  97. package/lib/convert/base64.js.map +1 -0
  98. package/lib/convert/image.d.ts +18 -0
  99. package/lib/{extra-apply/applySvg2Png.js → convert/image.js} +62 -53
  100. package/lib/convert/image.js.map +1 -0
  101. package/lib/core/__tests__/dimensions.test.d.ts +1 -0
  102. package/lib/core/__tests__/dimensions.test.js +42 -0
  103. package/lib/core/__tests__/dimensions.test.js.map +1 -0
  104. package/lib/core/__tests__/element.test.d.ts +1 -0
  105. package/lib/core/__tests__/element.test.js +81 -0
  106. package/lib/core/__tests__/element.test.js.map +1 -0
  107. package/lib/core/dimensions.d.ts +8 -0
  108. package/lib/core/dimensions.js +37 -0
  109. package/lib/core/dimensions.js.map +1 -0
  110. package/lib/core/element.d.ts +24 -0
  111. package/lib/core/element.js +58 -0
  112. package/lib/core/element.js.map +1 -0
  113. package/lib/index.d.ts +15 -10
  114. package/lib/index.js +48 -20
  115. package/lib/index.js.map +1 -1
  116. package/lib/optimize/__tests__/cleanup.test.d.ts +1 -0
  117. package/lib/optimize/__tests__/cleanup.test.js +97 -0
  118. package/lib/optimize/__tests__/cleanup.test.js.map +1 -0
  119. package/lib/optimize/__tests__/path.test.d.ts +1 -0
  120. package/lib/optimize/__tests__/path.test.js +43 -0
  121. package/lib/optimize/__tests__/path.test.js.map +1 -0
  122. package/lib/optimize/cleanup.d.ts +27 -0
  123. package/lib/optimize/cleanup.js +106 -0
  124. package/lib/optimize/cleanup.js.map +1 -0
  125. package/lib/optimize/path.d.ts +7 -0
  126. package/lib/optimize/path.js +63 -0
  127. package/lib/optimize/path.js.map +1 -0
  128. package/lib/types/index.d.ts +31 -0
  129. package/lib/types/index.js +6 -0
  130. package/lib/types/index.js.map +1 -0
  131. package/lib/utils/__tests__/path-validation.test.d.ts +1 -0
  132. package/lib/utils/__tests__/path-validation.test.js +69 -0
  133. package/lib/utils/__tests__/path-validation.test.js.map +1 -0
  134. package/lib/utils/__tests__/validation.test.d.ts +1 -0
  135. package/lib/utils/__tests__/validation.test.js +44 -0
  136. package/lib/utils/__tests__/validation.test.js.map +1 -0
  137. package/lib/utils/dom.d.ts +23 -0
  138. package/lib/utils/dom.js +31 -0
  139. package/lib/utils/dom.js.map +1 -0
  140. package/lib/utils/path-validation.d.ts +29 -0
  141. package/lib/utils/path-validation.js +109 -0
  142. package/lib/utils/path-validation.js.map +1 -0
  143. package/lib/utils/validation.d.ts +11 -0
  144. package/lib/utils/validation.js +35 -0
  145. package/lib/utils/validation.js.map +1 -0
  146. package/package.json +17 -4
  147. package/es/common.d.ts +0 -68
  148. package/es/common.js +0 -110
  149. package/es/common.js.map +0 -1
  150. package/es/extra-apply/applyDiffSvg.d.ts +0 -11
  151. package/es/extra-apply/applyDiffSvg.js +0 -63
  152. package/es/extra-apply/applyDiffSvg.js.map +0 -1
  153. package/es/extra-apply/applyRemoveNanCoordinates.d.ts +0 -6
  154. package/es/extra-apply/applyRemoveNanCoordinates.js +0 -75
  155. package/es/extra-apply/applyRemoveNanCoordinates.js.map +0 -1
  156. package/es/extra-apply/applySvg2Png.d.ts +0 -16
  157. package/es/extra-apply/applySvg2Png.js +0 -64
  158. package/es/extra-apply/applySvg2Png.js.map +0 -1
  159. package/es/utils/pixelLevelDiffPng.d.ts +0 -4
  160. package/es/utils/pixelLevelDiffPng.js +0 -29
  161. package/es/utils/pixelLevelDiffPng.js.map +0 -1
  162. package/es/validate.d.ts +0 -14
  163. package/es/validate.js +0 -44
  164. package/es/validate.js.map +0 -1
  165. package/lib/common.d.ts +0 -68
  166. package/lib/common.js +0 -117
  167. package/lib/common.js.map +0 -1
  168. package/lib/extra-apply/applyDiffSvg.d.ts +0 -11
  169. package/lib/extra-apply/applyDiffSvg.js +0 -116
  170. package/lib/extra-apply/applyDiffSvg.js.map +0 -1
  171. package/lib/extra-apply/applyRemoveNanCoordinates.d.ts +0 -6
  172. package/lib/extra-apply/applyRemoveNanCoordinates.js +0 -78
  173. package/lib/extra-apply/applyRemoveNanCoordinates.js.map +0 -1
  174. package/lib/extra-apply/applySvg2Png.d.ts +0 -16
  175. package/lib/extra-apply/applySvg2Png.js.map +0 -1
  176. package/lib/utils/pixelLevelDiffPng.d.ts +0 -4
  177. package/lib/utils/pixelLevelDiffPng.js +0 -36
  178. package/lib/utils/pixelLevelDiffPng.js.map +0 -1
  179. package/lib/validate.d.ts +0 -14
  180. package/lib/validate.js +0 -48
  181. package/lib/validate.js.map +0 -1
@@ -0,0 +1,34 @@
1
+ /**
2
+ * SVG dimensions and viewBox utilities
3
+ */
4
+ import { isValidSvgString } from '../utils/validation';
5
+ import { serializeSVG } from './element';
6
+ /**
7
+ * Extracts dimensions from an SVG element or string
8
+ */
9
+ export function getSVGDimensions(svgContent) {
10
+ const svgString = isValidSvgString(svgContent)
11
+ ? svgContent
12
+ : serializeSVG(svgContent);
13
+ const viewBoxRegex = /viewBox=["']?(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)["']?/i;
14
+ const widthRegex = /width=["']?(\d+(?:\.\d+)?)/i;
15
+ const heightRegex = /height=["']?(\d+(?:\.\d+)?)/i;
16
+ const viewBoxMatch = svgString.match(viewBoxRegex);
17
+ const widthMatch = svgString.match(widthRegex);
18
+ const heightMatch = svgString.match(heightRegex);
19
+ let width;
20
+ let height;
21
+ let viewBox;
22
+ if (viewBoxMatch) {
23
+ const [, x, y, w, h] = viewBoxMatch.map(Number);
24
+ viewBox = { x, y, width: w, height: h };
25
+ width = w;
26
+ height = h;
27
+ }
28
+ else {
29
+ width = widthMatch ? Number(widthMatch[1]) : 0;
30
+ height = heightMatch ? Number(heightMatch[1]) : 0;
31
+ }
32
+ return { width, height, viewBox };
33
+ }
34
+ //# sourceMappingURL=dimensions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dimensions.js","sourceRoot":"","sources":["../../src/core/dimensions.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAqB,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAGzC;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAA4B;IAC3D,MAAM,SAAS,GAAG,gBAAgB,CAAC,UAAU,CAAC;QAC5C,CAAC,CAAC,UAAU;QACZ,CAAC,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;IAE7B,MAAM,YAAY,GAAG,0FAA0F,CAAC;IAChH,MAAM,UAAU,GAAG,6BAA6B,CAAC;IACjD,MAAM,WAAW,GAAG,8BAA8B,CAAC;IAEnD,MAAM,YAAY,GAAG,SAAS,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IACnD,MAAM,UAAU,GAAG,SAAS,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC/C,MAAM,WAAW,GAAG,SAAS,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAEjD,IAAI,KAAa,CAAC;IAClB,IAAI,MAAc,CAAC;IACnB,IAAI,OAAiC,CAAC;IAEtC,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAChD,OAAO,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QACxC,KAAK,GAAG,CAAC,CAAC;QACV,MAAM,GAAG,CAAC,CAAC;IACb,CAAC;SAAM,CAAC;QACN,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/C,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AACpC,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Core SVG element manipulation functions
3
+ */
4
+ /**
5
+ * Creates an SVG element from a given SVG content string
6
+ */
7
+ export declare function createSVGElement(svgContent: string): Element;
8
+ /**
9
+ * Clones an SVG element deeply
10
+ *
11
+ * This function serializes the SVG element to a string, then parses it back
12
+ * to create a deep clone, ensuring all attributes and child nodes are duplicated.
13
+ */
14
+ export declare function cloneSVGElement(element: Element): Element;
15
+ /**
16
+ * Merges multiple SVG elements into a single SVG element
17
+ *
18
+ * Creates a new SVG element and appends clones of the provided elements to it.
19
+ */
20
+ export declare function mergeSVGElements(elements: Element[]): Element;
21
+ /**
22
+ * Serializes an SVG element or string to a string representation
23
+ */
24
+ export declare function serializeSVG(svgContent: Element | string): string;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Core SVG element manipulation functions
3
+ */
4
+ import { createDOMParser, createXMLSerializer, SVG_NAMESPACE, document } from '../utils/dom';
5
+ import { isValidSvgString, isValidSvgElement } from '../utils/validation';
6
+ /**
7
+ * Creates an SVG element from a given SVG content string
8
+ */
9
+ export function createSVGElement(svgContent) {
10
+ const parser = createDOMParser();
11
+ const svgElement = parser.parseFromString(svgContent, 'image/svg+xml').documentElement;
12
+ return svgElement;
13
+ }
14
+ /**
15
+ * Clones an SVG element deeply
16
+ *
17
+ * This function serializes the SVG element to a string, then parses it back
18
+ * to create a deep clone, ensuring all attributes and child nodes are duplicated.
19
+ */
20
+ export function cloneSVGElement(element) {
21
+ const serializer = createXMLSerializer();
22
+ const sourceCode = serializer.serializeToString(element);
23
+ const parser = createDOMParser();
24
+ const doc = parser.parseFromString(sourceCode, 'image/svg+xml');
25
+ return doc.documentElement;
26
+ }
27
+ /**
28
+ * Merges multiple SVG elements into a single SVG element
29
+ *
30
+ * Creates a new SVG element and appends clones of the provided elements to it.
31
+ */
32
+ export function mergeSVGElements(elements) {
33
+ const mergedSVG = document.createElementNS(SVG_NAMESPACE, 'svg');
34
+ elements.forEach((element) => {
35
+ mergedSVG.appendChild(cloneSVGElement(element));
36
+ });
37
+ return mergedSVG;
38
+ }
39
+ /**
40
+ * Serializes an SVG element or string to a string representation
41
+ */
42
+ export function serializeSVG(svgContent) {
43
+ if (isValidSvgString(svgContent)) {
44
+ return svgContent;
45
+ }
46
+ if (isValidSvgElement(svgContent)) {
47
+ const serializer = createXMLSerializer();
48
+ return serializer.serializeToString(svgContent);
49
+ }
50
+ throw new Error('The provided content is not a valid SVG string or SVG element.');
51
+ }
52
+ //# sourceMappingURL=element.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"element.js","sourceRoot":"","sources":["../../src/core/element.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC7F,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE1E;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAkB;IACjD,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;IACjC,MAAM,UAAU,GAAG,MAAM,CAAC,eAAe,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,eAAe,CAAC;IACvF,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,OAAgB;IAC9C,MAAM,UAAU,GAAG,mBAAmB,EAAE,CAAC;IACzC,MAAM,UAAU,GAAG,UAAU,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;IACzD,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;IACjC,MAAM,GAAG,GAAG,MAAM,CAAC,eAAe,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;IAChE,OAAO,GAAG,CAAC,eAAe,CAAC;AAC7B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAmB;IAClD,MAAM,SAAS,GAAG,QAAQ,CAAC,eAAe,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;IACjE,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC3B,SAAS,CAAC,WAAW,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IACH,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,UAA4B;IACvD,IAAI,gBAAgB,CAAC,UAAU,CAAC,EAAE,CAAC;QACjC,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,IAAI,iBAAiB,CAAC,UAAU,CAAC,EAAE,CAAC;QAClC,MAAM,UAAU,GAAG,mBAAmB,EAAE,CAAC;QACzC,OAAO,UAAU,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAClD,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;AACpF,CAAC"}
package/es/index.d.ts CHANGED
@@ -1,12 +1,17 @@
1
- import svg2Png from './extra-apply/applySvg2Png';
2
- import diffSvg from './extra-apply/applyDiffSvg';
3
- import removeNanCoordinates from './extra-apply/applyRemoveNanCoordinates';
4
- import { createSVGElement, cloneSVGElement, mergeSVGElements, convertSVGToBase64, convertBase64ToSVG } from './common';
5
- import pixelLevelDiffPng from './utils/pixelLevelDiffPng';
6
- declare const removeEmptyCoordinates: typeof removeNanCoordinates;
7
- export { svg2Png, diffSvg,
8
1
  /**
9
- * @deprecated
10
- * @see removeNanCoordinates
2
+ * SVG Toolbox - A comprehensive library for SVG manipulation and analysis
3
+ *
4
+ * @module svg-toolbox
11
5
  */
12
- removeEmptyCoordinates, removeNanCoordinates, createSVGElement, cloneSVGElement, mergeSVGElements, convertSVGToBase64, convertBase64ToSVG, pixelLevelDiffPng };
6
+ export { createSVGElement, cloneSVGElement, mergeSVGElements, serializeSVG } from './core/element';
7
+ export { getSVGDimensions } from './core/dimensions';
8
+ export { convertSVGToBase64, convertBase64ToSVG } from './convert/base64';
9
+ export { svgToImage, svg2Png } from './convert/image';
10
+ export { pixelLevelDiff, diffImages, diffSvg } from './compare/diff';
11
+ export { removeNanCoordinates } from './optimize/path';
12
+ export { removeEmptyAttributes, removeComments, normalizeWhitespace, optimizeSVG } from './optimize/cleanup';
13
+ export { extractColors } from './analyze/colors';
14
+ export { parsePathData, analyzePaths, getPathStatistics } from './analyze/paths';
15
+ export { isValidSvgString, isValidSvgElement } from './utils/validation';
16
+ export type { SVGDimensions, DiffResult, ConversionOptions, PathCommand, SVGColor } from './types';
17
+ export { removeNanCoordinates as removeEmptyCoordinates } from './optimize/path';
package/es/index.js CHANGED
@@ -1,13 +1,24 @@
1
- import svg2Png from './extra-apply/applySvg2Png';
2
- import diffSvg from './extra-apply/applyDiffSvg';
3
- import removeNanCoordinates from './extra-apply/applyRemoveNanCoordinates';
4
- import { createSVGElement, cloneSVGElement, mergeSVGElements, convertSVGToBase64, convertBase64ToSVG } from './common';
5
- import pixelLevelDiffPng from './utils/pixelLevelDiffPng';
6
- const removeEmptyCoordinates = removeNanCoordinates;
7
- export { svg2Png, diffSvg,
8
1
  /**
9
- * @deprecated
10
- * @see removeNanCoordinates
2
+ * SVG Toolbox - A comprehensive library for SVG manipulation and analysis
3
+ *
4
+ * @module svg-toolbox
11
5
  */
12
- removeEmptyCoordinates, removeNanCoordinates, createSVGElement, cloneSVGElement, mergeSVGElements, convertSVGToBase64, convertBase64ToSVG, pixelLevelDiffPng };
6
+ // Core element manipulation
7
+ export { createSVGElement, cloneSVGElement, mergeSVGElements, serializeSVG } from './core/element';
8
+ export { getSVGDimensions } from './core/dimensions';
9
+ // Conversion utilities
10
+ export { convertSVGToBase64, convertBase64ToSVG } from './convert/base64';
11
+ export { svgToImage, svg2Png } from './convert/image';
12
+ // Comparison utilities
13
+ export { pixelLevelDiff, diffImages, diffSvg } from './compare/diff';
14
+ // Optimization utilities
15
+ export { removeNanCoordinates } from './optimize/path';
16
+ export { removeEmptyAttributes, removeComments, normalizeWhitespace, optimizeSVG } from './optimize/cleanup';
17
+ // Analysis utilities
18
+ export { extractColors } from './analyze/colors';
19
+ export { parsePathData, analyzePaths, getPathStatistics } from './analyze/paths';
20
+ // Validation utilities
21
+ export { isValidSvgString, isValidSvgElement } from './utils/validation';
22
+ // Legacy exports for backward compatibility
23
+ export { removeNanCoordinates as removeEmptyCoordinates } from './optimize/path';
13
24
  //# sourceMappingURL=index.js.map
package/es/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,4BAA4B,CAAC;AACjD,OAAO,OAAO,MAAM,4BAA4B,CAAC;AACjD,OAAO,oBAAoB,MAAM,yCAAyC,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AACtH,OAAO,iBAAiB,MAAM,2BAA2B,CAAC;AAE1D,MAAM,sBAAsB,GAAG,oBAAoB,CAAA;AAEnD,OAAO,EACL,OAAO,EACP,OAAO;AACP;;;GAGG;AACH,sBAAsB,EACtB,oBAAoB,EACpB,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,kBAAkB,EAC3F,iBAAiB,EAClB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,4BAA4B;AAC5B,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,YAAY,EACb,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,uBAAuB;AACvB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,UAAU,EACV,OAAO,EACR,MAAM,iBAAiB,CAAC;AAEzB,uBAAuB;AACvB,OAAO,EACL,cAAc,EACd,UAAU,EACV,OAAO,EACR,MAAM,gBAAgB,CAAC;AAExB,yBAAyB;AACzB,OAAO,EACL,oBAAoB,EACrB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,qBAAqB,EACrB,cAAc,EACd,mBAAmB,EACnB,WAAW,EACZ,MAAM,oBAAoB,CAAC;AAE5B,qBAAqB;AACrB,OAAO,EACL,aAAa,EACd,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,aAAa,EACb,YAAY,EACZ,iBAAiB,EAClB,MAAM,iBAAiB,CAAC;AAEzB,uBAAuB;AACvB,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,oBAAoB,CAAC;AAW5B,4CAA4C;AAC5C,OAAO,EAAE,oBAAoB,IAAI,sBAAsB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,95 @@
1
+ import { removeEmptyAttributes, removeComments, normalizeWhitespace, optimizeSVG } from '../cleanup';
2
+ import { createSVGElement } from '../../core/element';
3
+ describe('SVG Cleanup', () => {
4
+ describe('removeEmptyAttributes', () => {
5
+ it('should remove empty attributes', () => {
6
+ const svgContent = '<svg><circle cx="50" cy="50" r="" fill="red" stroke="" /></svg>';
7
+ const result = removeEmptyAttributes(svgContent);
8
+ expect(result).toContain('cx="50"');
9
+ expect(result).toContain('fill="red"');
10
+ expect(result).not.toContain('r=""');
11
+ expect(result).not.toContain('stroke=""');
12
+ });
13
+ it('should work with SVG element input', () => {
14
+ const svgContent = '<svg><rect x="10" y="" width="100" height="50" /></svg>';
15
+ const element = createSVGElement(svgContent);
16
+ const result = removeEmptyAttributes(element);
17
+ expect(result).toContain('x="10"');
18
+ expect(result).not.toContain('y=""');
19
+ });
20
+ });
21
+ describe('removeComments', () => {
22
+ it('should remove HTML comments', () => {
23
+ const svgContent = '<svg><!-- This is a comment --><circle cx="50" cy="50" r="40" /></svg>';
24
+ const result = removeComments(svgContent);
25
+ expect(result).not.toContain('<!--');
26
+ expect(result).not.toContain('-->');
27
+ expect(result).toContain('circle');
28
+ });
29
+ it('should handle multiple comments', () => {
30
+ const svgContent = '<svg><!-- Comment 1 --><circle /><!-- Comment 2 --></svg>';
31
+ const result = removeComments(svgContent);
32
+ expect(result).not.toContain('Comment');
33
+ });
34
+ it('should handle nested comments correctly', () => {
35
+ // This tests the incomplete sanitization fix
36
+ // Input: "<!--<!-- comment -->-->" should become "" not "<!-- comment -->"
37
+ const svgContent = '<svg><!--<!-- nested comment -->--><circle /></svg>';
38
+ const result = removeComments(svgContent);
39
+ expect(result).not.toContain('<!--');
40
+ expect(result).not.toContain('-->');
41
+ expect(result).not.toContain('nested comment');
42
+ expect(result).toContain('circle');
43
+ });
44
+ it('should handle deeply nested comments', () => {
45
+ const svgContent = '<svg><!--<!--<!-- triple nested -->-->--><circle /></svg>';
46
+ const result = removeComments(svgContent);
47
+ expect(result).not.toContain('<!--');
48
+ expect(result).not.toContain('-->');
49
+ expect(result).toContain('circle');
50
+ });
51
+ it('should throw error for content exceeding maximum length', () => {
52
+ const largeContent = '<!--' + 'x'.repeat(10000001) + '-->';
53
+ expect(() => removeComments(largeContent)).toThrow('exceeds maximum allowed length');
54
+ });
55
+ it('should handle comments with newlines', () => {
56
+ const svgContent = '<svg><!--\nMulti-line\ncomment\n--><circle /></svg>';
57
+ const result = removeComments(svgContent);
58
+ expect(result).not.toContain('<!--');
59
+ expect(result).not.toContain('-->');
60
+ expect(result).not.toContain('Multi-line');
61
+ expect(result).toContain('circle');
62
+ });
63
+ });
64
+ describe('normalizeWhitespace', () => {
65
+ it('should normalize whitespace', () => {
66
+ const svgContent = '<svg> <circle cx="50" cy="50" /> </svg>';
67
+ const result = normalizeWhitespace(svgContent);
68
+ expect(result).toContain('<circle');
69
+ expect(result).not.toMatch(/\s{2,}/);
70
+ });
71
+ it('should remove whitespace between tags', () => {
72
+ const svgContent = '<svg>\n <circle />\n <rect />\n</svg>';
73
+ const result = normalizeWhitespace(svgContent);
74
+ expect(result).toMatch(/><circle/);
75
+ expect(result).toMatch(/><rect/);
76
+ });
77
+ });
78
+ describe('optimizeSVG', () => {
79
+ it('should apply all optimizations', () => {
80
+ const svgContent = '<svg><!-- Comment --><circle cx="50" cy="" r="40" fill="red" /> </svg>';
81
+ const result = optimizeSVG(svgContent);
82
+ expect(result).not.toContain('<!--');
83
+ expect(result).not.toContain('cy=""');
84
+ expect(result).toContain('circle');
85
+ expect(result).toContain('fill="red"');
86
+ });
87
+ it('should work with SVG element input', () => {
88
+ const svgContent = '<svg><rect x="10" y="20" width="100" /></svg>';
89
+ const element = createSVGElement(svgContent);
90
+ const result = optimizeSVG(element);
91
+ expect(result).toContain('rect');
92
+ });
93
+ });
94
+ });
95
+ //# sourceMappingURL=cleanup.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cleanup.test.js","sourceRoot":"","sources":["../../../src/optimize/__tests__/cleanup.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,41 @@
1
+ import { removeNanCoordinates } from '../path';
2
+ describe('Path Optimization', () => {
3
+ describe('removeNanCoordinates', () => {
4
+ it('should remove NaN values from path data', () => {
5
+ const svgContent = '<svg><path d="M 10,20 nan L 30,40 -nan Z" /></svg>';
6
+ const result = removeNanCoordinates(svgContent);
7
+ expect(result).not.toContain('nan');
8
+ expect(result).not.toContain('-nan');
9
+ expect(result).toContain('path');
10
+ });
11
+ it('should preserve valid path commands', () => {
12
+ const svgContent = '<svg><path d="M 10,20 L 30,40 Z" /></svg>';
13
+ const result = removeNanCoordinates(svgContent);
14
+ expect(result).toContain('M10');
15
+ expect(result).toContain('20');
16
+ expect(result).toContain('L30');
17
+ expect(result).toContain('40');
18
+ expect(result).toContain('Z');
19
+ });
20
+ it('should handle multiple paths', () => {
21
+ const svgContent = '<svg><path d="M 10,20 nan L 30,40" /><path d="M 50,60 L 70,80" /></svg>';
22
+ const result = removeNanCoordinates(svgContent);
23
+ expect(result).not.toContain('nan');
24
+ expect(result.split('path').length - 1).toBe(2);
25
+ });
26
+ it('should handle paths without d attribute', () => {
27
+ const svgContent = '<svg><path /><path d="M 10,20 L 30,40" /></svg>';
28
+ const result = removeNanCoordinates(svgContent);
29
+ expect(result).toContain('path');
30
+ });
31
+ it('should throw error if no SVG element found', () => {
32
+ expect(() => removeNanCoordinates('<div></div>')).toThrow('No SVG element found');
33
+ });
34
+ it('should handle case-insensitive NaN', () => {
35
+ const svgContent = '<svg><path d="M 10,20 NaN L 30,40 NAN Z" /></svg>';
36
+ const result = removeNanCoordinates(svgContent);
37
+ expect(result.toLowerCase()).not.toContain('nan');
38
+ });
39
+ });
40
+ });
41
+ //# sourceMappingURL=path.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.test.js","sourceRoot":"","sources":["../../../src/optimize/__tests__/path.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAE/C,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;QACpC,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;YACjD,MAAM,UAAU,GAAG,oDAAoD,CAAC;YACxE,MAAM,MAAM,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;YAEhD,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACpC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;YACrC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;YAC7C,MAAM,UAAU,GAAG,2CAA2C,CAAC;YAC/D,MAAM,MAAM,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;YAEhD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YAChC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAC/B,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YAChC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAC/B,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;YACtC,MAAM,UAAU,GAAG,yEAAyE,CAAC;YAC7F,MAAM,MAAM,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;YAEhD,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACpC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;YACjD,MAAM,UAAU,GAAG,iDAAiD,CAAC;YACrE,MAAM,MAAM,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;YAEhD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;YACpD,MAAM,CAAC,GAAG,EAAE,CAAC,oBAAoB,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC;QACpF,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;YAC5C,MAAM,UAAU,GAAG,mDAAmD,CAAC;YACvE,MAAM,MAAM,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;YAEhD,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * SVG cleanup and optimization utilities
3
+ */
4
+ /**
5
+ * Removes empty attributes from SVG elements
6
+ */
7
+ export declare function removeEmptyAttributes(svgContent: Element | string): string;
8
+ /**
9
+ * Removes comments from SVG content
10
+ *
11
+ * This function uses iterative replacement to handle nested comments correctly
12
+ * and includes input length validation to prevent ReDoS attacks.
13
+ *
14
+ * The function handles cases like:
15
+ * - Simple comments: <!-- comment -->
16
+ * - Nested comments: <!--<!-- inner -->-->
17
+ * - Multiple comments: <!-- one --><!-- two -->
18
+ */
19
+ export declare function removeComments(svgContent: string): string;
20
+ /**
21
+ * Normalizes whitespace in SVG content
22
+ */
23
+ export declare function normalizeWhitespace(svgContent: string): string;
24
+ /**
25
+ * Optimizes SVG by removing empty attributes, comments, and normalizing whitespace
26
+ */
27
+ export declare function optimizeSVG(svgContent: Element | string): string;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * SVG cleanup and optimization utilities
3
+ */
4
+ import { JSDOM } from 'jsdom';
5
+ import { serializeSVG } from '../core/element';
6
+ import { isValidSvgString } from '../utils/validation';
7
+ /**
8
+ * Removes empty attributes from SVG elements
9
+ */
10
+ export function removeEmptyAttributes(svgContent) {
11
+ const svgString = isValidSvgString(svgContent)
12
+ ? svgContent
13
+ : serializeSVG(svgContent);
14
+ const dom = new JSDOM(svgString, {
15
+ contentType: 'image/svg+xml'
16
+ });
17
+ const document = dom.window.document;
18
+ const svgElement = document.querySelector('svg');
19
+ if (!svgElement) {
20
+ throw new Error('No SVG element found in the provided content.');
21
+ }
22
+ function removeEmptyAttrs(element) {
23
+ const attrs = Array.from(element.attributes);
24
+ attrs.forEach(attr => {
25
+ if (!attr.value || attr.value.trim() === '') {
26
+ element.removeAttribute(attr.name);
27
+ }
28
+ });
29
+ Array.from(element.children).forEach(child => {
30
+ removeEmptyAttrs(child);
31
+ });
32
+ }
33
+ removeEmptyAttrs(svgElement);
34
+ return svgElement.outerHTML.trim();
35
+ }
36
+ /**
37
+ * Maximum allowed SVG content length to prevent ReDoS attacks
38
+ */
39
+ const MAX_SVG_CONTENT_LENGTH = 10000000; // 10MB should be sufficient for most SVG files
40
+ /**
41
+ * Removes comments from SVG content
42
+ *
43
+ * This function uses iterative replacement to handle nested comments correctly
44
+ * and includes input length validation to prevent ReDoS attacks.
45
+ *
46
+ * The function handles cases like:
47
+ * - Simple comments: <!-- comment -->
48
+ * - Nested comments: <!--<!-- inner -->-->
49
+ * - Multiple comments: <!-- one --><!-- two -->
50
+ */
51
+ export function removeComments(svgContent) {
52
+ // Validate input length to prevent ReDoS attacks
53
+ if (svgContent.length > MAX_SVG_CONTENT_LENGTH) {
54
+ throw new Error(`SVG content length exceeds maximum allowed length of ${MAX_SVG_CONTENT_LENGTH} characters`);
55
+ }
56
+ // Use iterative replacement to handle nested comments
57
+ // This prevents incomplete sanitization where nested comments could reappear
58
+ // Example: "<!--<!-- comment -->-->" should become "" not "<!-- comment -->"
59
+ let previous;
60
+ let result = svgContent;
61
+ let iterations = 0;
62
+ const MAX_ITERATIONS = 1000; // Prevent infinite loops
63
+ do {
64
+ previous = result;
65
+ // Match HTML comments and standalone delimiters:
66
+ // - Full comments: <!-- ... --> or <!-- ... --!>
67
+ // - Orphaned starts: <!--
68
+ // - Orphaned ends: --> or --!>
69
+ // The regex uses non-greedy matching (*?) to match the shortest possible comment
70
+ // and is applied iteratively until no further matches remain.
71
+ result = result.replace(/<!--[\s\S]*?--!?>|<!--|--!?>(?=[^>]|$)/g, '');
72
+ iterations++;
73
+ if (iterations > MAX_ITERATIONS) {
74
+ throw new Error('Comment removal exceeded maximum iterations. SVG content may be malformed.');
75
+ }
76
+ } while (result !== previous);
77
+ return result;
78
+ }
79
+ /**
80
+ * Normalizes whitespace in SVG content
81
+ */
82
+ export function normalizeWhitespace(svgContent) {
83
+ return svgContent
84
+ .replace(/\s+/g, ' ')
85
+ .replace(/>\s+</g, '><')
86
+ .trim();
87
+ }
88
+ /**
89
+ * Optimizes SVG by removing empty attributes, comments, and normalizing whitespace
90
+ */
91
+ export function optimizeSVG(svgContent) {
92
+ let svgString = isValidSvgString(svgContent)
93
+ ? svgContent
94
+ : serializeSVG(svgContent);
95
+ svgString = removeComments(svgString);
96
+ svgString = removeEmptyAttributes(svgString);
97
+ svgString = normalizeWhitespace(svgString);
98
+ return svgString;
99
+ }
100
+ //# sourceMappingURL=cleanup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../src/optimize/cleanup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * SVG path optimization utilities
3
+ */
4
+ /**
5
+ * Removes NaN and invalid coordinates from SVG path elements
6
+ */
7
+ export declare function removeNanCoordinates(svgContent: string): string;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * SVG path optimization utilities
3
+ */
4
+ import { JSDOM } from 'jsdom';
5
+ const PATH_COMMANDS_WITHOUT_PARAMS = ['z', 'Z'];
6
+ /**
7
+ * Removes NaN and invalid coordinates from SVG path elements
8
+ */
9
+ export function removeNanCoordinates(svgContent) {
10
+ const dom = new JSDOM(svgContent, {
11
+ contentType: 'image/svg+xml'
12
+ });
13
+ const document = dom.window.document;
14
+ const svgElement = document.querySelector('svg');
15
+ if (!svgElement) {
16
+ throw new Error('No SVG element found in the provided content.');
17
+ }
18
+ const paths = svgElement.querySelectorAll('path');
19
+ Array.from(paths).forEach((path) => {
20
+ const d = path.getAttribute('d');
21
+ if (d === null) {
22
+ return;
23
+ }
24
+ // Remove NaN values and split into commands
25
+ const commands = d
26
+ .replace(/nan|-nan/gi, ' ')
27
+ .split(/(?=[MmLlHhVvCcSsQqTtAaZz])/)
28
+ .map((command) => {
29
+ const type = command[0];
30
+ const isCommandWithoutParams = PATH_COMMANDS_WITHOUT_PARAMS.includes(type);
31
+ if (!isCommandWithoutParams) {
32
+ const params = command
33
+ .slice(1)
34
+ .split(/[\s,]+/)
35
+ .filter(param => param.trim() !== '')
36
+ .map(Number);
37
+ // Check if all parameters are valid numbers
38
+ const allValid = params.every(param => !isNaN(param) && isFinite(param));
39
+ if (allValid && params.length > 0) {
40
+ return { type, params };
41
+ }
42
+ }
43
+ else {
44
+ return { type, params: [] };
45
+ }
46
+ return null;
47
+ })
48
+ .filter((item) => item !== null)
49
+ .filter((command) => PATH_COMMANDS_WITHOUT_PARAMS.includes(command.type) || command.params.length > 0);
50
+ // Reconstruct the 'd' attribute
51
+ const modifiedD = commands
52
+ .map((command) => command.type + command.params.join(' '))
53
+ .join('');
54
+ path.setAttribute('d', modifiedD);
55
+ });
56
+ return svgElement.outerHTML.trim();
57
+ }
58
+ //# sourceMappingURL=path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.js","sourceRoot":"","sources":["../../src/optimize/path.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAE9B,MAAM,4BAA4B,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AAEhD;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAkB;IACrD,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,UAAU,EAAE;QAChC,WAAW,EAAE,eAAe;KAC7B,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC;IACrC,MAAM,UAAU,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAEjD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,CAAC;IAED,MAAM,KAAK,GAAG,UAAU,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAElD,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QACjC,MAAM,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YACf,OAAO;QACT,CAAC;QAED,4CAA4C;QAC5C,MAAM,QAAQ,GAAG,CAAC;aACf,OAAO,CAAC,YAAY,EAAE,GAAG,CAAC;aAC1B,KAAK,CAAC,4BAA4B,CAAC;aACnC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;YACf,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;YACxB,MAAM,sBAAsB,GAAG,4BAA4B,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAE3E,IAAI,CAAC,sBAAsB,EAAE,CAAC;gBAC5B,MAAM,MAAM,GAAG,OAAO;qBACnB,KAAK,CAAC,CAAC,CAAC;qBACR,KAAK,CAAC,QAAQ,CAAC;qBACf,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;qBACpC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAEf,4CAA4C;gBAC5C,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;gBAEzE,IAAI,QAAQ,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAClC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;gBAC1B,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;YAC9B,CAAC;YAED,OAAO,IAAI,CAAC;QACd,CAAC,CAAC;aACD,MAAM,CAAC,CAAC,IAAI,EAA8C,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC;aAC3E,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAClB,4BAA4B,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CACjF,CAAC;QAEJ,gCAAgC;QAChC,MAAM,SAAS,GAAG,QAAQ;aACvB,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;aACzD,IAAI,CAAC,EAAE,CAAC,CAAC;QAEZ,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,OAAO,UAAU,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;AACrC,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Type definitions for SVG Toolbox
3
+ */
4
+ export interface SVGDimensions {
5
+ width: number;
6
+ height: number;
7
+ viewBox?: {
8
+ x: number;
9
+ y: number;
10
+ width: number;
11
+ height: number;
12
+ };
13
+ }
14
+ export interface DiffResult {
15
+ diffPngBuffer: Buffer;
16
+ numDiffPixels: number;
17
+ }
18
+ export interface ConversionOptions {
19
+ scale?: number;
20
+ format?: 'png' | 'jpg' | 'jpeg' | 'webp';
21
+ quality?: number;
22
+ }
23
+ export interface PathCommand {
24
+ type: string;
25
+ params: number[];
26
+ }
27
+ export interface SVGColor {
28
+ fill?: string;
29
+ stroke?: string;
30
+ opacity?: number;
31
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Type definitions for SVG Toolbox
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1 @@
1
+ export {};