svg-path-simplify 0.3.0 → 0.3.5

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.
@@ -0,0 +1,67 @@
1
+
2
+ // split in chunks based on significant points
3
+
4
+ import { pathDataToD } from "./pathData_stringify";
5
+ import { renderPath } from "./visualize";
6
+
7
+
8
+ export function getPolyChunks(pts,
9
+ { closed = true,
10
+ keepCorners = true,
11
+ keepExtremes = true,
12
+ keepInflections = false
13
+ } = {}
14
+ ) {
15
+ let chunks = [[pts[0]]];
16
+ //let chunk = [pts[0]];
17
+ let idx = 0
18
+ let lastChunk = chunks[idx]
19
+
20
+ let l = pts.length
21
+
22
+ // render
23
+ for (let i = 1; i < l; i++) {
24
+ let p0 = i > 0 ? pts[i] : pts[l - 1];
25
+ let p1 = pts[i];
26
+ let p2 = i < l - 1 ? pts[i + 1] : pts[l - 1];
27
+
28
+ // start new chunk
29
+ // keepInflections && p1.isDirChange
30
+ if ((keepExtremes && p1.isExtreme || keepCorners && p1.isCorner )) {
31
+ idx++
32
+ chunks.push([])
33
+ }
34
+
35
+
36
+ lastChunk = chunks[idx]
37
+ lastChunk.push(p1)
38
+ }
39
+
40
+ // test render
41
+ //renderchunks(chunks)
42
+
43
+ return chunks;
44
+ }
45
+
46
+
47
+
48
+ function renderchunks(chunks) {
49
+
50
+ chunks.forEach((chunk, i) => {
51
+
52
+ let stroke = i % 2 === 0 ? 'orange' : 'blue';
53
+ let pathData = [{ type: 'M', values: [chunk[0].x, chunk[0].y] }]
54
+ let d = `M`
55
+
56
+ chunk.forEach(pt => {
57
+
58
+ pathData.push({ type: 'L', values: [pt.x, pt.y] })
59
+ d += ` ${[pt.x, pt.y].join(' ')}`
60
+
61
+ })
62
+
63
+ d = pathDataToD(pathData)
64
+ renderPath(markers, d, stroke, '2%', '0.5')
65
+ })
66
+
67
+ }
@@ -0,0 +1,51 @@
1
+ export function normalizePoly(pts, {
2
+ toObject = true,
3
+ toArray = false,
4
+ flatten = false
5
+ } = {}) {
6
+
7
+ if (flatten) pts = pts.flat(2);
8
+ let poly = toArray ? polyPtsToArray(pts) : polyArrayToObject(pts)
9
+ return poly
10
+ }
11
+
12
+
13
+ export function polyArrayToObject(pts) {
14
+
15
+ // is point object array
16
+ if (pts[0].x !== undefined && pts[0].y !== undefined) return pts
17
+
18
+ let poly = [];
19
+
20
+ // complex poly object array
21
+ if (Array.isArray(pts[0]) && pts[0][0].x !== undefined && pts[0][0].y !== undefined) {
22
+ return pts
23
+ }
24
+ // complex poly value array
25
+ else if (Array.isArray(pts[0][0]) && pts[0][0].length === 2) {
26
+ pts.forEach(sub => {
27
+ poly.push(sub.map(pt => { return { x: pt[0], y: pt[1] } }))
28
+ })
29
+ return poly
30
+ }
31
+
32
+ return pts.map(pt => { return { x: pt[0], y: pt[1] } })
33
+ }
34
+
35
+
36
+ export function polyPtsToArray(pts) {
37
+
38
+ // is already coordinate array
39
+ if (!Array.isArray(pts[0][0]) && pts[0].length === 2) return pts
40
+
41
+ let poly = [];
42
+ if (Array.isArray(pts[0][0]) && pts[0][0].length === 2) {
43
+ pts.forEach(sub => {
44
+ poly.push(sub.map(pt => [pt.x, pt.y]))
45
+ })
46
+ return poly
47
+ }
48
+
49
+ poly = Array.from(pts).map(pt => [pt.x, pt.y])
50
+ return poly
51
+ }
@@ -7,61 +7,84 @@
7
7
 
8
8
  import { checkLineIntersection, getDistManhattan, interpolate, mirrorCpts } from "./geometry";
9
9
  import { getPolyBBox } from "./geometry_bbox";
10
- import { analyzePoly, getPolyChunks, isClosedPolygon } from "./poly_analyze";
11
- import { fitCurveN } from "../poly-fit-curve-schneider";
12
10
  import { renderPath, renderPoint, renderPoly } from "./visualize";
13
- import { simplifyRDP } from "../simplify_poly_RDP";
11
+ import { simplifyPolyRDP } from "../simplify_poly_RDP";
14
12
  import { pathDataFromPoly } from "./pathData_fromPoly";
13
+ import { getPolyChunks } from "./poly_analyze_get_chunks";
14
+ import { analyzePoly, isClosedPolygon } from "./poly_analyze";
15
+ import { fitCurveSchneider } from "../poly-fit-curve-schneider";
16
+ import { simplifyPolyRD } from "../simplify_poly_radial_distance";
17
+
18
+
15
19
 
16
20
  export function simplifyPolygonToPathData(pts, {
17
21
  debug = false,
18
22
  width = 0,
19
23
  height = 0,
20
24
  denoise = 0.9,
21
- keepCorners=true,
22
- keepExtremes=true,
23
- keepInflections=false,
25
+ keepCorners = true,
26
+ keepExtremes = true,
27
+ keepInflections = false,
24
28
  manhattan = false,
25
29
  absolute = false,
26
30
  closed = true,
27
- tolerance = 1
31
+ tolerance = 1,
32
+ simplifyRD = 1,
33
+ simplifyRDP = 1,
28
34
  } = {}) {
29
35
 
30
36
 
31
- denoise = 0
32
37
 
38
+ /*
33
39
  // denoise via RDP
34
40
  if (denoise && denoise !== 1) {
35
- pts = simplifyRDP(pts, {
41
+ pts = simplifyPolyRDP(pts, {
36
42
  width,
37
43
  height,
38
44
  quality: denoise,
39
45
  manhattan,
40
46
  absolute
41
47
  })
48
+ }
49
+ */
50
+
51
+
52
+ /*
53
+ // simplify polygon
54
+ if (simplifyRD != 1) {
55
+ pts = simplifyPolyRD(pts, { quality: simplifyRD+'px' })
56
+ }
57
+
42
58
 
59
+ if (simplifyRDP != 1) {
60
+ pts = simplifyPolyRDP(pts, { quality: simplifyRDP+'px' })
43
61
  }
62
+ */
63
+
44
64
 
45
65
  // get topology of poly
46
- let polyAnalyzed = !keepExtremes&&!keepCorners ? pts : analyzePoly(pts, {
66
+ let polyAnalyzed = !keepExtremes && !keepCorners ? pts : analyzePoly(pts, {
47
67
  debug: false
48
68
  //width,
49
69
  //height
50
70
  })
51
71
 
72
+ //console.log(polyAnalyzed, polyAnalyzed2);
73
+ //return
52
74
 
53
75
  // split into segment chunks
54
- let chunks = !keepExtremes&&!keepCorners ? [polyAnalyzed] : getPolyChunks(polyAnalyzed, {keepCorners,keepExtremes, keepInflections});
76
+ let chunks = !keepExtremes && !keepCorners ? [polyAnalyzed] : getPolyChunks(polyAnalyzed, { keepCorners, keepExtremes, keepInflections });
55
77
 
56
78
 
57
79
  // Schneider curve fit
58
80
  let threshold = width && height ? (width + height) / 2 * 0.004 * tolerance : 2.5
59
81
 
60
- threshold=2
82
+ //threshold = 2
61
83
 
62
84
  let polyPath = simplifyPolyChunks(chunks, {
63
85
  closed,
64
- tolerance: threshold
86
+ tolerance: threshold,
87
+ keepCorners
65
88
  });
66
89
 
67
90
  return polyPath;
@@ -75,6 +98,7 @@ export function simplifyPolygonToPathData(pts, {
75
98
 
76
99
  export function simplifyPolyChunks(chunks = [], {
77
100
  closed = true,
101
+ keepCorners = true,
78
102
  tolerance = 1,
79
103
  } = {}) {
80
104
 
@@ -100,12 +124,14 @@ export function simplifyPolyChunks(chunks = [], {
100
124
  }
101
125
 
102
126
  // nothing to simplify
103
- if (chunklen < 2 || (chunklen === 2 && chunk[1].isExtreme) ) {
127
+ if (chunklen < 2 || (chunklen === 2 && chunk[1].isExtreme)) {
104
128
  pLast = chunk[chunk.length - 1]
105
129
  segments = chunk.map(com => { return { type: 'L', values: [com.x, com.y] } })
106
130
 
107
131
  } else {
108
- segments = fitCurveN(chunk, tolerance)
132
+ segments = fitCurveSchneider(chunk, {
133
+ maxError: tolerance, keepCorners
134
+ })
109
135
  }
110
136
 
111
137
  // remove first segment to connect to last segment
@@ -115,11 +141,12 @@ export function simplifyPolyChunks(chunks = [], {
115
141
 
116
142
 
117
143
  if (closed) pathData.push({ type: 'Z', values: [] })
118
-
119
- //console.log('!!!pathData', pathData);
144
+ //console.log('!!!pathData from poly', pathData);
120
145
 
121
146
  // refine extremes
122
- refineAdjacentExtremes(pathData)
147
+ let refineExtremes = false;
148
+ //refineExtremes=true;
149
+ if (refineExtremes) refineAdjacentExtremes(pathData)
123
150
  return pathData
124
151
 
125
152
  }
@@ -160,13 +187,13 @@ export function refineAdjacentExtremes(pathData) {
160
187
  let vertical2 = dx2 < dist1 && dy2 > dist1
161
188
 
162
189
 
163
- let distCpO=0, distCpN=0, t=1;
190
+ let distCpO = 0, distCpN = 0, t = 1;
164
191
  let cp2N, cp1N;
165
192
 
166
193
  if (horizontal1 || vertical1) {
167
194
 
168
195
  // adjust cp2 to horizontal
169
- cp2N = horizontal1 ? {x:com.values[2], y: p.y} : (vertical1 ? {x:p.x, y:com.values[3]} : {x:com.values[2], y:com.values[3]})
196
+ cp2N = horizontal1 ? { x: com.values[2], y: p.y } : (vertical1 ? { x: p.x, y: com.values[3] } : { x: com.values[2], y: com.values[3] })
170
197
 
171
198
  /*
172
199
  // adjust length
@@ -182,14 +209,14 @@ export function refineAdjacentExtremes(pathData) {
182
209
  com.values[3] = cp2N.y
183
210
 
184
211
  }
185
-
212
+
186
213
 
187
214
  if (horizontal2 || vertical2) {
188
215
  // adjust cp1 to horizontal
189
216
 
190
- cp1N = horizontal2 ?
191
- {x:comN.values[0], y: p.y} :
192
- (vertical2 ? {x:p.x, y:comN.values[1]} : {x:comN.values[0], y:comN.values[1]})
217
+ cp1N = horizontal2 ?
218
+ { x: comN.values[0], y: p.y } :
219
+ (vertical2 ? { x: p.x, y: comN.values[1] } : { x: comN.values[0], y: comN.values[1] })
193
220
 
194
221
  /*
195
222
  // adjust length
@@ -7,6 +7,42 @@
7
7
  import { getDistAv, getDistManhattan } from "./geometry";
8
8
 
9
9
 
10
+
11
+ export function detectAccuracyPoly(pts) {
12
+
13
+ let minDim = Infinity
14
+ let dims = [];
15
+ //console.log(pathData);
16
+
17
+ // add average distances
18
+ for (let i = 1, len = pts.length; i < len; i++) {
19
+ let pt = pts[i];
20
+ let { p0 = null, p = null, dimA = 0 } = pt;
21
+
22
+ // use existing averave dimension value or calculate
23
+ if ( p && p0) {
24
+ dimA = dimA ? dimA : getDistManhattan(p0, p);
25
+
26
+ if (dimA) dims.push(dimA);
27
+ if (dimA && dimA < minDim) minDim = dimA;
28
+ }
29
+ }
30
+
31
+ let dim_min = dims.sort()
32
+ let sliceIdx = Math.ceil(dim_min.length / 8);
33
+ dim_min = dim_min.slice(0, sliceIdx);
34
+ let minVal = dim_min.reduce((a, b) => a + b, 0) / sliceIdx;
35
+
36
+ let threshold = 75
37
+ let decimalsAuto = minVal > threshold * 1.5 ? 0 : Math.floor(threshold / minVal).toString().length
38
+ // clamp
39
+ return Math.min(Math.max(0, decimalsAuto), 8)
40
+
41
+ }
42
+
43
+
44
+
45
+
10
46
  export function detectAccuracy(pathData) {
11
47
 
12
48
  let minDim = Infinity
@@ -3,7 +3,41 @@
3
3
  * transform property object
4
4
  */
5
5
 
6
- export function parseCSSTransform(transformString, transformOrigin={x:0, y:0}) {
6
+ export function parseTransform(transformString, transformOrigin = { x: 0, y: 0 }) {
7
+
8
+ //let regex = /(\w+)\(([^)]+)\)/g;
9
+ let transArr = transformString.match(/[a-z]+\([^)]*\)/gi);
10
+
11
+ //console.log('transArr', transArr);
12
+
13
+ let transforms = [];
14
+ transArr.forEach(trans => {
15
+ let [prop, vals] = trans.split(/\(|\)/).filter(Boolean)
16
+ //let prop = vals[0];
17
+ vals = vals.split(/,| /).filter(Boolean).map(Number)
18
+ console.log('trans', prop, vals);
19
+
20
+ // rotate has origin
21
+ if(prop==='rotate' && vals.length===3){
22
+ transforms.push({prop:'translate', values:[vals[1], vals[2]]})
23
+ transforms.push({prop:'rotate', values:[vals[0]]})
24
+ transforms.push({prop:'translate', values:[-vals[1], -vals[2]]})
25
+ }else{
26
+ transforms.push({prop, values:[vals[0]]})
27
+ }
28
+ })
29
+
30
+ console.log('transforms', transforms);
31
+
32
+ //let values =
33
+
34
+ }
35
+
36
+
37
+ export function parseCSSTransform(transformString, transformOrigin = { x: 0, y: 0 }) {
38
+
39
+ if (!transformString) return false;
40
+
7
41
  let transformOptions = {
8
42
  transforms: [],
9
43
  transformOrigin,
@@ -53,7 +87,11 @@ export function parseCSSTransform(transformString, transformOrigin={x:0, y:0}) {
53
87
  case 'skewY':
54
88
  transformOptions.transforms.push({ skew: [0, values[0] || 0] });
55
89
  break;
90
+
56
91
  case 'rotate':
92
+
93
+ console.log('rotate', values);
94
+
57
95
  transformOptions.transforms.push({ rotate: [0, 0, values[0] || 0] });
58
96
  break;
59
97
  case 'matrix':
@@ -165,9 +203,9 @@ export function getMatrix2D(transformations = [], origin = { x: 0, y: 0 }) {
165
203
 
166
204
 
167
205
  // Process transformations in the provided order (right-to-left)
168
- for (const transform of transformations) {
169
- const type = Object.keys(transform)[0]; // Get the transformation type (e.g., "translate")
170
- const values = transform[type] || defaults[type]; // Use default values if none provided
206
+ for (let transform of transformations) {
207
+ let type = Object.keys(transform)[0]; // Get the transformation type (e.g., "translate")
208
+ let values = transform[type] || defaults[type]; // Use default values if none provided
171
209
 
172
210
  // Destructure values with fallbacks
173
211
  let [x, y = defaults[type][1]] = values
@@ -268,6 +306,6 @@ export function getCSSTransform({
268
306
  let cssParent = `perspective-origin:${perspectiveOrigin.x}px ${perspectiveOrigin.y}px; perspective:${perspective}px;`;
269
307
 
270
308
  css = `transform:${css.join(' ')};transform-origin:${transFormOrigin.x}px ${transFormOrigin.y}px;`;
271
- return {el:css, parent:cssParent}
309
+ return { el: css, parent: cssParent }
272
310
 
273
311
  }
@@ -1,6 +1,79 @@
1
1
  import { getMatrix, parseCSSTransform } from './svg-styles-getTransforms';
2
2
  import { attLookup } from './svg-styles-to-attributes-const';
3
3
 
4
+
5
+
6
+ export function getElementProps(el, {
7
+ removeNameSpaced = true,
8
+ decimals = -1
9
+ } = {}) {
10
+
11
+ let nodeName = el.nodeName.toLowerCase();
12
+ let attProps = getElAttributes(el)
13
+ let cssProps = getElStyleProps(el)
14
+
15
+
16
+ // normalize transform attributes
17
+ if (attProps['transform']) {
18
+
19
+ let transAtt = attProps['transform']
20
+ let transArr = transAtt.match(/[a-z]+\([^)]*\)/gi);
21
+
22
+
23
+ //console.log(`attProps['transform']`, transAtt);
24
+
25
+
26
+ let transforms = [];
27
+ transArr.forEach(trans => {
28
+ let [prop, vals] = trans.split(/\(|\)/).filter(Boolean)
29
+ //let prop = vals[0];
30
+ vals = vals.split(/,| /).filter(Boolean).map(Number)
31
+ console.log('trans', prop, vals);
32
+
33
+ let cssTrans = '';
34
+
35
+ // rotate has origin
36
+ if (prop === 'rotate' && vals.length === 3) {
37
+
38
+ cssTrans = `
39
+ translate(${vals[1]}px, ${vals[2]}px)
40
+ rotate(${vals[0]}deg)
41
+ translate(${-vals[1]}px, ${-vals[2]}px)
42
+ `
43
+ /*
44
+ transforms.push({ prop: 'translate', values: [vals[1], vals[2]] })
45
+ transforms.push({ prop: 'rotate', values: [vals[0]] })
46
+ transforms.push({ prop: 'translate', values: [-vals[1], -vals[2]] })
47
+ */
48
+ } else {
49
+ cssTrans = `
50
+ ${prop}(${vals[1]}px, ${vals[2]}px)
51
+ rotate(${vals[0]}deg)
52
+ translate(${-vals[1]}px, ${-vals[2]}px)
53
+ `
54
+
55
+
56
+ //transforms.push({ prop, values: [vals[0]] })
57
+ }
58
+ })
59
+
60
+ console.log('transforms', transforms);
61
+
62
+
63
+
64
+
65
+ }
66
+
67
+ // merge properties
68
+ let props = {
69
+ ...attProps,
70
+ ...cssProps
71
+ }
72
+
73
+
74
+ }
75
+
76
+
4
77
  export function svgStylesToAttributes(el, {
5
78
  removeNameSpaced = true,
6
79
  decimals = -1
@@ -10,6 +83,11 @@ export function svgStylesToAttributes(el, {
10
83
  let attProps = getElAttributes(el)
11
84
  let cssProps = getElStyleProps(el)
12
85
 
86
+ // normalize transform attributes
87
+ if (attProps['transform']) {
88
+ console.log(`attProps['transform']`, attProps['transform']);
89
+ }
90
+
13
91
  // merge properties
14
92
  let props = {
15
93
  ...attProps,
@@ -21,7 +99,7 @@ export function svgStylesToAttributes(el, {
21
99
 
22
100
  // parse CSS transforms
23
101
  let cssTrans = cssProps['transform']
24
-
102
+
25
103
  if (cssTrans) {
26
104
  let transStr = `${cssTrans}`
27
105
  let transformObj = parseCSSTransform(transStr)
@@ -98,13 +176,13 @@ function parseInlineStyle(styleAtt = '') {
98
176
  return props
99
177
  }
100
178
 
101
- function getElStyleProps(el) {
179
+ export function getElStyleProps(el) {
102
180
  let styleAtt = el.getAttribute('style')
103
181
  let props = styleAtt ? parseInlineStyle(styleAtt) : {}
104
182
  return props
105
183
  }
106
184
 
107
- function getElAttributes(el) {
185
+ export function getElAttributes(el) {
108
186
  let props = {}
109
187
  let atts = [...el.attributes].map((att) => att.name);
110
188
  let l = atts.length;
@@ -1,3 +1,4 @@
1
+ import { flattenTransforms } from "../svg_flatten_transforms";
1
2
  import { parsePathDataString } from "./pathData_parse";
2
3
  import { shapeElToPath } from "./pathData_parse_els";
3
4
  import { svgStylesToAttributes } from "./svg-styles-to-attributes";
@@ -27,6 +28,8 @@ export function cleanUpSVG(svgMarkup, {
27
28
  removeNameSpaced = true,
28
29
  attributesToGroup = true,
29
30
  shapesToPaths = false,
31
+ convertTransforms = false,
32
+ cleanUpStrokes=true,
30
33
  decimals = -1,
31
34
  excludedEls = [],
32
35
  } = {}) {
@@ -113,6 +116,24 @@ export function cleanUpSVG(svgMarkup, {
113
116
  //console.log('!!!svgMarkup', svgMarkup);
114
117
 
115
118
 
119
+ // remove empty defs
120
+ let defs = svg.querySelectorAll('defs')
121
+ defs.forEach(def=>{
122
+ let children=[...def.children]
123
+ let attributes = [...def.attributes]
124
+ if(!children.length){
125
+ def.remove()
126
+ }
127
+ })
128
+
129
+
130
+ /*
131
+ if(convertTransforms){
132
+ flattenTransforms(svg)
133
+ }
134
+ */
135
+
136
+
116
137
  if (returnDom) return svg
117
138
  let markup = stringifySVG(svg)
118
139