svg-path-simplify 0.0.7 → 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/dist/svg-path-simplify.esm.js +775 -169
- package/dist/svg-path-simplify.esm.min.js +1 -1
- package/dist/svg-path-simplify.js +4674 -4068
- package/dist/svg-path-simplify.min.js +1 -1
- package/dist/svg-path-simplify.node.js +775 -169
- package/dist/svg-path-simplify.node.min.js +1 -1
- package/index.html +11 -8
- package/package.json +5 -3
- package/src/dom-polyfill.js +29 -0
- package/src/dom-polyfill_back.js +22 -0
- package/src/index.js +7 -1
- package/src/pathData_simplify_cubic.js +1 -37
- package/src/pathData_simplify_cubic_extrapolate.js +43 -28
- package/src/pathSimplify-main.js +50 -9
- 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_parse_els.js +239 -0
- package/src/svgii/pathData_remove_collinear.js +3 -1
- package/src/svgii/pathData_remove_orphaned.js +20 -0
- package/src/svgii/pathData_remove_zerolength.js +12 -2
- package/src/svgii/pathData_reorder.js +3 -1
- package/src/svgii/pathData_split.js +1 -0
- package/src/svgii/rounding.js +25 -19
- package/src/svgii/simplify_refineExtremes.js +173 -0
- package/src/svgii/svg_cleanup.js +4 -1
- package/testSVG.js +39 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
//import { pathDataToAbsoluteOrRelative, pathDataToLonghands, cubicToArc } from './pathData_convert.js';
|
|
2
|
+
import { parsePathDataString, parsePathDataNormalized, stringifyPathData } from './pathData_parse.js';
|
|
3
|
+
|
|
4
|
+
export function shapeElToPath(el){
|
|
5
|
+
|
|
6
|
+
let nodeName = el.nodeName.toLowerCase();
|
|
7
|
+
if(nodeName==='path')return el;
|
|
8
|
+
|
|
9
|
+
let pathData = getPathDataFromEl(el);
|
|
10
|
+
let d = pathData.map(com=>{return `${com.type} ${com.values} `}).join(' ')
|
|
11
|
+
let attributes = [...el.attributes].map(att=>att.name);
|
|
12
|
+
|
|
13
|
+
let pathN = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
14
|
+
pathN.setAttribute('d', d );
|
|
15
|
+
|
|
16
|
+
let exclude = ['x', 'y', 'cx', 'cy', 'dx', 'dy', 'r', 'rx', 'ry', 'width', 'height', 'points']
|
|
17
|
+
|
|
18
|
+
attributes.forEach(att=>{
|
|
19
|
+
if(!exclude.includes(att)){
|
|
20
|
+
let val = el.getAttribute(att);
|
|
21
|
+
pathN.setAttribute(att, val)
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
//el.replaceWith(pathN)
|
|
26
|
+
return pathN
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
// retrieve pathdata from svg geometry elements
|
|
32
|
+
export function getPathDataFromEl(el, stringify=false) {
|
|
33
|
+
|
|
34
|
+
let pathData = [];
|
|
35
|
+
let type = el.nodeName;
|
|
36
|
+
let atts, attNames, d, x, y, width, height, r, rx, ry, cx, cy, x1, x2, y1, y2;
|
|
37
|
+
|
|
38
|
+
// convert relative or absolute units
|
|
39
|
+
const svgElUnitsToPixel = (el, decimals = 9) => {
|
|
40
|
+
//console.log(this);
|
|
41
|
+
const svg = el.nodeName !== "svg" ? el.closest("svg") : el;
|
|
42
|
+
|
|
43
|
+
// convert real life units to pixels
|
|
44
|
+
const translateUnitToPixel = (value) => {
|
|
45
|
+
|
|
46
|
+
if (value === null) {
|
|
47
|
+
return 0
|
|
48
|
+
}
|
|
49
|
+
//default dpi = 96
|
|
50
|
+
let dpi = 96;
|
|
51
|
+
let unit = value.match(/([a-z]+)/gi);
|
|
52
|
+
unit = unit ? unit[0] : "";
|
|
53
|
+
let val = parseFloat(value);
|
|
54
|
+
let rat;
|
|
55
|
+
|
|
56
|
+
// no unit - already pixes/user unit
|
|
57
|
+
if (!unit) {
|
|
58
|
+
return val;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
switch (unit) {
|
|
62
|
+
case "in":
|
|
63
|
+
rat = dpi;
|
|
64
|
+
break;
|
|
65
|
+
case "pt":
|
|
66
|
+
rat = (1 / 72) * 96;
|
|
67
|
+
break;
|
|
68
|
+
case "cm":
|
|
69
|
+
rat = (1 / 2.54) * 96;
|
|
70
|
+
break;
|
|
71
|
+
case "mm":
|
|
72
|
+
rat = ((1 / 2.54) * 96) / 10;
|
|
73
|
+
break;
|
|
74
|
+
// just a default approximation
|
|
75
|
+
case "em":
|
|
76
|
+
case "rem":
|
|
77
|
+
rat = 16;
|
|
78
|
+
break;
|
|
79
|
+
default:
|
|
80
|
+
rat = 1;
|
|
81
|
+
}
|
|
82
|
+
let valuePx = val * rat;
|
|
83
|
+
return +valuePx.toFixed(decimals);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// svg width and height attributes
|
|
87
|
+
let width = svg.getAttribute("width");
|
|
88
|
+
width = width ? translateUnitToPixel(width) : 300;
|
|
89
|
+
let height = svg.getAttribute("height");
|
|
90
|
+
height = width ? translateUnitToPixel(height) : 150;
|
|
91
|
+
|
|
92
|
+
//prefer viewBox values
|
|
93
|
+
let vB = svg.getAttribute("viewBox");
|
|
94
|
+
vB = vB
|
|
95
|
+
? vB
|
|
96
|
+
.replace(/,/g, " ")
|
|
97
|
+
.split(" ")
|
|
98
|
+
.filter(Boolean)
|
|
99
|
+
.map((val) => {
|
|
100
|
+
return +val;
|
|
101
|
+
})
|
|
102
|
+
: [];
|
|
103
|
+
|
|
104
|
+
let w = vB.length ? vB[2] : width;
|
|
105
|
+
let h = vB.length ? vB[3] : height;
|
|
106
|
+
let scaleX = w / 100;
|
|
107
|
+
let scaleY = h / 100;
|
|
108
|
+
let scalRoot = Math.sqrt((Math.pow(scaleX, 2) + Math.pow(scaleY, 2)) / 2);
|
|
109
|
+
|
|
110
|
+
let attsH = ["x", "width", "x1", "x2", "rx", "cx", "r"];
|
|
111
|
+
let attsV = ["y", "height", "y1", "y2", "ry", "cy"];
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
let atts = el.getAttributeNames();
|
|
115
|
+
atts.forEach((att) => {
|
|
116
|
+
let val = el.getAttribute(att);
|
|
117
|
+
let valAbs = val;
|
|
118
|
+
if (attsH.includes(att) || attsV.includes(att)) {
|
|
119
|
+
let scale = attsH.includes(att) ? scaleX : scaleY;
|
|
120
|
+
scale = att === "r" && w != h ? scalRoot : scale;
|
|
121
|
+
let unit = val.match(/([a-z|%]+)/gi);
|
|
122
|
+
unit = unit ? unit[0] : "";
|
|
123
|
+
if (val.includes("%")) {
|
|
124
|
+
valAbs = parseFloat(val) * scale;
|
|
125
|
+
}
|
|
126
|
+
//absolute units
|
|
127
|
+
else {
|
|
128
|
+
valAbs = translateUnitToPixel(val);
|
|
129
|
+
}
|
|
130
|
+
el.setAttribute(att, +valAbs);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
svgElUnitsToPixel(el)
|
|
136
|
+
|
|
137
|
+
const getAtts = (attNames) => {
|
|
138
|
+
atts = {}
|
|
139
|
+
attNames.forEach(att => {
|
|
140
|
+
atts[att] = +el.getAttribute(att)
|
|
141
|
+
})
|
|
142
|
+
return atts
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
switch (type) {
|
|
146
|
+
case 'path':
|
|
147
|
+
d = el.getAttribute("d");
|
|
148
|
+
pathData = parsePathDataNormalized(d);
|
|
149
|
+
break;
|
|
150
|
+
|
|
151
|
+
case 'rect':
|
|
152
|
+
attNames = ['x', 'y', 'width', 'height', 'rx', 'ry'];
|
|
153
|
+
({ x, y, width, height, rx, ry } = getAtts(attNames));
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
if (!rx && !ry) {
|
|
157
|
+
pathData = [
|
|
158
|
+
{ type: "M", values: [x, y] },
|
|
159
|
+
{ type: "L", values: [x + width, y] },
|
|
160
|
+
{ type: "L", values: [x + width, y + height] },
|
|
161
|
+
{ type: "L", values: [x, y + height] },
|
|
162
|
+
{ type: "Z", values: [] }
|
|
163
|
+
];
|
|
164
|
+
} else {
|
|
165
|
+
|
|
166
|
+
if (rx > width / 2) {
|
|
167
|
+
rx = width / 2;
|
|
168
|
+
}
|
|
169
|
+
if (ry > height / 2) {
|
|
170
|
+
ry = height / 2;
|
|
171
|
+
}
|
|
172
|
+
pathData = [
|
|
173
|
+
{ type: "M", values: [x + rx, y] },
|
|
174
|
+
{ type: "L", values: [x + width - rx, y] },
|
|
175
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x + width, y + ry] },
|
|
176
|
+
{ type: "L", values: [x + width, y + height - ry] },
|
|
177
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x + width - rx, y + height] },
|
|
178
|
+
{ type: "L", values: [x + rx, y + height] },
|
|
179
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x, y + height - ry] },
|
|
180
|
+
{ type: "L", values: [x, y + ry] },
|
|
181
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x + rx, y] },
|
|
182
|
+
{ type: "Z", values: [] }
|
|
183
|
+
];
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
|
|
187
|
+
case 'circle':
|
|
188
|
+
case 'ellipse':
|
|
189
|
+
|
|
190
|
+
attNames = ['cx', 'cy', 'rx', 'ry', 'r'];
|
|
191
|
+
({ cx, cy, r, rx, ry } = getAtts(attNames));
|
|
192
|
+
|
|
193
|
+
if (type === 'circle') {
|
|
194
|
+
r = r;
|
|
195
|
+
rx = r
|
|
196
|
+
ry = r
|
|
197
|
+
} else {
|
|
198
|
+
rx = rx ? rx : r;
|
|
199
|
+
ry = ry ? ry : r;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
pathData = [
|
|
203
|
+
{ type: "M", values: [cx + rx, cy] },
|
|
204
|
+
{ type: "A", values: [rx, ry, 0, 1, 1, cx - rx, cy] },
|
|
205
|
+
{ type: "A", values: [rx, ry, 0, 1, 1, cx + rx, cy] },
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
break;
|
|
209
|
+
case 'line':
|
|
210
|
+
attNames = ['x1', 'y1', 'x2', 'y2'];
|
|
211
|
+
({ x1, y1, x2, y2 } = getAtts(attNames));
|
|
212
|
+
pathData = [
|
|
213
|
+
{ type: "M", values: [x1, y1] },
|
|
214
|
+
{ type: "L", values: [x2, y2] }
|
|
215
|
+
];
|
|
216
|
+
break;
|
|
217
|
+
case 'polygon':
|
|
218
|
+
case 'polyline':
|
|
219
|
+
|
|
220
|
+
let points = el.getAttribute('points').replaceAll(',', ' ').split(' ').filter(Boolean)
|
|
221
|
+
|
|
222
|
+
for (let i = 0; i < points.length; i += 2) {
|
|
223
|
+
pathData.push({
|
|
224
|
+
type: (i === 0 ? "M" : "L"),
|
|
225
|
+
values: [+points[i], +points[i + 1]]
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
if (type === 'polygon') {
|
|
229
|
+
pathData.push({
|
|
230
|
+
type: "Z",
|
|
231
|
+
values: []
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return stringify ? stringifyPathData(pathData): pathData;
|
|
238
|
+
|
|
239
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getDistAv, getSquareDistance } from "./geometry.js";
|
|
2
2
|
import { getPolygonArea } from "./geometry_area.js";
|
|
3
|
+
import { checkBezierFlatness, commandIsFlat } from "./geometry_flatness.js";
|
|
3
4
|
import { renderPoint } from "./visualize.js";
|
|
4
5
|
|
|
5
6
|
export function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLinetos = true) {
|
|
@@ -50,6 +51,7 @@ export function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLine
|
|
|
50
51
|
//let areaBez = getPolygonArea([p0, ...cpts, p], true)
|
|
51
52
|
|
|
52
53
|
isFlatBez = checkBezierFlatness(p0, cpts, p)
|
|
54
|
+
//isFlatBez = commandIsFlat([p0, ...cpts, p]).flat
|
|
53
55
|
// console.log();
|
|
54
56
|
|
|
55
57
|
//isFlatBez = areaBez < distMax * 0.25
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function removeOrphanedM(pathData) {
|
|
2
|
+
|
|
3
|
+
for (let i = 0, l = pathData.length; i < l; i++) {
|
|
4
|
+
let com = pathData[i];
|
|
5
|
+
if (!com) continue;
|
|
6
|
+
let { type = null, values = [] } = com;
|
|
7
|
+
let comN = pathData[i + 1] ? pathData[i + 1] : null;
|
|
8
|
+
if ((type === 'M' || type === 'm')) {
|
|
9
|
+
|
|
10
|
+
if (!comN || (comN && (comN.type === 'Z' || comN.type === 'z'))) {
|
|
11
|
+
pathData[i] = null
|
|
12
|
+
pathData[i + 1] = null
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
pathData = pathData.filter(Boolean);
|
|
18
|
+
return pathData;
|
|
19
|
+
|
|
20
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
|
|
2
|
+
/*
|
|
2
3
|
// remove zero-length segments introduced by rounding
|
|
3
4
|
export function removeZeroLengthLinetos_post(pathData) {
|
|
4
5
|
let pathDataOpt = []
|
|
@@ -13,6 +14,7 @@ export function removeZeroLengthLinetos_post(pathData) {
|
|
|
13
14
|
})
|
|
14
15
|
return pathDataOpt
|
|
15
16
|
}
|
|
17
|
+
*/
|
|
16
18
|
|
|
17
19
|
export function removeZeroLengthLinetos(pathData) {
|
|
18
20
|
|
|
@@ -26,14 +28,22 @@ export function removeZeroLengthLinetos(pathData) {
|
|
|
26
28
|
let com = pathData[c];
|
|
27
29
|
let { type, values } = com;
|
|
28
30
|
|
|
29
|
-
let
|
|
30
|
-
|
|
31
|
+
let valsLen = values.length;
|
|
32
|
+
//let valsL = values.slice(-2);
|
|
33
|
+
//p = { x: valsL[0], y: valsL[1] };
|
|
34
|
+
p = { x: values[valsLen-2], y: values[valsLen-1] };
|
|
31
35
|
|
|
32
36
|
// skip lineto
|
|
33
37
|
if (type === 'L' && p.x === p0.x && p.y === p0.y) {
|
|
34
38
|
continue
|
|
35
39
|
}
|
|
36
40
|
|
|
41
|
+
// skip minified zero length
|
|
42
|
+
if (type === 'l' || type === 'v' || type === 'h') {
|
|
43
|
+
let noLength = type === 'l' ? (values.join('') === '00') : values[0] === 0;
|
|
44
|
+
if(noLength) continue
|
|
45
|
+
}
|
|
46
|
+
|
|
37
47
|
pathDataN.push(com)
|
|
38
48
|
p0 = p;
|
|
39
49
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { splitSubpaths, addExtemesToCommand } from './pathData_split.js';
|
|
2
|
-
import { getComThresh,
|
|
2
|
+
import { getComThresh, getPathDataVertices, getSquareDistance } from './geometry.js';
|
|
3
3
|
import { getPolyBBox } from './geometry_bbox.js';
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
import { renderPoint, renderPath } from './visualize.js';
|
|
7
7
|
import { getPolygonArea } from './geometry_area.js';
|
|
8
|
+
import { checkBezierFlatness, commandIsFlat } from "./geometry_flatness.js";
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
export function pathDataToTopLeft(pathData) {
|
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,6 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
1
4
|
export function removeEmptySVGEls(svg) {
|
|
2
5
|
let els = svg.querySelectorAll('g, defs');
|
|
3
6
|
els.forEach(el => {
|
|
@@ -22,7 +25,7 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
22
25
|
.querySelector("svg");
|
|
23
26
|
|
|
24
27
|
|
|
25
|
-
let allowed=['viewBox', 'xmlns', 'width', 'height', 'id', 'class'];
|
|
28
|
+
let allowed=['viewBox', 'xmlns', 'width', 'height', 'id', 'class', 'fill', 'stroke', 'stroke-width'];
|
|
26
29
|
removeExcludedAttribues(svg, allowed)
|
|
27
30
|
|
|
28
31
|
let removeEls = ['metadata', 'script']
|
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
|
+
*/
|