svg-path-simplify 0.2.7 → 0.3.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.
@@ -1,7 +1,8 @@
1
1
  import { deg2rad, rad2Deg } from "../constants";
2
- import { simplifyRDP } from "../simplify_poly_RDP";
3
- import { simplifyRD } from "../simplify_poly_radial_distance";
4
- import { getDistAv, getTatAngles, pointAtT } from "./geometry";
2
+ import { simplifyPolyRDP } from "../simplify_poly_RDP";
3
+ import { simplifyPolyRD } from "../simplify_poly_radial_distance";
4
+ import { getDistAv, getDistManhattan, getSquareDistance, getTatAngles, interpolate, pointAtT } from "./geometry";
5
+ import { getBezierArea, getPolygonArea } from "./geometry_area";
5
6
  import { getPolyBBox } from "./geometry_bbox";
6
7
  import { addDimensionData, analyzePathData } from "./pathData_analyze";
7
8
  import { arcToBezier } from "./pathData_convert";
@@ -10,309 +11,200 @@ import { addExtremePoints } from "./pathData_split";
10
11
  import { pathDataToD } from "./pathData_stringify";
11
12
  import { analyzePoly } from "./poly_analyze";
12
13
  import { getCurvePathData } from "./poly_to_pathdata";
14
+ import { detectAccuracyPoly, roundTo } from "./rounding";
13
15
  import { renderPoint } from "./visualize";
14
16
 
15
17
 
16
- export function getPathDataPolyPrecise(pathData = []) {
17
-
18
- let poly = [];
19
- for (let i = 0; i < pathData.length; i++) {
20
- let com = pathData[i]
21
- let prev = i > 0 ? pathData[i - 1] : pathData[i];
22
- let { type, values } = com;
23
- let p0 = { x: prev.values[prev.values.length - 2], y: prev.values[prev.values.length - 1] };
24
- let p = values.length ? { x: values[values.length - 2], y: values[values.length - 1] } : ''
25
- let cp1 = values.length ? { x: values[0], y: values[1] } : ''
26
-
27
- switch (type) {
28
-
29
- // convert to cubic to get polygon
30
- case 'A':
31
- if (typeof arcToBezier !== 'function') {
32
- //console.log('has no arc to cubic conversion');
33
- break;
34
- }
35
- let cubic = arcToBezier(p0, values)
36
- cubic.forEach(com => {
37
- let vals = com.values
38
- let cp1 = { x: vals[0], y: vals[1] }
39
- let cp2 = { x: vals[2], y: vals[3] }
40
- let p = { x: vals[4], y: vals[5] }
41
- poly.push(cp1, cp2, p)
42
- })
43
- break;
44
-
45
- case 'C':
46
- let cp2 = { x: values[2], y: values[3] }
47
- poly.push(cp1, cp2)
48
- break;
49
- case 'Q':
50
- poly.push(cp1)
51
- break;
52
- }
53
-
54
- // M and L commands
55
- if (type.toLowerCase() !== 'z') {
56
- poly.push(p)
57
- }
58
- }
59
-
60
- return poly;
61
- }
62
-
63
18
 
64
19
 
20
+ /**
21
+ * creates precise polygon approximation from pathdata
22
+ * converts arc to cubis
23
+ */
65
24
  export function pathDataToPolygon(pathData, {
66
- angles = [],
67
- split = 0,
68
- getPathData = true,
69
- width = 0,
70
- height = 0
25
+ precisionPoly = 1,
26
+ autoAccuracy=false,
27
+ polyFormat='points',
28
+ decimals=-1,
29
+ simplifyRD=1,
30
+ simplifyRDP=1,
71
31
  } = {}) {
72
32
 
33
+ //console.log(pathData);
73
34
 
74
35
  let l = pathData.length;
75
36
  let M = { x: pathData[0].values[0], y: pathData[0].values[1] }
76
37
  let p0 = M
77
-
78
- let minT = 1 / split * 0.5;
79
- let maxT = 1 - minT
38
+ let p = M
80
39
 
81
40
 
82
41
  // collect polygon vertices
83
42
  let pathDataPoly = []
84
- let pts = [p0]
85
-
86
- let ptsEnd = [0]
87
- let ptCount = 1
88
-
89
- // get max length
90
- let minLength = 0;
91
43
 
44
+ // end point vertices
45
+ let pts = [p0]
92
46
 
93
- split = !split ? 1 : split;
94
-
95
- if (width && height) {
96
- minLength = (width + height) * 0.025 / split
97
- } else {
98
- //let areas = pathData.map(com => com.cptArea || 0).filter(Boolean).sort()
99
- let lengths = pathData.map(com => com.dimA || 0).filter(Boolean).sort()
100
- minLength = lengths[0]
101
- //console.log('areas', areas, 'lengths', lengths);
102
- }
103
47
 
104
- //minLength*=0.5
48
+ let dims = []
49
+ let areas = []
105
50
 
51
+ // minimum dimension
106
52
  for (let i = 1; i < l; i++) {
107
53
  let com = pathData[i];
108
- //let comPrev = pathData[i - 1];
109
- let comNext = pathData[i + 1] || null;
110
- let { type, values } = com;
111
- let valuesL = values.length;
112
- let p = valuesL ? { x: values[valuesL - 2], y: values[valuesL - 1] } : M;
113
-
114
- if (type === 'C' || type === 'Q') {
115
-
116
- let cp1 = { x: values[0], y: values[1] }
117
- let cp2 = type === 'C' ? { x: values[2], y: values[3] } : cp1
118
- let cpts = type === 'C' ? [p0, cp1, cp2, p] : [p0, cp1, p];
119
-
120
-
121
- // calculate split according to length
122
- let length = com.dimA
123
- let rat = Math.floor(length / (minLength))
124
- split = Math.ceil(length / minLength)
125
-
126
- let tArr = []
127
- for (let i = 1; i < split; i++) {
128
- tArr.push(1 / split * i)
129
- }
130
-
131
- tArr.forEach(t => {
132
- let pt = pointAtT(cpts, t)
133
- pts.push(pt)
134
- ptCount++
135
- })
136
-
137
- }
138
-
139
- if (type === 'M') {
140
- M = p
141
- }
142
-
54
+ let { type, values, p0, p, dimA = 0 } = com;
143
55
 
144
- p.area = com.cptArea || 0
145
- p.isExtreme = com.extreme || false
146
- p.isCorner = com.corner || false
147
- p.isDirChange = com.directionChange || false;
56
+ dims.push(+dimA.toFixed(8))
148
57
 
149
58
  // segment end point
150
59
  pts.push(p)
151
-
152
- // exclude for polygon simplification
153
- if (com.extreme || com.corner || (comNext && comNext.type !== type) || type === 'L') {
154
- //console.log('is ext' , com);
155
- ptsEnd.push(ptCount)
156
- //renderPoint(markers, p, 'magenta', '2%', '0.5')
157
- }
158
-
159
- ptCount++
160
-
161
- p0 = p
162
-
163
60
  }
164
61
 
165
- // reduce poly vertices
166
- //pts = simplifyRD(pts, { quality: 0.5, exclude: ptsEnd, width, height })
167
- pts = simplifyRD(pts, { quality: 0.5, width, height })
168
- //pts = simplifyRDP(pts, { quality: 0.8, width, height })
169
- //console.log(ptsEnd);
170
62
 
171
- /*
172
- pts.forEach(pt => {
173
- //renderPoint(markers, pt, 'cyan', '1%', '0.5')
174
- })
175
- //console.log(pts);
176
- */
63
+ let pts2 = [pts[0]]
177
64
 
65
+ // adjustments for very small or large paths
66
+ dims = dims.filter(Boolean).sort()
67
+ let dimMax = dims[dims.length - 1]
178
68
 
179
- pathDataPoly = pathDataFromPoly(pts)
180
- return getPathData ? pathDataPoly : pts;
181
- }
69
+ let scale = dimMax > 2 && dimMax < 25 ? 1 : (20 / dimMax);
70
+ precisionPoly = precisionPoly * scale
182
71
 
72
+ // check how much segments contribute to total area
73
+ for (let i = 1; i < l; i++) {
74
+ let com = pathData[i];
75
+ let { type, values, p0, p, cp1 = null, cp2 = null, dimA } = com;
183
76
 
77
+ let distAv = (dimA)
184
78
 
185
- // old function
186
- export function pathDataToPolySingle(pathData, addExtremes = true) {
79
+ let cpts = cp1 && cp2 ? [p0, cp1, cp2, p] : (cp1 ? [p0, cp1, p] : []);
187
80
 
81
+ if (cpts.length) {
82
+ let ptM = cp2 ? interpolate(cp1, cp2, 0.5) : cp1
83
+ let distCpt1 = getDistManhattan(p0, ptM)
84
+ let distCpt2 = getDistManhattan(p, ptM)
85
+ let dist4 = (distCpt1 + distCpt2) * 0.2
86
+ distAv = (dist4 + dimA)
87
+ }
188
88
 
189
- let dimMin = Infinity;
190
- let dimMax = 0;
89
+ // calculate split value according to manhattan distance of segment
90
+ let rat = Math.ceil(distAv * 0.2 * precisionPoly)
91
+ let split = Math.ceil(rat)
191
92
 
192
93
 
193
- /**
194
- * add extremes to beziers
195
- * to reproduce the shape better
196
- */
197
- if (addExtremes) {
198
- pathData = addExtremePoints(pathData, 0.1, 0.9)
94
+ if (split && cpts.length) {
95
+ let step = split ? 1 / (split + 1) : 0
96
+ for (let j = 1; j <= split; j++) {
97
+ let t = step * j
98
+ let pt = pointAtT(cpts, t)
99
+ pts2.push(pt)
100
+ }
101
+ }
102
+ pts2.push(p)
199
103
  }
200
104
 
201
- //console.log(pathData);
202
105
 
203
- let pathDataPlus = analyzePathData(pathData);
204
- let { bb } = pathDataPlus;
205
- let thresh = (bb.width + bb.height) / 2 / 50
106
+ // simplify polygon
107
+ if(simplifyRD>0){
108
+ pts2 = simplifyPolyRD(pts2, {quality:simplifyRD+'px'})
109
+ }
206
110
 
207
- pathData = pathDataPlus.pathData
208
111
 
112
+ if(simplifyRDP>0){
113
+ pts2 = simplifyPolyRDP(pts2, {quality:simplifyRDP+'px'})
114
+ }
209
115
 
210
- /**
211
- * approximate min and max segment sizes
212
- * for segment splitting
213
- */
214
- let dimArr = pathData.filter(com => com.dimA).sort((a, b) => a.dimA - b.DimA)
215
- let dimMinL = dimArr[0].dimA
216
- let dimMaxL = dimArr[dimArr.length - 1].dimA
217
- //console.log('dimArr', dimArr, dimMaxL);
218
- if (dimMinL && dimMinL < dimMin) dimMin = dimMinL;
219
- if (dimMaxL && dimMaxL > dimMax) dimMax = dimMaxL;
220
116
 
221
- //console.log(dimMin, dimMax);
222
117
 
223
- // find split point based on smallest point distance
224
- dimMin = (dimMin * 2 + dimMax) / 2 / 4
225
- //dimMin = (bb.width + bb.height) / 2 / 8
118
+ pathDataPoly = pathDataFromPoly(pts2)
119
+ pathData = pathDataPoly
120
+
121
+ if(autoAccuracy){
122
+ decimals = detectAccuracyPoly(pts)
123
+ //console.log('decimals', decimals);
124
+ }
226
125
 
227
- // collect vertices
228
- let polyArr = [];
126
+ let poly = decimals>-1 ? pts2.map(pt => { return { x: roundTo(pt.x, decimals), y: roundTo(pt.y, decimals) } }) : pts2.map(pt => { return { x: pt.x, y: pt.y } })
229
127
 
230
- let p0 = { x: pathData[0].p0.x, y: pathData[0].p0.y, extreme: pathData[0].extreme, corner: pathData[0].corner }
231
- let poly = [p0];
128
+ if(polyFormat==='array'){
129
+ poly = poly.map(pt => { return [pt.x, pt.y] })
130
+ }
131
+ else if(polyFormat==='string'){
132
+ poly = poly.map(pt => { return [pt.x, pt.y].join(',') }).join(' ')
133
+ }
232
134
 
233
- for (let i = 1, l = pathData.length; i < l; i++) {
135
+ return { pathData, poly }
234
136
 
235
- let com = pathData[i];
236
- let { type, values, extreme = false, corner = false, dimA = null, p0, p, cp1 = null, cp2 = null } = com;
137
+ }
237
138
 
238
- dimA = getDistAv(p0, p);
239
139
 
240
140
 
241
- if (extreme) {
242
- //renderPoint(markers, p, 'cyan')
243
- }
244
141
 
245
- let split = (type === 'C' || type === 'Q') && dimA ? Math.ceil(dimA / dimMin) : 0;
142
+ /**
143
+ * creates precise polygon
144
+ * from command end points
145
+ * converts arc to cubis
146
+ */
147
+ export function getPathDataPolyPrecise(pathData = [], {
148
+ precision = 1
149
+ } = {}) {
246
150
 
151
+ let poly = [];
152
+ for (let i = 0; i < pathData.length; i++) {
153
+ let com = pathData[i]
154
+ let prev = i > 0 ? pathData[i - 1] : pathData[i];
155
+ let { type, values } = com;
156
+ let p0 = { x: prev.values[prev.values.length - 2], y: prev.values[prev.values.length - 1] };
157
+ let p = values.length ? { x: values[values.length - 2], y: values[values.length - 1] } : ''
158
+ let cp1 = values.length ? { x: values[0], y: values[1] } : ''
247
159
 
248
- //console.log(com);
249
- p.extreme = extreme
250
- p.corner = corner
160
+ switch (type) {
251
161
 
252
- //console.log(p);
162
+ // convert to cubic to get polygon
163
+ case 'A':
164
+ if (typeof arcToBezier !== 'function') {
165
+ //console.log('has no arc to cubic conversion');
166
+ break;
167
+ }
168
+ let cubic = arcToBezier(p0, values)
169
+ cubic.forEach(com => {
170
+ let vals = com.values
171
+ let cp1 = { x: vals[0], y: vals[1] }
172
+ let cp2 = { x: vals[2], y: vals[3] }
173
+ let p = { x: vals[4], y: vals[5] }
174
+ poly.push(cp1, cp2, p)
175
+ })
176
+ break;
253
177
 
254
- if ((type === 'C' || type === 'Q') && split) {
255
- let splitT = 1 / split;
256
- for (let i = 1; i < split; i++) {
257
- let t = splitT * i;
258
- let cpts = type === 'C' ? [cp1, cp2] : [cp1];
259
- let ptI = pointAtT([p0, ...cpts, p], t)
260
- poly.push(ptI)
261
- }
178
+ case 'C':
179
+ let cp2 = { x: values[2], y: values[3] }
180
+ poly.push(cp1, cp2)
181
+ break;
182
+ case 'Q':
183
+ poly.push(cp1)
184
+ break;
262
185
  }
263
- poly.push(p)
264
-
265
- }
266
-
267
186
 
268
- // remove short
269
- let remove = new Set([])
270
- for (let i = 1, l = poly.length; i < l; i++) {
271
- let p = poly[i - 1]
272
- let pN = poly[i]
273
-
274
- let dist1 = getDistAv(p, pN)
275
- if (dist1 < thresh && pN.extreme) {
276
- let pR = p.extreme ? pN : p
277
- let idx = p.extreme ? i : i - 1
278
- //console.log('remove', idx);
279
- remove.add(idx)
187
+ // M and L commands
188
+ if (type.toLowerCase() !== 'z') {
189
+ poly.push(p)
280
190
  }
281
191
  }
282
192
 
193
+ return poly;
194
+ }
283
195
 
284
- remove = Array.from(remove).reverse();
285
- //console.log(remove);
286
-
287
- for (let i = 0; i < remove.length; i++) {
288
- let idx = remove[i];
289
- //console.log('idx', idx);
290
- poly.splice(idx, 1)
291
- }
292
-
293
- poly.splice(poly.length - 1, poly.length)
294
196
 
295
197
 
296
- let polyAtt = poly.map(pt => `${pt.x} ${pt.y} `).join(' ')
297
- //console.log('polyAtt', polyAtt);
298
198
 
299
- //markers.insertAdjacentHTML('beforeend', `<polygon points="${polyAtt}" stroke="red" fill="none"/>`)
300
199
 
301
200
 
302
- poly = analyzePoly(poly, false)
303
- let pathDataP = getCurvePathData(poly, 0.666, true)
304
- let d = pathDataToD(pathDataP)
305
201
 
306
- console.log(d);
307
- //markers.insertAdjacentHTML('beforeend', `<path d="${d}" stroke="green" fill="none" stroke-width="1%"/>`)
308
202
 
309
203
 
310
204
 
311
205
 
312
206
 
313
- return poly
314
207
 
315
- }
316
208
 
317
209
 
318
210