svg-path-simplify 0.0.5 → 0.0.8
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.
- package/README.md +193 -11
- package/dist/svg-path-simplify.esm.js +890 -263
- package/dist/svg-path-simplify.esm.min.js +1 -1
- package/dist/svg-path-simplify.js +889 -263
- package/dist/svg-path-simplify.min.js +1 -1
- package/dist/svg-path-simplify.node.js +889 -263
- package/dist/svg-path-simplify.node.min.js +1 -1
- package/index.html +16 -8
- package/package.json +6 -3
- package/src/detect_input.js +2 -0
- package/src/dom-polyfill.js +29 -0
- package/src/dom-polyfill_back.js +22 -0
- package/src/index.js +11 -5
- package/src/pathData_simplify_cubic.js +1 -37
- package/src/pathData_simplify_cubic_extrapolate.js +45 -27
- package/src/pathSimplify-main.js +163 -68
- package/src/svgii/geometry.js +8 -155
- package/src/svgii/geometry_flatness.js +101 -0
- package/src/svgii/pathData_analyze.js +11 -596
- package/src/svgii/pathData_convert.js +7 -3
- package/src/svgii/pathData_parse_els.js +239 -0
- package/src/svgii/pathData_remove_collinear.js +13 -3
- package/src/svgii/pathData_remove_orphaned.js +20 -0
- package/src/svgii/pathData_remove_zerolength.js +28 -2
- package/src/svgii/pathData_reorder.js +3 -1
- package/src/svgii/pathData_split.js +5 -0
- package/src/svgii/pathData_toPolygon.js +98 -47
- package/src/svgii/poly_analyze.js +20 -16
- package/src/svgii/rounding.js +25 -19
- package/src/svgii/simplify_refineExtremes.js +173 -0
- package/src/svgii/svg_cleanup.js +14 -2
- package/test.js +14 -0
- package/testSVG.js +39 -0
- /package/src/svgii/{simplify_polygon.js → polygon_unite.js} +0 -0
package/src/svgii/rounding.js
CHANGED
|
@@ -11,7 +11,7 @@ export function detectAccuracy(pathData) {
|
|
|
11
11
|
|
|
12
12
|
// Reference first MoveTo command (M)
|
|
13
13
|
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
14
|
-
let p0 =
|
|
14
|
+
let p0 = M
|
|
15
15
|
let p = M
|
|
16
16
|
pathData[0].decimals = 0
|
|
17
17
|
let lastDec = 0;
|
|
@@ -29,34 +29,40 @@ export function detectAccuracy(pathData) {
|
|
|
29
29
|
let { type, values } = com;
|
|
30
30
|
|
|
31
31
|
let lastVals = values.length ? values.slice(-2) : [M.x, M.y];
|
|
32
|
-
p={x:lastVals[0], y:lastVals[1]}
|
|
32
|
+
p = { x: lastVals[0], y: lastVals[1] }
|
|
33
33
|
|
|
34
34
|
// use existing averave dimension value or calculate
|
|
35
|
-
let dimA = com.dimA ? +com.dimA.toFixed(8) : type!=='M' ? +getDistAv(p0, p).toFixed(8) : 0
|
|
35
|
+
let dimA = com.dimA ? +com.dimA.toFixed(8) : type !== 'M' ? +getDistAv(p0, p).toFixed(8) : 0
|
|
36
36
|
//let dimA = +getDistAv(p0, p).toFixed(8)
|
|
37
37
|
//console.log('dimA', dimA, com.dimA, type);
|
|
38
38
|
|
|
39
|
-
if(dimA) dims.add(dimA);
|
|
39
|
+
if (dimA) dims.add(dimA);
|
|
40
|
+
|
|
41
|
+
if (dimA && dimA < minDim) minDim = dimA;
|
|
42
|
+
if (dimA && dimA > maxDim) maxDim = dimA;
|
|
40
43
|
|
|
41
|
-
if(dimA && dimA<minDim) minDim = dimA;
|
|
42
|
-
if(dimA && dimA>maxDim) maxDim = dimA;
|
|
43
|
-
|
|
44
44
|
|
|
45
|
-
if(type==='M'){
|
|
46
|
-
M=p;
|
|
45
|
+
if (type === 'M') {
|
|
46
|
+
M = p;
|
|
47
47
|
}
|
|
48
48
|
p0 = p;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
let dim_min = Array.from(dims).sort()
|
|
53
|
-
let sliceIdx = Math.ceil(dim_min.length/8);
|
|
54
|
-
dim_min = dim_min.slice(0, sliceIdx );
|
|
55
53
|
|
|
56
|
-
|
|
54
|
+
/*
|
|
55
|
+
let minVal = dim_min.length > 15 ?
|
|
56
|
+
(dim_min[0] + dim_min[2]) / 2 :
|
|
57
|
+
dim_min[0];
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
let sliceIdx = Math.ceil(dim_min.length / 10);
|
|
61
|
+
dim_min = dim_min.slice(0, sliceIdx);
|
|
62
|
+
let minVal = dim_min.reduce((a, b) => a + b, 0) / sliceIdx;
|
|
57
63
|
|
|
58
|
-
let threshold =
|
|
59
|
-
let decimalsAuto =
|
|
64
|
+
let threshold = 40
|
|
65
|
+
let decimalsAuto = minVal > threshold*1.5 ? 0 : Math.floor(threshold / minVal).toString().length
|
|
60
66
|
|
|
61
67
|
// clamp
|
|
62
68
|
return Math.min(Math.max(0, decimalsAuto), 8)
|
|
@@ -145,16 +151,16 @@ export function roundPathData(pathData, decimals = -1) {
|
|
|
145
151
|
let hasDecimal = decimals == 'auto' && pathData[0].hasOwnProperty('decimals') ? true : false;
|
|
146
152
|
//console.log('decimals', decimals, hasDecimal);
|
|
147
153
|
|
|
148
|
-
for(let c=0, len=pathData.length; c<len; c++){
|
|
149
|
-
let com=pathData[c];
|
|
150
|
-
let {type, values} = com
|
|
154
|
+
for (let c = 0, len = pathData.length; c < len; c++) {
|
|
155
|
+
let com = pathData[c];
|
|
156
|
+
let { type, values } = com
|
|
151
157
|
|
|
152
|
-
if (decimals
|
|
158
|
+
if (decimals > -1 || hasDecimal) {
|
|
153
159
|
decimals = hasDecimal ? com.decimals : decimals;
|
|
154
160
|
|
|
155
161
|
|
|
156
162
|
//console.log('decimals', type, decimals);
|
|
157
|
-
pathData[c].values = com.values.map(val=>{return val ? +val.toFixed(decimals) : val });
|
|
163
|
+
pathData[c].values = com.values.map(val => { return val ? +val.toFixed(decimals) : val });
|
|
158
164
|
|
|
159
165
|
}
|
|
160
166
|
};
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { getCombinedByDominant } from "../pathData_simplify_cubic_extrapolate";
|
|
2
|
+
import { getDistAv, interpolate } from "./geometry";
|
|
3
|
+
import { getPathArea } from "./geometry_area";
|
|
4
|
+
import { getPathDataBBox } from "./geometry_bbox";
|
|
5
|
+
import { renderPoint } from "./visualize";
|
|
6
|
+
|
|
7
|
+
export function refineAdjacentExtremes(pathData, {
|
|
8
|
+
threshold = null, tolerance = 1
|
|
9
|
+
} = {}) {
|
|
10
|
+
|
|
11
|
+
//dimA = dimA ? dimA :
|
|
12
|
+
if (!threshold) {
|
|
13
|
+
let bb = getPathDataBBox(pathData);
|
|
14
|
+
threshold = (bb.width + bb.height) / 2 * 0.05
|
|
15
|
+
//console.log('new threshold', threshold);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let l = pathData.length
|
|
19
|
+
|
|
20
|
+
for (let i = 0; i < l; i++) {
|
|
21
|
+
let com = pathData[i];
|
|
22
|
+
let { type, values, extreme, corner=false, dimA, p0, p } = com;
|
|
23
|
+
let comN = pathData[i + 1] ? pathData[i + 1] : null;
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
// adjacent
|
|
27
|
+
//&& comN.extreme
|
|
28
|
+
if (comN && type === 'C' && comN.type === 'C' && extreme && !corner) {
|
|
29
|
+
|
|
30
|
+
// check dist
|
|
31
|
+
let diff = getDistAv(p, comN.p)
|
|
32
|
+
let isCose = diff < threshold;
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
if (isCose) {
|
|
36
|
+
//renderPoint(markers, comN.p, 'cyan', '1%', '0.5')
|
|
37
|
+
//console.log(comN);
|
|
38
|
+
//console.log(diff, threshold);
|
|
39
|
+
|
|
40
|
+
let dx1 = (com.cp1.x - comN.p0.x)
|
|
41
|
+
let dy1 = (com.cp1.y - comN.p0.y)
|
|
42
|
+
|
|
43
|
+
let horizontal = Math.abs(dy1) < Math.abs(dx1);
|
|
44
|
+
|
|
45
|
+
let pN = comN.p;
|
|
46
|
+
let ptI;
|
|
47
|
+
let t = 1;
|
|
48
|
+
|
|
49
|
+
if (comN.extreme) {
|
|
50
|
+
|
|
51
|
+
// extend cp2
|
|
52
|
+
if (horizontal) {
|
|
53
|
+
t = Math.abs(Math.abs(comN.cp2.x - comN.p.x) / Math.abs(com.cp2.x - com.p.x))
|
|
54
|
+
//console.log('t', t);
|
|
55
|
+
ptI = interpolate(comN.p, com.cp2, 1 + t)
|
|
56
|
+
com.cp2.x = ptI.x
|
|
57
|
+
//renderPoint(markers, com.cp2, 'cyan', '1%', '0.5')
|
|
58
|
+
//renderPoint(markers, ptI, 'orange', '1%', '0.5')
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
//renderPoint(markers, comN.p0, 'cyan', '1%', '0.5')
|
|
62
|
+
t = Math.abs(Math.abs(comN.cp2.y - comN.p.y) / Math.abs(com.cp2.y - com.p.y))
|
|
63
|
+
ptI = interpolate(comN.p, com.cp2, 1 + t)
|
|
64
|
+
com.cp2.y = ptI.y
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//merge commands
|
|
68
|
+
pathData[i + 1].values = [com.cp1.x, com.cp1.y, com.cp2.x, com.cp2.y, pN.x, pN.y]
|
|
69
|
+
pathData[i + 1].cp1 = com.cp1
|
|
70
|
+
pathData[i + 1].cp2 = com.cp2
|
|
71
|
+
pathData[i + 1].p0 = com.p0
|
|
72
|
+
pathData[i + 1].p = pN
|
|
73
|
+
pathData[i + 1].extreme = true
|
|
74
|
+
|
|
75
|
+
// nullify 1st
|
|
76
|
+
pathData[i] = null;
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// extend fist command
|
|
82
|
+
else {
|
|
83
|
+
|
|
84
|
+
let comN2 = pathData[i + 2] ? pathData[i + 2] : null;
|
|
85
|
+
if (!comN2 && comN2.type !== 'C') continue
|
|
86
|
+
|
|
87
|
+
//continue
|
|
88
|
+
|
|
89
|
+
// extrapolate
|
|
90
|
+
let comEx = getCombinedByDominant(comN, comN2, threshold, tolerance, false)
|
|
91
|
+
//console.log('comEx', comEx);
|
|
92
|
+
|
|
93
|
+
if (comEx.length === 1) {
|
|
94
|
+
pathData[i + 1] = null;
|
|
95
|
+
|
|
96
|
+
comEx = comEx[0]
|
|
97
|
+
|
|
98
|
+
pathData[i + 2].values = [comEx.cp1.x, comEx.cp1.y, comEx.cp2.x, comEx.cp2.y, comEx.p.x, comEx.p.y]
|
|
99
|
+
pathData[i + 2].cp1 = comEx.cp1
|
|
100
|
+
pathData[i + 2].cp2 = comEx.cp2
|
|
101
|
+
pathData[i + 2].p0 = comEx.p0
|
|
102
|
+
pathData[i + 2].p = comEx.p
|
|
103
|
+
pathData[i + 2].extreme = comEx.extreme
|
|
104
|
+
|
|
105
|
+
i++
|
|
106
|
+
continue
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// remove commands
|
|
116
|
+
pathData = pathData.filter(Boolean)
|
|
117
|
+
l = pathData.length
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* refine closing commands
|
|
123
|
+
*/
|
|
124
|
+
|
|
125
|
+
let closed = pathData[l - 1].type.toLowerCase() === 'z';
|
|
126
|
+
let lastIdx = closed ? l - 2 : l - 1;
|
|
127
|
+
let lastCom = pathData[lastIdx];
|
|
128
|
+
let penultimateCom = pathData[lastIdx - 1] || null;
|
|
129
|
+
let M = { x: pathData[0].values[0], y: pathData[0].values[1] }
|
|
130
|
+
|
|
131
|
+
let dec = 8
|
|
132
|
+
let lastVals = lastCom.values.slice(-2);
|
|
133
|
+
let isClosingTo = +lastVals[0].toFixed(dec) === +M.x.toFixed(dec) && +lastVals[1].toFixed(dec) === +M.y.toFixed(dec)
|
|
134
|
+
let fistExt = pathData[1].type === 'C' && pathData[1].extreme ? pathData[1] : null;
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
//renderPoint(markers, M, 'blue')
|
|
138
|
+
//renderPoint(markers, fistExt.cp1, 'blue')
|
|
139
|
+
//renderPoint(markers, fistExt.p0, 'blue')
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
let diff = getDistAv(lastCom.p0, lastCom.p)
|
|
144
|
+
let isCose = diff < threshold;
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
if (penultimateCom && penultimateCom.type === 'C' && isCose && isClosingTo && fistExt) {
|
|
148
|
+
|
|
149
|
+
let dx1 = Math.abs(fistExt.cp1.x - M.x)
|
|
150
|
+
let dy1 = Math.abs(fistExt.cp1.y - M.y)
|
|
151
|
+
|
|
152
|
+
let horizontal = dy1 < dx1;
|
|
153
|
+
//console.log(dx1, dx2);
|
|
154
|
+
//console.log('isCose', isCose, diff, dimA);
|
|
155
|
+
|
|
156
|
+
let comEx = getCombinedByDominant(penultimateCom, lastCom, threshold, tolerance, false)
|
|
157
|
+
console.log('comEx', comEx);
|
|
158
|
+
|
|
159
|
+
if (comEx.length === 1) {
|
|
160
|
+
pathData[lastIdx - 1] = comEx[0];
|
|
161
|
+
pathData[lastIdx] = null;
|
|
162
|
+
pathData = pathData.filter(Boolean)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
//console.log('pathData ex', pathData);
|
|
170
|
+
|
|
171
|
+
return pathData
|
|
172
|
+
|
|
173
|
+
}
|
package/src/svgii/svg_cleanup.js
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export function removeEmptySVGEls(svg) {
|
|
5
|
+
let els = svg.querySelectorAll('g, defs');
|
|
6
|
+
els.forEach(el => {
|
|
7
|
+
if (!el.children.length) el.remove()
|
|
8
|
+
})
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
1
13
|
export function cleanUpSVG(svgMarkup, {
|
|
2
14
|
returnDom=false,
|
|
3
15
|
removeHidden=true,
|
|
@@ -13,7 +25,7 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
13
25
|
.querySelector("svg");
|
|
14
26
|
|
|
15
27
|
|
|
16
|
-
let allowed=['viewBox', 'xmlns', 'width', 'height', 'id', 'class'];
|
|
28
|
+
let allowed=['viewBox', 'xmlns', 'width', 'height', 'id', 'class', 'fill', 'stroke', 'stroke-width'];
|
|
17
29
|
removeExcludedAttribues(svg, allowed)
|
|
18
30
|
|
|
19
31
|
let removeEls = ['metadata', 'script']
|
|
@@ -74,7 +86,7 @@ function removeNameSpaceAtts(el) {
|
|
|
74
86
|
});
|
|
75
87
|
}
|
|
76
88
|
|
|
77
|
-
function stringifySVG(svg){
|
|
89
|
+
export function stringifySVG(svg){
|
|
78
90
|
let markup = new XMLSerializer().serializeToString(svg);
|
|
79
91
|
markup = markup
|
|
80
92
|
.replace(/\t/g, "")
|
package/test.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { svgPathSimplify } from 'svg-path-simplify';
|
|
2
|
+
|
|
3
|
+
let pathDataString =
|
|
4
|
+
`
|
|
5
|
+
M 57.13 15.5
|
|
6
|
+
c 13.28 0 24.53 8.67 28.42 20.65
|
|
7
|
+
c 0.94 2.91 1.45 6.01 1.45 9.23
|
|
8
|
+
`
|
|
9
|
+
|
|
10
|
+
// try to simplify
|
|
11
|
+
let pathDataOpt = svgPathSimplify(pathDataString);
|
|
12
|
+
|
|
13
|
+
// simplified pathData
|
|
14
|
+
console.log(pathDataOpt)
|
package/testSVG.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// add suport for DOM manipulations
|
|
2
|
+
import { DOMParser, parseHTML } from 'linkedom';
|
|
3
|
+
//import { XMLSerializerPoly, DOMParserPoly } from 'svg-path-simplify/dom_polyfills.js';
|
|
4
|
+
import { svgPathSimplify } from 'svg-path-simplify';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
let svgMarkup =
|
|
10
|
+
`<?xml version="1.0" encoding="utf-8"?>
|
|
11
|
+
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
12
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
13
|
+
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="100px"
|
|
14
|
+
height="100px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
|
|
15
|
+
<g id="garamond">
|
|
16
|
+
<path id="path1" d="M16.2 125.7 L16.2 125.7 Q12.1 125.7 8.4 123.8 Q4.6 122 2.3 119.1 Q0 116.2 0.2 113.1 L0.2 113.1 L0.4 112.6 Q2.2 111.2 5 109.3 Q7.8 107.4 11.4 105.1 Q15 102.8 19.1 100.4 L19.1 100.4 L19.2 99.5 Q16.8 98.1 15.5 94.7 Q14.1 91.3 14.1 86.7 L14.1 86.7 Q14.1 81.1 16 75.5 Q17.9 70 21.1 65.5 Q24.2 61 28.1 58.3 Q32 55.6 35.9 55.6 L35.9 55.6 L46.1 57.2 L47.2 54.7 L47.7 54.6 L50.8 56.1 L51.6 57 Q49.2 61.4 47.6 65.1 Q45.9 68.9 45 72 Q44 75.1 43.7 77.8 L43.7 77.8 Q43.4 80.3 43.2 83.7 Q43 87.1 42.8 90.6 Q42.6 94.2 42.5 97.1 Q42.3 100.1 42.1 101.6 L42.1 101.6 Q41.7 105.1 40 108.5 Q38.2 112 35.6 115.1 Q32.9 118.2 29.7 120.6 Q26.5 123 23 124.3 Q19.5 125.7 16.2 125.7 ZM19.5 119.9 L19.5 119.9 Q25.9 119.9 30.4 115.3 Q34.9 110.7 35.9 102.7 L35.9 102.7 L38.4 82 L37.6 81.8 Q34.5 87.5 32.3 91 Q30 94.6 28 96.8 Q25.9 99 23.5 100.8 L23.5 100.8 Q20.4 103.1 16.8 105.3 Q13.2 107.6 10.7 109.4 Q8.1 111.3 8.1 112.3 L8.1 112.3 Q8.1 114 9.9 115.8 Q11.6 117.5 14.3 118.7 Q16.9 119.9 19.5 119.9 ZM24.8 92.9 L24.8 92.9 Q26.3 92.9 29 89.4 Q31.6 86 35.2 79.5 Q38.7 73.1 42.9 64.1 L42.9 64.1 Q40.9 63.1 39.1 62.4 Q37.2 61.7 35.7 61.3 Q34.1 61 32.8 61 L32.8 61 Q29.8 61 27.1 64 Q24.4 67.1 22.7 71.9 Q21 76.7 21 82 L21 82 Q21 86.3 22.2 89.6 Q23.3 92.9 24.8 92.9 Z "/>
|
|
17
|
+
</g>
|
|
18
|
+
</svg>`
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
*/
|
|
23
|
+
let document = new DOMParser().parseFromString(svgMarkup, 'image/svg+xml');
|
|
24
|
+
let svg = document.querySelector('svg');
|
|
25
|
+
let path = svg.querySelector('path');
|
|
26
|
+
let d = path.getAttribute('d')
|
|
27
|
+
|
|
28
|
+
let markup = document.toString()
|
|
29
|
+
markup = new XMLSerializer().serializeToString(svg)
|
|
30
|
+
|
|
31
|
+
console.log(markup);
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
// try to simplify
|
|
35
|
+
let svgOpt = svgPathSimplify(svgMarkup);
|
|
36
|
+
|
|
37
|
+
// simplified pathData
|
|
38
|
+
console.log(svgOpt)
|
|
39
|
+
*/
|
|
File without changes
|