svg-path-simplify 0.3.5 → 0.4.0
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 +3 -76
- package/dist/svg-path-simplify.esm.js +5505 -4030
- package/dist/svg-path-simplify.esm.min.js +2 -8
- package/dist/svg-path-simplify.js +5506 -4029
- package/dist/svg-path-simplify.min.js +2 -8
- package/dist/svg-path-simplify.pathdata.esm.js +1154 -1042
- package/dist/svg-path-simplify.pathdata.esm.min.js +2 -8
- package/docs/api.md +127 -0
- package/index.html +279 -257
- package/package.json +1 -1
- package/src/constants.js +10 -1
- package/src/index.js +7 -1
- package/src/pathData_simplify_cubic.js +1 -18
- package/src/pathSimplify-main.js +87 -30
- package/src/pathSimplify-only-pathdata.js +2 -2
- package/src/svg-getAttributes.js +13 -0
- package/src/svg_flatten_transforms.js +1 -1
- package/src/svg_getViewbox.js +23 -11
- package/src/svg_rootSVG.js +9 -0
- package/src/svgii/convert_colors.js +145 -0
- package/src/svgii/convert_units.js +159 -0
- package/src/svgii/geometry.js +24 -4
- package/src/svgii/geometry_bbox.js +2 -1
- package/src/svgii/geometry_bbox_element.js +46 -0
- package/src/svgii/pathData_analyze.js +34 -14
- package/src/svgii/pathData_convert.js +204 -34
- package/src/svgii/pathData_parse.js +2 -99
- package/src/svgii/pathData_parse_els.js +217 -128
- package/src/svgii/pathData_simplify_refineCorners.js +72 -43
- package/src/svgii/pathData_stringify.js +6 -5
- package/src/svgii/pathData_toPolygon.js +3 -1
- package/src/svgii/pathData_transform.js +307 -0
- package/src/svgii/poly_normalize.js +21 -1
- package/src/svgii/rounding.js +36 -5
- package/src/svgii/svg-styles-getTransforms.js +119 -8
- package/src/svgii/svg-styles-to-attributes-const.js +26 -6
- package/src/svgii/svg_cleanup.js +540 -74
- package/src/svgii/svg_el_parse_style_props.js +561 -0
- package/src/svgii/transform_qr_decompose.js +74 -0
- package/src/svgii/pathData_scale.js +0 -42
- package/src/svgii/stringify.js +0 -103
- package/src/svgii/svg-styles-to-attributes.js +0 -217
|
@@ -1,151 +1,239 @@
|
|
|
1
1
|
//import { pathDataToAbsoluteOrRelative, pathDataToLonghands, cubicToArc } from './pathData_convert.js';
|
|
2
|
-
import {
|
|
2
|
+
import { svgNs } from '../constants.js';
|
|
3
|
+
import { pathDataCubicsToArc } from '../pathData_simplify_cubicsToArcs.js';
|
|
4
|
+
import { getElementAtts } from '../svg-getAttributes.js';
|
|
5
|
+
import { getViewBox } from '../svg_getViewbox.js';
|
|
6
|
+
import { getRootSvg } from '../svg_rootSVG.js';
|
|
7
|
+
import { svgElUnitsToPixel } from './convert_units.js';
|
|
8
|
+
import { getPathDataVertices } from './geometry.js';
|
|
9
|
+
import { getPolygonArea } from './geometry_area.js';
|
|
10
|
+
import { getPolyBBox } from './geometry_bbox.js';
|
|
11
|
+
import { getPathDataVerbose } from './pathData_analyze.js';
|
|
12
|
+
import { parsePathDataNormalized } from './pathData_convert.js';
|
|
13
|
+
import { parsePathDataString, stringifyPathData } from './pathData_parse.js';
|
|
14
|
+
import { transformPathData } from './pathData_transform.js';
|
|
15
|
+
import { autoRound, roundTo } from './rounding.js';
|
|
16
|
+
import { attLookup } from './svg-styles-to-attributes-const.js';
|
|
17
|
+
import { qrDecomposeMatrix } from './transform_qr_decompose.js';
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
export function pathElToShape(el, {
|
|
21
|
+
convert_rects = false,
|
|
22
|
+
convert_ellipses = false,
|
|
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
|
+
}
|
|
3
87
|
|
|
4
|
-
|
|
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
|
+
|
|
151
|
+
export function shapeElToPath(el, { width = 0,
|
|
152
|
+
height = 0,
|
|
153
|
+
convert_rects = false,
|
|
154
|
+
convert_ellipses = false,
|
|
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
|
|
159
|
+
|
|
160
|
+
} = {}) {
|
|
5
161
|
|
|
6
162
|
let nodeName = el.nodeName.toLowerCase();
|
|
7
|
-
|
|
163
|
+
//console.log('shapeElToPath', nodeName);
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
if (
|
|
167
|
+
nodeName === 'path' && !matrix ||
|
|
168
|
+
nodeName === 'rect' && !convert_rects ||
|
|
169
|
+
(nodeName === 'circle' || nodeName === 'ellipse') && !convert_ellipses ||
|
|
170
|
+
(nodeName === 'polygon' || nodeName === 'polyline') && !convert_poly ||
|
|
171
|
+
(nodeName === 'line') && !convert_lines
|
|
172
|
+
) return el;
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
let pathData = getPathDataFromEl(el, { width, height });
|
|
176
|
+
|
|
177
|
+
// shape attributes – obsolete for path els
|
|
178
|
+
let exclude = ['d', 'x', 'y', 'x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'dx', 'dy', 'r', 'rx', 'ry', 'width', 'height', 'points'];
|
|
179
|
+
|
|
180
|
+
// transform pathData
|
|
181
|
+
if(matrix && Object.values(matrix).join('')!=='100100'){
|
|
182
|
+
pathData = transformPathData(pathData, matrix)
|
|
183
|
+
exclude.push('transform', 'transform-origin')
|
|
184
|
+
}
|
|
8
185
|
|
|
9
|
-
let pathData = getPathDataFromEl(el);
|
|
10
186
|
let d = pathData.map(com => { return `${com.type} ${com.values} ` }).join(' ')
|
|
11
187
|
let attributes = [...el.attributes].map(att => att.name);
|
|
12
188
|
|
|
13
|
-
|
|
14
|
-
//return []
|
|
15
|
-
|
|
16
|
-
let pathN = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
17
|
-
//let pathN = document.createElement('path');
|
|
189
|
+
let pathN = document.createElementNS(svgNs, 'path');
|
|
18
190
|
pathN.setAttribute('d', d);
|
|
19
191
|
|
|
20
|
-
let exclude = ['x', 'y', 'x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'dx', 'dy', 'r', 'rx', 'ry', 'width', 'height', 'points'];
|
|
21
192
|
|
|
193
|
+
// copy attributes
|
|
22
194
|
attributes.forEach(att => {
|
|
23
195
|
if (!exclude.includes(att)) {
|
|
24
|
-
//console.log(att, attributes, exclude);
|
|
25
196
|
let val = el.getAttribute(att);
|
|
26
197
|
pathN.setAttribute(att, val)
|
|
27
198
|
}
|
|
28
199
|
})
|
|
29
200
|
|
|
30
201
|
//el.replaceWith(pathN)
|
|
202
|
+
//console.log(pathN.outerHTML, d);
|
|
31
203
|
return pathN
|
|
32
204
|
|
|
33
205
|
}
|
|
206
|
+
/*
|
|
207
|
+
export function copyAttributes(newEl, oldEl){
|
|
34
208
|
|
|
209
|
+
let attributes = [...oldEl.attributes].map(att => att.name);
|
|
35
210
|
|
|
36
|
-
// retrieve pathdata from svg geometry elements
|
|
37
|
-
export function getPathDataFromEl(el, stringify = false) {
|
|
38
|
-
|
|
39
|
-
let pathData = [];
|
|
40
|
-
let type = el.nodeName.toLowerCase();
|
|
41
|
-
let atts, attNames, d, x, y, width, height, r, rx, ry, cx, cy, x1, x2, y1, y2;
|
|
42
|
-
|
|
43
|
-
// convert relative or absolute units
|
|
44
|
-
const svgElUnitsToPixel = (el, decimals = 9) => {
|
|
45
|
-
//console.log(this);
|
|
46
|
-
const svg = el.nodeName !== "svg" ? el.closest("svg") : el;
|
|
47
|
-
|
|
48
|
-
// convert real life units to pixels
|
|
49
|
-
const translateUnitToPixel = (value) => {
|
|
50
|
-
|
|
51
|
-
if (value === null) {
|
|
52
|
-
return 0
|
|
53
|
-
}
|
|
54
|
-
//default dpi = 96
|
|
55
|
-
let dpi = 96;
|
|
56
|
-
let unit = value.match(/([a-z]+)/gi);
|
|
57
|
-
unit = unit ? unit[0] : "";
|
|
58
|
-
let val = parseFloat(value);
|
|
59
|
-
let rat;
|
|
60
|
-
|
|
61
|
-
// no unit - already pixes/user unit
|
|
62
|
-
if (!unit) {
|
|
63
|
-
return val;
|
|
64
|
-
}
|
|
65
211
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
rat = dpi;
|
|
69
|
-
break;
|
|
70
|
-
case "pt":
|
|
71
|
-
rat = (1 / 72) * 96;
|
|
72
|
-
break;
|
|
73
|
-
case "cm":
|
|
74
|
-
rat = (1 / 2.54) * 96;
|
|
75
|
-
break;
|
|
76
|
-
case "mm":
|
|
77
|
-
rat = ((1 / 2.54) * 96) / 10;
|
|
78
|
-
break;
|
|
79
|
-
// just a default approximation
|
|
80
|
-
case "em":
|
|
81
|
-
case "rem":
|
|
82
|
-
rat = 16;
|
|
83
|
-
break;
|
|
84
|
-
default:
|
|
85
|
-
rat = 1;
|
|
86
|
-
}
|
|
87
|
-
let valuePx = val * rat;
|
|
88
|
-
return +valuePx.toFixed(decimals);
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
// svg width and height attributes
|
|
92
|
-
let width = svg.getAttribute("width");
|
|
93
|
-
width = width ? translateUnitToPixel(width) : 300;
|
|
94
|
-
let height = svg.getAttribute("height");
|
|
95
|
-
height = width ? translateUnitToPixel(height) : 150;
|
|
96
|
-
|
|
97
|
-
//prefer viewBox values
|
|
98
|
-
let vB = svg.getAttribute("viewBox");
|
|
99
|
-
vB = vB
|
|
100
|
-
? vB
|
|
101
|
-
.replace(/,/g, " ")
|
|
102
|
-
.split(" ")
|
|
103
|
-
.filter(Boolean)
|
|
104
|
-
.map((val) => {
|
|
105
|
-
return +val;
|
|
106
|
-
})
|
|
107
|
-
: [];
|
|
212
|
+
}
|
|
213
|
+
*/
|
|
108
214
|
|
|
109
|
-
let w = vB.length ? vB[2] : width;
|
|
110
|
-
let h = vB.length ? vB[3] : height;
|
|
111
|
-
let scaleX = w / 100;
|
|
112
|
-
let scaleY = h / 100;
|
|
113
|
-
let scalRoot = Math.sqrt((Math.pow(scaleX, 2) + Math.pow(scaleY, 2)) / 2);
|
|
114
215
|
|
|
115
|
-
|
|
116
|
-
|
|
216
|
+
// retrieve pathdata from svg geometry elements
|
|
217
|
+
export function getPathDataFromEl(el, {
|
|
218
|
+
stringify = false,
|
|
219
|
+
width = 0,
|
|
220
|
+
height = 0
|
|
221
|
+
} = {}) {
|
|
117
222
|
|
|
223
|
+
let pathData = [];
|
|
224
|
+
let type = el.nodeName.toLowerCase();
|
|
225
|
+
let attNames, d, x, y, r, rx, ry, cx, cy, x1, x2, y1, y2;
|
|
118
226
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
let scale = attsH.includes(att) ? scaleX : scaleY;
|
|
125
|
-
scale = att === "r" && w != h ? scalRoot : scale;
|
|
126
|
-
let unit = val.match(/([a-z|%]+)/gi);
|
|
127
|
-
unit = unit ? unit[0] : "";
|
|
128
|
-
if (val.includes("%")) {
|
|
129
|
-
valAbs = parseFloat(val) * scale;
|
|
130
|
-
}
|
|
131
|
-
//absolute units
|
|
132
|
-
else {
|
|
133
|
-
valAbs = translateUnitToPixel(val);
|
|
134
|
-
}
|
|
135
|
-
el.setAttribute(att, +valAbs);
|
|
136
|
-
}
|
|
137
|
-
});
|
|
227
|
+
if (!width || !height) {
|
|
228
|
+
let svg = getRootSvg(el);
|
|
229
|
+
let viewBox = getViewBox(svg)
|
|
230
|
+
width = viewBox.width;
|
|
231
|
+
height = viewBox.height;
|
|
138
232
|
}
|
|
139
233
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
atts = {}
|
|
144
|
-
attNames.forEach(att => {
|
|
145
|
-
atts[att] = +el.getAttribute(att)
|
|
146
|
-
})
|
|
147
|
-
return atts
|
|
148
|
-
}
|
|
234
|
+
// convert relative and physical units to user-units
|
|
235
|
+
let atts = svgElUnitsToPixel(el, { width, height })
|
|
236
|
+
//console.log('atts', atts);
|
|
149
237
|
|
|
150
238
|
switch (type) {
|
|
151
239
|
case 'path':
|
|
@@ -155,8 +243,7 @@ export function getPathDataFromEl(el, stringify = false) {
|
|
|
155
243
|
|
|
156
244
|
case 'rect':
|
|
157
245
|
attNames = ['x', 'y', 'width', 'height', 'rx', 'ry'];
|
|
158
|
-
({ x, y, width, height, rx, ry } =
|
|
159
|
-
|
|
246
|
+
({ x=0, y=0, width=0, height=0, rx=0, ry=0 } = atts);
|
|
160
247
|
|
|
161
248
|
if (!rx && !ry) {
|
|
162
249
|
pathData = [
|
|
@@ -168,8 +255,8 @@ export function getPathDataFromEl(el, stringify = false) {
|
|
|
168
255
|
];
|
|
169
256
|
} else {
|
|
170
257
|
|
|
171
|
-
rx=rx? rx : ry;
|
|
172
|
-
ry=ry ? ry : rx;
|
|
258
|
+
rx = rx ? rx : ry;
|
|
259
|
+
ry = ry ? ry : rx;
|
|
173
260
|
|
|
174
261
|
if (rx > width / 2) {
|
|
175
262
|
rx = width / 2;
|
|
@@ -196,7 +283,7 @@ export function getPathDataFromEl(el, stringify = false) {
|
|
|
196
283
|
case 'ellipse':
|
|
197
284
|
|
|
198
285
|
attNames = ['cx', 'cy', 'rx', 'ry', 'r'];
|
|
199
|
-
({ cx, cy, r, rx, ry } =
|
|
286
|
+
({ cx=0, cy=0, r, rx, ry } = atts);
|
|
200
287
|
|
|
201
288
|
let isCircle = type === 'circle';
|
|
202
289
|
|
|
@@ -209,9 +296,11 @@ export function getPathDataFromEl(el, stringify = false) {
|
|
|
209
296
|
ry = ry ? ry : r;
|
|
210
297
|
}
|
|
211
298
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
let
|
|
299
|
+
|
|
300
|
+
// simplified radii for circles
|
|
301
|
+
let rxS = isCircle && r >= 1 ? 1 : rx;
|
|
302
|
+
let ryS = isCircle && r >= 1 ? 1 : ry;
|
|
303
|
+
|
|
215
304
|
|
|
216
305
|
pathData = [
|
|
217
306
|
{ type: "M", values: [cx + rx, cy] },
|
|
@@ -222,7 +311,7 @@ export function getPathDataFromEl(el, stringify = false) {
|
|
|
222
311
|
break;
|
|
223
312
|
case 'line':
|
|
224
313
|
attNames = ['x1', 'y1', 'x2', 'y2'];
|
|
225
|
-
({ x1, y1, x2, y2 } =
|
|
314
|
+
({ x1, y1, x2, y2 } = atts);
|
|
226
315
|
pathData = [
|
|
227
316
|
{ type: "M", values: [x1, y1] },
|
|
228
317
|
{ type: "L", values: [x2, y2] }
|
|
@@ -231,12 +320,12 @@ export function getPathDataFromEl(el, stringify = false) {
|
|
|
231
320
|
case 'polygon':
|
|
232
321
|
case 'polyline':
|
|
233
322
|
|
|
234
|
-
let points = el.getAttribute('points').
|
|
323
|
+
let points = el.getAttribute('points').split(/,| /).filter(Boolean).map(Number)
|
|
235
324
|
|
|
236
325
|
for (let i = 0; i < points.length; i += 2) {
|
|
237
326
|
pathData.push({
|
|
238
327
|
type: (i === 0 ? "M" : "L"),
|
|
239
|
-
values: [
|
|
328
|
+
values: [points[i], points[i + 1]]
|
|
240
329
|
});
|
|
241
330
|
}
|
|
242
331
|
if (type === 'polygon') {
|
|
@@ -19,8 +19,8 @@ export function refineRoundedCorners(pathData, {
|
|
|
19
19
|
|
|
20
20
|
let isClosed = pathData[l - 1].type.toLowerCase() === 'z';
|
|
21
21
|
let zIsLineto = isClosed ?
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
(pathData[l - 1].p.x === pathData[0].p0.x && pathData[l - 1].p.y === pathData[0].p0.y)
|
|
23
|
+
: false;
|
|
24
24
|
|
|
25
25
|
let lastOff = isClosed ? 2 : 1;
|
|
26
26
|
|
|
@@ -30,12 +30,8 @@ export function refineRoundedCorners(pathData, {
|
|
|
30
30
|
let firstIsLine = pathData[1].type === 'L';
|
|
31
31
|
let firstIsBez = pathData[1].type === 'C';
|
|
32
32
|
|
|
33
|
-
//console.log('lastIsLine', lastIsLine, 'firstIsLine', firstIsLine, 'lastIsBez', lastIsBez, 'firstIsBez', firstIsBez, 'isClosed', isClosed, 'comLast1', comLast1);
|
|
34
33
|
|
|
35
34
|
let normalizeClose = isClosed && firstIsBez && (lastIsLine || zIsLineto);
|
|
36
|
-
//let adjustStart = false
|
|
37
|
-
//normalizeClose = false
|
|
38
|
-
//console.log('normalizeClose', normalizeClose);
|
|
39
35
|
|
|
40
36
|
// normalize closepath to lineto
|
|
41
37
|
if (normalizeClose) {
|
|
@@ -53,7 +49,7 @@ export function refineRoundedCorners(pathData, {
|
|
|
53
49
|
if ((type === 'L' && comN && comN.type === 'C') ||
|
|
54
50
|
(type === 'C' && comN && comN.type === 'L')
|
|
55
51
|
) {
|
|
56
|
-
let comL0 = type==='L' ? com : null;
|
|
52
|
+
let comL0 = type === 'L' ? com : null;
|
|
57
53
|
let comL1 = null;
|
|
58
54
|
let comBez = [];
|
|
59
55
|
let offset = 0;
|
|
@@ -66,7 +62,7 @@ export function refineRoundedCorners(pathData, {
|
|
|
66
62
|
//renderPoint(markers, com.p, 'purple')
|
|
67
63
|
}
|
|
68
64
|
|
|
69
|
-
if(!comL0) {
|
|
65
|
+
if (!comL0) {
|
|
70
66
|
pathDataN.push(com)
|
|
71
67
|
continue
|
|
72
68
|
}
|
|
@@ -94,14 +90,14 @@ export function refineRoundedCorners(pathData, {
|
|
|
94
90
|
}
|
|
95
91
|
|
|
96
92
|
if (comL1) {
|
|
93
|
+
//console.log('comL1', comL1);
|
|
97
94
|
|
|
98
95
|
// linetos
|
|
99
96
|
let len1 = getDistManhattan(comL0.p0, comL0.p)
|
|
100
97
|
let len2 = getDistManhattan(comL1.p0, comL1.p)
|
|
101
98
|
|
|
102
99
|
// bezier
|
|
103
|
-
//
|
|
104
|
-
let comBezLen = comBez.length;
|
|
100
|
+
//let comBezLen = comBez.length;
|
|
105
101
|
//let len3 = getDistManhattan(comBez[0].p0, comBez[comBezLen - 1].p)
|
|
106
102
|
let len3 = getDistManhattan(comL0.p, comL1.p0)
|
|
107
103
|
|
|
@@ -113,54 +109,87 @@ export function refineRoundedCorners(pathData, {
|
|
|
113
109
|
let signChange = (area1 < 0 && area2 > 0) || (area1 > 0 && area2 < 0)
|
|
114
110
|
|
|
115
111
|
// exclude mid bezier segments that are larger than surrounding linetos
|
|
116
|
-
let bezThresh = len3*0.5 * tolerance
|
|
117
|
-
let isSmall = bezThresh < len1 && bezThresh < len2
|
|
112
|
+
let bezThresh = len3 * 0.5 * tolerance
|
|
113
|
+
let isSmall = bezThresh < len1 && bezThresh < len2;
|
|
118
114
|
|
|
119
115
|
|
|
120
116
|
//len1 > len3 && len2 > len3
|
|
121
|
-
if (comBez.length && !signChange &&
|
|
117
|
+
if (comBez.length && !signChange && isSmall) {
|
|
122
118
|
|
|
123
|
-
let isFlatBezier = Math.abs(area2)
|
|
124
|
-
let ptQ = !isFlatBezier ? checkLineIntersection(comL0.p0, comL0.p, comL1.
|
|
119
|
+
let isFlatBezier = Math.abs(area2) < getSquareDistance(comBez[0].p0, comBez[0].p) * 0.005
|
|
120
|
+
let ptQ = !isFlatBezier ? checkLineIntersection(comL0.p0, comL0.p, comL1.p, comL1.p0, false, true) : null
|
|
125
121
|
|
|
126
|
-
if (!
|
|
122
|
+
if (!ptQ) {
|
|
123
|
+
pathDataN.push(com);
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// check sign change
|
|
128
|
+
if (ptQ) {
|
|
129
|
+
let area0 = getPolygonArea([comL0.p0, comL0.p, comL1.p0, comL1.p], false);
|
|
130
|
+
let area0_abs = Math.abs(area0);
|
|
131
|
+
let area1 = getPolygonArea([comL0.p0, comL0.p, ptQ, comL1.p0, comL1.p], false);
|
|
132
|
+
let area1_abs = Math.abs(area1);
|
|
133
|
+
let areaDiff = Math.abs(area0_abs - area1_abs) / area0_abs
|
|
134
|
+
//console.log('areaDiff', areaDiff);
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
/*
|
|
138
|
+
renderPoint(markers, comL0.p0, 'green', '0.5%', '0.5')
|
|
139
|
+
renderPoint(markers, comL0.p, 'red', '1.5%', '0.5')
|
|
140
|
+
renderPoint(markers, comL1.p0, 'blue', '0.5%', '0.5')
|
|
141
|
+
renderPoint(markers, comL1.p, 'orange', '0.5%', '0.5')
|
|
142
|
+
if(!area0) {
|
|
143
|
+
pathDataN.push(com);
|
|
144
|
+
continue
|
|
145
|
+
}
|
|
146
|
+
*/
|
|
147
|
+
|
|
148
|
+
let signChange = area0 < 0 && area1 > 0 || area0 > 0 && area1 < 0;
|
|
149
|
+
|
|
150
|
+
if (!ptQ || signChange || areaDiff > 0.5) {
|
|
151
|
+
//console.log(signChange, area0, area1, 'pts', comL0.p0, comL0.p, comL1.p0, comL1.p);
|
|
152
|
+
//renderPoint(markers, ptQ, 'cyan', '0.5%', '0.5')
|
|
153
|
+
pathDataN.push(com);
|
|
154
|
+
continue
|
|
155
|
+
}
|
|
127
156
|
|
|
128
|
-
|
|
129
|
-
let ptM = pointAtT([comL0.p, ptQ, comL1.p0], 0.5)
|
|
157
|
+
}
|
|
130
158
|
|
|
131
|
-
let ptM_bez = comBez.length===1 ? pointAtT( [comBez[0].p0, comBez[0].cp1, comBez[0].cp2, comBez[0].p], 0.5 ) : comBez[0].p ;
|
|
132
159
|
|
|
133
|
-
|
|
160
|
+
// final check: mid point proximity
|
|
161
|
+
let ptM = pointAtT([comL0.p, ptQ, comL1.p0], 0.5)
|
|
162
|
+
let ptM_bez = comBez.length === 1 ? pointAtT([comBez[0].p0, comBez[0].cp1, comBez[0].cp2, comBez[0].p], 0.5) : comBez[0].p;
|
|
134
163
|
|
|
135
|
-
|
|
136
|
-
//renderPoint(markers, ptM_bez, 'green', '0.5%', '0.5')
|
|
164
|
+
let dist1 = getDistManhattan(ptM, ptM_bez) * 0.75
|
|
137
165
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
//renderPoint(markers, ptM_bez, 'cyan', '0.5%', '0.5')
|
|
141
|
-
//renderPoint(markers, ptQ, 'magenta', '0.5%', '0.5')
|
|
142
|
-
pathDataN.push(com);
|
|
143
|
-
continue;
|
|
166
|
+
//renderPoint(markers, ptM, 'red', '0.5%', '0.5')
|
|
167
|
+
//renderPoint(markers, ptM_bez, 'green', '0.5%', '0.5')
|
|
144
168
|
|
|
145
|
-
|
|
169
|
+
// not in tolerance – return original command
|
|
170
|
+
if (bezThresh && dist1 > bezThresh && dist1 > len3 * 0.3) {
|
|
171
|
+
//renderPoint(markers, ptM_bez, 'cyan', '0.5%', '0.5')
|
|
172
|
+
//renderPoint(markers, ptQ, 'magenta', '0.5%', '0.5')
|
|
173
|
+
pathDataN.push(com);
|
|
174
|
+
continue;
|
|
146
175
|
|
|
147
|
-
|
|
176
|
+
} else {
|
|
148
177
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// add quadratic command
|
|
155
|
-
pathDataN.push(comL0, comQ);
|
|
156
|
-
i += offset;
|
|
157
|
-
//i++
|
|
178
|
+
//renderPoint(markers, ptQ, 'magenta', '0.5%', '0.5')
|
|
179
|
+
let comQ = { type: 'Q', values: [ptQ.x, ptQ.y, comL1.p0.x, comL1.p0.y] }
|
|
180
|
+
comQ.p0 = comL0.p;
|
|
181
|
+
comQ.cp1 = ptQ;
|
|
182
|
+
comQ.p = comL1.p0;
|
|
158
183
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
184
|
+
// add quadratic command
|
|
185
|
+
pathDataN.push(comL0, comQ);
|
|
186
|
+
i += offset;
|
|
187
|
+
//i++
|
|
162
188
|
|
|
189
|
+
//offset++
|
|
190
|
+
continue;
|
|
163
191
|
}
|
|
192
|
+
|
|
164
193
|
}
|
|
165
194
|
}
|
|
166
195
|
}
|
|
@@ -177,7 +206,7 @@ export function refineRoundedCorners(pathData, {
|
|
|
177
206
|
|
|
178
207
|
|
|
179
208
|
// revert close path normalization
|
|
180
|
-
if (normalizeClose
|
|
209
|
+
if (normalizeClose || (isClosed && pathDataN[pathDataN.length - 1].type !== 'Z')) {
|
|
181
210
|
pathDataN.push({ type: 'Z', values: [] })
|
|
182
211
|
}
|
|
183
212
|
|
|
@@ -15,16 +15,18 @@ export function pathDataToD(pathData, optimize = 0) {
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
let d = '';
|
|
18
|
+
let valsString = pathData[0].values.join(" ");
|
|
18
19
|
let separator_command = beautify ? `\n` : (minify ? '' : ' ');
|
|
19
|
-
let separator_type =
|
|
20
|
+
let separator_type = !minify ? ' ' : '';
|
|
20
21
|
|
|
21
|
-
d = `${pathData[0].type}${separator_type}${
|
|
22
|
+
d = `${pathData[0].type}${separator_type}${valsString}${separator_command}`;
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
for (let i = 1; i < len; i++) {
|
|
25
26
|
let com0 = pathData[i - 1];
|
|
26
27
|
let com = pathData[i];
|
|
27
28
|
let { type, values } = com;
|
|
29
|
+
valsString = '';
|
|
28
30
|
|
|
29
31
|
// Minify Arc commands (A/a) – actually sucks!
|
|
30
32
|
if (minify && (type === 'A' || type === 'a')) {
|
|
@@ -36,7 +38,7 @@ export function pathDataToD(pathData, optimize = 0) {
|
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
// Omit type for repeated commands
|
|
39
|
-
type = (minify && com0.type === com.type && com.type.toLowerCase() !== 'm'
|
|
41
|
+
type = (minify && com0.type === com.type && com.type.toLowerCase() !== 'm')
|
|
40
42
|
? " "
|
|
41
43
|
: (minify && com0.type === "M" && com.type === "L"
|
|
42
44
|
? " "
|
|
@@ -46,9 +48,7 @@ export function pathDataToD(pathData, optimize = 0) {
|
|
|
46
48
|
// concatenate subsequent floating point values
|
|
47
49
|
if (minify) {
|
|
48
50
|
|
|
49
|
-
//console.log(optimize, beautify, minify);
|
|
50
51
|
|
|
51
|
-
let valsString = '';
|
|
52
52
|
let prevWasFloat = false;
|
|
53
53
|
|
|
54
54
|
for (let v = 0, l = values.length; v < l; v++) {
|
|
@@ -85,6 +85,7 @@ export function pathDataToD(pathData, optimize = 0) {
|
|
|
85
85
|
|
|
86
86
|
if (minify) {
|
|
87
87
|
d = d
|
|
88
|
+
.replace(/[A-Za-z]0(?=\.)/g, m => m[0])
|
|
88
89
|
.replace(/ 0\./g, " .") // Space before small decimals
|
|
89
90
|
.replace(/ -/g, "-") // Remove space before negatives
|
|
90
91
|
.replace(/-0\./g, "-.") // Remove leading zero from negative decimals
|
|
@@ -129,9 +129,11 @@ simplifyRDP=1,
|
|
|
129
129
|
poly = poly.map(pt => { return [pt.x, pt.y] })
|
|
130
130
|
}
|
|
131
131
|
else if(polyFormat==='string'){
|
|
132
|
-
poly = poly.map(pt => { return [pt.x, pt.y].join(',') }).join(' ')
|
|
132
|
+
poly = poly.map(pt => { return [pt.x, pt.y].join(',') }).flat().join(' ')
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
//console.log(pathData);
|
|
136
|
+
|
|
135
137
|
return { pathData, poly }
|
|
136
138
|
|
|
137
139
|
}
|