svg-path-simplify 0.4.2 → 0.4.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.
- package/CHANGELOG.md +21 -0
- package/README.md +7 -4
- package/dist/svg-path-simplify.esm.js +3593 -1279
- package/dist/svg-path-simplify.esm.min.js +2 -2
- package/dist/svg-path-simplify.js +3594 -1278
- package/dist/svg-path-simplify.min.js +2 -2
- package/dist/svg-path-simplify.pathdata.esm.js +1017 -538
- package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
- package/dist/svg-path-simplify.poly.cjs +9 -8
- package/docs/privacy-webapp.md +24 -0
- package/index.html +331 -152
- package/package.json +1 -1
- package/src/constants.js +4 -0
- package/src/css_parse.js +317 -0
- package/src/detect_input.js +76 -28
- package/src/index.js +8 -0
- package/src/pathData_simplify_cubic.js +26 -16
- package/src/pathData_simplify_harmonize_cpts.js +77 -1
- package/src/pathData_simplify_revertToquadratics.js +0 -1
- package/src/pathSimplify-main.js +304 -276
- package/src/pathSimplify-only-pathdata.js +7 -2
- package/src/pathSimplify-presets.js +254 -0
- package/src/poly-fit-curve-schneider.js +14 -7
- package/src/simplify_poly_RC.js +102 -0
- package/src/simplify_poly_RDP.js +109 -1
- package/src/simplify_poly_radial_distance.js +3 -3
- package/src/string_helpers.js +130 -4
- package/src/svg-getAttributes.js +4 -2
- package/src/svgii/convert_units.js +1 -1
- package/src/svgii/geometry.js +322 -5
- package/src/svgii/geometry_bbox_element.js +1 -1
- package/src/svgii/geometry_deduceRadius.js +116 -27
- package/src/svgii/geometry_length.js +253 -0
- package/src/svgii/pathData_analyze.js +18 -0
- package/src/svgii/pathData_convert.js +193 -89
- package/src/svgii/pathData_fix_directions.js +12 -14
- package/src/svgii/pathData_fromPoly.js +3 -3
- package/src/svgii/pathData_getLength.js +86 -0
- package/src/svgii/pathData_parse.js +2 -0
- package/src/svgii/pathData_parse_els.js +66 -68
- package/src/svgii/pathData_reorder.js +122 -16
- package/src/svgii/pathData_simplify_refineCorners.js +130 -35
- package/src/svgii/pathData_simplify_refine_round.js +420 -0
- package/src/svgii/pathData_split_to_groups.js +168 -0
- package/src/svgii/pathData_stringify.js +26 -64
- package/src/svgii/pathData_toPolygon.js +3 -4
- package/src/svgii/poly_analyze.js +61 -0
- package/src/svgii/poly_normalize.js +11 -2
- package/src/svgii/poly_to_pathdata.js +85 -24
- package/src/svgii/rounding.js +80 -78
- package/src/svgii/svg_cleanup.js +421 -619
- package/src/svgii/svg_cleanup_convertPathLength.js +39 -0
- package/src/svgii/svg_cleanup_general_svg_atts.js +97 -0
- package/src/svgii/svg_cleanup_normalize_transforms.js +83 -0
- package/src/svgii/svg_cleanup_remove_els_and_atts.js +77 -0
- package/src/svgii/svg_cleanup_ungroup.js +36 -0
- package/src/svgii/svg_el_parse_style_props.js +72 -47
- package/src/svgii/svg_getElementLength.js +67 -0
- package/src/svgii/svg_validate.js +220 -0
- package/tests/testSVG.js +14 -1
- package/src/svgii/pathData_refine_round.js +0 -222
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { checkLineIntersection, getDistManhattan, getSquareDistance, interpolate, pointAtT } from "./geometry";
|
|
2
2
|
import { getPolygonArea } from "./geometry_area";
|
|
3
|
+
import { getArcFromPoly } from "./geometry_deduceRadius";
|
|
3
4
|
import { commandIsFlat } from "./geometry_flatness";
|
|
5
|
+
import { pathDataToD } from "./pathData_stringify";
|
|
6
|
+
import { roundTo } from "./rounding";
|
|
4
7
|
import { renderPoint } from "./visualize";
|
|
5
8
|
|
|
6
9
|
export function refineRoundedCorners(pathData, {
|
|
7
10
|
threshold = 0,
|
|
11
|
+
simplifyQuadraticCorners = false,
|
|
8
12
|
tolerance = 1
|
|
9
13
|
} = {}) {
|
|
10
14
|
|
|
@@ -30,6 +34,9 @@ export function refineRoundedCorners(pathData, {
|
|
|
30
34
|
let firstIsLine = pathData[1].type === 'L';
|
|
31
35
|
let firstIsBez = pathData[1].type === 'C';
|
|
32
36
|
|
|
37
|
+
// in case we have simplified a corner connecting to the start
|
|
38
|
+
let M_adj = null;
|
|
39
|
+
|
|
33
40
|
|
|
34
41
|
let normalizeClose = isClosed && firstIsBez && (lastIsLine || zIsLineto);
|
|
35
42
|
|
|
@@ -70,15 +77,18 @@ export function refineRoundedCorners(pathData, {
|
|
|
70
77
|
// closing corner to start
|
|
71
78
|
if (isClosed && lastIsBez && firstIsLine && i === l - lastOff - 1) {
|
|
72
79
|
comL1 = pathData[1]
|
|
80
|
+
//???
|
|
73
81
|
comBez = [pathData[l - lastOff]]
|
|
82
|
+
|
|
74
83
|
//renderPoint(markers, com.p)
|
|
75
84
|
}
|
|
76
85
|
|
|
86
|
+
// collect enclosed bezier segments
|
|
77
87
|
for (let j = i + 1; j < l; j++) {
|
|
78
88
|
let comN = pathData[j] ? pathData[j] : null;
|
|
79
89
|
let comPrev = pathData[j - 1];
|
|
80
90
|
|
|
81
|
-
if (comPrev.type === 'C') {
|
|
91
|
+
if (comPrev.type === 'C' && j > 2) {
|
|
82
92
|
comBez.push(comPrev)
|
|
83
93
|
}
|
|
84
94
|
|
|
@@ -89,9 +99,13 @@ export function refineRoundedCorners(pathData, {
|
|
|
89
99
|
offset++
|
|
90
100
|
}
|
|
91
101
|
|
|
102
|
+
|
|
103
|
+
//comBez = comBez.filter(com=> com.values.join(''))
|
|
104
|
+
|
|
92
105
|
if (comL1) {
|
|
93
106
|
//console.log('comL1', comL1);
|
|
94
107
|
|
|
108
|
+
|
|
95
109
|
// linetos
|
|
96
110
|
let len1 = getDistManhattan(comL0.p0, comL0.p)
|
|
97
111
|
let len2 = getDistManhattan(comL1.p0, comL1.p)
|
|
@@ -113,43 +127,77 @@ export function refineRoundedCorners(pathData, {
|
|
|
113
127
|
let isSmall = bezThresh < len1 && bezThresh < len2;
|
|
114
128
|
|
|
115
129
|
|
|
130
|
+
/*
|
|
131
|
+
*/
|
|
132
|
+
|
|
133
|
+
|
|
116
134
|
//len1 > len3 && len2 > len3
|
|
117
135
|
if (comBez.length && !signChange && isSmall) {
|
|
118
136
|
|
|
119
|
-
|
|
137
|
+
|
|
138
|
+
let isSquare = false;
|
|
139
|
+
|
|
140
|
+
if (comBez.length === 1) {
|
|
141
|
+
let dx = Math.abs(comBez[0].p.x - comBez[0].p0.x)
|
|
142
|
+
let dy = Math.abs(comBez[0].p.y - comBez[0].p0.y)
|
|
143
|
+
let diff = (dx - dy)
|
|
144
|
+
let rat = Math.abs(diff / dx)
|
|
145
|
+
isSquare = rat < 0.01;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
let preferArcs = true;
|
|
150
|
+
preferArcs = false;
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
// if rectangular prefer arcs
|
|
154
|
+
if (preferArcs && isSquare) {
|
|
155
|
+
|
|
156
|
+
let pM = pointAtT([comBez[0].p0, comBez[0].cp1, comBez[0].cp2, comBez[0].p], 0.5)
|
|
157
|
+
|
|
158
|
+
let arcProps = getArcFromPoly([comBez[0].p0, pM, comBez[0].p])
|
|
159
|
+
let { r, centroid, deltaAngle } = arcProps;
|
|
160
|
+
|
|
161
|
+
let sweep = deltaAngle > 0 ? 1 : 0;
|
|
162
|
+
//let largeArc = Math.abs(deltaAngle) > Math.PI ? 1 : 0;
|
|
163
|
+
let largeArc = 0;
|
|
164
|
+
|
|
165
|
+
let comArc = { type: 'A', values: [r, r, 0, largeArc, sweep, comBez[0].p.x, comBez[0].p.y] }
|
|
166
|
+
|
|
167
|
+
pathDataN.push(comL0, comArc);
|
|
168
|
+
i += offset
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
let areaThresh = getSquareDistance(comBez[0].p0, comBez[0].p) * 0.005
|
|
177
|
+
let isFlatBezier = Math.abs(area2) < areaThresh;
|
|
178
|
+
let isFlatBezier2 = Math.abs(area2) < areaThresh * 10
|
|
179
|
+
|
|
180
|
+
|
|
120
181
|
let ptQ = !isFlatBezier ? checkLineIntersection(comL0.p0, comL0.p, comL1.p, comL1.p0, false, true) : null
|
|
121
182
|
|
|
122
|
-
|
|
183
|
+
|
|
184
|
+
// exit: is rather flat or has no intersection
|
|
185
|
+
//|| (isFlatBezier && comBez.length === 1)
|
|
186
|
+
if (!ptQ || (isFlatBezier2 && comBez.length === 1)) {
|
|
123
187
|
pathDataN.push(com);
|
|
124
188
|
continue
|
|
125
189
|
}
|
|
126
190
|
|
|
127
|
-
// check sign change
|
|
191
|
+
// check sign change - exit if present
|
|
128
192
|
if (ptQ) {
|
|
129
193
|
let area0 = getPolygonArea([comL0.p0, comL0.p, comL1.p0, comL1.p], false);
|
|
130
194
|
let area0_abs = Math.abs(area0);
|
|
131
195
|
let area1 = getPolygonArea([comL0.p0, comL0.p, ptQ, comL1.p0, comL1.p], false);
|
|
132
196
|
let area1_abs = Math.abs(area1);
|
|
133
197
|
let areaDiff = Math.abs(area0_abs - area1_abs) / area0_abs
|
|
134
|
-
//console.log('areaDiff', areaDiff);
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
/*
|
|
138
|
-
renderPoint(markers, comL0.p0, 'green', '0.5%', '0.5')
|
|
139
|
-
renderPoint(markers, comL0.p, 'red', '1.5%', '0.5')
|
|
140
|
-
renderPoint(markers, comL1.p0, 'blue', '0.5%', '0.5')
|
|
141
|
-
renderPoint(markers, comL1.p, 'orange', '0.5%', '0.5')
|
|
142
|
-
if(!area0) {
|
|
143
|
-
pathDataN.push(com);
|
|
144
|
-
continue
|
|
145
|
-
}
|
|
146
|
-
*/
|
|
147
|
-
|
|
148
198
|
let signChange = area0 < 0 && area1 > 0 || area0 > 0 && area1 < 0;
|
|
149
199
|
|
|
150
200
|
if (!ptQ || signChange || areaDiff > 0.5) {
|
|
151
|
-
//console.log(signChange, area0, area1, 'pts', comL0.p0, comL0.p, comL1.p0, comL1.p);
|
|
152
|
-
//renderPoint(markers, ptQ, 'cyan', '0.5%', '0.5')
|
|
153
201
|
pathDataN.push(com);
|
|
154
202
|
continue
|
|
155
203
|
}
|
|
@@ -168,28 +216,71 @@ export function refineRoundedCorners(pathData, {
|
|
|
168
216
|
|
|
169
217
|
// not in tolerance – return original command
|
|
170
218
|
if (bezThresh && dist1 > bezThresh && dist1 > len3 * 0.3) {
|
|
171
|
-
//renderPoint(markers, ptM_bez, 'cyan', '0.5%', '0.5')
|
|
172
|
-
//renderPoint(markers, ptQ, 'magenta', '0.5%', '0.5')
|
|
173
219
|
pathDataN.push(com);
|
|
174
220
|
continue;
|
|
175
221
|
|
|
176
|
-
}
|
|
222
|
+
}
|
|
177
223
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
comQ.p0 = comL0.p;
|
|
181
|
-
comQ.cp1 = ptQ;
|
|
182
|
-
comQ.p = comL1.p0;
|
|
224
|
+
// return simplified quadratic Bézier command
|
|
225
|
+
let p_Q = comL1.p0;
|
|
183
226
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
227
|
+
// adjust previous end point to better fit the cubic curvature
|
|
228
|
+
let adjustQ = !simplifyQuadraticCorners;
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
if (adjustQ) {
|
|
232
|
+
//let t = 0.1333
|
|
233
|
+
let t = 0.1666
|
|
234
|
+
let p0_adj = interpolate(ptQ, comL0.p, (1 + t))
|
|
235
|
+
p_Q = interpolate(ptQ, comL1.p0, (1 + t))
|
|
236
|
+
|
|
237
|
+
// round for large enough segments
|
|
238
|
+
let isH = ptQ.y===comL0.p.y
|
|
239
|
+
let isV = ptQ.x===comL0.p.x
|
|
240
|
+
let isH2 = ptQ.y===comL1.p0.y
|
|
241
|
+
let isV2 = ptQ.x===comL1.p0.x
|
|
242
|
+
|
|
243
|
+
if(isSquare && com.dimA>3){
|
|
244
|
+
let dec = 0.5;
|
|
245
|
+
if(isH) p0_adj.x = roundTo(p0_adj.x, dec)
|
|
246
|
+
if(isV) p0_adj.y = roundTo(p0_adj.y, dec)
|
|
247
|
+
if(isH2) p_Q.x = roundTo(p_Q.x, dec)
|
|
248
|
+
if(isV2) p_Q.y = roundTo(p_Q.y, dec)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
/*
|
|
253
|
+
renderPoint(markers, p0_adj, 'orange')
|
|
254
|
+
renderPoint(markers, p_Q, 'orange')
|
|
255
|
+
renderPoint(markers, comL0.p, 'green')
|
|
256
|
+
renderPoint(markers, comL1.p0, 'magenta')
|
|
257
|
+
*/
|
|
258
|
+
|
|
259
|
+
// set new M starting point
|
|
260
|
+
if (i === l - lastOff - 1) {
|
|
261
|
+
//renderPoint(markers, p0_adj, 'red')
|
|
262
|
+
M_adj = p_Q
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// adjust previous lineto end point
|
|
266
|
+
comL0.values = [p0_adj.x, p0_adj.y]
|
|
267
|
+
comL0.p = p0_adj;
|
|
188
268
|
|
|
189
|
-
//offset++
|
|
190
|
-
continue;
|
|
191
269
|
}
|
|
192
270
|
|
|
271
|
+
let comQ = { type: 'Q', values: [ptQ.x, ptQ.y, p_Q.x, p_Q.y] }
|
|
272
|
+
comQ.cp1 = ptQ;
|
|
273
|
+
comQ.p0 = comL0.p;
|
|
274
|
+
comQ.p = p_Q;
|
|
275
|
+
|
|
276
|
+
// add quadratic command
|
|
277
|
+
pathDataN.push(comL0, comQ);
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
i += offset;
|
|
282
|
+
continue;
|
|
283
|
+
|
|
193
284
|
}
|
|
194
285
|
}
|
|
195
286
|
}
|
|
@@ -203,6 +294,11 @@ export function refineRoundedCorners(pathData, {
|
|
|
203
294
|
|
|
204
295
|
}
|
|
205
296
|
|
|
297
|
+
// correct starting point connecting with last corner rounding
|
|
298
|
+
if (M_adj) {
|
|
299
|
+
pathDataN[0].values = [M_adj.x, M_adj.y]
|
|
300
|
+
pathDataN[0].p0 = M_adj;
|
|
301
|
+
}
|
|
206
302
|
|
|
207
303
|
|
|
208
304
|
// revert close path normalization
|
|
@@ -210,7 +306,6 @@ export function refineRoundedCorners(pathData, {
|
|
|
210
306
|
pathDataN.push({ type: 'Z', values: [] })
|
|
211
307
|
}
|
|
212
308
|
|
|
213
|
-
|
|
214
309
|
//console.log(pathDataN);
|
|
215
310
|
|
|
216
311
|
return pathDataN;
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import { checkLineIntersection, getAngle, getDistAv, getDistManhattan, getDistance, getPointOnEllipse, getSquareDistance, pointAtT, rotatePoint } from "./geometry";
|
|
2
|
+
import { getPolygonArea } from "./geometry_area";
|
|
3
|
+
import { getArcFromPoly } from "./geometry_deduceRadius";
|
|
4
|
+
import { arcToBezierResolved, convertSmallArcsToLinetos, revertCubicQuadratic } from "./pathData_convert";
|
|
5
|
+
import { pathDataToD } from "./pathData_stringify";
|
|
6
|
+
import { renderPath, renderPoint, renderPoly } from "./visualize";
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export function simplifyAdjacentRound(pathData, {
|
|
10
|
+
threshold = 0,
|
|
11
|
+
tolerance = 1,
|
|
12
|
+
// take arcs or cubic beziers
|
|
13
|
+
toCubic = false,
|
|
14
|
+
debug = false
|
|
15
|
+
} = {}) {
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
// fix small Arcs
|
|
19
|
+
pathData = convertSmallArcsToLinetos(pathData);
|
|
20
|
+
//return pathData
|
|
21
|
+
|
|
22
|
+
// min size threshold for corners
|
|
23
|
+
threshold *= tolerance;
|
|
24
|
+
|
|
25
|
+
let l = pathData.length;
|
|
26
|
+
|
|
27
|
+
// add fist command
|
|
28
|
+
let pathDataN = [pathData[0]]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
// find adjacent cubics between extremes
|
|
32
|
+
//console.log(pathData);
|
|
33
|
+
|
|
34
|
+
for (let i = 1; i < l; i++) {
|
|
35
|
+
let comPrev = pathData[i - 1];
|
|
36
|
+
let com = pathData[i];
|
|
37
|
+
let comN = pathData[i + 1] || null;
|
|
38
|
+
|
|
39
|
+
if (!comN) {
|
|
40
|
+
pathDataN.push(com);
|
|
41
|
+
break
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let { type, extreme = false, p0, p, dimA = 0 } = com;
|
|
45
|
+
// for short segment detection
|
|
46
|
+
let dimAN = comN.dimA;
|
|
47
|
+
let dimA0 = dimA + dimAN;
|
|
48
|
+
let thresh = 0.1
|
|
49
|
+
let extreme0 = extreme
|
|
50
|
+
|
|
51
|
+
// ignore short linetos
|
|
52
|
+
let isShortN = dimAN < dimA0 * thresh;
|
|
53
|
+
//let isFlat =
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
// adjacent cubic commands - accept short in between linetos
|
|
57
|
+
if ((type === 'C') && (comN.type === 'C' || isShortN)) {
|
|
58
|
+
|
|
59
|
+
//console.log((comN.type !== 'C' && isShortN), comN);
|
|
60
|
+
let candidates = []
|
|
61
|
+
|
|
62
|
+
for (let j = i + 1; j < l; j++) {
|
|
63
|
+
let comN = pathData[j];
|
|
64
|
+
let { type, extreme = false, corner = false, dimA = 0 } = comN;
|
|
65
|
+
let isShort = dimA < dimA0 * thresh;
|
|
66
|
+
|
|
67
|
+
// skip for type change(unless very short), extremes or corners
|
|
68
|
+
/*
|
|
69
|
+
if ( (comN.extreme || comN.corner) ) {
|
|
70
|
+
if(!extreme && !corner) candidates.push(comN)
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
//|| (type !== 'C' && !isShort && !corner && !extreme)
|
|
76
|
+
if (extreme || corner) {
|
|
77
|
+
|
|
78
|
+
/*
|
|
79
|
+
if (comN.extreme) {
|
|
80
|
+
renderPoint(markers, comN.p, 'cyan')
|
|
81
|
+
}
|
|
82
|
+
else if (comN.corner) {
|
|
83
|
+
renderPoint(markers, comN.p, 'magenta')
|
|
84
|
+
}
|
|
85
|
+
else if (type !== 'C') {
|
|
86
|
+
console.log(type);
|
|
87
|
+
renderPoint(markers, comN.p, 'orange')
|
|
88
|
+
}
|
|
89
|
+
*/
|
|
90
|
+
|
|
91
|
+
if ((extreme || corner) && type === 'C') {
|
|
92
|
+
//renderPoint(markers, com.p, 'purple')
|
|
93
|
+
//break
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
//&& comN.type !== 'C'
|
|
97
|
+
if (isShort && comN.type !== 'C') {
|
|
98
|
+
//renderPoint(markers, comN.p, 'purple')
|
|
99
|
+
//candidates.push(comN)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
if ((extreme && !corner)) {
|
|
104
|
+
//console.log(comN);
|
|
105
|
+
//if(extreme) renderPoint(markers, comN.p0, 'purple')
|
|
106
|
+
//candidates.push(comN)
|
|
107
|
+
candidates.push(comN)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
candidates.push(comN)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// try to create arc command
|
|
118
|
+
if (candidates.length > 1) {
|
|
119
|
+
|
|
120
|
+
let clen = candidates.length;
|
|
121
|
+
let pts = [com.p0, com.p,];
|
|
122
|
+
|
|
123
|
+
// add interpolated points to prevent wrong arc replacements
|
|
124
|
+
candidates.forEach(c => {
|
|
125
|
+
if (c.type === 'C') {
|
|
126
|
+
let pt = pointAtT([c.p0, c.cp1, c.cp2, c.p], 0.5)
|
|
127
|
+
pts.push(pt)
|
|
128
|
+
}
|
|
129
|
+
pts.push(c.p)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
//let pts = [com.p0, com.p, ...candidates.map(com => com.p)];
|
|
133
|
+
//console.log('pts', pts);
|
|
134
|
+
|
|
135
|
+
let precise = true
|
|
136
|
+
let arcProps = getArcFromPoly(pts, precise)
|
|
137
|
+
|
|
138
|
+
// could be combined
|
|
139
|
+
if (arcProps) {
|
|
140
|
+
//console.log(arcProps, pts);
|
|
141
|
+
|
|
142
|
+
let { centroid, r, deltaAngle, startAngle, endAngle } = arcProps;
|
|
143
|
+
let sweep = deltaAngle > 0 ? 1 : 0;
|
|
144
|
+
//let area = getPolygonArea(pts)
|
|
145
|
+
//let sweep = area > 0 ? 1 : 0;
|
|
146
|
+
let largeArc = Math.abs(deltaAngle) > Math.PI ? 1 : 0;
|
|
147
|
+
largeArc = 0;
|
|
148
|
+
let comLast = candidates[clen - 1]
|
|
149
|
+
let p = comLast.p
|
|
150
|
+
|
|
151
|
+
let comArc = { type: 'A', values: [r, r, 0, largeArc, sweep, p.x, p.y] }
|
|
152
|
+
|
|
153
|
+
//console.log(comArc);
|
|
154
|
+
|
|
155
|
+
comArc.dimA = getDistManhattan(p0, p)
|
|
156
|
+
comArc.p0 = p0
|
|
157
|
+
comArc.p = p
|
|
158
|
+
comArc.error = 0
|
|
159
|
+
comArc.directionChange = comLast.directionChange
|
|
160
|
+
comArc.extreme = comLast.extreme
|
|
161
|
+
comArc.corner = comLast.corner
|
|
162
|
+
pathDataN.push(comArc)
|
|
163
|
+
|
|
164
|
+
i += candidates.length
|
|
165
|
+
continue
|
|
166
|
+
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// arc radius calculation failed - return original
|
|
170
|
+
else {
|
|
171
|
+
pathDataN.push(com)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// could not be simplified – return original command
|
|
176
|
+
else {
|
|
177
|
+
pathDataN.push(com)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
}
|
|
181
|
+
// all other commands
|
|
182
|
+
else {
|
|
183
|
+
pathDataN.push(com)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
//console.log(pathDataN);
|
|
188
|
+
return pathDataN
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
export function refineRoundSegments(pathData, {
|
|
193
|
+
threshold = 0,
|
|
194
|
+
tolerance = 1,
|
|
195
|
+
// take arcs or cubic beziers
|
|
196
|
+
toCubic = false,
|
|
197
|
+
debug = false
|
|
198
|
+
} = {}) {
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
// min size threshold for corners
|
|
202
|
+
threshold *= tolerance;
|
|
203
|
+
|
|
204
|
+
let l = pathData.length;
|
|
205
|
+
|
|
206
|
+
// add fist command
|
|
207
|
+
let pathDataN = [pathData[0]]
|
|
208
|
+
|
|
209
|
+
// just for debugging
|
|
210
|
+
let pathDataTest = []
|
|
211
|
+
|
|
212
|
+
for (let i = 1; i < l; i++) {
|
|
213
|
+
let com = pathData[i];
|
|
214
|
+
let { type } = com;
|
|
215
|
+
let comP = pathData[i - 1];
|
|
216
|
+
let comN = pathData[i + 1] ? pathData[i + 1] : null;
|
|
217
|
+
let comN2 = pathData[i + 2] ? pathData[i + 2] : null;
|
|
218
|
+
let comN3 = pathData[i + 3] ? pathData[i + 3] : null;
|
|
219
|
+
let comBez = null;
|
|
220
|
+
|
|
221
|
+
if ((com.type === 'C' || com.type === 'Q')) comBez = com;
|
|
222
|
+
else if (comN && (comN.type === 'C' || comN.type === 'Q')) comBez = comN;
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
let cpts = comBez ? (comBez.type === 'C' ? [comBez.p0, comBez.cp1, comBez.cp2, comBez.p] : [comBez.p0, comBez.cp1, comBez.p]) : []
|
|
226
|
+
|
|
227
|
+
let areaBez = 0;
|
|
228
|
+
let areaLines = 0;
|
|
229
|
+
let signChange = false;
|
|
230
|
+
let L1, L2;
|
|
231
|
+
let combine = false
|
|
232
|
+
|
|
233
|
+
let p0_S, p_S;
|
|
234
|
+
let poly = []
|
|
235
|
+
let pMid;
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
// 2. line-line-bezier-line-line
|
|
239
|
+
if (
|
|
240
|
+
comN2 && comN3 &&
|
|
241
|
+
comP.type === 'L' &&
|
|
242
|
+
type === 'L' &&
|
|
243
|
+
comBez &&
|
|
244
|
+
comN2.type === 'L' &&
|
|
245
|
+
(comN3.type === 'L' || comN3.type === 'Z')
|
|
246
|
+
) {
|
|
247
|
+
|
|
248
|
+
L1 = [com.p0, com.p];
|
|
249
|
+
L2 = [comN2.p0, comN2.p];
|
|
250
|
+
p0_S = com.p0
|
|
251
|
+
p_S = comN2.p
|
|
252
|
+
|
|
253
|
+
// don't allow sign changes
|
|
254
|
+
areaBez = getPolygonArea(cpts, false)
|
|
255
|
+
areaLines = getPolygonArea([...L1, ...L2], false)
|
|
256
|
+
signChange = (areaBez < 0 && areaLines > 0) || (areaBez > 0 && areaLines < 0)
|
|
257
|
+
|
|
258
|
+
if (!signChange) {
|
|
259
|
+
|
|
260
|
+
// mid point of mid bezier
|
|
261
|
+
pMid = pointAtT(cpts, 0.5)
|
|
262
|
+
|
|
263
|
+
// add to poly
|
|
264
|
+
poly = [p0_S, pMid, p_S]
|
|
265
|
+
|
|
266
|
+
combine = true
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 1. line-bezier-bezier-line
|
|
272
|
+
else if (comN && (type === 'C' || type === 'Q') && comP.type === 'L') {
|
|
273
|
+
|
|
274
|
+
//renderPoint(markers, com.p)
|
|
275
|
+
|
|
276
|
+
// 1.2 next is cubic next is lineto
|
|
277
|
+
if (comN2 && comN2.type === 'L' && (comN.type === 'C' || comN.type === 'Q')) {
|
|
278
|
+
|
|
279
|
+
combine = true
|
|
280
|
+
|
|
281
|
+
L1 = [comP.p0, comP.p];
|
|
282
|
+
L2 = [comN2.p0, comN2.p];
|
|
283
|
+
p0_S = comP.p
|
|
284
|
+
p_S = comN2.p0
|
|
285
|
+
|
|
286
|
+
// mid point of mid bezier
|
|
287
|
+
pMid = comBez.p
|
|
288
|
+
|
|
289
|
+
// add to poly
|
|
290
|
+
poly = [p0_S, comBez.p, p_S]
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* calculate either combined
|
|
299
|
+
* cubic or arc commands
|
|
300
|
+
*/
|
|
301
|
+
if (combine) {
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
// try to find center of arc
|
|
305
|
+
let arcProps = getArcFromPoly(poly)
|
|
306
|
+
if (arcProps) {
|
|
307
|
+
|
|
308
|
+
let { centroid, r, deltaAngle, startAngle, endAngle } = arcProps;
|
|
309
|
+
|
|
310
|
+
let xAxisRotation = 0;
|
|
311
|
+
let sweep = deltaAngle > 0 ? 1 : 0;
|
|
312
|
+
let largeArc = Math.abs(deltaAngle) > Math.PI ? 1 : 0;
|
|
313
|
+
|
|
314
|
+
let pCM = rotatePoint(p0_S, centroid.x, centroid.y, deltaAngle * 0.5)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
let dist2 = getDistAv(pCM, pMid)
|
|
318
|
+
let thresh = getDistAv(p0_S, p_S) * 0.05
|
|
319
|
+
let bezierCommands;
|
|
320
|
+
|
|
321
|
+
// point is close enough
|
|
322
|
+
if (dist2 < thresh) {
|
|
323
|
+
|
|
324
|
+
//toCubic = false;
|
|
325
|
+
|
|
326
|
+
bezierCommands = arcToBezierResolved(
|
|
327
|
+
{
|
|
328
|
+
p0: p0_S,
|
|
329
|
+
p: p_S,
|
|
330
|
+
centroid,
|
|
331
|
+
rx: r,
|
|
332
|
+
ry: r,
|
|
333
|
+
xAxisRotation,
|
|
334
|
+
sweep,
|
|
335
|
+
largeArc,
|
|
336
|
+
deltaAngle,
|
|
337
|
+
startAngle,
|
|
338
|
+
endAngle
|
|
339
|
+
}
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
if (bezierCommands.length === 1) {
|
|
343
|
+
|
|
344
|
+
// prefer more compact quadratic - otherwise arcs
|
|
345
|
+
let comBezier = revertCubicQuadratic(p0_S, bezierCommands[0].cp1, bezierCommands[0].cp2, p_S)
|
|
346
|
+
|
|
347
|
+
if (comBezier.type === 'Q') {
|
|
348
|
+
toCubic = true
|
|
349
|
+
}else{
|
|
350
|
+
comBezier = bezierCommands[0]
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
com = comBezier
|
|
354
|
+
//console.log('bezierCommands', comBezier);
|
|
355
|
+
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
// prefer arcs if 2 cubics are required
|
|
360
|
+
if (bezierCommands.length > 1) toCubic = false;
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
//toCubic = false
|
|
364
|
+
|
|
365
|
+
// return elliptic arc commands
|
|
366
|
+
if (!toCubic) {
|
|
367
|
+
// rewrite simplified command
|
|
368
|
+
com.type = 'A'
|
|
369
|
+
com.values = [r, r, xAxisRotation, largeArc, sweep, p_S.x, p_S.y];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
//console.log(com);
|
|
373
|
+
|
|
374
|
+
com.p0 = p0_S;
|
|
375
|
+
com.p = p_S;
|
|
376
|
+
com.extreme = false;
|
|
377
|
+
com.corner = false;
|
|
378
|
+
|
|
379
|
+
// test rendering
|
|
380
|
+
//debug=true
|
|
381
|
+
|
|
382
|
+
/*
|
|
383
|
+
if (debug) {
|
|
384
|
+
// arcs
|
|
385
|
+
if (!toCubic) {
|
|
386
|
+
pathDataTest = [
|
|
387
|
+
{ type: 'M', values: [p0_S.x, p0_S.y] },
|
|
388
|
+
{ type: 'A', values: [r, r, xAxisRotation, largeArc, sweep, p_S.x, p_S.y] },
|
|
389
|
+
]
|
|
390
|
+
}
|
|
391
|
+
// cubics
|
|
392
|
+
else {
|
|
393
|
+
pathDataTest = [
|
|
394
|
+
{ type: 'M', values: [p0_S.x, p0_S.y] },
|
|
395
|
+
...bezierCommands
|
|
396
|
+
]
|
|
397
|
+
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
let d = pathDataToD(pathDataTest);
|
|
401
|
+
renderPath(markers, d, 'orange', '0.5%', '0.5')
|
|
402
|
+
}
|
|
403
|
+
*/
|
|
404
|
+
|
|
405
|
+
pathDataN.push(com);
|
|
406
|
+
i++
|
|
407
|
+
continue
|
|
408
|
+
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// pass through
|
|
414
|
+
pathDataN.push(com)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
//let d= pathDataToD(pathDataN)
|
|
418
|
+
//console.log('!pathDataN', d);
|
|
419
|
+
return pathDataN;
|
|
420
|
+
}
|