svg-path-simplify 0.0.7 → 0.0.9
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 +25 -5
- package/dist/svg-path-simplify.esm.js +1250 -562
- package/dist/svg-path-simplify.esm.min.js +1 -1
- package/dist/svg-path-simplify.js +4756 -4068
- package/dist/svg-path-simplify.min.js +1 -1
- package/dist/svg-path-simplify.node.js +1250 -562
- package/dist/svg-path-simplify.node.min.js +1 -1
- package/index.html +89 -29
- package/package.json +5 -3
- package/src/detect_input.js +17 -10
- package/src/dom-polyfill.js +29 -0
- package/src/dom-polyfill_back.js +22 -0
- package/src/index.js +10 -1
- package/src/pathData_simplify_cubic.js +114 -143
- package/src/pathData_simplify_cubic_extrapolate.js +64 -35
- package/src/pathSimplify-main.js +113 -165
- package/src/svgii/geometry.js +8 -155
- package/src/svgii/geometry_flatness.js +94 -0
- package/src/svgii/pathData_analyze.js +15 -596
- package/src/svgii/pathData_convert.js +26 -17
- package/src/svgii/pathData_interpolate.js +65 -0
- package/src/svgii/pathData_parse.js +25 -9
- package/src/svgii/pathData_parse_els.js +245 -0
- package/src/svgii/pathData_remove_collinear.js +33 -28
- package/src/svgii/pathData_remove_orphaned.js +21 -0
- package/src/svgii/pathData_remove_zerolength.js +17 -3
- package/src/svgii/pathData_reorder.js +9 -3
- package/src/svgii/pathData_simplify_refineCorners.js +160 -0
- package/src/svgii/pathData_simplify_refineExtremes.js +208 -0
- package/src/svgii/pathData_split.js +43 -15
- package/src/svgii/pathData_stringify.js +3 -12
- package/src/svgii/rounding.js +35 -27
- package/src/svgii/svg_cleanup.js +4 -1
- package/testSVG.js +39 -0
- package/src/pathData_simplify_cubic_arr.js +0 -50
- package/src/svgii/simplify.js +0 -248
- package/src/svgii/simplify_bezier.js +0 -470
- package/src/svgii/simplify_linetos.js +0 -93
package/src/pathSimplify-main.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { detectInputType } from './detect_input';
|
|
2
|
-
import {
|
|
2
|
+
import { simplifyPathDataCubic } from './pathData_simplify_cubic';
|
|
3
3
|
import { getPathDataVertices, pointAtT } from './svgii/geometry';
|
|
4
4
|
import { getPolyBBox } from './svgii/geometry_bbox';
|
|
5
|
-
import { analyzePathData
|
|
5
|
+
import { analyzePathData } from './svgii/pathData_analyze';
|
|
6
6
|
import { combineArcs, convertPathData, cubicCommandToArc, revertCubicQuadratic } from './svgii/pathData_convert';
|
|
7
7
|
import { parsePathDataNormalized } from './svgii/pathData_parse';
|
|
8
|
+
import { shapeElToPath } from './svgii/pathData_parse_els';
|
|
8
9
|
import { pathDataRemoveColinear } from './svgii/pathData_remove_collinear';
|
|
9
|
-
import {
|
|
10
|
+
import { removeOrphanedM } from './svgii/pathData_remove_orphaned';
|
|
11
|
+
import { removeZeroLengthLinetos } from './svgii/pathData_remove_zerolength';
|
|
10
12
|
import { optimizeClosePath, pathDataToTopLeft } from './svgii/pathData_reorder';
|
|
11
13
|
import { reversePathData } from './svgii/pathData_reverse';
|
|
12
14
|
import { addExtremePoints, splitSubpaths } from './svgii/pathData_split';
|
|
@@ -15,8 +17,10 @@ import { pathDataToD } from './svgii/pathData_stringify';
|
|
|
15
17
|
import { analyzePoly } from './svgii/poly_analyze';
|
|
16
18
|
import { getCurvePathData } from './svgii/poly_to_pathdata';
|
|
17
19
|
import { detectAccuracy } from './svgii/rounding';
|
|
20
|
+
import { refineAdjacentExtremes } from './svgii/pathData_simplify_refineExtremes';
|
|
18
21
|
import { cleanUpSVG, removeEmptySVGEls, stringifySVG } from './svgii/svg_cleanup';
|
|
19
22
|
import { renderPoint } from './svgii/visualize';
|
|
23
|
+
import { refineRoundedCorners } from './svgii/pathData_simplify_refineCorners';
|
|
20
24
|
|
|
21
25
|
export function svgPathSimplify(input = '', {
|
|
22
26
|
|
|
@@ -38,15 +42,20 @@ export function svgPathSimplify(input = '', {
|
|
|
38
42
|
|
|
39
43
|
simplifyBezier = true,
|
|
40
44
|
optimizeOrder = true,
|
|
45
|
+
removeZeroLength = true,
|
|
41
46
|
removeColinear = true,
|
|
42
47
|
flatBezierToLinetos = true,
|
|
43
48
|
revertToQuadratics = true,
|
|
44
49
|
|
|
50
|
+
refineExtremes = true,
|
|
51
|
+
refineCorners = false,
|
|
52
|
+
|
|
45
53
|
keepExtremes = true,
|
|
46
54
|
keepCorners = true,
|
|
47
55
|
extrapolateDominant = true,
|
|
48
56
|
keepInflections = false,
|
|
49
57
|
addExtremes = false,
|
|
58
|
+
removeOrphanSubpaths = false,
|
|
50
59
|
|
|
51
60
|
|
|
52
61
|
// svg path optimizations
|
|
@@ -61,6 +70,7 @@ export function svgPathSimplify(input = '', {
|
|
|
61
70
|
mergePaths = false,
|
|
62
71
|
removeHidden = true,
|
|
63
72
|
removeUnused = true,
|
|
73
|
+
shapesToPaths = true,
|
|
64
74
|
|
|
65
75
|
|
|
66
76
|
} = {}) {
|
|
@@ -77,34 +87,54 @@ export function svgPathSimplify(input = '', {
|
|
|
77
87
|
let report = {};
|
|
78
88
|
let d = '';
|
|
79
89
|
let mode = inputType === 'svgMarkup' ? 1 : 0;
|
|
90
|
+
//console.log(inputType);
|
|
80
91
|
|
|
81
92
|
let paths = []
|
|
82
93
|
|
|
83
|
-
|
|
84
94
|
/**
|
|
85
95
|
* normalize input
|
|
86
96
|
* switch mode
|
|
87
97
|
*/
|
|
88
98
|
|
|
89
99
|
// original size
|
|
90
|
-
svgSize = new Blob([input]).size;
|
|
100
|
+
//svgSize = new Blob([input]).size;
|
|
101
|
+
svgSize = input.length;
|
|
102
|
+
|
|
91
103
|
|
|
92
|
-
// single path
|
|
104
|
+
// mode:0 – single path
|
|
93
105
|
if (!mode) {
|
|
94
106
|
if (inputType === 'pathDataString') {
|
|
95
107
|
d = input
|
|
96
108
|
} else if (inputType === 'polyString') {
|
|
97
109
|
d = 'M' + input
|
|
98
110
|
}
|
|
111
|
+
else if (inputType === 'pathData') {
|
|
112
|
+
d = input;
|
|
113
|
+
|
|
114
|
+
// stringify to compare lengths
|
|
115
|
+
//let dStr = pathDataToD(d);
|
|
116
|
+
let dStr = d.map(com=>{return `${com.type} ${com.values.join(' ')}`}).join(' ') ;
|
|
117
|
+
svgSize = dStr.length;
|
|
118
|
+
|
|
119
|
+
}
|
|
120
|
+
|
|
99
121
|
paths.push({ d, el: null })
|
|
100
122
|
}
|
|
101
|
-
// process svg
|
|
123
|
+
// mode:1 – process complete svg DOM
|
|
102
124
|
else {
|
|
103
125
|
//sanitize
|
|
104
126
|
let returnDom = true
|
|
105
127
|
svg = cleanUpSVG(input, { returnDom, removeHidden, removeUnused }
|
|
106
128
|
);
|
|
107
129
|
|
|
130
|
+
if (shapesToPaths) {
|
|
131
|
+
let shapes = svg.querySelectorAll('polygon, polyline, line, rect, circle, ellipse');
|
|
132
|
+
shapes.forEach(shape => {
|
|
133
|
+
let path = shapeElToPath(shape);
|
|
134
|
+
shape.replaceWith(path)
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
108
138
|
// collect paths
|
|
109
139
|
let pathEls = svg.querySelectorAll('path')
|
|
110
140
|
pathEls.forEach(path => {
|
|
@@ -112,11 +142,11 @@ export function svgPathSimplify(input = '', {
|
|
|
112
142
|
})
|
|
113
143
|
}
|
|
114
144
|
|
|
115
|
-
|
|
116
|
-
//console.log('inputType', inputType, 'mode', mode);
|
|
145
|
+
|
|
117
146
|
|
|
118
147
|
/**
|
|
119
148
|
* process all paths
|
|
149
|
+
* try simplifications and removals
|
|
120
150
|
*/
|
|
121
151
|
|
|
122
152
|
// SVG optimization options
|
|
@@ -129,36 +159,43 @@ export function svgPathSimplify(input = '', {
|
|
|
129
159
|
// combinded path data for SVGs with mergePaths enabled
|
|
130
160
|
let pathData_merged = [];
|
|
131
161
|
|
|
132
|
-
paths.
|
|
133
|
-
let { d, el } = path;
|
|
162
|
+
for (let i = 0, l = paths.length; l && i < l; i++) {
|
|
134
163
|
|
|
135
|
-
let
|
|
136
|
-
|
|
164
|
+
let path = paths[i];
|
|
165
|
+
let { d, el } = path;
|
|
137
166
|
|
|
138
|
-
|
|
139
|
-
let pathData = JSON.parse(JSON.stringify(pathDataO));
|
|
167
|
+
let pathData = parsePathDataNormalized(d, { quadraticToCubic, toAbsolute, arcToCubic });
|
|
140
168
|
|
|
141
169
|
// count commands for evaluation
|
|
142
|
-
let comCount =
|
|
170
|
+
let comCount = pathData.length
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
if (removeOrphanSubpaths) pathData = removeOrphanedM(pathData);
|
|
143
174
|
|
|
144
175
|
/**
|
|
145
176
|
* get sub paths
|
|
146
177
|
*/
|
|
147
178
|
let subPathArr = splitSubpaths(pathData);
|
|
179
|
+
let lenSub = subPathArr.length;
|
|
180
|
+
|
|
148
181
|
|
|
149
182
|
// cleaned up pathData
|
|
150
|
-
let pathDataArrN =
|
|
183
|
+
//let pathDataArrN = new Array(lenSub);
|
|
184
|
+
|
|
185
|
+
// reset array
|
|
186
|
+
let pathDataFlat = []
|
|
187
|
+
|
|
188
|
+
for (let i = 0; i < lenSub; i++) {
|
|
151
189
|
|
|
152
|
-
for (let i = 0, l = subPathArr.length; i < l; i++) {
|
|
153
190
|
|
|
154
191
|
//let { pathData, bb } = subPathArr[i];
|
|
155
192
|
let pathDataSub = subPathArr[i];
|
|
156
193
|
|
|
157
|
-
// try simplification in reversed order
|
|
158
|
-
if (reverse) pathDataSub = reversePathData(pathDataSub);
|
|
159
194
|
|
|
160
195
|
// remove zero length linetos
|
|
161
|
-
if (removeColinear) pathDataSub = removeZeroLengthLinetos(pathDataSub)
|
|
196
|
+
if (removeColinear || removeZeroLength) pathDataSub = removeZeroLengthLinetos(pathDataSub)
|
|
197
|
+
//console.log(removeColinear, removeZeroLength);
|
|
198
|
+
|
|
162
199
|
|
|
163
200
|
// add extremes
|
|
164
201
|
//let tMin=0.2, tMax=0.8;
|
|
@@ -169,8 +206,10 @@ export function svgPathSimplify(input = '', {
|
|
|
169
206
|
// sort to top left
|
|
170
207
|
if (optimizeOrder) pathDataSub = pathDataToTopLeft(pathDataSub);
|
|
171
208
|
|
|
172
|
-
|
|
173
|
-
|
|
209
|
+
|
|
210
|
+
// Preprocessing: remove colinear - ignore flat beziers (removed later)
|
|
211
|
+
if (removeColinear) pathDataSub = pathDataRemoveColinear(pathDataSub, { tolerance, flatBezierToLinetos: false });
|
|
212
|
+
|
|
174
213
|
|
|
175
214
|
// analyze pathdata to add info about signicant properties such as extremes, corners
|
|
176
215
|
let pathDataPlus = analyzePathData(pathDataSub);
|
|
@@ -178,28 +217,29 @@ export function svgPathSimplify(input = '', {
|
|
|
178
217
|
|
|
179
218
|
// simplify beziers
|
|
180
219
|
let { pathData, bb, dimA } = pathDataPlus;
|
|
181
|
-
|
|
182
|
-
//let pathDataN = pathData;
|
|
183
|
-
|
|
184
|
-
//console.log(pathDataPlus);
|
|
185
|
-
|
|
186
220
|
pathData = simplifyBezier ? simplifyPathDataCubic(pathData, { simplifyBezier, keepInflections, keepExtremes, keepCorners, extrapolateDominant, revertToQuadratics, tolerance, reverse }) : pathData;
|
|
187
221
|
|
|
222
|
+
// refine extremes
|
|
223
|
+
if (refineExtremes) {
|
|
224
|
+
let thresholdEx = (bb.width + bb.height) / 2 * 0.05
|
|
225
|
+
pathData = refineAdjacentExtremes(pathData, { threshold: thresholdEx, tolerance })
|
|
226
|
+
}
|
|
227
|
+
|
|
188
228
|
|
|
189
229
|
// cubic to arcs
|
|
190
230
|
if (cubicToArc) {
|
|
191
231
|
|
|
192
|
-
let thresh =
|
|
232
|
+
let thresh = 1;
|
|
193
233
|
|
|
194
|
-
|
|
234
|
+
for(let c=0, l=pathData.length; c<l; c++){
|
|
235
|
+
let com = pathData[c]
|
|
195
236
|
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
196
237
|
if (type === 'C') {
|
|
197
238
|
//console.log(com);
|
|
198
239
|
let comA = cubicCommandToArc(p0, cp1, cp2, p, thresh)
|
|
199
240
|
if (comA.isArc) pathData[c] = comA.com;
|
|
200
|
-
//if (comQ.type === 'Q') pathDataN[c] = comQ
|
|
201
241
|
}
|
|
202
|
-
}
|
|
242
|
+
}
|
|
203
243
|
|
|
204
244
|
// combine adjacent cubics
|
|
205
245
|
pathData = combineArcs(pathData)
|
|
@@ -207,19 +247,30 @@ export function svgPathSimplify(input = '', {
|
|
|
207
247
|
}
|
|
208
248
|
|
|
209
249
|
|
|
250
|
+
// post processing: remove flat beziers
|
|
251
|
+
if (removeColinear && flatBezierToLinetos) {
|
|
252
|
+
pathData = pathDataRemoveColinear(pathData, { tolerance, flatBezierToLinetos });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
// refine corners
|
|
257
|
+
if(refineCorners){
|
|
258
|
+
let threshold = (bb.width + bb.height) / 2 * 0.1
|
|
259
|
+
pathData = refineRoundedCorners(pathData, { threshold, tolerance })
|
|
260
|
+
//console.log(refineCorners);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
|
|
210
265
|
// simplify to quadratics
|
|
211
266
|
if (revertToQuadratics) {
|
|
212
|
-
|
|
267
|
+
for(let c=0, l=pathData.length; c<l; c++){
|
|
268
|
+
let com = pathData[c]
|
|
213
269
|
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
214
270
|
if (type === 'C') {
|
|
215
271
|
//console.log(com);
|
|
216
272
|
let comQ = revertCubicQuadratic(p0, cp1, cp2, p)
|
|
217
273
|
if (comQ.type === 'Q') {
|
|
218
|
-
/*
|
|
219
|
-
comQ.p0 = com.p0
|
|
220
|
-
comQ.cp1 = {x:comQ.values[0], y:comQ.values[1]}
|
|
221
|
-
comQ.p = com.p
|
|
222
|
-
*/
|
|
223
274
|
comQ.extreme = com.extreme
|
|
224
275
|
comQ.corner = com.corner
|
|
225
276
|
comQ.dimA = com.dimA
|
|
@@ -227,25 +278,32 @@ export function svgPathSimplify(input = '', {
|
|
|
227
278
|
pathData[c] = comQ
|
|
228
279
|
}
|
|
229
280
|
}
|
|
230
|
-
}
|
|
281
|
+
}
|
|
231
282
|
}
|
|
232
283
|
|
|
233
|
-
//if (removeColinear) pathDataSub = pathDataRemoveColinear(pathDataSub, tolerance, flatBezierToLinetos);
|
|
234
284
|
|
|
235
285
|
// optimize close path
|
|
236
286
|
if (optimizeOrder) pathData = optimizeClosePath(pathData)
|
|
237
287
|
|
|
238
|
-
// poly
|
|
239
|
-
//let poly = pathDataToPolySingle(pathData, true)
|
|
240
|
-
//console.log('poly', poly);
|
|
241
|
-
|
|
242
288
|
|
|
243
289
|
// update
|
|
244
|
-
|
|
290
|
+
pathDataFlat.push(...pathData)
|
|
291
|
+
//pathDataArrN[i]=(pathData)
|
|
292
|
+
|
|
245
293
|
}
|
|
246
294
|
|
|
295
|
+
|
|
247
296
|
// flatten compound paths
|
|
248
|
-
pathData =
|
|
297
|
+
pathData = pathDataFlat;
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
//console.log('pathData', pathData);
|
|
301
|
+
|
|
302
|
+
if (autoAccuracy) {
|
|
303
|
+
decimals = detectAccuracy(pathData)
|
|
304
|
+
pathOptions.decimals = decimals
|
|
305
|
+
//console.log('!decimals', decimals);
|
|
306
|
+
}
|
|
249
307
|
|
|
250
308
|
|
|
251
309
|
// collect for merged svg paths
|
|
@@ -255,26 +313,20 @@ export function svgPathSimplify(input = '', {
|
|
|
255
313
|
// single output
|
|
256
314
|
else {
|
|
257
315
|
|
|
258
|
-
/**
|
|
259
|
-
* detect accuracy
|
|
260
|
-
*/
|
|
261
|
-
if (autoAccuracy) {
|
|
262
|
-
decimals = detectAccuracy(pathData)
|
|
263
|
-
}
|
|
264
|
-
|
|
265
316
|
// optimize path data
|
|
266
317
|
pathData = convertPathData(pathData, pathOptions)
|
|
267
318
|
|
|
268
319
|
// remove zero-length segments introduced by rounding
|
|
269
|
-
pathData =
|
|
320
|
+
pathData = removeZeroLengthLinetos(pathData);
|
|
270
321
|
|
|
271
322
|
// compare command count
|
|
272
323
|
let comCountS = pathData.length
|
|
273
324
|
|
|
274
325
|
let dOpt = pathDataToD(pathData, minifyD)
|
|
275
|
-
svgSizeOpt = new Blob([dOpt]).size;
|
|
276
|
-
|
|
326
|
+
//svgSizeOpt = new Blob([dOpt]).size;
|
|
327
|
+
svgSizeOpt = dOpt.length
|
|
277
328
|
|
|
329
|
+
compression = +(100 / svgSize * (svgSizeOpt)).toFixed(2)
|
|
278
330
|
|
|
279
331
|
path.d = dOpt
|
|
280
332
|
path.report = {
|
|
@@ -289,7 +341,7 @@ export function svgPathSimplify(input = '', {
|
|
|
289
341
|
// apply new path for svgs
|
|
290
342
|
if (el) el.setAttribute('d', dOpt)
|
|
291
343
|
}
|
|
292
|
-
}
|
|
344
|
+
};
|
|
293
345
|
|
|
294
346
|
/**
|
|
295
347
|
* stringify new SVG
|
|
@@ -297,12 +349,12 @@ export function svgPathSimplify(input = '', {
|
|
|
297
349
|
if (mode) {
|
|
298
350
|
|
|
299
351
|
if (pathData_merged.length) {
|
|
352
|
+
|
|
300
353
|
// optimize path data
|
|
301
354
|
let pathData = convertPathData(pathData_merged, pathOptions)
|
|
302
355
|
|
|
303
356
|
// remove zero-length segments introduced by rounding
|
|
304
|
-
pathData =
|
|
305
|
-
|
|
357
|
+
pathData = removeZeroLengthLinetos(pathData);
|
|
306
358
|
|
|
307
359
|
let dOpt = pathDataToD(pathData, minifyD)
|
|
308
360
|
|
|
@@ -310,7 +362,6 @@ export function svgPathSimplify(input = '', {
|
|
|
310
362
|
// apply new path for svgs
|
|
311
363
|
paths[0].el.setAttribute('d', dOpt)
|
|
312
364
|
|
|
313
|
-
|
|
314
365
|
// remove other paths
|
|
315
366
|
for (let i = 1; i < paths.length; i++) {
|
|
316
367
|
let pathEl = paths[i].el
|
|
@@ -324,7 +375,8 @@ export function svgPathSimplify(input = '', {
|
|
|
324
375
|
|
|
325
376
|
|
|
326
377
|
svg = stringifySVG(svg);
|
|
327
|
-
svgSizeOpt = new Blob([svg]).size
|
|
378
|
+
//svgSizeOpt = new Blob([svg]).size
|
|
379
|
+
svgSizeOpt = svg.length;
|
|
328
380
|
//compression = +(100/svgSize * (svgSize-svgSizeOpt)).toFixed(2)
|
|
329
381
|
compression = +(100 / svgSize * (svgSizeOpt)).toFixed(2)
|
|
330
382
|
|
|
@@ -348,109 +400,5 @@ export function svgPathSimplify(input = '', {
|
|
|
348
400
|
|
|
349
401
|
|
|
350
402
|
|
|
351
|
-
function simplifyPathDataCubic(pathData, {
|
|
352
|
-
keepExtremes = true,
|
|
353
|
-
keepInflections = true,
|
|
354
|
-
keepCorners = true,
|
|
355
|
-
extrapolateDominant = true,
|
|
356
|
-
tolerance = 1,
|
|
357
|
-
reverse = false
|
|
358
|
-
} = {}) {
|
|
359
|
-
|
|
360
|
-
let pathDataN = [pathData[0]];
|
|
361
|
-
|
|
362
|
-
for (let i = 2, l = pathData.length; l && i <= l; i++) {
|
|
363
|
-
let com = pathData[i - 1];
|
|
364
|
-
let comN = i < l ? pathData[i] : null;
|
|
365
|
-
let typeN = comN?.type || null;
|
|
366
|
-
//let isCornerN = comN?.corner || null;
|
|
367
|
-
//let isExtremeN = comN?.extreme || null;
|
|
368
|
-
let isDirChange = com?.directionChange || null;
|
|
369
|
-
let isDirChangeN = comN?.directionChange || null;
|
|
370
|
-
|
|
371
|
-
let { type, values, p0, p, cp1 = null, cp2 = null, extreme = false, corner = false, dimA = 0 } = com;
|
|
372
|
-
|
|
373
|
-
// count simplifications
|
|
374
|
-
let success = 0;
|
|
375
|
-
|
|
376
|
-
// next is also cubic
|
|
377
|
-
if (type === 'C' && typeN === 'C') {
|
|
378
|
-
|
|
379
|
-
// cannot be combined as crossing extremes or corners
|
|
380
|
-
if (
|
|
381
|
-
(keepInflections && isDirChangeN) ||
|
|
382
|
-
(keepCorners && corner) ||
|
|
383
|
-
(!isDirChange && keepExtremes && extreme)
|
|
384
|
-
) {
|
|
385
|
-
//renderPoint(markers, p, 'red', '1%')
|
|
386
|
-
pathDataN.push(com)
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// try simplification
|
|
390
|
-
else {
|
|
391
|
-
//renderPoint(markers, p, 'magenta', '1%')
|
|
392
|
-
let combined = combineCubicPairs(com, comN, extrapolateDominant, tolerance)
|
|
393
|
-
let error = 0;
|
|
394
|
-
|
|
395
|
-
// combining successful! try next segment
|
|
396
|
-
if (combined.length === 1) {
|
|
397
|
-
com = combined[0]
|
|
398
|
-
let offset = 1;
|
|
399
|
-
|
|
400
|
-
// add cumulative error to prevent distortions
|
|
401
|
-
error += com.error;
|
|
402
|
-
//console.log('!error', error);
|
|
403
|
-
|
|
404
|
-
// find next candidates
|
|
405
|
-
for (let n = i + 1; error < tolerance && n < l; n++) {
|
|
406
|
-
let comN = pathData[n]
|
|
407
|
-
if (comN.type !== 'C' ||
|
|
408
|
-
(
|
|
409
|
-
(keepInflections && comN.directionChange) ||
|
|
410
|
-
(keepCorners && com.corner) ||
|
|
411
|
-
(keepExtremes && com.extreme)
|
|
412
|
-
)
|
|
413
|
-
) {
|
|
414
|
-
break
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
let combined = combineCubicPairs(com, comN, extrapolateDominant, tolerance)
|
|
418
|
-
if (combined.length === 1) {
|
|
419
|
-
// add cumulative error to prevent distortions
|
|
420
|
-
//console.log('combined', combined);
|
|
421
|
-
error += combined[0].error * 0.5;
|
|
422
|
-
offset++
|
|
423
|
-
}
|
|
424
|
-
com = combined[0]
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
//com.opt = true
|
|
428
|
-
pathDataN.push(com)
|
|
429
|
-
|
|
430
|
-
if (i < l) {
|
|
431
|
-
i += offset
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
} else {
|
|
435
|
-
pathDataN.push(com)
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
} // end of bezier command
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
// other commands
|
|
443
|
-
else {
|
|
444
|
-
pathDataN.push(com)
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
} // end command loop
|
|
448
|
-
|
|
449
|
-
// reverse back
|
|
450
|
-
if (reverse) pathDataN = reversePathData(pathDataN)
|
|
451
|
-
|
|
452
|
-
return pathDataN
|
|
453
|
-
}
|
|
454
|
-
|
|
455
403
|
|
|
456
404
|
|
package/src/svgii/geometry.js
CHANGED
|
@@ -23,19 +23,24 @@ export function getAngle(p1, p2, normalize = false) {
|
|
|
23
23
|
* http://jsfiddle.net/justin_c_rounds/Gd2S2/light/
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
export function checkLineIntersection(p1, p2, p3, p4, exact = true) {
|
|
26
|
+
export function checkLineIntersection(p1=null, p2=null, p3=null, p4=null, exact = true, debug=false) {
|
|
27
27
|
// if the lines intersect, the result contains the x and y of the intersection (treating the lines as infinite) and booleans for whether line segment 1 or line segment 2 contain the point
|
|
28
28
|
let denominator, a, b, numerator1, numerator2;
|
|
29
29
|
let intersectionPoint = {}
|
|
30
30
|
|
|
31
|
+
if(!p1 || !p2 || !p3 || !p4){
|
|
32
|
+
if(debug) console.warn('points missing');
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
|
|
31
36
|
try {
|
|
32
37
|
denominator = ((p4.y - p3.y) * (p2.x - p1.x)) - ((p4.x - p3.x) * (p2.y - p1.y));
|
|
33
38
|
if (denominator == 0) {
|
|
34
39
|
return false;
|
|
35
40
|
}
|
|
36
|
-
|
|
37
41
|
} catch {
|
|
38
|
-
console.
|
|
42
|
+
if(debug) console.warn('!catch', p1, p2, 'p3:', p3, 'p4:', p4);
|
|
43
|
+
return false
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
a = p1.y - p3.y;
|
|
@@ -52,9 +57,6 @@ export function checkLineIntersection(p1, p2, p3, p4, exact = true) {
|
|
|
52
57
|
y: p1.y + (a * (p2.y - p1.y))
|
|
53
58
|
}
|
|
54
59
|
|
|
55
|
-
// console.log('intersectionPoint', intersectionPoint, p1, p2);
|
|
56
|
-
|
|
57
|
-
|
|
58
60
|
|
|
59
61
|
let intersection = false;
|
|
60
62
|
// if line1 is a segment and line2 is infinite, they intersect if:
|
|
@@ -874,156 +876,8 @@ export function intersectLines(p1, p2, p3, p4) {
|
|
|
874
876
|
|
|
875
877
|
|
|
876
878
|
|
|
877
|
-
/**
|
|
878
|
-
* check polygon flatness helper
|
|
879
|
-
* basically a reduced shoelace algorithm
|
|
880
|
-
*/
|
|
881
|
-
export function commandIsFlat0(points, tolerance = 0.025) {
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
let xArr = points.map(pt => { return pt.x })
|
|
885
|
-
let yArr = points.map(pt => { return pt.y })
|
|
886
|
-
|
|
887
|
-
let xMin = Math.min(...xArr)
|
|
888
|
-
let xMax = Math.max(...xArr)
|
|
889
|
-
let yMin = Math.min(...yArr)
|
|
890
|
-
let yMax = Math.max(...yArr)
|
|
891
|
-
let w = xMax - xMin
|
|
892
|
-
let h = yMax - yMin
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
if (points.length < 3 || (w === 0 || h === 0)) {
|
|
896
|
-
return { area: 0, flat: true, thresh: 0.0001, ratio: 0 };
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
tolerance = 0.5;
|
|
900
|
-
let thresh = (w + h) * 0.5 * tolerance;
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
//let thresh = tolerance;
|
|
904
|
-
//console.log('w,h', w, h, thresh);
|
|
905
|
-
|
|
906
|
-
let area = 0;
|
|
907
|
-
for (let i = 0; i < points.length; i++) {
|
|
908
|
-
let addX = points[i].x;
|
|
909
|
-
let addY = points[i === points.length - 1 ? 0 : i + 1].y;
|
|
910
|
-
let subX = points[i === points.length - 1 ? 0 : i + 1].x;
|
|
911
|
-
let subY = points[i].y;
|
|
912
|
-
area += addX * addY * 0.5 - subX * subY * 0.5;
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
//console.log('flatArea', area, points);
|
|
916
|
-
area = +Math.abs(area).toFixed(9);
|
|
917
|
-
|
|
918
|
-
let ratio = area / thresh;
|
|
919
|
-
let isFlat = area === 0 ? true : (ratio < 0.15 ? true : false);
|
|
920
|
-
//isFlat= false
|
|
921
|
-
|
|
922
|
-
return { area: area, flat: isFlat, thresh: thresh, ratio: ratio };
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
export function commandIsFlat(points, tolerance = 0.025) {
|
|
927
|
-
|
|
928
|
-
let p0 = points[0];
|
|
929
|
-
let p = points[points.length - 1];
|
|
930
|
-
|
|
931
|
-
let xArr = points.map(pt => { return pt.x })
|
|
932
|
-
let yArr = points.map(pt => { return pt.y })
|
|
933
|
-
|
|
934
|
-
let xMin = Math.min(...xArr)
|
|
935
|
-
let xMax = Math.max(...xArr)
|
|
936
|
-
let yMin = Math.min(...yArr)
|
|
937
|
-
let yMax = Math.max(...yArr)
|
|
938
|
-
let w = xMax - xMin
|
|
939
|
-
let h = yMax - yMin
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
if (points.length < 3 || (w === 0 || h === 0)) {
|
|
943
|
-
return { area: 0, flat: true, thresh: 0.0001, ratio: 0 };
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
let squareDist = getSquareDistance(p0, p)
|
|
947
|
-
let squareDist1 = getSquareDistance(p0, points[0])
|
|
948
|
-
let squareDist2 = points.length > 3 ? getSquareDistance(p, points[1]) : squareDist1;
|
|
949
|
-
let squareDistAvg = (squareDist1 + squareDist2) / 2
|
|
950
|
-
|
|
951
|
-
tolerance = 0.5;
|
|
952
|
-
let thresh = (w + h) * 0.5 * tolerance;
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
//let thresh = tolerance;
|
|
956
|
-
//console.log('w,h', w, h, thresh);
|
|
957
|
-
|
|
958
|
-
let area = 0;
|
|
959
|
-
for (let i = 0, l = points.length; i < l; i++) {
|
|
960
|
-
let addX = points[i].x;
|
|
961
|
-
let addY = points[i === points.length - 1 ? 0 : i + 1].y;
|
|
962
|
-
let subX = points[i === points.length - 1 ? 0 : i + 1].x;
|
|
963
|
-
let subY = points[i].y;
|
|
964
|
-
area += addX * addY * 0.5 - subX * subY * 0.5;
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
//console.log('flatArea', area, points);
|
|
968
|
-
area = +Math.abs(area).toFixed(9);
|
|
969
|
-
|
|
970
|
-
let diff = Math.abs(area - squareDist);
|
|
971
|
-
let areaDiff = Math.abs(100 - (100 / area * (area + diff)))
|
|
972
|
-
let areaThresh = 1000
|
|
973
|
-
|
|
974
|
-
//let ratio = area / (squareDistAvg/areaThresh);
|
|
975
|
-
let ratio = area / (squareDistAvg);
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
//let isFlat = area === 0 ? true : (ratio < 0.15 ? true : false);
|
|
979
|
-
//let isFlat = area === 0 ? true : (area < squareDist/areaThresh ? true : false);
|
|
980
|
-
|
|
981
|
-
let isFlat = area === 0 ? true : area < squareDistAvg / areaThresh;
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
return { area: area, flat: isFlat, thresh: thresh, ratio: ratio, squareDist: squareDist, areaThresh: squareDist / areaThresh };
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
export function checkBezierFlatness(p0, cpts, p) {
|
|
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
879
|
|
|
1005
|
-
let cross1 = Math.abs(dx1 * dy2 - dy1 * dx2);
|
|
1006
880
|
|
|
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
|
-
}
|
|
1027
881
|
|
|
1028
882
|
/**
|
|
1029
883
|
* sloppy distance calculation
|
|
@@ -1041,7 +895,6 @@ export function getDistAv(pt1, pt2) {
|
|
|
1041
895
|
let diff = Math.abs(diffX + diffY) / 2;
|
|
1042
896
|
*/
|
|
1043
897
|
|
|
1044
|
-
|
|
1045
898
|
return diff;
|
|
1046
899
|
}
|
|
1047
900
|
|