svg-path-simplify 0.0.1 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +339 -21
  2. package/README.md +61 -2
  3. package/dist/svg-path-simplify.esm.js +4308 -0
  4. package/dist/svg-path-simplify.esm.min.js +1 -0
  5. package/dist/svg-path-simplify.js +4334 -0
  6. package/dist/svg-path-simplify.min.js +1 -0
  7. package/dist/svg-path-simplify.node.js +4331 -0
  8. package/dist/svg-path-simplify.node.min.js +1 -0
  9. package/index.html +230 -0
  10. package/package.json +5 -6
  11. package/src/constants.js +4 -0
  12. package/src/detect_input.js +42 -0
  13. package/src/index.js +21 -3
  14. package/src/pathData_simplify_cubic.js +324 -0
  15. package/src/pathData_simplify_cubic_arr.js +50 -0
  16. package/src/pathData_simplify_cubic_extrapolate.js +220 -0
  17. package/src/pathSimplify-main.js +400 -0
  18. package/src/svg_getViewbox.js +32 -0
  19. package/src/svgii/...parse.js +402 -0
  20. package/src/svgii/geometry.js +1143 -0
  21. package/src/svgii/geometry_area.js +265 -0
  22. package/src/svgii/geometry_bbox.js +223 -0
  23. package/src/svgii/pathData_analyze.js +896 -0
  24. package/src/svgii/pathData_convert.js +1180 -0
  25. package/src/svgii/pathData_parse.js +487 -0
  26. package/src/svgii/pathData_remove_collinear.js +98 -0
  27. package/src/svgii/pathData_remove_zerolength.js +28 -0
  28. package/src/svgii/pathData_reorder.js +238 -0
  29. package/src/svgii/pathData_reverse.js +124 -0
  30. package/src/svgii/pathData_scale.js +42 -0
  31. package/src/svgii/pathData_split.js +449 -0
  32. package/src/svgii/pathData_stringify.js +145 -0
  33. package/src/svgii/pathData_toPolygon.js +92 -0
  34. package/src/svgii/pathdata_cleanup.js +363 -0
  35. package/src/svgii/poly_analyze.js +172 -0
  36. package/src/svgii/poly_to_pathdata.js +185 -0
  37. package/src/svgii/rounding.js +162 -0
  38. package/src/svgii/simplify.js +248 -0
  39. package/src/svgii/simplify_bezier.js +470 -0
  40. package/src/svgii/simplify_linetos.js +93 -0
  41. package/src/svgii/simplify_polygon.js +135 -0
  42. package/src/svgii/stringify.js +103 -0
  43. package/src/svgii/svg_cleanup.js +86 -0
  44. package/src/svgii/visualize.js +317 -0
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Serialize pathData array to a minified "d" attribute string.
3
+ */
4
+ export function pathDataToD(pathData, optimize = 1) {
5
+
6
+ let beautify = optimize>1;
7
+ let minify = beautify ? false : true;
8
+
9
+ // Convert first "M" to "m" if followed by "l" (when minified)
10
+ if (pathData[1].type === "l" && minify) {
11
+ pathData[0].type = "m";
12
+ }
13
+
14
+ let d = '';
15
+ if(beautify) {
16
+ d = `${pathData[0].type} ${pathData[0].values.join(" ")}\n`;
17
+ }else{
18
+ d = `${pathData[0].type}${pathData[0].values.join(" ")}`;
19
+ }
20
+
21
+
22
+ for (let i = 1, len = pathData.length; i < len; i++) {
23
+ let com0 = pathData[i - 1];
24
+ let com = pathData[i];
25
+ let { type, values } = com;
26
+
27
+ // Minify Arc commands (A/a) – actually sucks!
28
+ if (minify && (type === 'A' || type === 'a')) {
29
+ values = [
30
+ values[0], values[1], values[2],
31
+ `${values[3]}${values[4]}${values[5]}`,
32
+ values[6]
33
+ ];
34
+ }
35
+
36
+ // Omit type for repeated commands
37
+ type = (com0.type === com.type && com.type.toLowerCase() !== 'm' && minify)
38
+ ? " "
39
+ : (
40
+ (com0.type === "m" && com.type === "l") ||
41
+ (com0.type === "M" && com.type === "l") ||
42
+ (com0.type === "M" && com.type === "L")
43
+ ) && minify
44
+ ? " "
45
+ : com.type;
46
+
47
+
48
+ // concatenate subsequent floating point values
49
+ if (minify) {
50
+
51
+ //console.log(optimize, beautify, minify);
52
+
53
+ let valsString = '';
54
+ let prevWasFloat = false;
55
+
56
+ for (let v = 0, l = values.length; v < l; v++) {
57
+ let val = values[v];
58
+ let valStr = val.toString();
59
+ let isFloat = valStr.includes('.');
60
+ let isSmallFloat = isFloat && Math.abs(val) < 1;
61
+
62
+
63
+ // Remove leading zero from small floats *only* if the previous was also a float
64
+ if (isSmallFloat && prevWasFloat) {
65
+ valStr = valStr.replace(/^0\./, '.');
66
+ }
67
+
68
+ // Add space unless this is the first value OR previous was a small float
69
+ if (v > 0 && !(prevWasFloat && isSmallFloat)) {
70
+ valsString += ' ';
71
+ }
72
+ //console.log(isSmallFloat, prevWasFloat, valStr);
73
+
74
+ valsString += valStr
75
+ //.replace(/-0./g, '-.').replace(/ -./g, '-.')
76
+ prevWasFloat = isSmallFloat;
77
+ }
78
+
79
+ //console.log('minify', valsString);
80
+ d += `${type}${valsString}`;
81
+
82
+ }
83
+ // regular non-minified output
84
+ else{
85
+ if(beautify) {
86
+ d += `${type} ${values.join(' ')}\n`;
87
+ }else{
88
+ d += `${type}${values.join(' ')}`;
89
+ }
90
+ }
91
+ }
92
+
93
+ if (minify) {
94
+ d = d
95
+ .replace(/ 0\./g, " .") // Space before small decimals
96
+ .replace(/ -/g, "-") // Remove space before negatives
97
+ .replace(/-0\./g, "-.") // Remove leading zero from negative decimals
98
+ .replace(/Z/g, "z"); // Convert uppercase 'Z' to lowercase
99
+ }
100
+
101
+
102
+ return d;
103
+ }
@@ -0,0 +1,86 @@
1
+ export function cleanUpSVG(svgMarkup, {
2
+ returnDom=false,
3
+ removeHidden=true,
4
+ removeUnused=true,
5
+ }={}) {
6
+ svgMarkup = cleanSvgPrologue(svgMarkup);
7
+
8
+ // replace namespaced refs
9
+ svgMarkup = svgMarkup.replaceAll("xlink:href=", "href=");
10
+
11
+ let svg = new DOMParser()
12
+ .parseFromString(svgMarkup, "text/html")
13
+ .querySelector("svg");
14
+
15
+
16
+ let allowed=['viewBox', 'xmlns', 'width', 'height', 'id', 'class'];
17
+ removeExcludedAttribues(svg, allowed)
18
+
19
+ let removeEls = ['metadata', 'script']
20
+
21
+ let els = svg.querySelectorAll('*')
22
+ els.forEach(el=>{
23
+ let name = el.nodeName;
24
+ // remove hidden elements
25
+ let style = el.getAttribute('style') || ''
26
+ let isHiddenByStyle = style ? style.trim().includes('display:none') : false;
27
+ let isHidden = (el.getAttribute('display') && el.getAttribute('display') === 'none') || isHiddenByStyle;
28
+ if(name.includes(':') || removeEls.includes(name) || (removeHidden && isHidden )) {
29
+ el.remove();
30
+ }else{
31
+ // remove BS elements
32
+ removeNameSpaceAtts(el)
33
+ }
34
+ })
35
+
36
+ if(returnDom) return svg
37
+
38
+ let markup = stringifySVG(svg)
39
+ console.log(markup);
40
+
41
+ return markup;
42
+ }
43
+
44
+ function cleanSvgPrologue(svgString) {
45
+ return (
46
+ svgString
47
+ // Remove XML prologues like <?xml ... ?>
48
+ .replace(/<\?xml[\s\S]*?\?>/gi, "")
49
+ // Remove DOCTYPE declarations
50
+ .replace(/<!DOCTYPE[\s\S]*?>/gi, "")
51
+ // Remove comments <!-- ... -->
52
+ .replace(/<!--[\s\S]*?-->/g, "")
53
+ // Trim extra whitespace
54
+ .trim()
55
+ );
56
+ }
57
+
58
+ function removeExcludedAttribues(el, allowed=['viewBox', 'xmlns', 'width', 'height', 'id', 'class']){
59
+ let atts = [...el.attributes].map((att) => att.name);
60
+ atts.forEach((att) => {
61
+ if (!allowed.includes(att)) {
62
+ el.removeAttribute(att);
63
+ }
64
+ });
65
+ }
66
+
67
+
68
+ function removeNameSpaceAtts(el) {
69
+ let atts = [...el.attributes].map((att) => att.name);
70
+ atts.forEach((att) => {
71
+ if (att.includes(":")) {
72
+ el.removeAttribute(att);
73
+ }
74
+ });
75
+ }
76
+
77
+ function stringifySVG(svg){
78
+ let markup = new XMLSerializer().serializeToString(svg);
79
+ markup = markup
80
+ .replace(/\t/g, "")
81
+ .replace(/[\n\r|]/g, "\n")
82
+ .replace(/\n\s*\n/g, '\n')
83
+ .replace(/ +/g, ' ')
84
+
85
+ return markup
86
+ }
@@ -0,0 +1,317 @@
1
+
2
+ //import { splitSubpaths } from "./pathData_split";
3
+
4
+ //import { getPathDataVertices } from "./geometry";
5
+ //import { getPolygonArea } from "./geometry_area";
6
+
7
+ export function renderCommands(pathData, target = 'svg1') {
8
+
9
+ const splitSubpaths = (pathData) => {
10
+ let subPathArr = [];
11
+
12
+ //split segments after M command
13
+ let subPathIndices = pathData.map((com, i) => (com.type.toLowerCase() === 'm' ? i : -1)).filter(i => i !== -1);
14
+
15
+ // no compound path
16
+ if (subPathIndices.length === 1) {
17
+ return [pathData]
18
+ }
19
+ subPathIndices.forEach((index, i) => {
20
+ subPathArr.push(pathData.slice(index, subPathIndices[i + 1]));
21
+ });
22
+
23
+ return subPathArr;
24
+ }
25
+
26
+ //parent group
27
+ let svg_g_top = `<g class="cpt_g cpt_g_top">`;
28
+ let svg_g = `<g class="cpt_g">`;
29
+
30
+ svg_g += `
31
+
32
+ <style>
33
+
34
+ .cpt_handle {
35
+ stroke: #ccc;
36
+ }
37
+
38
+ .cpt_g {
39
+ stroke-width: 0.3%;
40
+ color: red;
41
+ }
42
+
43
+ .cpt_handle {
44
+ stroke: currentColor;
45
+ }
46
+
47
+ .cpt_handle_tip {
48
+ r: 1%;
49
+ fill: currentColor;
50
+ }
51
+
52
+ .cpt_handle_tip_m {
53
+ r: 1.5%;
54
+ display:none;
55
+ fill: transparent;
56
+ stroke: currentColor;
57
+ }
58
+ </style>
59
+
60
+
61
+ <symbol id="ccw_circle" viewBox="-5 -5 110 110" overflow="visible">
62
+ <path fill="none" stroke="currentColor"
63
+ d="M 4 31 c 8 -19 26 -31 46 -31 c 28 0 50 22 50 50 c 0 28 -22 50 -50 50 c -14 0 -36 -6 -46 -31
64
+ m -2 -76 v 37 h 37
65
+ "/>
66
+ </symbol>
67
+
68
+ <symbol id="cw_circle" viewBox="-5 -5 110 110" overflow="visible">
69
+ <path fill="none" stroke="currentColor" transform="scale(-1 1)" transform-origin="center"
70
+ d="M 4 31 c 8 -19 26 -31 46 -31 c 28 0 50 22 50 50 c 0 28 -22 50 -50 50 c -14 0 -36 -6 -46 -31
71
+ m -2 -76 v 37 h 37
72
+ "/>
73
+ </symbol>
74
+
75
+
76
+ `;
77
+
78
+
79
+
80
+ //split in sub paths
81
+ let pathDataArr = splitSubpaths(pathData);
82
+ let M, cp1, cp2, cp1_0, cp2_0, p0, p;
83
+
84
+ pathDataArr.forEach((sub, s) => {
85
+
86
+ M = { x: sub[0].values[0], y: sub[0].values[1] }
87
+ p0 = M
88
+
89
+ let svg = `<g class="cpt_g">`;
90
+ let svg_cpts_top = ``
91
+
92
+ let polyPts = getPathDataVertices(sub);
93
+ let area = getPolygonArea(polyPts);
94
+ let cw = area<0 ? false : true;
95
+
96
+
97
+ sub.forEach((com, i) => {
98
+ let { type, values } = com;
99
+
100
+ if (type === 'Z') {
101
+ //p = M
102
+ //renderPoint(svg1, p, 'red', '2%')
103
+ } else {
104
+
105
+ let valsL = type != 'V' && type != 'H' ? values.slice(-2) :
106
+ (type === 'V' ? [p0.x, values[0]] : [values[0], p0.y]);
107
+ p = { x: valsL[0], y: valsL[1] };
108
+ //renderPoint(svg1, p, 'green', '3%')
109
+
110
+ let transCW = cw ? 'scale(-1 1)' : 'scale(1 1)';
111
+
112
+ if (type === 'M') {
113
+
114
+ //<use href="#ccw_circle" stroke-width="4"/>
115
+
116
+ if(cw){
117
+ svg_g_top += `<use href="#cw_circle" stroke-width="50%" style="color:#fff" x="${M.x}" y="${M.y}" width="2.5%" height="2.5%" transform="translate(-1.25 -1.25)" transform-origin="center" overflow="visible" />
118
+ <use href="#cw_circle" stroke-width="15%" x="${M.x}" y="${M.y}" width="2.5%" height="2.5%" data-transform-origin="center" transform="translate(-1.25 -1.25)" overflow="visible" />`
119
+ }else{
120
+ svg_g_top += `<use href="#ccw_circle" stroke-width="50%" style="color:#fff" x="${M.x}" y="${M.y}" width="2.5%" height="2.5%" transform="translate(-1.25 -1.25)" transform-origin="center" overflow="visible" />
121
+ <use href="#ccw_circle" stroke-width="15%" x="${M.x}" y="${M.y}" width="2.5%" height="2.5%" data-transform-origin="center" transform="translate(-1.25 -1.25)" overflow="visible" />`
122
+ }
123
+ //M = p;
124
+ //renderPoint(svg1, M, 'green', '2%');
125
+
126
+ }
127
+
128
+ if (type === 'C' || type === 'Q') {
129
+ cp1 = { x: values[0], y: values[1] }
130
+ //renderPoint(svg1, cp1, 'cyan');
131
+
132
+ if (type === 'Q') {
133
+ cp2 = cp1
134
+ svg += `<polyline fill="none" class="cpt_handle cpt_handle_q cpt_handle_cp1 " points="${p0.x} ${p0.y} ${cp1.x} ${cp1.y} ${p.x} ${p.y}"/>`;
135
+ svg += `<circle class="cpt_handle_tip cpt_handle_tip_q" cx="${cp1.x}" cy="${cp1.y}" r="1%" >
136
+ <title>${type.toLowerCase()} - x:${cp1.x}, y: ${cp1.y}</title></circle>`
137
+ }
138
+
139
+ if (type === 'C') {
140
+ cp2 = { x: values[2], y: values[3] }
141
+ //renderPoint(svg1, cp2, 'cyan')
142
+
143
+ svg += `<line class="cpt_handle cpt_handle_c cpt_handle_cp1 " x1="${p0.x}" y1="${p0.y}" x2="${cp1.x}" y2="${cp1.y}"/>`;
144
+ svg += `<circle class="cpt_handle_tip" cx="${cp1.x}" cy="${cp1.y}" r="1%" >
145
+ <title>${type.toLowerCase()} - x:${cp1.x}, y: ${cp1.y}</title></circle>`
146
+
147
+
148
+ svg += `<line class="cpt_handle cpt_handle_c cpt_handle_cp2" x1="${p.x}" y1="${p.y}" x2="${cp2.x}" y2="${cp2.y}"/>`;
149
+ svg += `<circle class="cpt_handle_tip" cx="${cp2.x}" cy="${cp2.y}" r="1%" >
150
+ <title>${type.toLowerCase()} - x:${cp2.x}, y: ${cp2.y}</title></circle>`
151
+
152
+ }
153
+ }
154
+
155
+ if (type === 'T') {
156
+
157
+ //get reflected cpt
158
+ // new control points
159
+ cp1 = { x: 2 * p0.x - cp2_0.x, y: 2 * p0.y - cp2_0.y };
160
+ cp2 = cp1
161
+ svg += `<polyline fill="none" class="cpt_handle cpt_handle_t cpt_handle_cp1 " points="${p0.x} ${p0.y} ${cp1.x} ${cp1.y} ${p.x} ${p.y}"/>`;
162
+
163
+ }
164
+
165
+ if (type === 'S') {
166
+
167
+ //get reflected cpt
168
+ let cp2_2 = { x: values[0], y: values[1] }
169
+ // new control points
170
+ let cp1_2 = { x: 2 * p0.x - cp2_0.x, y: 2 * p0.y - cp2_0.y };
171
+
172
+ svg += `<line class="cpt_handle cpt_handle_s cpt_handle_cp1 " x1="${p0.x}" y1="${p0.y}" x2="${cp1_2.x}" y2="${cp1_2.y}"/>`;
173
+ svg += `<line class="cpt_handle cpt_handle_s cpt_handle_cp2" x1="${p.x}" y1="${p.y}" x2="${cp2_2.x}" y2="${cp2_2.y}"/>`;
174
+ svg += `<circle class="cpt_handle_tip cpt_handle_tip_s" cx="${cp2_2.x}" cy="${cp2_2.y}" r="1%" >
175
+ <title>${type.toLowerCase()} - x:${cp2_2.x}, y: ${cp2_2.y}</title></circle>`
176
+
177
+ }
178
+
179
+ if (type !== 'Z') {
180
+ svg += `<circle class="cpt_handle_tip cpt_handle_tip_${type.toLowerCase()}" cx="${p.x}" cy="${p.y}" r="1%" >
181
+ <title>P x:${p.x}, y: ${p.y}</title></circle>`
182
+
183
+ }
184
+
185
+ }
186
+
187
+ p0 = p
188
+ cp1_0 = cp1
189
+ cp2_0 = cp2
190
+
191
+ })
192
+
193
+
194
+ svg_g += svg + '</g>';
195
+
196
+ })
197
+
198
+ svg_g += `</g>`;
199
+ svg_g += svg_g_top+'</g>';
200
+ //console.log('svg', svg, svg1);
201
+ //svg1.insertAdjacentHTML('beforeend', svg)
202
+
203
+ return svg_g
204
+
205
+ }
206
+
207
+
208
+ export function renderPoint(
209
+ svg,
210
+ coords,
211
+ fill = "red",
212
+ r = "1%",
213
+ opacity = "1",
214
+ title = '',
215
+ render = true,
216
+ id = "",
217
+ className = ""
218
+ ) {
219
+ if (Array.isArray(coords)) {
220
+ coords = {
221
+ x: coords[0],
222
+ y: coords[1]
223
+ };
224
+ }
225
+ let marker = `<circle class="${className}" opacity="${opacity}" id="${id}" cx="${coords.x}" cy="${coords.y}" r="${r}" fill="${fill}">
226
+ <title>${title}</title></circle>`;
227
+
228
+ if (render) {
229
+ svg.insertAdjacentHTML("beforeend", marker);
230
+ } else {
231
+ return marker;
232
+ }
233
+ }
234
+
235
+
236
+ export function renderPath(svg, d = '', stroke = 'green', strokeWidth = '1%', render = true) {
237
+
238
+ let path = `<path d="${d}" fill="none" stroke="${stroke}" stroke-width="${strokeWidth}" /> `;
239
+
240
+ if (render) {
241
+ svg.insertAdjacentHTML("beforeend", path);
242
+ } else {
243
+ return path;
244
+ }
245
+
246
+
247
+ }
248
+
249
+
250
+
251
+
252
+ // debug helper: render lines
253
+ export function renderPoly(svg, pts, strokeWidth = "1%", stroke = "purple", render = true) {
254
+ pts = pts.map(pt => { return [pt.x, pt.y] }).flat().join(' ');
255
+
256
+ let poly =
257
+ `<polyline stroke-width="${strokeWidth}" points="${pts}" stroke="${stroke}" />`;
258
+
259
+ if (render) {
260
+ svg.insertAdjacentHTML("beforeend", poly);
261
+
262
+ } else {
263
+ return poly
264
+ }
265
+ }
266
+
267
+
268
+ //!!! delete- rename
269
+ export function renderPerpendicularLine(pt, len = 10, angle) {
270
+ let ptA = {
271
+ x: pt.x + len * Math.cos(angle),
272
+ y: pt.y + len * Math.sin(angle)
273
+ };
274
+ return ptA;
275
+ }
276
+
277
+
278
+
279
+ export function addMarkers() {
280
+
281
+
282
+ let markerMarkup =
283
+ `<svg id="svgMarkers" style="width:0; height:0; position:absolute; z-index:-1;float:left;">
284
+ <defs>
285
+ <marker id="markerStart" overflow="visible" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="strokeWidth"
286
+ markerWidth="10" markerHeight="10" orient="auto-start-reverse">
287
+ <circle cx="5" cy="5" r="3" fill="green" fill-opacity="1" />>
288
+
289
+ <marker id="markerEnd" overflow="visible" viewBox="0 0 10 10" refX="5" refY="5"
290
+ markerUnits="strokeWidth" markerWidth="10" markerHeight="10" orient="auto-start-reverse">
291
+ <circle cx="5" cy="5" r="2" fill="red" fill-opacity="0.5" />
292
+ </marker>
293
+ </defs>
294
+ </svg>`
295
+
296
+ let style = `
297
+ <style>
298
+ .showMarkers {
299
+ marker-start: url(#markerStart);
300
+ marker-mid: url(#markerEnd);
301
+ stroke-width: 0.33%;
302
+ }
303
+ </style>
304
+ `
305
+ document.body.insertAdjacentHTML('afterbegin', style + markerMarkup)
306
+
307
+ }
308
+
309
+
310
+ /**
311
+ * adjust viewBox
312
+ */
313
+ export function adjustViewBox(svg) {
314
+ let bb = svg.getBBox();
315
+ let [x, y, width, height] = [bb.x, bb.y, bb.width, bb.height];
316
+ svg.setAttribute("viewBox", [x, y, width, height].join(" "));
317
+ }