svg-toolbox 1.1.11 → 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 +145 -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,176 +1,196 @@
1
- <h1 align="center">
2
- <br/>
3
- <img width="260" alt="image" src="https://github.com/user-attachments/assets/fcb0c4f5-8094-44d2-bfca-e4b5f5120f73" />
4
- <br/>
5
- </h1>
1
+ # SVG Toolbox
6
2
 
7
-
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.
3
+ 一个功能全面的 SVG 操作和分析工具库,提供 SVG 元素的创建、转换、优化、比较和分析等功能。
10
4
 
11
5
  [![npm version](https://img.shields.io/npm/v/svg-toolbox.svg?style=for-the-badge)](https://www.npmjs.com/package/svg-toolbox)
12
6
  [![npm downloads](https://img.shields.io/npm/dy/svg-toolbox.svg?style=for-the-badge)](https://www.npmjs.com/package/svg-toolbox)
13
- [![deps](https://img.shields.io/github/license/SteamedBread2333/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)
8
+
9
+ ## 安装
14
10
 
15
- ## Installation
16
11
  ```bash
17
12
  npm install svg-toolbox
18
13
  ```
19
14
 
20
- ## Table of Contents
15
+ ## 适用场景
21
16
 
22
- - [SVG Utility Functions](#svg-utility-functions)
23
- - [Installation](#installation)
24
- - [Table of Contents](#table-of-contents)
25
- - [Usage](#usage)
26
- - [createSVGElement](#createsvgelement)
27
- - [cloneSVGElement](#clonesvgelement)
28
- - [mergeSVGElements](#mergesvgelements)
29
- - [convertSVGToBase64](#convertsvgtobase64)
30
- - [convertBase64ToSVG](#convertbase64tosvg)
31
- - [diffSvg](#diffsvg)
32
- - [svg2png](#svg2png)
33
- - [removeNanCoordinates](#removenancoordinates)
34
- - [License](#license)
17
+ ### 1. SVG 元素操作场景
35
18
 
36
- ## Usage
19
+ 当你需要在 Node.js 环境中创建、克隆或合并 SVG 元素时,可以使用核心元素操作功能:
37
20
 
38
- ### createSVGElement
21
+ - **动态生成 SVG 图形**:根据数据动态创建 SVG 元素
22
+ - **SVG 模板复用**:克隆现有 SVG 元素作为模板
23
+ - **组合多个 SVG**:将多个独立的 SVG 图形合并为一个
39
24
 
40
- Creates an SVG element from a given SVG content string.
25
+ ### 2. 格式转换场景
41
26
 
42
- ```typescript
43
- const svgElement = createSVGElement(`<svg><path d="M10 20L30 40Z" /></svg>`);
44
- console.log(svgElement);
45
- ```
46
- ### cloneSVGElement
27
+ 当你需要将 SVG 转换为其他格式或进行编码转换时:
47
28
 
48
- Clones an SVG element deeply.
29
+ - **Web 应用嵌入**:将 SVG 转换为 Base64 数据 URI,方便嵌入 HTML/CSS
30
+ - **图片导出**:将 SVG 转换为 PNG、JPG 或 WebP 格式用于下载或分享
31
+ - **跨平台兼容**:在不同系统间传输 SVG 数据时使用 Base64 编码
49
32
 
50
- ```typescript
51
- import { cloneSVGElement } from 'svg-toolbox';
52
- import { JSDOM } from 'jsdom';
53
-
54
- const dom = new JSDOM(`<!DOCTYPE html><html><body></body></html>`);
55
- const { document } = dom.window;
56
-
57
- const originalElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
58
- originalElement.setAttribute('cx', '50');
59
- originalElement.setAttribute('cy', '50');
60
- originalElement.setAttribute('r', '40');
61
- originalElement.setAttribute('stroke', 'black');
62
- originalElement.setAttribute('stroke-width', '3');
63
- originalElement.setAttribute('fill', 'red');
64
-
65
- const clonedElement = cloneSVGElement(originalElement);
66
- console.log(clonedElement);
67
- ```
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)
68
78
 
69
- ### mergeSVGElements
70
- Merges multiple SVG elements into a single SVG element.
79
+ 提供图像对比和差异检测:
80
+
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
+ ### 基础操作
71
107
 
72
108
  ```typescript
73
- import { mergeSVGElements } from 'svg-toolbox';
74
- import { JSDOM } from 'jsdom';
75
-
76
- const dom = new JSDOM(`<!DOCTYPE html><html><body></body></html>`);
77
- const { document } = dom.window;
78
-
79
- const svgElement1 = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
80
- svgElement1.setAttribute('cx', '50');
81
- svgElement1.setAttribute('cy', '50');
82
- svgElement1.setAttribute('r', '40');
83
- svgElement1.setAttribute('stroke', 'black');
84
- svgElement1.setAttribute('stroke-width', '3');
85
- svgElement1.setAttribute('fill', 'red');
86
-
87
- const svgElement2 = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
88
- svgElement2.setAttribute('x', '10');
89
- svgElement2.setAttribute('y', '10');
90
- svgElement2.setAttribute('width', '100');
91
- svgElement2.setAttribute('height', '100');
92
- svgElement2.setAttribute('fill', 'blue');
93
-
94
- const mergedElement = mergeSVGElements([svgElement1, svgElement2]);
95
- 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]);
96
119
  ```
97
120
 
98
- ### convertSVGToBase64
99
- Converts an SVG element or SVG string to a Base64-encoded string.
121
+ ### 格式转换
100
122
 
101
123
  ```typescript
102
- import { createSVGElement, convertSVGToBase64, convertBase64ToSVG } from 'svg-toolbox';
103
-
104
- const svgElement = createSVGElement(`<svg><path d="M10 20L30 40Z" /></svg>`);
124
+ import { convertSVGToBase64, svgToImage } from 'svg-toolbox';
105
125
 
106
- const base64String = convertSVGToBase64(svgElement);
107
- console.log('convertSVGToBase64 param element', base64String);
126
+ // 转换为 Base64
127
+ const base64 = convertSVGToBase64('<svg>...</svg>');
108
128
 
109
- const svgString = convertBase64ToSVG(base64String);
110
- console.log('convertBase64ToSVG', svgString);
129
+ // 转换为 PNG
130
+ const pngBuffer = await svgToImage('input.svg', { scale: 2, format: 'png' });
111
131
 
112
- const svgBase64 = convertSVGToBase64(svgString);
113
- console.log('convertSVGToBase64 param string', svgBase64);
132
+ // 转换为 WebP
133
+ const webpBuffer = await svgToImage('input.svg', { format: 'webp', quality: 90 });
114
134
  ```
115
135
 
116
- ### convertBase64ToSVG
117
- Converts a Base64-encoded string back to an SVG string.
136
+ ### 图像比较
118
137
 
119
138
  ```typescript
120
- import { convertBase64ToSVG } from 'svg-toolbox';
139
+ import { diffImages } from 'svg-toolbox';
121
140
 
122
- const base64String = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxjaXJjbGUgY3g9IjUwIiBjeT0iNTAiIHI9IjQwIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjMiIGZpbGw9InJlZCIgLz48L3N2Zz4=';
123
- const svgString = convertBase64ToSVG(base64String);
124
- console.log(svgString);
141
+ // 比较两个图像并生成差异图
142
+ const result = await diffImages('image1.svg', 'image2.svg', 'diff.png');
143
+ console.log(`差异像素数: ${result.numDiffPixels}`);
125
144
  ```
126
145
 
127
- ### diffSvg
128
- Compares two PNG images and generates a diff image.
146
+ ### SVG 优化
129
147
 
130
148
  ```typescript
131
- import { diffSvg } from 'svg-toolbox';
149
+ import { optimizeSVG, removeNanCoordinates } from 'svg-toolbox';
132
150
 
133
- const pathA = 'path/to/first/image.png';
134
- const pathB = 'path/to/second/image.png';
135
- 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>');
136
153
 
137
- // diffPngBuffer is a Buffer object containing the diff image in PNG format.
138
- // numDiffPixels is the number of different pixels between the two images.
139
- const { diffPngBuffer, numDiffPixels } = await diffSvg(pathA, pathB, diffFilePath)
140
- const diffPngBase64 = `data:image/png;base64,${diffPngBuffer.toString('base64')}`;
141
- console.log(`Number of different pixels: ${numDiffPixels}`);
154
+ // 移除 NaN 坐标
155
+ const cleaned = removeNanCoordinates('<svg><path d="M 10,20 nan L 30,40" /></svg>');
142
156
  ```
143
157
 
144
- ### svg2png
145
- Converts an SVG file to PNG format.
158
+ ### 内容分析
146
159
 
147
160
  ```typescript
148
- // Callback/Promise
149
- import { svg2Png } from 'svg-toolbox';
150
-
151
- const svgPath = 'path/to/input/image.svg';
152
- const pngPath = 'path/to/output/image.png';
153
- const scale = 2; // Scaling factor
161
+ import { extractColors, getPathStatistics } from 'svg-toolbox';
154
162
 
155
- svg2Png(svgPath, pngPath, scale);
163
+ // 提取颜色
164
+ const colors = extractColors('<svg><circle fill="red" stroke="blue" /></svg>');
156
165
 
157
- // Async/await
158
- const pngBuffer = await svg2Png(svgPath, scale);
159
- const pngBase64 = `data:image/png;base64,${pngBuffer.toString('base64')}`;
160
- 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}`);
161
169
  ```
162
170
 
163
- ### removeNanCoordinates
164
- Parses and normalizes the d attribute of all path elements in an SVG content.
171
+ ## API 文档
165
172
 
166
- ```typescript
167
- import { removeNanCoordinates } from 'svg-toolbox';
173
+ 详细的 API 文档请参考 [TypeScript 类型定义](./src/types/index.ts) 和源代码注释。
174
+
175
+ ## 开发
168
176
 
169
- const svgContent = `<svg><path d="M 10,20 nan L 30,40 -nan Z" /></svg>`;
170
- const normalizedSvgContent = removeNanCoordinates(svgContent);
171
- console.log(normalizedSvgContent);
177
+ ```bash
178
+ # 安装依赖
179
+ npm install
180
+
181
+ # 运行测试
182
+ npm test
183
+
184
+ # 构建项目
185
+ npm run build
172
186
 
187
+ # 监听模式测试
188
+ npm run test:watch
189
+
190
+ # 生成覆盖率报告
191
+ npm run test:coverage
173
192
  ```
174
193
 
175
- ## License
194
+ ## 许可证
195
+
176
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":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;YACpC,MAAM,UAAU,GAAG,sDAAsD,CAAC;YAC1E,MAAM,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;YAEzC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;YACzC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;YACtC,MAAM,UAAU,GAAG,qDAAqD,CAAC;YACzE,MAAM,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;YAEzC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;YACvC,MAAM,UAAU,GAAG,gDAAgD,CAAC;YACpE,MAAM,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;YAEzC,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC;YACpD,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;YACnD,MAAM,UAAU,GAAG,2EAA2E,CAAC;YAC/F,MAAM,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;YAEzC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACxD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACjE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;YAC5C,MAAM,UAAU,GAAG,qCAAqC,CAAC;YACzD,MAAM,OAAO,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;YAC7C,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;YAEtC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;YACvC,MAAM,UAAU,GAAG,6DAA6D,CAAC;YACjF,MAAM,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;YAEzC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -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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"}