svg-path-simplify 0.0.2 → 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.
- package/LICENSE +339 -0
- package/README.md +33 -1
- package/dist/svg-path-simplify.esm.js +455 -187
- package/dist/svg-path-simplify.esm.min.js +1 -1
- package/dist/svg-path-simplify.js +455 -186
- package/dist/svg-path-simplify.min.js +1 -1
- package/dist/svg-path-simplify.node.js +455 -186
- package/dist/svg-path-simplify.node.min.js +1 -1
- package/index.html +17 -9
- package/package.json +4 -5
- package/src/detect_input.js +42 -0
- package/src/index.js +3 -0
- package/src/pathSimplify-main.js +195 -89
- package/src/svg_getViewbox.js +32 -0
- package/src/svgii/geometry.js +51 -4
- package/src/svgii/pathData_remove_collinear.js +31 -18
- package/src/svgii/pathData_reorder.js +43 -9
- package/src/svgii/pathData_stringify.js +11 -12
- package/src/svgii/rounding.js +14 -6
- package/src/svgii/svg_cleanup.js +7 -1
package/src/pathSimplify-main.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { detectInputType } from './detect_input';
|
|
1
2
|
import { combineCubicPairs } from './pathData_simplify_cubic';
|
|
2
3
|
import { getPathDataVertices, pointAtT } from './svgii/geometry';
|
|
3
4
|
import { getPolyBBox } from './svgii/geometry_bbox';
|
|
@@ -14,9 +15,10 @@ import { pathDataToPolyPlus } from './svgii/pathData_toPolygon';
|
|
|
14
15
|
import { analyzePoly } from './svgii/poly_analyze';
|
|
15
16
|
import { getCurvePathData } from './svgii/poly_to_pathdata';
|
|
16
17
|
import { detectAccuracy } from './svgii/rounding';
|
|
18
|
+
import { cleanUpSVG } from './svgii/svg_cleanup';
|
|
17
19
|
import { renderPoint } from './svgii/visualize';
|
|
18
20
|
|
|
19
|
-
export function svgPathSimplify(
|
|
21
|
+
export function svgPathSimplify(input = '', {
|
|
20
22
|
toAbsolute = true,
|
|
21
23
|
toRelative = true,
|
|
22
24
|
toShorthands = true,
|
|
@@ -45,147 +47,251 @@ export function svgPathSimplify(d = '', {
|
|
|
45
47
|
revertToQuadratics = true,
|
|
46
48
|
minifyD = 0,
|
|
47
49
|
tolerance = 1,
|
|
48
|
-
reverse = false
|
|
50
|
+
reverse = false,
|
|
51
|
+
|
|
52
|
+
// svg cleanup options
|
|
53
|
+
removeHidden = true,
|
|
54
|
+
removeUnused = true,
|
|
55
|
+
|
|
56
|
+
// return svg markup or object
|
|
57
|
+
getObject = false
|
|
58
|
+
|
|
49
59
|
} = {}) {
|
|
50
60
|
|
|
61
|
+
// clamp tolerance
|
|
62
|
+
tolerance = Math.max(0.1, tolerance);
|
|
63
|
+
|
|
64
|
+
let inputType = detectInputType(input);
|
|
51
65
|
|
|
52
|
-
let
|
|
66
|
+
let svg = '';
|
|
67
|
+
let svgSize = 0;
|
|
68
|
+
let svgSizeOpt = 0;
|
|
69
|
+
let compression = 0;
|
|
70
|
+
let report = {};
|
|
71
|
+
let d = '';
|
|
72
|
+
let mode = inputType === 'svgMarkup' ? 1 : 0;
|
|
53
73
|
|
|
54
|
-
|
|
55
|
-
let pathData = JSON.parse(JSON.stringify(pathDataO));
|
|
74
|
+
let paths = []
|
|
56
75
|
|
|
57
|
-
// count commands for evaluation
|
|
58
|
-
let comCount = pathDataO.length
|
|
59
76
|
|
|
60
77
|
/**
|
|
61
|
-
*
|
|
78
|
+
* normalize input
|
|
79
|
+
* switch mode
|
|
62
80
|
*/
|
|
63
|
-
let subPathArr = splitSubpaths(pathData);
|
|
64
81
|
|
|
65
|
-
//
|
|
66
|
-
|
|
82
|
+
// original size
|
|
83
|
+
svgSize = new Blob([input]).size;
|
|
67
84
|
|
|
68
|
-
|
|
85
|
+
// single path
|
|
86
|
+
if (!mode) {
|
|
87
|
+
if (inputType === 'pathDataString') {
|
|
88
|
+
d = input
|
|
89
|
+
} else if (inputType === 'polyString') {
|
|
90
|
+
d = 'M' + input
|
|
91
|
+
}
|
|
92
|
+
paths.push({ d, el: null })
|
|
93
|
+
}
|
|
94
|
+
// process svg
|
|
95
|
+
else {
|
|
96
|
+
//sanitize
|
|
97
|
+
let returnDom = true
|
|
98
|
+
svg = cleanUpSVG(input, { returnDom, removeHidden, removeUnused }
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// collect paths
|
|
102
|
+
let pathEls = svg.querySelectorAll('path')
|
|
103
|
+
pathEls.forEach(path => {
|
|
104
|
+
paths.push({ d: path.getAttribute('d'), el: path })
|
|
105
|
+
})
|
|
106
|
+
}
|
|
69
107
|
|
|
70
|
-
|
|
71
|
-
|
|
108
|
+
//console.log(paths);
|
|
109
|
+
//console.log('inputType', inputType, 'mode', mode);
|
|
72
110
|
|
|
73
|
-
|
|
74
|
-
|
|
111
|
+
/**
|
|
112
|
+
* process all paths
|
|
113
|
+
*/
|
|
114
|
+
paths.forEach(path => {
|
|
115
|
+
let { d, el } = path;
|
|
75
116
|
|
|
76
|
-
|
|
77
|
-
|
|
117
|
+
let pathDataO = parsePathDataNormalized(d, { quadraticToCubic, toAbsolute, arcToCubic });
|
|
118
|
+
//console.log(pathDataO);
|
|
78
119
|
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
let tMin = 0, tMax = 1;
|
|
82
|
-
if (addExtremes) pathDataSub = addExtremePoints(pathDataSub, tMin, tMax)
|
|
120
|
+
// create clone for fallback
|
|
121
|
+
let pathData = JSON.parse(JSON.stringify(pathDataO));
|
|
83
122
|
|
|
123
|
+
// count commands for evaluation
|
|
124
|
+
let comCount = pathDataO.length
|
|
84
125
|
|
|
85
|
-
|
|
86
|
-
|
|
126
|
+
/**
|
|
127
|
+
* get sub paths
|
|
128
|
+
*/
|
|
129
|
+
let subPathArr = splitSubpaths(pathData);
|
|
87
130
|
|
|
88
|
-
//
|
|
89
|
-
|
|
131
|
+
// cleaned up pathData
|
|
132
|
+
let pathDataArrN = [];
|
|
90
133
|
|
|
91
|
-
|
|
92
|
-
let pathDataPlus = analyzePathData(pathDataSub);
|
|
134
|
+
for (let i = 0, l = subPathArr.length; i < l; i++) {
|
|
93
135
|
|
|
136
|
+
//let { pathData, bb } = subPathArr[i];
|
|
137
|
+
let pathDataSub = subPathArr[i];
|
|
94
138
|
|
|
95
|
-
|
|
96
|
-
|
|
139
|
+
// try simplification in reversed order
|
|
140
|
+
if (reverse) pathDataSub = reversePathData(pathDataSub);
|
|
97
141
|
|
|
142
|
+
// remove zero length linetos
|
|
143
|
+
if (removeColinear) pathDataSub = removeZeroLengthLinetos(pathDataSub)
|
|
98
144
|
|
|
145
|
+
// add extremes
|
|
146
|
+
//let tMin=0.2, tMax=0.8;
|
|
147
|
+
let tMin = 0, tMax = 1;
|
|
148
|
+
if (addExtremes) pathDataSub = addExtremePoints(pathDataSub, tMin, tMax)
|
|
99
149
|
|
|
100
150
|
|
|
151
|
+
// sort to top left
|
|
152
|
+
if (optimizeOrder) pathDataSub = pathDataToTopLeft(pathDataSub);
|
|
101
153
|
|
|
102
|
-
|
|
154
|
+
// remove colinear/flat
|
|
155
|
+
if (removeColinear) pathDataSub = pathDataRemoveColinear(pathDataSub, tolerance, flatBezierToLinetos);
|
|
103
156
|
|
|
157
|
+
// analyze pathdata to add info about signicant properties such as extremes, corners
|
|
158
|
+
let pathDataPlus = analyzePathData(pathDataSub);
|
|
104
159
|
|
|
105
160
|
|
|
106
|
-
|
|
107
|
-
|
|
161
|
+
// simplify beziers
|
|
162
|
+
let { pathData, bb, dimA } = pathDataPlus;
|
|
108
163
|
|
|
164
|
+
//let pathDataN = pathData;
|
|
109
165
|
|
|
166
|
+
//console.log(pathDataPlus);
|
|
110
167
|
|
|
111
|
-
|
|
112
|
-
if(cubicToArc){
|
|
168
|
+
pathData = simplifyBezier ? simplifyPathData(pathData, { simplifyBezier, keepInflections, keepExtremes, keepCorners, extrapolateDominant, revertToQuadratics, tolerance, reverse }) : pathData;
|
|
113
169
|
|
|
114
|
-
let thresh = 3;
|
|
115
170
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
let comA = cubicCommandToArc(p0, cp1, cp2, p, thresh)
|
|
121
|
-
if(comA.isArc) pathDataN[c] = comA.com;
|
|
122
|
-
//if (comQ.type === 'Q') pathDataN[c] = comQ
|
|
123
|
-
}
|
|
124
|
-
})
|
|
171
|
+
// cubic to arcs
|
|
172
|
+
if (cubicToArc) {
|
|
173
|
+
|
|
174
|
+
let thresh = 3;
|
|
125
175
|
|
|
126
|
-
|
|
127
|
-
|
|
176
|
+
pathData.forEach((com, c) => {
|
|
177
|
+
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
178
|
+
if (type === 'C') {
|
|
179
|
+
//console.log(com);
|
|
180
|
+
let comA = cubicCommandToArc(p0, cp1, cp2, p, thresh)
|
|
181
|
+
if (comA.isArc) pathData[c] = comA.com;
|
|
182
|
+
//if (comQ.type === 'Q') pathDataN[c] = comQ
|
|
183
|
+
}
|
|
184
|
+
})
|
|
128
185
|
|
|
186
|
+
// combine adjacent cubics
|
|
187
|
+
pathData = combineArcs(pathData)
|
|
188
|
+
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
// simplify to quadratics
|
|
193
|
+
if (revertToQuadratics) {
|
|
194
|
+
pathData.forEach((com, c) => {
|
|
195
|
+
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
196
|
+
if (type === 'C') {
|
|
197
|
+
//console.log(com);
|
|
198
|
+
let comQ = revertCubicQuadratic(p0, cp1, cp2, p)
|
|
199
|
+
if (comQ.type === 'Q') pathData[c] = comQ
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
// update
|
|
206
|
+
pathDataArrN.push(pathData)
|
|
129
207
|
}
|
|
130
208
|
|
|
131
209
|
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
})
|
|
210
|
+
// flatten compound paths
|
|
211
|
+
pathData = pathDataArrN.flat();
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* detect accuracy
|
|
215
|
+
*/
|
|
216
|
+
if (autoAccuracy) {
|
|
217
|
+
decimals = detectAccuracy(pathData)
|
|
142
218
|
}
|
|
143
219
|
|
|
144
220
|
|
|
221
|
+
// optimize
|
|
222
|
+
let pathOptions = {
|
|
223
|
+
toRelative,
|
|
224
|
+
toShorthands,
|
|
225
|
+
decimals,
|
|
226
|
+
}
|
|
145
227
|
|
|
146
228
|
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
}
|
|
229
|
+
// optimize path data
|
|
230
|
+
pathData = convertPathData(pathData, pathOptions)
|
|
150
231
|
|
|
151
232
|
|
|
152
|
-
|
|
153
|
-
|
|
233
|
+
// remove zero-length segments introduced by rounding
|
|
234
|
+
let pathDataOpt = []
|
|
154
235
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
236
|
+
pathData.forEach((com, i) => {
|
|
237
|
+
let { type, values } = com;
|
|
238
|
+
if (type === 'l' || type === 'v' || type === 'h') {
|
|
239
|
+
let hasLength = type === 'l' ? (values.join('') !== '00') : values[0] !== 0
|
|
240
|
+
if (hasLength) pathDataOpt.push(com)
|
|
241
|
+
} else {
|
|
242
|
+
pathDataOpt.push(com)
|
|
243
|
+
}
|
|
244
|
+
})
|
|
161
245
|
|
|
246
|
+
pathData = pathDataOpt;
|
|
162
247
|
|
|
163
248
|
|
|
164
|
-
|
|
165
|
-
|
|
249
|
+
// compare command count
|
|
250
|
+
let comCountS = pathData.length
|
|
166
251
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
252
|
+
let dOpt = pathDataToD(pathData, minifyD)
|
|
253
|
+
svgSizeOpt = new Blob([dOpt]).size;
|
|
254
|
+
//compression = +(100/svgSize * (svgSize - svgSizeOpt)).toFixed(2)
|
|
255
|
+
compression = +(100 / svgSize * (svgSizeOpt)).toFixed(2)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
path.d = dOpt
|
|
259
|
+
path.report = {
|
|
260
|
+
original: comCount,
|
|
261
|
+
new: comCountS,
|
|
262
|
+
saved: comCount - comCountS,
|
|
263
|
+
compression,
|
|
264
|
+
decimals,
|
|
265
|
+
//success: comCountS < comCount
|
|
266
|
+
}
|
|
173
267
|
|
|
268
|
+
// apply new path for svgs
|
|
269
|
+
if (el) el.setAttribute('d', dOpt)
|
|
174
270
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// stringify new SVG
|
|
274
|
+
if (mode) {
|
|
275
|
+
svg = new XMLSerializer().serializeToString(svg);
|
|
276
|
+
svgSizeOpt = new Blob([svg]).size
|
|
277
|
+
//compression = +(100/svgSize * (svgSize-svgSizeOpt)).toFixed(2)
|
|
278
|
+
compression = +(100 / svgSize * (svgSizeOpt)).toFixed(2)
|
|
279
|
+
|
|
280
|
+
svgSize = +(svgSize / 1024).toFixed(3)
|
|
281
|
+
svgSizeOpt = +(svgSizeOpt / 1024).toFixed(3)
|
|
282
|
+
|
|
283
|
+
report = {
|
|
284
|
+
svgSize,
|
|
285
|
+
svgSizeOpt,
|
|
286
|
+
compression
|
|
287
|
+
}
|
|
178
288
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
new: comCountS,
|
|
182
|
-
saved: comCount - comCountS,
|
|
183
|
-
decimals,
|
|
184
|
-
success: comCountS < comCount
|
|
289
|
+
} else {
|
|
290
|
+
({ d, report } = paths[0]);
|
|
185
291
|
}
|
|
186
292
|
|
|
187
|
-
return { pathData, d: dOpt, report };
|
|
188
293
|
|
|
294
|
+
return !getObject ? (d ? d : svg) : { svg, d, report, inputType, mode };
|
|
189
295
|
|
|
190
296
|
}
|
|
191
297
|
|
|
@@ -221,9 +327,9 @@ function simplifyPathData(pathData, {
|
|
|
221
327
|
|
|
222
328
|
// cannot be combined as crossing extremes or corners
|
|
223
329
|
if (
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
330
|
+
(keepInflections && isDirChangeN) ||
|
|
331
|
+
(keepCorners && corner) ||
|
|
332
|
+
(!isDirChange && keepExtremes && extreme)
|
|
227
333
|
) {
|
|
228
334
|
//renderPoint(markers, p, 'red', '1%')
|
|
229
335
|
pathDataN.push(com)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* get viewBox
|
|
3
|
+
* either from explicit attribute or
|
|
4
|
+
* width and height attributes
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export function getViewBox(svg = null, round = false) {
|
|
8
|
+
|
|
9
|
+
// browser default
|
|
10
|
+
if (!svg) return { x: 0, y: 0, width: 300, height: 150 }
|
|
11
|
+
|
|
12
|
+
let style = window.getComputedStyle(svg);
|
|
13
|
+
|
|
14
|
+
// the baseVal API method also converts physical units to pixels/user-units
|
|
15
|
+
let w = svg.hasAttribute('width') ? svg.width.baseVal.value : parseFloat(style.width) || 300;
|
|
16
|
+
let h = svg.hasAttribute('height') ? svg.height.baseVal.value : parseFloat(style.height) || 150;
|
|
17
|
+
|
|
18
|
+
let viewBox = svg.getAttribute('viewBox') ? svg.viewBox.baseVal : { x: 0, y: 0, width: w, height: h };
|
|
19
|
+
|
|
20
|
+
// remove SVG constructor
|
|
21
|
+
let { x, y, width, height } = viewBox;
|
|
22
|
+
viewBox = { x, y, width, height };
|
|
23
|
+
|
|
24
|
+
// round to integers
|
|
25
|
+
if (round) {
|
|
26
|
+
for (let prop in viewBox) {
|
|
27
|
+
viewBox[prop] = Math.ceil(viewBox[prop]);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return viewBox
|
|
32
|
+
}
|
package/src/svgii/geometry.js
CHANGED
|
@@ -52,7 +52,7 @@ export function checkLineIntersection(p1, p2, p3, p4, exact = true) {
|
|
|
52
52
|
y: p1.y + (a * (p2.y - p1.y))
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
// console.log('intersectionPoint', intersectionPoint, p1, p2);
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
|
|
@@ -985,16 +985,63 @@ export function commandIsFlat(points, tolerance = 0.025) {
|
|
|
985
985
|
}
|
|
986
986
|
|
|
987
987
|
|
|
988
|
+
export function checkBezierFlatness(p0, cpts, p) {
|
|
988
989
|
|
|
990
|
+
let isFlat = false;
|
|
991
|
+
|
|
992
|
+
let isCubic = cpts.length===2;
|
|
993
|
+
|
|
994
|
+
let cp1 = cpts[0]
|
|
995
|
+
let cp2 = isCubic ? cpts[1] : cp1;
|
|
996
|
+
|
|
997
|
+
if(p0.x===cp1.x && p0.y===cp1.y && p.x===cp2.x && p.y===cp2.y) return true;
|
|
998
|
+
|
|
999
|
+
let dx1 = cp1.x - p0.x;
|
|
1000
|
+
let dy1 = cp1.y - p0.y;
|
|
1001
|
+
|
|
1002
|
+
let dx2 = p.x - cp2.x;
|
|
1003
|
+
let dy2 = p.y - cp2.y;
|
|
1004
|
+
|
|
1005
|
+
let cross1 = Math.abs(dx1 * dy2 - dy1 * dx2);
|
|
1006
|
+
|
|
1007
|
+
if(!cross1) return true
|
|
1008
|
+
|
|
1009
|
+
let dx0 = p.x - p0.x;
|
|
1010
|
+
let dy0 = p.y - p0.y;
|
|
1011
|
+
let cross0 = Math.abs(dx0 * dy1 - dy0 * dx1);
|
|
1012
|
+
|
|
1013
|
+
if(!cross0) return true
|
|
1014
|
+
|
|
1015
|
+
//let diff = Math.abs(cross0 - cross1)
|
|
1016
|
+
//let rat0 = 1/cross0 * diff;
|
|
1017
|
+
let rat = (cross0/cross1)
|
|
1018
|
+
|
|
1019
|
+
if (rat<1.1 ) {
|
|
1020
|
+
//console.log('cross', cross0, cross1, 'rat', rat, rat0, diff );
|
|
1021
|
+
isFlat = true;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
return isFlat;
|
|
1025
|
+
|
|
1026
|
+
}
|
|
989
1027
|
|
|
990
1028
|
/**
|
|
991
1029
|
* sloppy distance calculation
|
|
992
1030
|
* based on x/y differences
|
|
993
1031
|
*/
|
|
994
1032
|
export function getDistAv(pt1, pt2) {
|
|
995
|
-
|
|
996
|
-
let
|
|
1033
|
+
|
|
1034
|
+
let diffX = Math.abs(pt2.x - pt1.x);
|
|
1035
|
+
let diffY = Math.abs(pt2.y - pt1.y);
|
|
997
1036
|
let diff = (diffX + diffY) / 2;
|
|
1037
|
+
|
|
1038
|
+
/*
|
|
1039
|
+
let diffX = pt2.x - pt1.x;
|
|
1040
|
+
let diffY = pt2.y - pt1.y;
|
|
1041
|
+
let diff = Math.abs(diffX + diffY) / 2;
|
|
1042
|
+
*/
|
|
1043
|
+
|
|
1044
|
+
|
|
998
1045
|
return diff;
|
|
999
1046
|
}
|
|
1000
1047
|
|
|
@@ -1067,7 +1114,7 @@ export function reducePoints(points, maxPoints = 48) {
|
|
|
1067
1114
|
}
|
|
1068
1115
|
|
|
1069
1116
|
|
|
1070
|
-
export function mirrorCpts(cpt2_0, pt0, cpt2, pt1, outgoing = true, t=0.666) {
|
|
1117
|
+
export function mirrorCpts(cpt2_0, pt0, cpt2, pt1, outgoing = true, t = 0.666) {
|
|
1071
1118
|
|
|
1072
1119
|
// hypotenuse angle
|
|
1073
1120
|
let ang0 = getAngle(pt0, pt1, true);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getSquareDistance } from "./geometry.js";
|
|
1
|
+
import { checkBezierFlatness, getDistAv, getSquareDistance } from "./geometry.js";
|
|
2
2
|
import { getPolygonArea } from "./geometry_area.js";
|
|
3
3
|
import { renderPoint } from "./visualize.js";
|
|
4
4
|
|
|
@@ -17,49 +17,62 @@ export function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLine
|
|
|
17
17
|
let comPrev = pathData[c - 1];
|
|
18
18
|
let com = pathData[c];
|
|
19
19
|
let comN = pathData[c + 1] || pathData[l - 1];
|
|
20
|
-
let p1 = comN.type === '
|
|
20
|
+
let p1 = comN.type.toLowerCase() === 'z' ? M : { x: comN.values[comN.values.length - 2], y: comN.values[comN.values.length - 1] }
|
|
21
21
|
|
|
22
22
|
let { type, values } = com;
|
|
23
23
|
let valsL = values.slice(-2)
|
|
24
24
|
p = type !== 'Z' ? { x: valsL[0], y: valsL[1] } : M;
|
|
25
25
|
|
|
26
|
-
let
|
|
27
|
-
[{ x: values[0], y: values[1] }, { x: values[2], y: values[3] }] :
|
|
28
|
-
(type === 'Q' ? [{ x: values[0], y: values[1] }] : []);
|
|
26
|
+
let area = getPolygonArea([p0, p, p1], true)
|
|
29
27
|
|
|
28
|
+
let distSquare = getSquareDistance(p0, p1)
|
|
29
|
+
let distMax = distSquare / 100 * tolerance
|
|
30
30
|
|
|
31
|
-
let
|
|
32
|
-
let
|
|
33
|
-
let distMax = distSquare / 500 * tolerance
|
|
31
|
+
let isFlat = area < distMax;
|
|
32
|
+
let isFlatBez = false;
|
|
34
33
|
|
|
35
|
-
let isFlat = area < distMax
|
|
36
|
-
|
|
37
|
-
if(!flatBezierToLinetos && type==='C') isFlat = false;
|
|
38
|
-
//let isFlat = flatBezierToLinetos && type === 'C' ? area < distMax : false
|
|
39
34
|
|
|
35
|
+
if (!flatBezierToLinetos && type === 'C') isFlat = false;
|
|
36
|
+
//let isFlat = flatBezierToLinetos && type === 'C' ? area < distMax : false
|
|
40
37
|
|
|
41
38
|
// convert flat beziers to linetos
|
|
42
|
-
if (flatBezierToLinetos && type === 'C') {
|
|
39
|
+
if (flatBezierToLinetos && (type === 'C' || type === 'Q')) {
|
|
40
|
+
|
|
41
|
+
let cpts = type === 'C' ?
|
|
42
|
+
[{ x: values[0], y: values[1] }, { x: values[2], y: values[3] }] :
|
|
43
|
+
(type === 'Q' ? [{ x: values[0], y: values[1] }] : []);
|
|
43
44
|
|
|
44
|
-
let areaBez = getPolygonArea([p0, ...cpts, p], true)
|
|
45
|
-
let isFlatBez = areaBez < distSquare / 1000
|
|
46
45
|
|
|
47
|
-
|
|
46
|
+
//let areaBez = getPolygonArea([p0, ...cpts, p], true)
|
|
47
|
+
|
|
48
|
+
isFlatBez = checkBezierFlatness(p0, cpts, p)
|
|
49
|
+
// console.log();
|
|
50
|
+
|
|
51
|
+
//isFlatBez = areaBez < distMax * 0.25
|
|
52
|
+
//console.log('isFlatBez', isFlatBez);
|
|
53
|
+
//isFlatBez = false
|
|
54
|
+
|
|
55
|
+
//&& comPrev.type !== 'C'
|
|
56
|
+
if (isFlatBez && c < l - 1 && comPrev.type !== 'C') {
|
|
57
|
+
type = "L"
|
|
48
58
|
com.type = "L"
|
|
49
59
|
com.values = valsL
|
|
60
|
+
|
|
61
|
+
//renderPoint(markers, p)
|
|
50
62
|
}
|
|
51
63
|
|
|
52
64
|
}
|
|
53
65
|
|
|
54
|
-
|
|
55
66
|
// update end point
|
|
56
67
|
p0 = p;
|
|
57
68
|
|
|
58
69
|
// colinear – exclude arcs (as always =) as semicircles won't have an area
|
|
59
|
-
if (
|
|
70
|
+
if ( isFlat && c < l - 1 && (type === 'L' || (flatBezierToLinetos && isFlatBez))) {
|
|
71
|
+
//console.log(area,distMax );
|
|
60
72
|
continue;
|
|
61
73
|
}
|
|
62
74
|
|
|
75
|
+
|
|
63
76
|
if (type === 'M') {
|
|
64
77
|
M = p
|
|
65
78
|
p0 = M
|
|
@@ -7,8 +7,42 @@ import { renderPoint, renderPath } from './visualize.js';
|
|
|
7
7
|
import { getPolygonArea } from './geometry_area.js';
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
export function pathDataToTopLeft(pathData) {
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
let len = pathData.length;
|
|
13
|
+
let isClosed = pathData[len - 1].type.toLowerCase() === 'z'
|
|
14
|
+
|
|
15
|
+
// we can't change starting point for non closed paths
|
|
16
|
+
if (!isClosed) {
|
|
17
|
+
return pathData
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let newIndex = 0;
|
|
21
|
+
|
|
22
|
+
//get top most index
|
|
23
|
+
let indices = [];
|
|
24
|
+
for (let i = 0; i < len; i++) {
|
|
25
|
+
let com = pathData[i];
|
|
26
|
+
let { type, values } = com;
|
|
27
|
+
let valsLen = values.length
|
|
28
|
+
if (valsLen) {
|
|
29
|
+
let p = { type: type, x: values[valsLen-2], y: values[valsLen-1], index: 0}
|
|
30
|
+
p.index = i
|
|
31
|
+
indices.push(p)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// reorder to top left most
|
|
36
|
+
indices = indices.sort((a, b) => +a.y.toFixed(3) - +b.y.toFixed(3) || a.x - b.x);
|
|
37
|
+
newIndex = indices[0].index
|
|
38
|
+
|
|
39
|
+
return newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
export function optimizeClosePath(pathData, removeFinalLineto = false, reorder = true) {
|
|
12
46
|
|
|
13
47
|
let pathDataNew = [];
|
|
14
48
|
let len = pathData.length;
|
|
@@ -21,18 +55,18 @@ export function pathDataToTopLeft(pathData, removeFinalLineto = false, reorder =
|
|
|
21
55
|
// check if order is ideal
|
|
22
56
|
let penultimateCom = pathData[len - 2];
|
|
23
57
|
let penultimateType = penultimateCom.type;
|
|
24
|
-
let penultimateComCoords = penultimateCom.values.slice(-2).map(val
|
|
58
|
+
let penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8))
|
|
25
59
|
|
|
26
60
|
// last L command ends at M
|
|
27
|
-
let isClosingCommand =
|
|
61
|
+
let isClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y
|
|
28
62
|
|
|
29
63
|
// if last segment is not closing or a lineto
|
|
30
|
-
let skipReorder = pathData[1].type!=='L' && (!isClosingCommand || penultimateType==='L'
|
|
31
|
-
skipReorder=false
|
|
64
|
+
let skipReorder = pathData[1].type !== 'L' && (!isClosingCommand || penultimateType === 'L')
|
|
65
|
+
skipReorder = false
|
|
32
66
|
|
|
33
67
|
|
|
34
68
|
// we can't change starting point for non closed paths
|
|
35
|
-
if (!isClosed
|
|
69
|
+
if (!isClosed) {
|
|
36
70
|
return pathData
|
|
37
71
|
}
|
|
38
72
|
|
|
@@ -62,15 +96,15 @@ export function pathDataToTopLeft(pathData, removeFinalLineto = false, reorder =
|
|
|
62
96
|
// find top most lineto
|
|
63
97
|
|
|
64
98
|
if (linetos.length) {
|
|
65
|
-
let curveAfterLine = indices.filter(com => (com.type !== 'L' && com.type !== 'M') && com.prevCom &&
|
|
66
|
-
|
|
99
|
+
let curveAfterLine = indices.filter(com => (com.type !== 'L' && com.type !== 'M') && com.prevCom &&
|
|
100
|
+
com.prevCom === 'L' || com.prevCom === 'M' && penultimateType === 'L').sort((a, b) => a.y - b.y || a.x - b.x)[0]
|
|
67
101
|
|
|
68
102
|
newIndex = curveAfterLine ? curveAfterLine.index - 1 : 0
|
|
69
103
|
|
|
70
104
|
}
|
|
71
105
|
// use top most command
|
|
72
106
|
else {
|
|
73
|
-
indices = indices.sort((a, b) => +a.y.toFixed(1) - +b.y.toFixed(1) || a.x - b.x
|
|
107
|
+
indices = indices.sort((a, b) => +a.y.toFixed(1) - +b.y.toFixed(1) || a.x - b.x);
|
|
74
108
|
newIndex = indices[0].index
|
|
75
109
|
}
|
|
76
110
|
|