svg-toolbox 1.1.10 → 1.1.12

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 (162) hide show
  1. package/README.md +146 -125
  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 +78 -0
  13. package/es/analyze/paths.js.map +1 -0
  14. package/es/compare/diff.d.ts +18 -0
  15. package/es/compare/diff.js +64 -0
  16. package/es/compare/diff.js.map +1 -0
  17. package/es/convert/__tests__/base64.test.d.ts +1 -0
  18. package/es/convert/__tests__/base64.test.js +49 -0
  19. package/es/convert/__tests__/base64.test.js.map +1 -0
  20. package/es/convert/base64.d.ts +11 -0
  21. package/es/convert/base64.js +38 -0
  22. package/es/convert/base64.js.map +1 -0
  23. package/es/convert/image.d.ts +18 -0
  24. package/es/convert/image.js +58 -0
  25. package/es/convert/image.js.map +1 -0
  26. package/es/core/__tests__/dimensions.test.d.ts +1 -0
  27. package/es/core/__tests__/dimensions.test.js +40 -0
  28. package/es/core/__tests__/dimensions.test.js.map +1 -0
  29. package/es/core/__tests__/element.test.d.ts +1 -0
  30. package/es/core/__tests__/element.test.js +79 -0
  31. package/es/core/__tests__/element.test.js.map +1 -0
  32. package/es/core/dimensions.d.ts +8 -0
  33. package/es/core/dimensions.js +34 -0
  34. package/es/core/dimensions.js.map +1 -0
  35. package/es/core/element.d.ts +24 -0
  36. package/es/core/element.js +52 -0
  37. package/es/core/element.js.map +1 -0
  38. package/es/index.d.ts +15 -10
  39. package/es/index.js +21 -10
  40. package/es/index.js.map +1 -1
  41. package/es/optimize/__tests__/cleanup.test.d.ts +1 -0
  42. package/es/optimize/__tests__/cleanup.test.js +66 -0
  43. package/es/optimize/__tests__/cleanup.test.js.map +1 -0
  44. package/es/optimize/__tests__/path.test.d.ts +1 -0
  45. package/es/optimize/__tests__/path.test.js +41 -0
  46. package/es/optimize/__tests__/path.test.js.map +1 -0
  47. package/es/optimize/cleanup.d.ts +19 -0
  48. package/es/optimize/cleanup.js +63 -0
  49. package/es/optimize/cleanup.js.map +1 -0
  50. package/es/optimize/path.d.ts +7 -0
  51. package/es/optimize/path.js +58 -0
  52. package/es/optimize/path.js.map +1 -0
  53. package/es/types/index.d.ts +31 -0
  54. package/es/types/index.js +5 -0
  55. package/es/types/index.js.map +1 -0
  56. package/es/utils/__tests__/validation.test.d.ts +1 -0
  57. package/es/utils/__tests__/validation.test.js +42 -0
  58. package/es/utils/__tests__/validation.test.js.map +1 -0
  59. package/es/utils/dom.d.ts +23 -0
  60. package/es/utils/dom.js +25 -0
  61. package/es/utils/dom.js.map +1 -0
  62. package/es/utils/validation.d.ts +11 -0
  63. package/es/utils/validation.js +30 -0
  64. package/es/utils/validation.js.map +1 -0
  65. package/lib/analyze/__tests__/colors.test.d.ts +1 -0
  66. package/lib/analyze/__tests__/colors.test.js +46 -0
  67. package/lib/analyze/__tests__/colors.test.js.map +1 -0
  68. package/lib/analyze/__tests__/paths.test.d.ts +1 -0
  69. package/lib/analyze/__tests__/paths.test.js +73 -0
  70. package/lib/analyze/__tests__/paths.test.js.map +1 -0
  71. package/lib/analyze/colors.d.ts +8 -0
  72. package/lib/analyze/colors.js +58 -0
  73. package/lib/analyze/colors.js.map +1 -0
  74. package/lib/analyze/paths.d.ts +20 -0
  75. package/lib/analyze/paths.js +83 -0
  76. package/lib/analyze/paths.js.map +1 -0
  77. package/lib/compare/diff.d.ts +18 -0
  78. package/lib/{extra-apply/applyDiffSvg.js → compare/diff.js} +72 -58
  79. package/lib/compare/diff.js.map +1 -0
  80. package/lib/convert/__tests__/base64.test.d.ts +1 -0
  81. package/lib/convert/__tests__/base64.test.js +51 -0
  82. package/lib/convert/__tests__/base64.test.js.map +1 -0
  83. package/lib/convert/base64.d.ts +11 -0
  84. package/lib/convert/base64.js +42 -0
  85. package/lib/convert/base64.js.map +1 -0
  86. package/lib/convert/image.d.ts +18 -0
  87. package/lib/{extra-apply/applySvg2Png.js → convert/image.js} +59 -53
  88. package/lib/convert/image.js.map +1 -0
  89. package/lib/core/__tests__/dimensions.test.d.ts +1 -0
  90. package/lib/core/__tests__/dimensions.test.js +42 -0
  91. package/lib/core/__tests__/dimensions.test.js.map +1 -0
  92. package/lib/core/__tests__/element.test.d.ts +1 -0
  93. package/lib/core/__tests__/element.test.js +81 -0
  94. package/lib/core/__tests__/element.test.js.map +1 -0
  95. package/lib/core/dimensions.d.ts +8 -0
  96. package/lib/core/dimensions.js +37 -0
  97. package/lib/core/dimensions.js.map +1 -0
  98. package/lib/core/element.d.ts +24 -0
  99. package/lib/core/element.js +58 -0
  100. package/lib/core/element.js.map +1 -0
  101. package/lib/index.d.ts +15 -10
  102. package/lib/index.js +48 -20
  103. package/lib/index.js.map +1 -1
  104. package/lib/optimize/__tests__/cleanup.test.d.ts +1 -0
  105. package/lib/optimize/__tests__/cleanup.test.js +68 -0
  106. package/lib/optimize/__tests__/cleanup.test.js.map +1 -0
  107. package/lib/optimize/__tests__/path.test.d.ts +1 -0
  108. package/lib/optimize/__tests__/path.test.js +43 -0
  109. package/lib/optimize/__tests__/path.test.js.map +1 -0
  110. package/lib/optimize/cleanup.d.ts +19 -0
  111. package/lib/optimize/cleanup.js +69 -0
  112. package/lib/optimize/cleanup.js.map +1 -0
  113. package/lib/optimize/path.d.ts +7 -0
  114. package/lib/optimize/path.js +63 -0
  115. package/lib/optimize/path.js.map +1 -0
  116. package/lib/types/index.d.ts +31 -0
  117. package/lib/types/index.js +6 -0
  118. package/lib/types/index.js.map +1 -0
  119. package/lib/utils/__tests__/validation.test.d.ts +1 -0
  120. package/lib/utils/__tests__/validation.test.js +44 -0
  121. package/lib/utils/__tests__/validation.test.js.map +1 -0
  122. package/lib/utils/dom.d.ts +23 -0
  123. package/lib/utils/dom.js +31 -0
  124. package/lib/utils/dom.js.map +1 -0
  125. package/lib/utils/validation.d.ts +11 -0
  126. package/lib/utils/validation.js +35 -0
  127. package/lib/utils/validation.js.map +1 -0
  128. package/package.json +8 -2
  129. package/es/common.d.ts +0 -68
  130. package/es/common.js +0 -110
  131. package/es/common.js.map +0 -1
  132. package/es/extra-apply/applyDiffSvg.d.ts +0 -11
  133. package/es/extra-apply/applyDiffSvg.js +0 -63
  134. package/es/extra-apply/applyDiffSvg.js.map +0 -1
  135. package/es/extra-apply/applyRemoveNanCoordinates.d.ts +0 -6
  136. package/es/extra-apply/applyRemoveNanCoordinates.js +0 -75
  137. package/es/extra-apply/applyRemoveNanCoordinates.js.map +0 -1
  138. package/es/extra-apply/applySvg2Png.d.ts +0 -16
  139. package/es/extra-apply/applySvg2Png.js +0 -64
  140. package/es/extra-apply/applySvg2Png.js.map +0 -1
  141. package/es/utils/pixelLevelDiffPng.d.ts +0 -4
  142. package/es/utils/pixelLevelDiffPng.js +0 -29
  143. package/es/utils/pixelLevelDiffPng.js.map +0 -1
  144. package/es/validate.d.ts +0 -14
  145. package/es/validate.js +0 -44
  146. package/es/validate.js.map +0 -1
  147. package/lib/common.d.ts +0 -68
  148. package/lib/common.js +0 -117
  149. package/lib/common.js.map +0 -1
  150. package/lib/extra-apply/applyDiffSvg.d.ts +0 -11
  151. package/lib/extra-apply/applyDiffSvg.js.map +0 -1
  152. package/lib/extra-apply/applyRemoveNanCoordinates.d.ts +0 -6
  153. package/lib/extra-apply/applyRemoveNanCoordinates.js +0 -78
  154. package/lib/extra-apply/applyRemoveNanCoordinates.js.map +0 -1
  155. package/lib/extra-apply/applySvg2Png.d.ts +0 -16
  156. package/lib/extra-apply/applySvg2Png.js.map +0 -1
  157. package/lib/utils/pixelLevelDiffPng.d.ts +0 -4
  158. package/lib/utils/pixelLevelDiffPng.js +0 -36
  159. package/lib/utils/pixelLevelDiffPng.js.map +0 -1
  160. package/lib/validate.d.ts +0 -14
  161. package/lib/validate.js +0 -48
  162. package/lib/validate.js.map +0 -1
package/README.md CHANGED
@@ -1,175 +1,196 @@
1
- <h1 align="center">
2
- <br/>
3
- <img width="200" alt="image" src="https://github.com/user-attachments/assets/5fb4fbd0-10b4-4abf-93fd-d98b17845f34" />
4
- <br/>
5
- </h1>
1
+ # SVG Toolbox
6
2
 
3
+ 一个功能全面的 SVG 操作和分析工具库,提供 SVG 元素的创建、转换、优化、比较和分析等功能。
7
4
 
8
- # SVG Utility Functions
9
- This module provides utility functions for working with SVG elements and files, including creating, cloning, merging, converting SVG to Base64, comparing SVG images, normalizing path data, and converting SVG to PNG format.
5
+ [![npm version](https://img.shields.io/npm/v/svg-toolbox.svg?style=for-the-badge)](https://www.npmjs.com/package/svg-toolbox)
6
+ [![npm downloads](https://img.shields.io/npm/dy/svg-toolbox.svg?style=for-the-badge)](https://www.npmjs.com/package/svg-toolbox)
7
+ [![License](https://img.shields.io/github/license/SteamedBread2333/svg-toolbox.svg?style=for-the-badge)](https://www.npmjs.com/package/svg-toolbox)
10
8
 
11
- [![npm version](https://img.shields.io/npm/v/svg-toolbox.svg?style=flat-square)](https://www.npmjs.com/package/svg-toolbox)
12
- [![npm downloads](https://img.shields.io/npm/dt/svg-toolbox.svg?style=flat-square)](https://www.npmjs.com/package/svg-toolbox)
9
+ ## 安装
13
10
 
14
- ## Installation
15
11
  ```bash
16
12
  npm install svg-toolbox
17
13
  ```
18
14
 
19
- ## Table of Contents
15
+ ## 适用场景
20
16
 
21
- - [SVG Utility Functions](#svg-utility-functions)
22
- - [Installation](#installation)
23
- - [Table of Contents](#table-of-contents)
24
- - [Usage](#usage)
25
- - [createSVGElement](#createsvgelement)
26
- - [cloneSVGElement](#clonesvgelement)
27
- - [mergeSVGElements](#mergesvgelements)
28
- - [convertSVGToBase64](#convertsvgtobase64)
29
- - [convertBase64ToSVG](#convertbase64tosvg)
30
- - [diffSvg](#diffsvg)
31
- - [svg2png](#svg2png)
32
- - [removeNanCoordinates](#removenancoordinates)
33
- - [License](#license)
17
+ ### 1. SVG 元素操作场景
34
18
 
35
- ## Usage
19
+ 当你需要在 Node.js 环境中创建、克隆或合并 SVG 元素时,可以使用核心元素操作功能:
36
20
 
37
- ### createSVGElement
21
+ - **动态生成 SVG 图形**:根据数据动态创建 SVG 元素
22
+ - **SVG 模板复用**:克隆现有 SVG 元素作为模板
23
+ - **组合多个 SVG**:将多个独立的 SVG 图形合并为一个
38
24
 
39
- Creates an SVG element from a given SVG content string.
25
+ ### 2. 格式转换场景
40
26
 
41
- ```typescript
42
- const svgElement = createSVGElement(`<svg><path d="M10 20L30 40Z" /></svg>`);
43
- console.log(svgElement);
44
- ```
45
- ### cloneSVGElement
27
+ 当你需要将 SVG 转换为其他格式或进行编码转换时:
46
28
 
47
- Clones an SVG element deeply.
29
+ - **Web 应用嵌入**:将 SVG 转换为 Base64 数据 URI,方便嵌入 HTML/CSS
30
+ - **图片导出**:将 SVG 转换为 PNG、JPG 或 WebP 格式用于下载或分享
31
+ - **跨平台兼容**:在不同系统间传输 SVG 数据时使用 Base64 编码
48
32
 
49
- ```typescript
50
- import { cloneSVGElement } from 'svg-toolbox';
51
- import { JSDOM } from 'jsdom';
52
-
53
- const dom = new JSDOM(`<!DOCTYPE html><html><body></body></html>`);
54
- const { document } = dom.window;
55
-
56
- const originalElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
57
- originalElement.setAttribute('cx', '50');
58
- originalElement.setAttribute('cy', '50');
59
- originalElement.setAttribute('r', '40');
60
- originalElement.setAttribute('stroke', 'black');
61
- originalElement.setAttribute('stroke-width', '3');
62
- originalElement.setAttribute('fill', 'red');
63
-
64
- const clonedElement = cloneSVGElement(originalElement);
65
- console.log(clonedElement);
66
- ```
33
+ ### 3. 视觉回归测试场景
34
+
35
+ 当你需要比较 SVG 渲染结果以确保视觉一致性时:
36
+
37
+ - **自动化测试**:在 CI/CD 流程中检测 SVG 渲染变化
38
+ - **版本对比**:比较不同版本的 SVG 文件差异
39
+ - **质量保证**:确保 SVG 修改不会引入意外的视觉变化
40
+
41
+ ### 4. SVG 优化场景
42
+
43
+ 当你需要清理和优化 SVG 代码时:
44
+
45
+ - **性能优化**:移除无效坐标和空属性,减小文件大小
46
+ - **代码清理**:移除注释和多余空白,提高可读性
47
+ - **数据修复**:修复包含 NaN 或无效值的路径数据
48
+
49
+ ### 5. SVG 分析场景
50
+
51
+ 当你需要深入了解 SVG 内容时:
52
+
53
+ - **设计审查**:提取 SVG 中使用的所有颜色
54
+ - **路径分析**:分析路径命令的结构和复杂度
55
+ - **尺寸检测**:自动提取 SVG 的尺寸信息
56
+
57
+ ## 功能模块
58
+
59
+ ### 核心功能 (Core)
60
+
61
+ 提供基础的 SVG 元素操作能力:
62
+
63
+ - `createSVGElement` - 从字符串创建 SVG 元素
64
+ - `cloneSVGElement` - 深度克隆 SVG 元素
65
+ - `mergeSVGElements` - 合并多个 SVG 元素
66
+ - `getSVGDimensions` - 获取 SVG 尺寸信息
67
+
68
+ ### 转换功能 (Convert)
69
+
70
+ 提供格式转换和编码功能:
71
+
72
+ - `convertSVGToBase64` - SVG 转 Base64 编码
73
+ - `convertBase64ToSVG` - Base64 解码为 SVG
74
+ - `svgToImage` - SVG 转图片格式(PNG/JPG/WebP)
75
+ - `svg2Png` - SVG 转 PNG(兼容旧版 API)
76
+
77
+ ### 比较功能 (Compare)
78
+
79
+ 提供图像对比和差异检测:
67
80
 
68
- ### mergeSVGElements
69
- Merges multiple SVG elements into a single SVG element.
81
+ - `diffImages` - 比较两个图像文件并生成差异图
82
+ - `pixelLevelDiff` - 像素级别的图像差异比较
83
+ - `diffSvg` - SVG 差异比较(兼容旧版 API)
84
+
85
+ ### 优化功能 (Optimize)
86
+
87
+ 提供 SVG 代码优化和清理:
88
+
89
+ - `removeNanCoordinates` - 移除路径中的 NaN 坐标
90
+ - `removeEmptyAttributes` - 移除空属性
91
+ - `removeComments` - 移除注释
92
+ - `normalizeWhitespace` - 规范化空白字符
93
+ - `optimizeSVG` - 综合优化 SVG 代码
94
+
95
+ ### 分析功能 (Analyze)
96
+
97
+ 提供 SVG 内容分析:
98
+
99
+ - `extractColors` - 提取 SVG 中使用的颜色
100
+ - `parsePathData` - 解析路径数据
101
+ - `analyzePaths` - 分析所有路径元素
102
+ - `getPathStatistics` - 获取路径统计信息
103
+
104
+ ## 使用示例
105
+
106
+ ### 基础操作
70
107
 
71
108
  ```typescript
72
- import { mergeSVGElements } from 'svg-toolbox';
73
- import { JSDOM } from 'jsdom';
74
-
75
- const dom = new JSDOM(`<!DOCTYPE html><html><body></body></html>`);
76
- const { document } = dom.window;
77
-
78
- const svgElement1 = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
79
- svgElement1.setAttribute('cx', '50');
80
- svgElement1.setAttribute('cy', '50');
81
- svgElement1.setAttribute('r', '40');
82
- svgElement1.setAttribute('stroke', 'black');
83
- svgElement1.setAttribute('stroke-width', '3');
84
- svgElement1.setAttribute('fill', 'red');
85
-
86
- const svgElement2 = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
87
- svgElement2.setAttribute('x', '10');
88
- svgElement2.setAttribute('y', '10');
89
- svgElement2.setAttribute('width', '100');
90
- svgElement2.setAttribute('height', '100');
91
- svgElement2.setAttribute('fill', 'blue');
92
-
93
- const mergedElement = mergeSVGElements([svgElement1, svgElement2]);
94
- console.log(mergedElement);
109
+ import { createSVGElement, cloneSVGElement, mergeSVGElements } from 'svg-toolbox';
110
+
111
+ // 创建 SVG 元素
112
+ const svg = createSVGElement('<svg><circle cx="50" cy="50" r="40" /></svg>');
113
+
114
+ // 克隆元素
115
+ const cloned = cloneSVGElement(svg);
116
+
117
+ // 合并多个元素
118
+ const merged = mergeSVGElements([svg, cloned]);
95
119
  ```
96
120
 
97
- ### convertSVGToBase64
98
- Converts an SVG element or SVG string to a Base64-encoded string.
121
+ ### 格式转换
99
122
 
100
123
  ```typescript
101
- import { createSVGElement, convertSVGToBase64, convertBase64ToSVG } from 'svg-toolbox';
102
-
103
- const svgElement = createSVGElement(`<svg><path d="M10 20L30 40Z" /></svg>`);
124
+ import { convertSVGToBase64, svgToImage } from 'svg-toolbox';
104
125
 
105
- const base64String = convertSVGToBase64(svgElement);
106
- console.log('convertSVGToBase64 param element', base64String);
126
+ // 转换为 Base64
127
+ const base64 = convertSVGToBase64('<svg>...</svg>');
107
128
 
108
- const svgString = convertBase64ToSVG(base64String);
109
- console.log('convertBase64ToSVG', svgString);
129
+ // 转换为 PNG
130
+ const pngBuffer = await svgToImage('input.svg', { scale: 2, format: 'png' });
110
131
 
111
- const svgBase64 = convertSVGToBase64(svgString);
112
- console.log('convertSVGToBase64 param string', svgBase64);
132
+ // 转换为 WebP
133
+ const webpBuffer = await svgToImage('input.svg', { format: 'webp', quality: 90 });
113
134
  ```
114
135
 
115
- ### convertBase64ToSVG
116
- Converts a Base64-encoded string back to an SVG string.
136
+ ### 图像比较
117
137
 
118
138
  ```typescript
119
- import { convertBase64ToSVG } from 'svg-toolbox';
139
+ import { diffImages } from 'svg-toolbox';
120
140
 
121
- const base64String = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxjaXJjbGUgY3g9IjUwIiBjeT0iNTAiIHI9IjQwIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjMiIGZpbGw9InJlZCIgLz48L3N2Zz4=';
122
- const svgString = convertBase64ToSVG(base64String);
123
- console.log(svgString);
141
+ // 比较两个图像并生成差异图
142
+ const result = await diffImages('image1.svg', 'image2.svg', 'diff.png');
143
+ console.log(`差异像素数: ${result.numDiffPixels}`);
124
144
  ```
125
145
 
126
- ### diffSvg
127
- Compares two PNG images and generates a diff image.
146
+ ### SVG 优化
128
147
 
129
148
  ```typescript
130
- import { diffSvg } from 'svg-toolbox';
149
+ import { optimizeSVG, removeNanCoordinates } from 'svg-toolbox';
131
150
 
132
- const pathA = 'path/to/first/image.png';
133
- const pathB = 'path/to/second/image.png';
134
- const diffFilePath = 'path/to/save/diff/image.png';
151
+ // 综合优化
152
+ const optimized = optimizeSVG('<svg><!-- comment --><path d="M 10,20 nan L 30,40" /></svg>');
135
153
 
136
- // diffPngBuffer is a Buffer object containing the diff image in PNG format.
137
- // numDiffPixels is the number of different pixels between the two images.
138
- const { diffPngBuffer, numDiffPixels } = await diffSvg(pathA, pathB, diffFilePath)
139
- const diffPngBase64 = `data:image/png;base64,${diffPngBuffer.toString('base64')}`;
140
- console.log(`Number of different pixels: ${numDiffPixels}`);
154
+ // 移除 NaN 坐标
155
+ const cleaned = removeNanCoordinates('<svg><path d="M 10,20 nan L 30,40" /></svg>');
141
156
  ```
142
157
 
143
- ### svg2png
144
- Converts an SVG file to PNG format.
158
+ ### 内容分析
145
159
 
146
160
  ```typescript
147
- // Callback/Promise
148
- import { svg2Png } from 'svg-toolbox';
149
-
150
- const svgPath = 'path/to/input/image.svg';
151
- const pngPath = 'path/to/output/image.png';
152
- const scale = 2; // Scaling factor
161
+ import { extractColors, getPathStatistics } from 'svg-toolbox';
153
162
 
154
- svg2Png(svgPath, pngPath, scale);
163
+ // 提取颜色
164
+ const colors = extractColors('<svg><circle fill="red" stroke="blue" /></svg>');
155
165
 
156
- // Async/await
157
- const pngBuffer = await svg2Png(svgPath, scale);
158
- const pngBase64 = `data:image/png;base64,${pngBuffer.toString('base64')}`;
159
- console.log(pngBase64);
166
+ // 获取路径统计
167
+ const stats = getPathStatistics('<svg><path d="M 10,20 L 30,40 Z" /></svg>');
168
+ console.log(`路径数: ${stats.totalPaths}, 命令数: ${stats.totalCommands}`);
160
169
  ```
161
170
 
162
- ### removeNanCoordinates
163
- Parses and normalizes the d attribute of all path elements in an SVG content.
171
+ ## API 文档
164
172
 
165
- ```typescript
166
- import { removeNanCoordinates } from 'svg-toolbox';
173
+ 详细的 API 文档请参考 [TypeScript 类型定义](./src/types/index.ts) 和源代码注释。
174
+
175
+ ## 开发
167
176
 
168
- const svgContent = `<svg><path d="M 10,20 nan L 30,40 -nan Z" /></svg>`;
169
- const normalizedSvgContent = removeNanCoordinates(svgContent);
170
- console.log(normalizedSvgContent);
177
+ ```bash
178
+ # 安装依赖
179
+ npm install
180
+
181
+ # 运行测试
182
+ npm test
183
+
184
+ # 构建项目
185
+ npm run build
171
186
 
187
+ # 监听模式测试
188
+ npm run test:watch
189
+
190
+ # 生成覆盖率报告
191
+ npm run test:coverage
172
192
  ```
173
193
 
174
- ## License
194
+ ## 许可证
195
+
175
196
  MIT License
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,44 @@
1
+ import { extractColors } from '../colors';
2
+ import { createSVGElement } from '../../core/element';
3
+ describe('Color Analysis', () => {
4
+ describe('extractColors', () => {
5
+ it('should extract fill colors', () => {
6
+ const svgContent = '<svg><circle fill="red" /><rect fill="blue" /></svg>';
7
+ const colors = extractColors(svgContent);
8
+ expect(colors.length).toBeGreaterThan(0);
9
+ expect(colors.some(c => c.fill === 'red')).toBe(true);
10
+ expect(colors.some(c => c.fill === 'blue')).toBe(true);
11
+ });
12
+ it('should extract stroke colors', () => {
13
+ const svgContent = '<svg><path stroke="green" stroke-width="2" /></svg>';
14
+ const colors = extractColors(svgContent);
15
+ expect(colors.some(c => c.stroke === 'green')).toBe(true);
16
+ });
17
+ it('should extract opacity values', () => {
18
+ const svgContent = '<svg><circle fill="red" opacity="0.5" /></svg>';
19
+ const colors = extractColors(svgContent);
20
+ const redColor = colors.find(c => c.fill === 'red');
21
+ expect(redColor?.opacity).toBe(0.5);
22
+ });
23
+ it('should ignore transparent and none values', () => {
24
+ const svgContent = '<svg><circle fill="none" stroke="transparent" /><rect fill="red" /></svg>';
25
+ const colors = extractColors(svgContent);
26
+ expect(colors.some(c => c.fill === 'none')).toBe(false);
27
+ expect(colors.some(c => c.stroke === 'transparent')).toBe(false);
28
+ expect(colors.some(c => c.fill === 'red')).toBe(true);
29
+ });
30
+ it('should work with SVG element input', () => {
31
+ const svgContent = '<svg><circle fill="purple" /></svg>';
32
+ const element = createSVGElement(svgContent);
33
+ const colors = extractColors(element);
34
+ expect(colors.some(c => c.fill === 'purple')).toBe(true);
35
+ });
36
+ it('should handle nested elements', () => {
37
+ const svgContent = '<svg><g><circle fill="red" /><rect fill="blue" /></g></svg>';
38
+ const colors = extractColors(svgContent);
39
+ expect(colors.some(c => c.fill === 'red')).toBe(true);
40
+ expect(colors.some(c => c.fill === 'blue')).toBe(true);
41
+ });
42
+ });
43
+ });
44
+ //# sourceMappingURL=colors.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"colors.test.js","sourceRoot":"","sources":["../../../src/analyze/__tests__/colors.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,71 @@
1
+ import { parsePathData, analyzePaths, getPathStatistics } from '../paths';
2
+ import { createSVGElement } from '../../core/element';
3
+ describe('Path Analysis', () => {
4
+ describe('parsePathData', () => {
5
+ it('should parse simple path commands', () => {
6
+ const pathData = 'M 10,20 L 30,40 Z';
7
+ const commands = parsePathData(pathData);
8
+ expect(commands.length).toBe(3);
9
+ expect(commands[0].type).toBe('M');
10
+ expect(commands[0].params).toEqual([10, 20]);
11
+ expect(commands[1].type).toBe('L');
12
+ expect(commands[2].type).toBe('Z');
13
+ });
14
+ it('should handle lowercase commands', () => {
15
+ const pathData = 'm 10,20 l 30,40 z';
16
+ const commands = parsePathData(pathData);
17
+ expect(commands.length).toBe(3);
18
+ expect(commands[0].type).toBe('m');
19
+ });
20
+ it('should handle multiple parameters', () => {
21
+ const pathData = 'M 10,20 L 30,40 50,60';
22
+ const commands = parsePathData(pathData);
23
+ expect(commands[1].params.length).toBe(4);
24
+ });
25
+ it('should handle cubic bezier commands', () => {
26
+ const pathData = 'M 10,20 C 20,30 40,50 60,70';
27
+ const commands = parsePathData(pathData);
28
+ expect(commands.length).toBe(2);
29
+ expect(commands[1].type).toBe('C');
30
+ expect(commands[1].params.length).toBe(6);
31
+ });
32
+ });
33
+ describe('analyzePaths', () => {
34
+ it('should analyze all paths in SVG', () => {
35
+ const svgContent = '<svg><path id="path1" d="M 10,20 L 30,40" /><path id="path2" d="M 50,60 L 70,80" /></svg>';
36
+ const analysis = analyzePaths(svgContent);
37
+ expect(analysis.size).toBe(2);
38
+ expect(analysis.has('path1')).toBe(true);
39
+ expect(analysis.has('path2')).toBe(true);
40
+ });
41
+ it('should use index as id if path has no id', () => {
42
+ const svgContent = '<svg><path d="M 10,20" /><path d="M 30,40" /></svg>';
43
+ const analysis = analyzePaths(svgContent);
44
+ expect(analysis.has('path-0')).toBe(true);
45
+ expect(analysis.has('path-1')).toBe(true);
46
+ });
47
+ it('should work with SVG element input', () => {
48
+ const svgContent = '<svg><path d="M 10,20 L 30,40" /></svg>';
49
+ const element = createSVGElement(svgContent);
50
+ const analysis = analyzePaths(element);
51
+ expect(analysis.size).toBe(1);
52
+ });
53
+ });
54
+ describe('getPathStatistics', () => {
55
+ it('should return correct statistics', () => {
56
+ const svgContent = '<svg><path d="M 10,20 L 30,40 Z" /><path d="M 50,60 L 70,80" /></svg>';
57
+ const stats = getPathStatistics(svgContent);
58
+ expect(stats.totalPaths).toBe(2);
59
+ expect(stats.totalCommands).toBeGreaterThan(0);
60
+ expect(stats.commandTypes['M']).toBeGreaterThan(0);
61
+ expect(stats.commandTypes['L']).toBeGreaterThan(0);
62
+ });
63
+ it('should count command types correctly', () => {
64
+ const svgContent = '<svg><path d="M 10,20 M 30,40 L 50,60 L 70,80" /></svg>';
65
+ const stats = getPathStatistics(svgContent);
66
+ expect(stats.commandTypes['M']).toBe(2);
67
+ expect(stats.commandTypes['L']).toBe(2);
68
+ });
69
+ });
70
+ });
71
+ //# sourceMappingURL=paths.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.test.js","sourceRoot":"","sources":["../../../src/analyze/__tests__/paths.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * SVG color analysis utilities
3
+ */
4
+ import { SVGColor } from '../types';
5
+ /**
6
+ * Extracts all colors used in an SVG
7
+ */
8
+ export declare function extractColors(svgContent: Element | string): SVGColor[];
@@ -0,0 +1,55 @@
1
+ /**
2
+ * SVG color analysis utilities
3
+ */
4
+ import { JSDOM } from 'jsdom';
5
+ import { serializeSVG } from '../core/element';
6
+ import { isValidSvgString } from '../utils/validation';
7
+ /**
8
+ * Extracts all colors used in an SVG
9
+ */
10
+ export function extractColors(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
+ const colors = [];
23
+ const colorSet = new Set();
24
+ function extractFromElement(element) {
25
+ const fill = element.getAttribute('fill');
26
+ const stroke = element.getAttribute('stroke');
27
+ const opacity = element.getAttribute('opacity');
28
+ if (fill && fill !== 'none' && fill !== 'transparent') {
29
+ const colorKey = `${fill}-${opacity || '1'}`;
30
+ if (!colorSet.has(colorKey)) {
31
+ colorSet.add(colorKey);
32
+ colors.push({
33
+ fill,
34
+ opacity: opacity ? parseFloat(opacity) : undefined
35
+ });
36
+ }
37
+ }
38
+ if (stroke && stroke !== 'none' && stroke !== 'transparent') {
39
+ const colorKey = `stroke-${stroke}-${opacity || '1'}`;
40
+ if (!colorSet.has(colorKey)) {
41
+ colorSet.add(colorKey);
42
+ colors.push({
43
+ stroke,
44
+ opacity: opacity ? parseFloat(opacity) : undefined
45
+ });
46
+ }
47
+ }
48
+ Array.from(element.children).forEach(child => {
49
+ extractFromElement(child);
50
+ });
51
+ }
52
+ extractFromElement(svgElement);
53
+ return colors;
54
+ }
55
+ //# sourceMappingURL=colors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"colors.js","sourceRoot":"","sources":["../../src/analyze/colors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAC9B,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAqB,MAAM,qBAAqB,CAAC;AAG1E;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,UAA4B;IACxD,MAAM,SAAS,GAAG,gBAAgB,CAAC,UAAU,CAAC;QAC5C,CAAC,CAAC,UAAU;QACZ,CAAC,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;IAE7B,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,SAAS,EAAE;QAC/B,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,MAAM,GAAe,EAAE,CAAC;IAC9B,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IAEnC,SAAS,kBAAkB,CAAC,OAAgB;QAC1C,MAAM,IAAI,GAAG,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QAC9C,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;QAEhD,IAAI,IAAI,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,aAAa,EAAE,CAAC;YACtD,MAAM,QAAQ,GAAG,GAAG,IAAI,IAAI,OAAO,IAAI,GAAG,EAAE,CAAC;YAC7C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC5B,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBACvB,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI;oBACJ,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;iBACnD,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,IAAI,MAAM,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,aAAa,EAAE,CAAC;YAC5D,MAAM,QAAQ,GAAG,UAAU,MAAM,IAAI,OAAO,IAAI,GAAG,EAAE,CAAC;YACtD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC5B,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBACvB,MAAM,CAAC,IAAI,CAAC;oBACV,MAAM;oBACN,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;iBACnD,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;YAC3C,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;IACL,CAAC;IAED,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAE/B,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * SVG path analysis utilities
3
+ */
4
+ import { PathCommand } from '../types';
5
+ /**
6
+ * Parses the 'd' attribute of a path element into command objects
7
+ */
8
+ export declare function parsePathData(pathData: string): PathCommand[];
9
+ /**
10
+ * Analyzes all paths in an SVG and returns their command structures
11
+ */
12
+ export declare function analyzePaths(svgContent: Element | string): Map<string, PathCommand[]>;
13
+ /**
14
+ * Gets statistics about paths in an SVG
15
+ */
16
+ export declare function getPathStatistics(svgContent: Element | string): {
17
+ totalPaths: number;
18
+ totalCommands: number;
19
+ commandTypes: Record<string, number>;
20
+ };
@@ -0,0 +1,78 @@
1
+ /**
2
+ * SVG path analysis utilities
3
+ */
4
+ import { JSDOM } from 'jsdom';
5
+ import { serializeSVG } from '../core/element';
6
+ import { isValidSvgString } from '../utils/validation';
7
+ /**
8
+ * Parses the 'd' attribute of a path element into command objects
9
+ */
10
+ export function parsePathData(pathData) {
11
+ const commands = [];
12
+ // Split by path commands (M, L, H, V, C, S, Q, T, A, Z)
13
+ const commandRegex = /([MmLlHhVvCcSsQqTtAaZz])((?:\s*-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\s*,?\s*)*)/g;
14
+ let match;
15
+ while ((match = commandRegex.exec(pathData)) !== null) {
16
+ const type = match[1];
17
+ const paramsStr = match[2].trim();
18
+ if (type.toLowerCase() === 'z') {
19
+ commands.push({ type, params: [] });
20
+ }
21
+ else if (paramsStr) {
22
+ const params = paramsStr
23
+ .split(/[\s,]+/)
24
+ .filter(p => p.trim() !== '')
25
+ .map(Number)
26
+ .filter(n => !isNaN(n));
27
+ commands.push({ type, params });
28
+ }
29
+ }
30
+ return commands;
31
+ }
32
+ /**
33
+ * Analyzes all paths in an SVG and returns their command structures
34
+ */
35
+ export function analyzePaths(svgContent) {
36
+ const svgString = isValidSvgString(svgContent)
37
+ ? svgContent
38
+ : serializeSVG(svgContent);
39
+ const dom = new JSDOM(svgString, {
40
+ contentType: 'image/svg+xml'
41
+ });
42
+ const document = dom.window.document;
43
+ const svgElement = document.querySelector('svg');
44
+ if (!svgElement) {
45
+ throw new Error('No SVG element found in the provided content.');
46
+ }
47
+ const paths = svgElement.querySelectorAll('path');
48
+ const pathAnalysis = new Map();
49
+ Array.from(paths).forEach((path, index) => {
50
+ const d = path.getAttribute('d');
51
+ if (d) {
52
+ const id = path.getAttribute('id') || `path-${index}`;
53
+ pathAnalysis.set(id, parsePathData(d));
54
+ }
55
+ });
56
+ return pathAnalysis;
57
+ }
58
+ /**
59
+ * Gets statistics about paths in an SVG
60
+ */
61
+ export function getPathStatistics(svgContent) {
62
+ const pathAnalysis = analyzePaths(svgContent);
63
+ let totalCommands = 0;
64
+ const commandTypes = {};
65
+ pathAnalysis.forEach(commands => {
66
+ totalCommands += commands.length;
67
+ commands.forEach(cmd => {
68
+ const type = cmd.type.toUpperCase();
69
+ commandTypes[type] = (commandTypes[type] || 0) + 1;
70
+ });
71
+ });
72
+ return {
73
+ totalPaths: pathAnalysis.size,
74
+ totalCommands,
75
+ commandTypes
76
+ };
77
+ }
78
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/analyze/paths.ts"],"names":[],"mappings":"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"}