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
|
@@ -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) {
|
|
@@ -25,8 +26,12 @@ export function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLine
|
|
|
25
26
|
|
|
26
27
|
let area = getPolygonArea([p0, p, p1], true)
|
|
27
28
|
|
|
29
|
+
let distSquare0 = getSquareDistance(p0, p)
|
|
30
|
+
let distSquare1 = getSquareDistance(p, p1)
|
|
28
31
|
let distSquare = getSquareDistance(p0, p1)
|
|
29
|
-
|
|
32
|
+
//distSquare = (distSquare0+distSquare1) / 2;
|
|
33
|
+
|
|
34
|
+
let distMax = distSquare / 200 * tolerance
|
|
30
35
|
|
|
31
36
|
let isFlat = area < distMax;
|
|
32
37
|
let isFlatBez = false;
|
|
@@ -46,6 +51,7 @@ export function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLine
|
|
|
46
51
|
//let areaBez = getPolygonArea([p0, ...cpts, p], true)
|
|
47
52
|
|
|
48
53
|
isFlatBez = checkBezierFlatness(p0, cpts, p)
|
|
54
|
+
//isFlatBez = commandIsFlat([p0, ...cpts, p]).flat
|
|
49
55
|
// console.log();
|
|
50
56
|
|
|
51
57
|
//isFlatBez = areaBez < distMax * 0.25
|
|
@@ -67,8 +73,12 @@ export function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLine
|
|
|
67
73
|
p0 = p;
|
|
68
74
|
|
|
69
75
|
// colinear – exclude arcs (as always =) as semicircles won't have an area
|
|
70
|
-
|
|
76
|
+
//&& comN.type==='L'
|
|
77
|
+
if ( isFlat && c < l - 1 && (type === 'L' || (flatBezierToLinetos && isFlatBez)) ) {
|
|
71
78
|
//console.log(area,distMax );
|
|
79
|
+
//renderPoint(markers, p)
|
|
80
|
+
|
|
81
|
+
//if(comN.type!=='L' ){}
|
|
72
82
|
continue;
|
|
73
83
|
}
|
|
74
84
|
|
|
@@ -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,3 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
/*
|
|
3
|
+
// remove zero-length segments introduced by rounding
|
|
4
|
+
export function removeZeroLengthLinetos_post(pathData) {
|
|
5
|
+
let pathDataOpt = []
|
|
6
|
+
pathData.forEach((com, i) => {
|
|
7
|
+
let { type, values } = com;
|
|
8
|
+
if (type === 'l' || type === 'v' || type === 'h') {
|
|
9
|
+
let hasLength = type === 'l' ? (values.join('') !== '00') : values[0] !== 0
|
|
10
|
+
if (hasLength) pathDataOpt.push(com)
|
|
11
|
+
} else {
|
|
12
|
+
pathDataOpt.push(com)
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
return pathDataOpt
|
|
16
|
+
}
|
|
17
|
+
*/
|
|
18
|
+
|
|
1
19
|
export function removeZeroLengthLinetos(pathData) {
|
|
2
20
|
|
|
3
21
|
let M = { x: pathData[0].values[0], y: pathData[0].values[1] }
|
|
@@ -10,14 +28,22 @@ export function removeZeroLengthLinetos(pathData) {
|
|
|
10
28
|
let com = pathData[c];
|
|
11
29
|
let { type, values } = com;
|
|
12
30
|
|
|
13
|
-
let
|
|
14
|
-
|
|
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] };
|
|
15
35
|
|
|
16
36
|
// skip lineto
|
|
17
37
|
if (type === 'L' && p.x === p0.x && p.y === p0.y) {
|
|
18
38
|
continue
|
|
19
39
|
}
|
|
20
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
|
+
|
|
21
47
|
pathDataN.push(com)
|
|
22
48
|
p0 = p;
|
|
23
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) {
|
|
@@ -2,6 +2,7 @@ import { pointAtT, svgArcToCenterParam, getBezierExtremeT } from "./geometry";
|
|
|
2
2
|
import { renderPoint, renderPath } from "./visualize";
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
/**
|
|
6
7
|
* split segments into chunks to
|
|
7
8
|
* prevent simplification across
|
|
@@ -280,6 +281,8 @@ export function addExtemesToCommand(p0, values, tMin=0, tMax=1) {
|
|
|
280
281
|
|
|
281
282
|
if(tArr.length){
|
|
282
283
|
let commandsSplit = splitCommandAtTValues(p0, values, tArr)
|
|
284
|
+
//console.log('commandsSplit', commandsSplit);
|
|
285
|
+
|
|
283
286
|
pathDataNew.push(...commandsSplit)
|
|
284
287
|
extremeCount += commandsSplit.length;
|
|
285
288
|
}else{
|
|
@@ -322,6 +325,8 @@ export function addExtremePoints(pathData, tMin=0, tMax=1) {
|
|
|
322
325
|
// add extremes
|
|
323
326
|
if (type === 'C' || type === 'Q') {
|
|
324
327
|
let comExt = addExtemesToCommand(p0, values, tMin, tMax).pathData;
|
|
328
|
+
let comExt2 = com;
|
|
329
|
+
//comExt2.valu
|
|
325
330
|
//console.log('comExt', comExt);
|
|
326
331
|
pathDataNew.push(...comExt )
|
|
327
332
|
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { pointAtT } from "./geometry";
|
|
1
|
+
import { getDistAv, pointAtT } from "./geometry";
|
|
2
2
|
import { getPolyBBox } from "./geometry_bbox";
|
|
3
3
|
import { addDimensionData, analyzePathData } from "./pathData_analyze";
|
|
4
4
|
import { addExtremePoints } from "./pathData_split";
|
|
5
|
+
import { pathDataToD } from "./pathData_stringify";
|
|
6
|
+
import { analyzePoly } from "./poly_analyze";
|
|
7
|
+
import { getCurvePathData } from "./poly_to_pathdata";
|
|
8
|
+
import { renderPoint } from "./visualize";
|
|
5
9
|
|
|
6
|
-
export function pathDataToPolyPlus(pathDataArr = [], addExtremes=true) {
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
export function pathDataToPolySingle(pathData, addExtremes = true) {
|
|
12
|
+
|
|
10
13
|
|
|
11
14
|
let dimMin = Infinity;
|
|
12
15
|
let dimMax = 0;
|
|
@@ -16,77 +19,125 @@ export function pathDataToPolyPlus(pathDataArr = [], addExtremes=true) {
|
|
|
16
19
|
* add extremes to beziers
|
|
17
20
|
* to reproduce the shape better
|
|
18
21
|
*/
|
|
19
|
-
if(addExtremes){
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
//pathDataArr[i] = addExtremePoints(pathData)
|
|
23
|
-
let pathDataE = addExtremePoints(pathData)
|
|
24
|
-
|
|
25
|
-
//pathDataArr[i] = analyzePathData(pathDataE).pathData
|
|
26
|
-
pathDataArr[i] = addDimensionData(pathDataE)
|
|
27
|
-
|
|
28
|
-
})
|
|
22
|
+
if (addExtremes) {
|
|
23
|
+
pathData = addExtremePoints(pathData, 0.1, 0.9)
|
|
29
24
|
}
|
|
30
25
|
|
|
26
|
+
//console.log(pathData);
|
|
27
|
+
|
|
28
|
+
let pathDataPlus = analyzePathData(pathData);
|
|
29
|
+
let { bb } = pathDataPlus;
|
|
30
|
+
let thresh = (bb.width + bb.height) / 2 / 50
|
|
31
|
+
|
|
32
|
+
pathData = pathDataPlus.pathData
|
|
33
|
+
|
|
31
34
|
|
|
32
35
|
/**
|
|
33
36
|
* approximate min and max segment sizes
|
|
34
37
|
* for segment splitting
|
|
35
38
|
*/
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (dimMinL && dimMinL < dimMin) dimMin = dimMinL;
|
|
43
|
-
if (dimMaxL && dimMaxL > dimMax) dimMax = dimMaxL;
|
|
44
|
-
|
|
45
|
-
})
|
|
39
|
+
let dimArr = pathData.filter(com => com.dimA).sort((a, b) => a.dimA - b.DimA)
|
|
40
|
+
let dimMinL = dimArr[0].dimA
|
|
41
|
+
let dimMaxL = dimArr[dimArr.length - 1].dimA
|
|
42
|
+
//console.log('dimArr', dimArr, dimMaxL);
|
|
43
|
+
if (dimMinL && dimMinL < dimMin) dimMin = dimMinL;
|
|
44
|
+
if (dimMaxL && dimMaxL > dimMax) dimMax = dimMaxL;
|
|
46
45
|
|
|
47
46
|
//console.log(dimMin, dimMax);
|
|
48
47
|
|
|
49
48
|
// find split point based on smallest point distance
|
|
50
|
-
dimMin = (dimMin * 2 + dimMax) / 2
|
|
49
|
+
dimMin = (dimMin * 2 + dimMax) / 2 / 4
|
|
50
|
+
//dimMin = (bb.width + bb.height) / 2 / 8
|
|
51
51
|
|
|
52
52
|
// collect vertices
|
|
53
53
|
let polyArr = [];
|
|
54
54
|
|
|
55
|
+
let p0 = { x: pathData[0].p0.x, y: pathData[0].p0.y, extreme: pathData[0].extreme, corner: pathData[0].corner }
|
|
56
|
+
let poly = [p0];
|
|
57
|
+
|
|
58
|
+
for (let i = 1, l = pathData.length; i < l; i++) {
|
|
55
59
|
|
|
56
|
-
|
|
60
|
+
let com = pathData[i];
|
|
61
|
+
let { type, values, extreme = false, corner = false, dimA = null, p0, p, cp1 = null, cp2 = null } = com;
|
|
57
62
|
|
|
58
|
-
|
|
63
|
+
dimA = getDistAv(p0, p);
|
|
59
64
|
|
|
60
|
-
pathData.forEach(com => {
|
|
61
65
|
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
if(extreme){
|
|
67
|
+
//renderPoint(markers, p, 'cyan')
|
|
68
|
+
}
|
|
64
69
|
|
|
65
|
-
|
|
70
|
+
let split = (type === 'C' || type === 'Q') && dimA ? Math.ceil(dimA / dimMin) : 0;
|
|
66
71
|
|
|
67
|
-
let splitT = 1 / split;
|
|
68
72
|
|
|
69
|
-
|
|
73
|
+
//console.log(com);
|
|
74
|
+
p.extreme = extreme
|
|
75
|
+
p.corner = corner
|
|
70
76
|
|
|
71
|
-
|
|
72
|
-
let ptI = pointAtT([p0, cp1, cp2, p], t)
|
|
73
|
-
poly.push(ptI)
|
|
74
|
-
}
|
|
77
|
+
//console.log(p);
|
|
75
78
|
|
|
79
|
+
if ((type === 'C' || type === 'Q') && split) {
|
|
80
|
+
let splitT = 1 / split;
|
|
81
|
+
for (let i = 1; i < split; i++) {
|
|
82
|
+
let t = splitT * i;
|
|
83
|
+
let cpts = type === 'C' ? [cp1, cp2] : [cp1];
|
|
84
|
+
let ptI = pointAtT([p0, ...cpts, p], t)
|
|
85
|
+
poly.push(ptI)
|
|
76
86
|
}
|
|
77
|
-
|
|
78
|
-
|
|
87
|
+
}
|
|
88
|
+
poly.push(p)
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
// remove short
|
|
94
|
+
let remove = new Set([])
|
|
95
|
+
for (let i = 1, l = poly.length; i < l; i++) {
|
|
96
|
+
let p = poly[i - 1]
|
|
97
|
+
let pN = poly[i]
|
|
98
|
+
|
|
99
|
+
let dist1 = getDistAv(p, pN)
|
|
100
|
+
if (dist1 < thresh && pN.extreme) {
|
|
101
|
+
let pR = p.extreme ? pN : p
|
|
102
|
+
let idx = p.extreme ? i : i - 1
|
|
103
|
+
//console.log('remove', idx);
|
|
104
|
+
remove.add(idx)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
79
108
|
|
|
80
|
-
|
|
109
|
+
remove = Array.from(remove).reverse();
|
|
110
|
+
//console.log(remove);
|
|
81
111
|
|
|
82
|
-
|
|
83
|
-
let
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
112
|
+
for (let i = 0; i < remove.length; i++) {
|
|
113
|
+
let idx = remove[i];
|
|
114
|
+
//console.log('idx', idx);
|
|
115
|
+
poly.splice(idx, 1)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
poly.splice(poly.length-1, poly.length)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
let polyAtt = poly.map(pt => `${pt.x} ${pt.y} `).join(' ')
|
|
122
|
+
//console.log('polyAtt', polyAtt);
|
|
123
|
+
|
|
124
|
+
//markers.insertAdjacentHTML('beforeend', `<polygon points="${polyAtt}" stroke="red" fill="none"/>`)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
poly = analyzePoly(poly, false)
|
|
128
|
+
let pathDataP = getCurvePathData(poly, 0.666, true)
|
|
129
|
+
let d = pathDataToD(pathDataP)
|
|
87
130
|
|
|
88
|
-
|
|
131
|
+
console.log(d);
|
|
132
|
+
//markers.insertAdjacentHTML('beforeend', `<path d="${d}" stroke="green" fill="none" stroke-width="1%"/>`)
|
|
89
133
|
|
|
90
|
-
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
return poly
|
|
91
139
|
|
|
92
140
|
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
@@ -3,7 +3,7 @@ import { getPolygonArea } from "./geometry_area";
|
|
|
3
3
|
import { getPolyBBox } from "./geometry_bbox";
|
|
4
4
|
import { renderPoint } from "./visualize";
|
|
5
5
|
|
|
6
|
-
export function analyzePoly(pts) {
|
|
6
|
+
export function analyzePoly(pts, debug=false) {
|
|
7
7
|
|
|
8
8
|
let l = pts.length;
|
|
9
9
|
let polyArea = getPolygonArea(pts, true)
|
|
@@ -75,22 +75,26 @@ export function analyzePoly(pts) {
|
|
|
75
75
|
/*
|
|
76
76
|
*/
|
|
77
77
|
|
|
78
|
-
if
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
78
|
+
if(debug){
|
|
79
|
+
|
|
80
|
+
if ((isExtreme && isCorner)) {
|
|
81
|
+
isExtreme = false;
|
|
82
|
+
directionChange = false;
|
|
83
|
+
//isCorner = false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (isExtreme) {
|
|
87
|
+
renderPoint(markers, pt1, 'cyan', '1%');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (isCorner) {
|
|
91
|
+
renderPoint(markers, pt1, 'purple', '0.5%');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (directionChange) {
|
|
95
|
+
renderPoint(markers, pt1, 'orange', '1.5%', '0.5');
|
|
96
|
+
}
|
|
91
97
|
|
|
92
|
-
if (directionChange) {
|
|
93
|
-
renderPoint(markers, pt1, 'orange', '1.5%', '0.5');
|
|
94
98
|
}
|
|
95
99
|
|
|
96
100
|
|