svg-path-simplify 0.4.1 → 0.4.3
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 +19 -0
- package/README.md +6 -4
- package/dist/svg-path-simplify.esm.js +2450 -888
- package/dist/svg-path-simplify.esm.min.js +2 -2
- package/dist/svg-path-simplify.js +2450 -888
- package/dist/svg-path-simplify.min.js +2 -2
- package/dist/svg-path-simplify.pathdata.esm.js +167 -85
- package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
- package/docs/privacy-webapp.md +24 -0
- package/index.html +333 -132
- package/package.json +5 -2
- package/src/css_parse.js +317 -0
- package/src/detect_input.js +34 -4
- package/src/pathData_simplify_harmonize_cpts.js +77 -1
- package/src/pathSimplify-main.js +246 -262
- package/src/pathSimplify-presets.js +243 -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 +144 -0
- package/src/svgii/convert_units.js +8 -2
- package/src/svgii/geometry.js +182 -3
- package/src/svgii/geometry_length.js +237 -0
- package/src/svgii/pathData_convert.js +43 -1
- package/src/svgii/pathData_fix_directions.js +6 -0
- 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 +189 -189
- 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 +8 -7
- package/src/svgii/svg-styles-to-attributes-const.js +1 -0
- package/src/svgii/svg_cleanup.js +467 -421
- package/src/svgii/svg_cleanup_convertPathLength.js +32 -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 +72 -0
- package/src/svgii/svg_cleanup_ungroup.js +36 -0
- package/src/svgii/svg_el_parse_style_props.js +76 -28
- package/src/svgii/svg_getElementLength.js +67 -0
- package/tests/testSVG_shape.js +59 -0
- package/tests/testSVG_transform.js +61 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { deg2rad } from "../constants";
|
|
2
|
+
import { svgArcToCenterParam, toParametricAngle } from "./geometry";
|
|
3
|
+
import { getCircleArcLength, getEllipseLengthLG, getLegendreGaussValues, getLength, waArr_global } from "./geometry_length";
|
|
4
|
+
import { getPathDataVerbose } from "./pathData_analyze";
|
|
5
|
+
import { splitSubpaths } from "./pathData_split";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export function getPathDataLength(pathData = []) {
|
|
9
|
+
let len = 0
|
|
10
|
+
let pathDataArr = splitSubpaths(pathData);
|
|
11
|
+
|
|
12
|
+
for (let i = 0; i < pathDataArr.length; i++) {
|
|
13
|
+
let pathData = pathDataArr[i]
|
|
14
|
+
|
|
15
|
+
// add verbose point data if not present
|
|
16
|
+
if (pathData[0].p === undefined) pathData = getPathDataVerbose(pathData);
|
|
17
|
+
|
|
18
|
+
// Calculate Legendre Gauss weight and abscissa values
|
|
19
|
+
if (!waArr_global.length) {
|
|
20
|
+
//console.log('no LG');
|
|
21
|
+
let waArr = getLegendreGaussValues(48)
|
|
22
|
+
waArr.forEach(wa => {
|
|
23
|
+
waArr_global.push(wa)
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let waArr = waArr_global;
|
|
28
|
+
|
|
29
|
+
pathData.forEach(com => {
|
|
30
|
+
let { type, values, p0, p, cp1 = null, cp2 = null } = com;
|
|
31
|
+
let pts = [p0]
|
|
32
|
+
if (type === 'C' || type === 'Q') pts.push(cp1)
|
|
33
|
+
if (type === 'C') pts.push(cp2)
|
|
34
|
+
pts.push(p)
|
|
35
|
+
let comLen = 0
|
|
36
|
+
|
|
37
|
+
if (type === 'A') {
|
|
38
|
+
|
|
39
|
+
// get parametrized arc properties
|
|
40
|
+
let [largeArc, sweep] = [com.values[3], com.values[4]];
|
|
41
|
+
let arcData = svgArcToCenterParam(p0.x, p0.y, com.values[0], com.values[1], com.values[2], largeArc, sweep, p.x, p.y, false)
|
|
42
|
+
let { cx, cy, rx, ry, startAngle, endAngle, deltaAngle, xAxisRotation } = arcData
|
|
43
|
+
|
|
44
|
+
//is circle
|
|
45
|
+
if (rx === ry) {
|
|
46
|
+
comLen = getCircleArcLength(rx, Math.abs(deltaAngle))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// is ellipse
|
|
50
|
+
else {
|
|
51
|
+
xAxisRotation = xAxisRotation * deg2rad;
|
|
52
|
+
startAngle = toParametricAngle((startAngle - xAxisRotation), rx, ry)
|
|
53
|
+
endAngle = toParametricAngle((endAngle - xAxisRotation), rx, ry)
|
|
54
|
+
|
|
55
|
+
// recalculate parametrized delta
|
|
56
|
+
let deltaAngle_param = endAngle - startAngle;
|
|
57
|
+
|
|
58
|
+
let signChange = deltaAngle > 0 && deltaAngle_param < 0 || deltaAngle < 0 && deltaAngle_param > 0;
|
|
59
|
+
|
|
60
|
+
//deltaAngle = xAxisRotation>0 ? endAngle- startAngle: deltaAngle;
|
|
61
|
+
deltaAngle = signChange ? deltaAngle : deltaAngle_param;
|
|
62
|
+
|
|
63
|
+
// adjust end angle
|
|
64
|
+
if (sweep && startAngle > endAngle) {
|
|
65
|
+
endAngle += Math.PI * 2
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!sweep && startAngle < endAngle) {
|
|
69
|
+
endAngle -= Math.PI * 2
|
|
70
|
+
}
|
|
71
|
+
comLen = getEllipseLengthLG(rx, ry, startAngle, endAngle, waArr)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
else {
|
|
76
|
+
comLen = getLength(pts, {
|
|
77
|
+
t: 1,
|
|
78
|
+
waArr
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
len += comLen;
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return len;
|
|
86
|
+
}
|
|
@@ -103,8 +103,10 @@ const sanitizeArc = (val='', valueIndex=0) => {
|
|
|
103
103
|
|
|
104
104
|
|
|
105
105
|
export function parsePathDataString(d, debug = true, limit=0) {
|
|
106
|
+
if(!d) return []
|
|
106
107
|
d = d.trim();
|
|
107
108
|
|
|
109
|
+
|
|
108
110
|
if(limit) console.log('!!!limit', limit);
|
|
109
111
|
|
|
110
112
|
let pathDataObj = {
|
|
@@ -16,160 +16,25 @@ import { autoRound, roundTo } from './rounding.js';
|
|
|
16
16
|
import { attLookup } from './svg-styles-to-attributes-const.js';
|
|
17
17
|
import { qrDecomposeMatrix } from './transform_qr_decompose.js';
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
convert_poly = false,
|
|
24
|
-
convert_lines = false
|
|
25
|
-
} = {}) {
|
|
26
|
-
|
|
27
|
-
//console.log('pathElToShape', convert_rects, convert_ellipses, convert_lines );
|
|
28
|
-
|
|
29
|
-
let pathData = parsePathDataNormalized(el.getAttribute('d'));
|
|
30
|
-
let coms = Array.from(new Set(pathData.map(com => com.type))).join('')
|
|
31
|
-
|
|
32
|
-
let hasArcs = (/[a]/gi).test(coms)
|
|
33
|
-
let hasBeziers = (/[csqt]/gi).test(coms)
|
|
34
|
-
let hasLines = (/[l]/gi).test(coms)
|
|
35
|
-
let isPoly = !(/[acqts]/gi).test(coms)
|
|
36
|
-
let closed = (/[z]/gi).test(coms)
|
|
37
|
-
let shape = null;
|
|
38
|
-
let type = null
|
|
39
|
-
|
|
40
|
-
let attributes = getElementAtts(el)
|
|
41
|
-
let attsNew = {}
|
|
42
|
-
let decimals = 7;
|
|
43
|
-
|
|
44
|
-
if (isPoly) {
|
|
45
|
-
|
|
46
|
-
// is line
|
|
47
|
-
if (pathData.length === 2 && convert_lines) {
|
|
48
|
-
type = 'line'
|
|
49
|
-
shape = document.createElementNS(svgNs, type)
|
|
50
|
-
let [x1, y1, x2, y2] = [...pathData[0].values, ...pathData[1].values].map(val => roundTo(val, decimals))
|
|
51
|
-
attsNew = { x1, y1, x2, y2 }
|
|
52
|
-
}
|
|
53
|
-
// polygon, polyline or rect
|
|
54
|
-
else {
|
|
55
|
-
|
|
56
|
-
let vertices = getPathDataVertices(pathData);
|
|
57
|
-
let bb = getPolyBBox(vertices)
|
|
58
|
-
let areaPoly = getPolygonArea(vertices, true)
|
|
59
|
-
let areaRect = bb.width * bb.height;
|
|
60
|
-
let areaDiff = Math.abs(1 - areaRect / areaPoly);
|
|
61
|
-
|
|
62
|
-
// is rect
|
|
63
|
-
if (convert_rects && areaDiff < 0.01) {
|
|
64
|
-
type = 'rect'
|
|
65
|
-
shape = document.createElementNS(svgNs, type)
|
|
66
|
-
let { x, y, width, height } = bb
|
|
67
|
-
attsNew = { x, y, width, height }
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
// polyline or polygon
|
|
71
|
-
else if(convert_poly) {
|
|
72
|
-
type = closed ? 'polygon' : 'polyline';
|
|
73
|
-
shape = document.createElementNS(svgNs, type)
|
|
74
|
-
let points = vertices.map(pt => { return [pt.x, pt.y] }).flat().map(val => roundTo(val, decimals)).join(' ')
|
|
75
|
-
attsNew = { points }
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
// circles or ellipses
|
|
80
|
-
else if (!hasLines && convert_ellipses) {
|
|
81
|
-
|
|
82
|
-
// try to convert cubics to arcs
|
|
83
|
-
if (!hasArcs && hasBeziers) {
|
|
84
|
-
pathData = pathDataCubicsToArc(pathData, { areaThreshold: 2.5 })
|
|
85
|
-
hasArcs = pathData.filter(com => com.type === 'A').length;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (hasArcs) {
|
|
90
|
-
let pathData2 = getPathDataVerbose(pathData, { addArcParams: true })
|
|
91
|
-
let arcComs = pathData2.filter(com => com.type === 'A')
|
|
92
|
-
|
|
93
|
-
let cxVals = new Set();
|
|
94
|
-
let cyVals = new Set();
|
|
95
|
-
let rxVals = new Set();
|
|
96
|
-
let ryVals = new Set();
|
|
97
|
-
|
|
98
|
-
if (arcComs.length > 1) {
|
|
99
|
-
//console.log('!!!arcComs', arcComs);
|
|
100
|
-
pathData2.forEach(com => {
|
|
101
|
-
if (com.type === 'A') {
|
|
102
|
-
//console.log('params', com, com.cx, com.cy, com.rx, com.ry);
|
|
103
|
-
cxVals.add(roundTo(com.cx, decimals))
|
|
104
|
-
cyVals.add(roundTo(com.cy, decimals))
|
|
105
|
-
rxVals.add(roundTo(com.rx, decimals))
|
|
106
|
-
ryVals.add(roundTo(com.ry, decimals))
|
|
107
|
-
}
|
|
108
|
-
})
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
cxVals = Array.from(cxVals)
|
|
112
|
-
cyVals = Array.from(cyVals)
|
|
113
|
-
rxVals = Array.from(rxVals)
|
|
114
|
-
ryVals = Array.from(ryVals)
|
|
115
|
-
|
|
116
|
-
if(cxVals.length===1 && cyVals.length===1 && rxVals.length===1 && ryVals.length===1){
|
|
117
|
-
let [rx, ry, cx, cy] = [rxVals[0], ryVals[0], cxVals[0], cyVals[0]]
|
|
118
|
-
type = rx===ry ? 'circle' : 'ellipse';
|
|
119
|
-
shape = document.createElementNS(svgNs, type)
|
|
120
|
-
attsNew = type==='circle' ? { r:rx, cx, cy } : {rx, ry, cx, cy}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// if el could be replaced
|
|
127
|
-
if (shape) {
|
|
128
|
-
let ignore = ['id', 'class']
|
|
129
|
-
|
|
130
|
-
// set shape attributes
|
|
131
|
-
for (let att in attsNew) {
|
|
132
|
-
shape.setAttribute(att, attsNew[att])
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// copy old attributes
|
|
136
|
-
for (let att in attributes) {
|
|
137
|
-
//console.log(attributes);
|
|
138
|
-
if (attLookup.atts[att].includes(type) || ignore.includes(att) || att.startsWith('data-')) {
|
|
139
|
-
shape.setAttribute(att, attributes[att])
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// replace
|
|
144
|
-
el = shape;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return el;
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Convert shapes to paths
|
|
21
|
+
* converts also transforms
|
|
22
|
+
*/
|
|
151
23
|
export function shapeElToPath(el, { width = 0,
|
|
152
24
|
height = 0,
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
convert_poly = false,
|
|
156
|
-
convert_lines = false,
|
|
157
|
-
//matrix={a:1, b:0, c:0, d:1, e:0, f:0},
|
|
158
|
-
matrix=null
|
|
25
|
+
convertShapes = [],
|
|
26
|
+
matrix = null
|
|
159
27
|
|
|
160
28
|
} = {}) {
|
|
161
29
|
|
|
30
|
+
|
|
162
31
|
let nodeName = el.nodeName.toLowerCase();
|
|
163
32
|
//console.log('shapeElToPath', nodeName);
|
|
164
33
|
|
|
165
34
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
(nodeName === 'circle' || nodeName === 'ellipse') && !convert_ellipses ||
|
|
170
|
-
(nodeName === 'polygon' || nodeName === 'polyline') && !convert_poly ||
|
|
171
|
-
(nodeName === 'line') && !convert_lines
|
|
172
|
-
) return el;
|
|
35
|
+
|
|
36
|
+
if (!convertShapes.includes(nodeName)) return el;
|
|
37
|
+
//console.log(convertShapes);
|
|
173
38
|
|
|
174
39
|
|
|
175
40
|
let pathData = getPathDataFromEl(el, { width, height });
|
|
@@ -178,8 +43,9 @@ export function shapeElToPath(el, { width = 0,
|
|
|
178
43
|
let exclude = ['d', 'x', 'y', 'x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'dx', 'dy', 'r', 'rx', 'ry', 'width', 'height', 'points'];
|
|
179
44
|
|
|
180
45
|
// transform pathData
|
|
181
|
-
if(matrix && Object.values(matrix).join('')!=='100100'){
|
|
46
|
+
if (matrix && Object.values(matrix).join('') !== '100100') {
|
|
182
47
|
pathData = transformPathData(pathData, matrix)
|
|
48
|
+
//console.log('transformPathData', pathData);
|
|
183
49
|
exclude.push('transform', 'transform-origin')
|
|
184
50
|
}
|
|
185
51
|
|
|
@@ -203,15 +69,8 @@ export function shapeElToPath(el, { width = 0,
|
|
|
203
69
|
return pathN
|
|
204
70
|
|
|
205
71
|
}
|
|
206
|
-
/*
|
|
207
|
-
export function copyAttributes(newEl, oldEl){
|
|
208
|
-
|
|
209
|
-
let attributes = [...oldEl.attributes].map(att => att.name);
|
|
210
72
|
|
|
211
73
|
|
|
212
|
-
}
|
|
213
|
-
*/
|
|
214
|
-
|
|
215
74
|
|
|
216
75
|
// retrieve pathdata from svg geometry elements
|
|
217
76
|
export function getPathDataFromEl(el, {
|
|
@@ -233,7 +92,6 @@ export function getPathDataFromEl(el, {
|
|
|
233
92
|
|
|
234
93
|
// convert relative and physical units to user-units
|
|
235
94
|
let atts = svgElUnitsToPixel(el, { width, height })
|
|
236
|
-
//console.log('atts', atts);
|
|
237
95
|
|
|
238
96
|
switch (type) {
|
|
239
97
|
case 'path':
|
|
@@ -244,39 +102,7 @@ export function getPathDataFromEl(el, {
|
|
|
244
102
|
case 'rect':
|
|
245
103
|
attNames = ['x', 'y', 'width', 'height', 'rx', 'ry'];
|
|
246
104
|
({ x=0, y=0, width=0, height=0, rx=0, ry=0 } = atts);
|
|
247
|
-
|
|
248
|
-
if (!rx && !ry) {
|
|
249
|
-
pathData = [
|
|
250
|
-
{ type: "M", values: [x, y] },
|
|
251
|
-
{ type: "L", values: [x + width, y] },
|
|
252
|
-
{ type: "L", values: [x + width, y + height] },
|
|
253
|
-
{ type: "L", values: [x, y + height] },
|
|
254
|
-
{ type: "Z", values: [] }
|
|
255
|
-
];
|
|
256
|
-
} else {
|
|
257
|
-
|
|
258
|
-
rx = rx ? rx : ry;
|
|
259
|
-
ry = ry ? ry : rx;
|
|
260
|
-
|
|
261
|
-
if (rx > width / 2) {
|
|
262
|
-
rx = width / 2;
|
|
263
|
-
}
|
|
264
|
-
if (ry > height / 2) {
|
|
265
|
-
ry = height / 2;
|
|
266
|
-
}
|
|
267
|
-
pathData = [
|
|
268
|
-
{ type: "M", values: [x + rx, y] },
|
|
269
|
-
{ type: "L", values: [x + width - rx, y] },
|
|
270
|
-
{ type: "A", values: [rx, ry, 0, 0, 1, x + width, y + ry] },
|
|
271
|
-
{ type: "L", values: [x + width, y + height - ry] },
|
|
272
|
-
{ type: "A", values: [rx, ry, 0, 0, 1, x + width - rx, y + height] },
|
|
273
|
-
{ type: "L", values: [x + rx, y + height] },
|
|
274
|
-
{ type: "A", values: [rx, ry, 0, 0, 1, x, y + height - ry] },
|
|
275
|
-
{ type: "L", values: [x, y + ry] },
|
|
276
|
-
{ type: "A", values: [rx, ry, 0, 0, 1, x + rx, y] },
|
|
277
|
-
{ type: "Z", values: [] }
|
|
278
|
-
];
|
|
279
|
-
}
|
|
105
|
+
pathData = rectToPathData(x, y, width, height, rx, ry);
|
|
280
106
|
break;
|
|
281
107
|
|
|
282
108
|
case 'circle':
|
|
@@ -296,7 +122,6 @@ export function getPathDataFromEl(el, {
|
|
|
296
122
|
ry = ry ? ry : r;
|
|
297
123
|
}
|
|
298
124
|
|
|
299
|
-
|
|
300
125
|
// simplified radii for circles
|
|
301
126
|
let rxS = isCircle && r >= 1 ? 1 : rx;
|
|
302
127
|
let ryS = isCircle && r >= 1 ? 1 : ry;
|
|
@@ -339,4 +164,179 @@ export function getPathDataFromEl(el, {
|
|
|
339
164
|
|
|
340
165
|
return stringify ? stringifyPathData(pathData) : pathData;
|
|
341
166
|
|
|
342
|
-
};
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
export function rectToPathData(x = 0, y = 0, width = 0, height = 0, rx = 0, ry = 0) {
|
|
171
|
+
let pathData = [];
|
|
172
|
+
|
|
173
|
+
if (!rx && !ry) {
|
|
174
|
+
pathData = [
|
|
175
|
+
{ type: "M", values: [x, y] },
|
|
176
|
+
{ type: "L", values: [x + width, y] },
|
|
177
|
+
{ type: "L", values: [x + width, y + height] },
|
|
178
|
+
{ type: "L", values: [x, y + height] },
|
|
179
|
+
{ type: "Z", values: [] }
|
|
180
|
+
];
|
|
181
|
+
} else {
|
|
182
|
+
|
|
183
|
+
rx = rx ? rx : ry;
|
|
184
|
+
ry = ry ? ry : rx;
|
|
185
|
+
|
|
186
|
+
if (rx > width / 2) {
|
|
187
|
+
rx = width / 2;
|
|
188
|
+
}
|
|
189
|
+
if (ry > height / 2) {
|
|
190
|
+
ry = height / 2;
|
|
191
|
+
}
|
|
192
|
+
pathData = [
|
|
193
|
+
{ type: "M", values: [x + rx, y] },
|
|
194
|
+
{ type: "L", values: [x + width - rx, y] },
|
|
195
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x + width, y + ry] },
|
|
196
|
+
{ type: "L", values: [x + width, y + height - ry] },
|
|
197
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x + width - rx, y + height] },
|
|
198
|
+
{ type: "L", values: [x + rx, y + height] },
|
|
199
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x, y + height - ry] },
|
|
200
|
+
{ type: "L", values: [x, y + ry] },
|
|
201
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x + rx, y] },
|
|
202
|
+
{ type: "Z", values: [] }
|
|
203
|
+
];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return pathData
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
export function pathElToShape(el, {
|
|
214
|
+
convertShapes = [],
|
|
215
|
+
} = {}) {
|
|
216
|
+
|
|
217
|
+
//console.log('pathElToShape', convert_rects, convert_ellipses, convert_lines );
|
|
218
|
+
|
|
219
|
+
let pathData = parsePathDataNormalized(el.getAttribute('d'));
|
|
220
|
+
let coms = Array.from(new Set(pathData.map(com => com.type))).join('')
|
|
221
|
+
|
|
222
|
+
let hasArcs = (/[a]/gi).test(coms)
|
|
223
|
+
let hasBeziers = (/[csqt]/gi).test(coms)
|
|
224
|
+
let hasLines = (/[l]/gi).test(coms)
|
|
225
|
+
let isPoly = !(/[acqts]/gi).test(coms)
|
|
226
|
+
let closed = (/[z]/gi).test(coms)
|
|
227
|
+
let shape = null;
|
|
228
|
+
let type = null
|
|
229
|
+
|
|
230
|
+
let attributes = getElementAtts(el)
|
|
231
|
+
let attsNew = {}
|
|
232
|
+
let decimals = 7;
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
if (isPoly) {
|
|
236
|
+
|
|
237
|
+
//console.log('pathsToShapes', isPoly);
|
|
238
|
+
|
|
239
|
+
// is line
|
|
240
|
+
if (pathData.length === 2 && convertShapes.includes('line')) {
|
|
241
|
+
type = 'line'
|
|
242
|
+
shape = document.createElementNS(svgNs, type)
|
|
243
|
+
let [x1, y1, x2, y2] = [...pathData[0].values, ...pathData[1].values].map(val => roundTo(val, decimals))
|
|
244
|
+
attsNew = { x1, y1, x2, y2 }
|
|
245
|
+
}
|
|
246
|
+
// polygon, polyline or rect
|
|
247
|
+
else {
|
|
248
|
+
|
|
249
|
+
let vertices = getPathDataVertices(pathData);
|
|
250
|
+
let bb = getPolyBBox(vertices)
|
|
251
|
+
let areaPoly = getPolygonArea(vertices, true)
|
|
252
|
+
let areaRect = bb.width * bb.height;
|
|
253
|
+
let areaDiff = Math.abs(1 - areaRect / areaPoly);
|
|
254
|
+
|
|
255
|
+
// is rect
|
|
256
|
+
if (convertShapes.includes('rect') && areaDiff < 0.01) {
|
|
257
|
+
type = 'rect'
|
|
258
|
+
shape = document.createElementNS(svgNs, type)
|
|
259
|
+
let { x, y, width, height } = bb
|
|
260
|
+
attsNew = { x, y, width, height }
|
|
261
|
+
|
|
262
|
+
}
|
|
263
|
+
// polyline or polygon
|
|
264
|
+
else if (convertShapes.includes('polygon') || convertShapes.includes('polyline')) {
|
|
265
|
+
type = closed ? 'polygon' : 'polyline';
|
|
266
|
+
shape = document.createElementNS(svgNs, type)
|
|
267
|
+
let points = vertices.map(pt => { return [pt.x, pt.y] }).flat().map(val => roundTo(val, decimals)).join(' ')
|
|
268
|
+
attsNew = { points }
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// circles or ellipses
|
|
273
|
+
else if (!hasLines && (convertShapes.includes('circle') || convertShapes.includes('ellipse'))) {
|
|
274
|
+
|
|
275
|
+
// try to convert cubics to arcs
|
|
276
|
+
if (!hasArcs && hasBeziers) {
|
|
277
|
+
pathData = pathDataCubicsToArc(pathData, { areaThreshold: 2.5 })
|
|
278
|
+
hasArcs = pathData.filter(com => com.type === 'A').length;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
if (hasArcs) {
|
|
283
|
+
let pathData2 = getPathDataVerbose(pathData, { addArcParams: true })
|
|
284
|
+
let arcComs = pathData2.filter(com => com.type === 'A')
|
|
285
|
+
|
|
286
|
+
let cxVals = new Set();
|
|
287
|
+
let cyVals = new Set();
|
|
288
|
+
let rxVals = new Set();
|
|
289
|
+
let ryVals = new Set();
|
|
290
|
+
|
|
291
|
+
if (arcComs.length > 1) {
|
|
292
|
+
//console.log('!!!arcComs', arcComs);
|
|
293
|
+
pathData2.forEach(com => {
|
|
294
|
+
if (com.type === 'A') {
|
|
295
|
+
//console.log('params', com, com.cx, com.cy, com.rx, com.ry);
|
|
296
|
+
cxVals.add(roundTo(com.cx, decimals))
|
|
297
|
+
cyVals.add(roundTo(com.cy, decimals))
|
|
298
|
+
rxVals.add(roundTo(com.rx, decimals))
|
|
299
|
+
ryVals.add(roundTo(com.ry, decimals))
|
|
300
|
+
}
|
|
301
|
+
})
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
cxVals = Array.from(cxVals)
|
|
305
|
+
cyVals = Array.from(cyVals)
|
|
306
|
+
rxVals = Array.from(rxVals)
|
|
307
|
+
ryVals = Array.from(ryVals)
|
|
308
|
+
|
|
309
|
+
if (cxVals.length === 1 && cyVals.length === 1 && rxVals.length === 1 && ryVals.length === 1) {
|
|
310
|
+
let [rx, ry, cx, cy] = [rxVals[0], ryVals[0], cxVals[0], cyVals[0]]
|
|
311
|
+
type = rx === ry ? 'circle' : 'ellipse';
|
|
312
|
+
shape = document.createElementNS(svgNs, type)
|
|
313
|
+
attsNew = type === 'circle' ? { r: rx, cx, cy } : { rx, ry, cx, cy }
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
// if el could be replaced
|
|
320
|
+
if (shape) {
|
|
321
|
+
let ignore = ['id', 'class']
|
|
322
|
+
|
|
323
|
+
// set shape attributes
|
|
324
|
+
for (let att in attsNew) {
|
|
325
|
+
shape.setAttribute(att, attsNew[att])
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// copy old attributes
|
|
329
|
+
for (let att in attributes) {
|
|
330
|
+
//console.log(attributes);
|
|
331
|
+
if (attLookup.atts[att].includes(type) || ignore.includes(att) || att.startsWith('data-')) {
|
|
332
|
+
shape.setAttribute(att, attributes[att])
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// replace
|
|
336
|
+
el = shape;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
//console.log(el);
|
|
340
|
+
return el;
|
|
341
|
+
|
|
342
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { svgNs } from "../constants";
|
|
2
|
+
import { getPathDataVertices, isPointInPolygon } from "./geometry";
|
|
3
|
+
import { checkBBoxIntersections, getPolyBBox } from "./geometry_bbox";
|
|
4
|
+
import { convertPathData } from "./pathData_convert";
|
|
5
|
+
import { pathDataToD } from "./pathData_stringify";
|
|
6
|
+
import { roundTo } from "./rounding";
|
|
7
|
+
import { renderPoint } from "./visualize";
|
|
8
|
+
|
|
9
|
+
export function splitCompundGroups(pathDataPlusArr = [], {
|
|
10
|
+
toRelative = true,
|
|
11
|
+
toShorthands = true,
|
|
12
|
+
minifyD = 0,
|
|
13
|
+
decimals = 3,
|
|
14
|
+
addDimensions = false
|
|
15
|
+
} = {}) {
|
|
16
|
+
|
|
17
|
+
//console.log('???pathDataPlusArr', pathDataPlusArr);
|
|
18
|
+
let pathDataSplit = [];
|
|
19
|
+
pathDataPlusArr = JSON.parse(JSON.stringify(pathDataPlusArr))
|
|
20
|
+
let len = pathDataPlusArr.length;
|
|
21
|
+
|
|
22
|
+
//let bb0 =
|
|
23
|
+
let xArr = [];
|
|
24
|
+
let yArr = []
|
|
25
|
+
|
|
26
|
+
// refine bbox and add cpt polygon
|
|
27
|
+
for (let i = 0; i < len; i++) {
|
|
28
|
+
let sub = pathDataPlusArr[i]
|
|
29
|
+
let { pathData, bb } = sub
|
|
30
|
+
|
|
31
|
+
// console.log(bb);
|
|
32
|
+
// include control points for better overlapping approximation
|
|
33
|
+
//let poly = getPathDataVertices(pathData, true);
|
|
34
|
+
//let bb2 = getPolyBBox(poly);
|
|
35
|
+
|
|
36
|
+
if (bb.width && bb.height) {
|
|
37
|
+
} else {
|
|
38
|
+
let poly = getPathDataVertices(pathData, true);
|
|
39
|
+
bb = getPolyBBox(poly);
|
|
40
|
+
pathDataPlusArr[i].bb = bb;
|
|
41
|
+
//console.log(bb, sub);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
xArr.push(bb.left, bb.right)
|
|
45
|
+
yArr.push(bb.top, bb.bottom)
|
|
46
|
+
sub.includes = []
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* check overlapping
|
|
52
|
+
* sub paths
|
|
53
|
+
*/
|
|
54
|
+
for (let i = 0, l = pathDataPlusArr.length; i < l; i++) {
|
|
55
|
+
let sub1 = pathDataPlusArr[i];
|
|
56
|
+
let { bb, poly } = sub1;
|
|
57
|
+
|
|
58
|
+
for (let j = 0; j < l; j++) {
|
|
59
|
+
|
|
60
|
+
let sub1 = pathDataPlusArr[j];
|
|
61
|
+
if (i === j) continue;
|
|
62
|
+
|
|
63
|
+
//let [bb1, poly1] = [sub1.bb, sub1.poly];
|
|
64
|
+
let bb1 = sub1.bb;
|
|
65
|
+
//let poly1 = sub1.poly
|
|
66
|
+
|
|
67
|
+
// test sample on-path points
|
|
68
|
+
let ptM = { x: bb1.x + bb1.width * 0.5, y: bb1.y + bb1.height * 0.5 };
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
let inPoly = false;
|
|
72
|
+
if (ptM.x >= bb.x && ptM.y >= bb.y && ptM.x <= bb.right && ptM.y <= bb.bottom) {
|
|
73
|
+
inPoly = true;
|
|
74
|
+
pathDataPlusArr[i].includes.push(j);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* combine overlapping
|
|
83
|
+
* compound paths
|
|
84
|
+
*/
|
|
85
|
+
for (let i = 0, l = pathDataPlusArr.length; i < l; i++) {
|
|
86
|
+
let sub = pathDataPlusArr[i];
|
|
87
|
+
let { includes } = sub;
|
|
88
|
+
|
|
89
|
+
includes.forEach(s => {
|
|
90
|
+
let pathData = pathDataPlusArr[s].pathData;
|
|
91
|
+
if (pathData.length) {
|
|
92
|
+
pathDataPlusArr[i].pathData.push(...pathData);
|
|
93
|
+
pathDataPlusArr[s].pathData = [];
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// remove empty els due to grouping
|
|
99
|
+
pathDataPlusArr = pathDataPlusArr.filter(sub => sub.pathData.length);
|
|
100
|
+
|
|
101
|
+
// try to find row left to right order
|
|
102
|
+
//pathDataPlusArr = pathDataPlusArr.sort((a, b) => ((a.bb.x + a.bb.y * 3) - (b.bb.x + b.bb.y * 3)))
|
|
103
|
+
//pathDataPlusArr = pathDataPlusArr.sort((a, b) => ((a.bb.x + a.bb.y * 2) - (b.bb.x + b.bb.y * 2)))
|
|
104
|
+
pathDataPlusArr = pathDataPlusArr.sort((a, b) => ((a.bb.x ) - (b.bb.x)))
|
|
105
|
+
|
|
106
|
+
// create SVG
|
|
107
|
+
let x = Math.min(...xArr);
|
|
108
|
+
let y = Math.min(...yArr);
|
|
109
|
+
let right = Math.max(...xArr);
|
|
110
|
+
let bottom = Math.max(...yArr);
|
|
111
|
+
let width = right - x;
|
|
112
|
+
let height = bottom - y;
|
|
113
|
+
|
|
114
|
+
[x, y, width, height] = [x, y, width, height].map(val => roundTo(val, decimals));
|
|
115
|
+
|
|
116
|
+
let dimensionAtts = addDimensions ? `width="${width}" height="${height}"` : ''
|
|
117
|
+
let svgSplit = `<svg ${dimensionAtts} viewBox="${x} ${y} ${width} ${height}" xmlns="${svgNs}">`;
|
|
118
|
+
|
|
119
|
+
pathDataPlusArr.forEach(sub => {
|
|
120
|
+
let { pathData } = sub;
|
|
121
|
+
|
|
122
|
+
pathData = convertPathData(pathData, { toRelative, toShorthands, decimals });
|
|
123
|
+
let d = pathDataToD(pathData, minifyD);
|
|
124
|
+
svgSplit += `<path d="${d}"/>`;
|
|
125
|
+
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
svgSplit += '</svg>';
|
|
129
|
+
|
|
130
|
+
let splitObj = { pathData: pathDataPlusArr, svg: svgSplit }
|
|
131
|
+
//console.log('splitObj', splitObj);
|
|
132
|
+
return splitObj
|
|
133
|
+
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
/*
|
|
138
|
+
function checkBBoxIntersections2(bb, bb1) {
|
|
139
|
+
let [x, y, width, height, right, bottom] = [
|
|
140
|
+
bb.x,
|
|
141
|
+
bb.y,
|
|
142
|
+
bb.width,
|
|
143
|
+
bb.height,
|
|
144
|
+
bb.x + bb.width,
|
|
145
|
+
bb.y + bb.height
|
|
146
|
+
];
|
|
147
|
+
let [x1, y1, width1, height1, right1, bottom1] = [
|
|
148
|
+
bb1.x,
|
|
149
|
+
bb1.y,
|
|
150
|
+
bb1.width,
|
|
151
|
+
bb1.height,
|
|
152
|
+
bb1.x + bb1.width,
|
|
153
|
+
bb1.y + bb1.height
|
|
154
|
+
];
|
|
155
|
+
let intersects = false;
|
|
156
|
+
//console.log('bb', bb, bb1);
|
|
157
|
+
//console.log();
|
|
158
|
+
|
|
159
|
+
if (x < x1 && right > right1 && y < y1 && bottom > bottom1) {
|
|
160
|
+
intersects = true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
console.log('???', intersects, 'dims', width, height, '2', width1, height1);
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
return intersects;
|
|
167
|
+
}
|
|
168
|
+
*/
|