svg-path-simplify 0.3.4 → 0.3.6
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/.github/ISSUE_TEMPLATE/bug_report.yml +28 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +35 -0
- package/dist/svg-path-simplify.esm.js +4104 -3481
- package/dist/svg-path-simplify.esm.min.js +2 -8
- package/dist/svg-path-simplify.js +4105 -3480
- package/dist/svg-path-simplify.min.js +2 -8
- package/dist/svg-path-simplify.pathdata.esm.js +1090 -1039
- package/dist/svg-path-simplify.pathdata.esm.min.js +2 -8
- package/index.html +493 -116
- package/package.json +1 -1
- package/site.webmanifest +21 -0
- package/src/constants.js +3 -1
- package/src/index.js +7 -1
- package/src/pathData_simplify_cubic.js +1 -10
- package/src/pathSimplify-main.js +71 -28
- package/src/pathSimplify-only-pathdata.js +2 -2
- package/src/svg-getAttributes.js +13 -0
- package/src/svg_flatten_transforms.js +43 -0
- package/src/svg_getViewbox.js +23 -11
- package/src/svg_rootSVG.js +9 -0
- package/src/svgii/convert_colors.js +98 -0
- package/src/svgii/convert_units.js +144 -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 +1 -1
- package/src/svgii/pathData_convert.js +143 -29
- package/src/svgii/pathData_parse.js +2 -99
- package/src/svgii/pathData_parse_els.js +198 -125
- package/src/svgii/pathData_simplify_refineCorners.js +72 -43
- package/src/svgii/pathData_stringify.js +6 -5
- package/src/svgii/poly_normalize.js +21 -1
- package/src/svgii/rounding.js +36 -5
- package/src/svgii/svg-styles-getTransforms.js +43 -5
- package/src/svgii/svg-styles-to-attributes-const.js +8 -3
- package/src/svgii/svg-styles-to-attributes.js +106 -9
- package/src/svgii/svg_cleanup.js +291 -35
- package/src/svgii/svg_el_parse_style_props.js +423 -0
- package/src/svgii/stringify.js +0 -103
|
@@ -1,20 +1,176 @@
|
|
|
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 { autoRound, roundTo } from './rounding.js';
|
|
15
|
+
import { attLookup } from './svg-styles-to-attributes-const.js';
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
export function pathElToShape(el, {
|
|
19
|
+
convert_rects = false,
|
|
20
|
+
convert_ellipses = false,
|
|
21
|
+
convert_poly = false,
|
|
22
|
+
convert_lines = false
|
|
23
|
+
} = {}) {
|
|
24
|
+
|
|
25
|
+
//console.log('pathElToShape', convert_rects, convert_ellipses, convert_lines );
|
|
26
|
+
|
|
27
|
+
let pathData = parsePathDataNormalized(el.getAttribute('d'));
|
|
28
|
+
let coms = Array.from(new Set(pathData.map(com => com.type))).join('')
|
|
29
|
+
|
|
30
|
+
let hasArcs = (/[a]/gi).test(coms)
|
|
31
|
+
let hasBeziers = (/[csqt]/gi).test(coms)
|
|
32
|
+
let hasLines = (/[l]/gi).test(coms)
|
|
33
|
+
let isPoly = !(/[acqts]/gi).test(coms)
|
|
34
|
+
let closed = (/[z]/gi).test(coms)
|
|
35
|
+
let shape = null;
|
|
36
|
+
let type = null
|
|
37
|
+
|
|
38
|
+
let attributes = getElementAtts(el)
|
|
39
|
+
let attsNew = {}
|
|
40
|
+
let decimals = 7;
|
|
41
|
+
|
|
42
|
+
if (isPoly) {
|
|
43
|
+
|
|
44
|
+
// is line
|
|
45
|
+
if (pathData.length === 2 && convert_lines) {
|
|
46
|
+
type = 'line'
|
|
47
|
+
shape = document.createElementNS(svgNs, type)
|
|
48
|
+
let [x1, y1, x2, y2] = [...pathData[0].values, ...pathData[1].values].map(val => roundTo(val, decimals))
|
|
49
|
+
attsNew = { x1, y1, x2, y2 }
|
|
50
|
+
}
|
|
51
|
+
// polygon, polyline or rect
|
|
52
|
+
else {
|
|
53
|
+
|
|
54
|
+
let vertices = getPathDataVertices(pathData);
|
|
55
|
+
let bb = getPolyBBox(vertices)
|
|
56
|
+
let areaPoly = getPolygonArea(vertices, true)
|
|
57
|
+
let areaRect = bb.width * bb.height;
|
|
58
|
+
let areaDiff = Math.abs(1 - areaRect / areaPoly);
|
|
59
|
+
|
|
60
|
+
// is rect
|
|
61
|
+
if (convert_rects && areaDiff < 0.01) {
|
|
62
|
+
type = 'rect'
|
|
63
|
+
shape = document.createElementNS(svgNs, type)
|
|
64
|
+
let { x, y, width, height } = bb
|
|
65
|
+
attsNew = { x, y, width, height }
|
|
66
|
+
|
|
67
|
+
}
|
|
68
|
+
// polyline or polygon
|
|
69
|
+
else if(convert_poly) {
|
|
70
|
+
type = closed ? 'polygon' : 'polyline';
|
|
71
|
+
shape = document.createElementNS(svgNs, type)
|
|
72
|
+
let points = vertices.map(pt => { return [pt.x, pt.y] }).flat().map(val => roundTo(val, decimals)).join(' ')
|
|
73
|
+
attsNew = { points }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// circles or ellipses
|
|
78
|
+
else if (!hasLines && convert_ellipses) {
|
|
79
|
+
|
|
80
|
+
// try to convert cubics to arcs
|
|
81
|
+
if (!hasArcs && hasBeziers) {
|
|
82
|
+
pathData = pathDataCubicsToArc(pathData, { areaThreshold: 2.5 })
|
|
83
|
+
hasArcs = pathData.filter(com => com.type === 'A').length;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if (hasArcs) {
|
|
88
|
+
let pathData2 = getPathDataVerbose(pathData, { addArcParams: true })
|
|
89
|
+
let arcComs = pathData2.filter(com => com.type === 'A')
|
|
90
|
+
|
|
91
|
+
let cxVals = new Set();
|
|
92
|
+
let cyVals = new Set();
|
|
93
|
+
let rxVals = new Set();
|
|
94
|
+
let ryVals = new Set();
|
|
95
|
+
|
|
96
|
+
if (arcComs.length > 1) {
|
|
97
|
+
//console.log('!!!arcComs', arcComs);
|
|
98
|
+
pathData2.forEach(com => {
|
|
99
|
+
if (com.type === 'A') {
|
|
100
|
+
//console.log('params', com, com.cx, com.cy, com.rx, com.ry);
|
|
101
|
+
cxVals.add(roundTo(com.cx, decimals))
|
|
102
|
+
cyVals.add(roundTo(com.cy, decimals))
|
|
103
|
+
rxVals.add(roundTo(com.rx, decimals))
|
|
104
|
+
ryVals.add(roundTo(com.ry, decimals))
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
cxVals = Array.from(cxVals)
|
|
110
|
+
cyVals = Array.from(cyVals)
|
|
111
|
+
rxVals = Array.from(rxVals)
|
|
112
|
+
ryVals = Array.from(ryVals)
|
|
113
|
+
|
|
114
|
+
if(cxVals.length===1 && cyVals.length===1 && rxVals.length===1 && ryVals.length===1){
|
|
115
|
+
let [rx, ry, cx, cy] = [rxVals[0], ryVals[0], cxVals[0], cyVals[0]]
|
|
116
|
+
type = rx===ry ? 'circle' : 'ellipse';
|
|
117
|
+
shape = document.createElementNS(svgNs, type)
|
|
118
|
+
attsNew = type==='circle' ? { r:rx, cx, cy } : {rx, ry, cx, cy}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
// if el could be replaced
|
|
125
|
+
if (shape) {
|
|
126
|
+
let ignore = ['id', 'class']
|
|
127
|
+
|
|
128
|
+
// set shape attributes
|
|
129
|
+
for (let att in attsNew) {
|
|
130
|
+
shape.setAttribute(att, attsNew[att])
|
|
131
|
+
}
|
|
3
132
|
|
|
4
|
-
|
|
133
|
+
// copy old attributes
|
|
134
|
+
for (let att in attributes) {
|
|
135
|
+
//console.log(attributes);
|
|
136
|
+
if (attLookup.atts[att].includes(type) || ignore.includes(att) || att.startsWith('data-')) {
|
|
137
|
+
shape.setAttribute(att, attributes[att])
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// replace
|
|
142
|
+
el = shape;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return el;
|
|
146
|
+
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function shapeElToPath(el, { width = 0,
|
|
150
|
+
height = 0,
|
|
151
|
+
convert_rects = false,
|
|
152
|
+
convert_ellipses = false,
|
|
153
|
+
convert_poly = false,
|
|
154
|
+
convert_lines = false
|
|
155
|
+
|
|
156
|
+
} = {}) {
|
|
5
157
|
|
|
6
158
|
let nodeName = el.nodeName.toLowerCase();
|
|
7
|
-
if (nodeName === 'path') return el;
|
|
8
159
|
|
|
9
|
-
|
|
160
|
+
if (
|
|
161
|
+
nodeName === 'path' ||
|
|
162
|
+
nodeName === 'rect' && !convert_rects ||
|
|
163
|
+
(nodeName === 'circle' || nodeName === 'ellipse') && !convert_ellipses ||
|
|
164
|
+
(nodeName === 'polygon' || nodeName === 'polyline') && !convert_poly ||
|
|
165
|
+
(nodeName === 'line') && !convert_lines
|
|
166
|
+
) return el;
|
|
167
|
+
|
|
168
|
+
let pathData = getPathDataFromEl(el, { width, height });
|
|
10
169
|
let d = pathData.map(com => { return `${com.type} ${com.values} ` }).join(' ')
|
|
11
170
|
let attributes = [...el.attributes].map(att => att.name);
|
|
12
171
|
|
|
13
|
-
//console.log(d);
|
|
14
|
-
//return []
|
|
15
172
|
|
|
16
|
-
let pathN = document.createElementNS(
|
|
17
|
-
//let pathN = document.createElement('path');
|
|
173
|
+
let pathN = document.createElementNS(svgNs, 'path');
|
|
18
174
|
pathN.setAttribute('d', d);
|
|
19
175
|
|
|
20
176
|
let exclude = ['x', 'y', 'x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'dx', 'dy', 'r', 'rx', 'ry', 'width', 'height', 'points'];
|
|
@@ -31,121 +187,37 @@ export function shapeElToPath(el) {
|
|
|
31
187
|
return pathN
|
|
32
188
|
|
|
33
189
|
}
|
|
190
|
+
/*
|
|
191
|
+
export function copyAttributes(newEl, oldEl){
|
|
34
192
|
|
|
193
|
+
let attributes = [...oldEl.attributes].map(att => att.name);
|
|
35
194
|
|
|
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
195
|
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
66
|
-
switch (unit) {
|
|
67
|
-
case "in":
|
|
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
|
-
: [];
|
|
196
|
+
}
|
|
197
|
+
*/
|
|
108
198
|
|
|
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
199
|
|
|
115
|
-
|
|
116
|
-
|
|
200
|
+
// retrieve pathdata from svg geometry elements
|
|
201
|
+
export function getPathDataFromEl(el, {
|
|
202
|
+
stringify = false,
|
|
203
|
+
width = 0,
|
|
204
|
+
height = 0
|
|
205
|
+
} = {}) {
|
|
117
206
|
|
|
207
|
+
let pathData = [];
|
|
208
|
+
let type = el.nodeName.toLowerCase();
|
|
209
|
+
let attNames, d, x, y, r, rx, ry, cx, cy, x1, x2, y1, y2;
|
|
118
210
|
|
|
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
|
-
});
|
|
211
|
+
if (!width || !height) {
|
|
212
|
+
let svg = getRootSvg(el);
|
|
213
|
+
let viewBox = getViewBox(svg)
|
|
214
|
+
width = viewBox.width;
|
|
215
|
+
height = viewBox.height;
|
|
138
216
|
}
|
|
139
217
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
atts = {}
|
|
144
|
-
attNames.forEach(att => {
|
|
145
|
-
atts[att] = +el.getAttribute(att)
|
|
146
|
-
})
|
|
147
|
-
return atts
|
|
148
|
-
}
|
|
218
|
+
// convert relative and physical units to user-units
|
|
219
|
+
let atts = svgElUnitsToPixel(el, { width, height })
|
|
220
|
+
//console.log('atts', atts);
|
|
149
221
|
|
|
150
222
|
switch (type) {
|
|
151
223
|
case 'path':
|
|
@@ -155,8 +227,7 @@ export function getPathDataFromEl(el, stringify = false) {
|
|
|
155
227
|
|
|
156
228
|
case 'rect':
|
|
157
229
|
attNames = ['x', 'y', 'width', 'height', 'rx', 'ry'];
|
|
158
|
-
({ x, y, width, height, rx, ry } =
|
|
159
|
-
|
|
230
|
+
({ x=0, y=0, width=0, height=0, rx=0, ry=0 } = atts);
|
|
160
231
|
|
|
161
232
|
if (!rx && !ry) {
|
|
162
233
|
pathData = [
|
|
@@ -168,8 +239,8 @@ export function getPathDataFromEl(el, stringify = false) {
|
|
|
168
239
|
];
|
|
169
240
|
} else {
|
|
170
241
|
|
|
171
|
-
rx=rx? rx : ry;
|
|
172
|
-
ry=ry ? ry : rx;
|
|
242
|
+
rx = rx ? rx : ry;
|
|
243
|
+
ry = ry ? ry : rx;
|
|
173
244
|
|
|
174
245
|
if (rx > width / 2) {
|
|
175
246
|
rx = width / 2;
|
|
@@ -196,7 +267,7 @@ export function getPathDataFromEl(el, stringify = false) {
|
|
|
196
267
|
case 'ellipse':
|
|
197
268
|
|
|
198
269
|
attNames = ['cx', 'cy', 'rx', 'ry', 'r'];
|
|
199
|
-
({ cx, cy, r, rx, ry } =
|
|
270
|
+
({ cx=0, cy=0, r, rx, ry } = atts);
|
|
200
271
|
|
|
201
272
|
let isCircle = type === 'circle';
|
|
202
273
|
|
|
@@ -209,9 +280,11 @@ export function getPathDataFromEl(el, stringify = false) {
|
|
|
209
280
|
ry = ry ? ry : r;
|
|
210
281
|
}
|
|
211
282
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
let
|
|
283
|
+
|
|
284
|
+
// simplified radii for circles
|
|
285
|
+
let rxS = isCircle && r >= 1 ? 1 : rx;
|
|
286
|
+
let ryS = isCircle && r >= 1 ? 1 : ry;
|
|
287
|
+
|
|
215
288
|
|
|
216
289
|
pathData = [
|
|
217
290
|
{ type: "M", values: [cx + rx, cy] },
|
|
@@ -222,7 +295,7 @@ export function getPathDataFromEl(el, stringify = false) {
|
|
|
222
295
|
break;
|
|
223
296
|
case 'line':
|
|
224
297
|
attNames = ['x1', 'y1', 'x2', 'y2'];
|
|
225
|
-
({ x1, y1, x2, y2 } =
|
|
298
|
+
({ x1, y1, x2, y2 } = atts);
|
|
226
299
|
pathData = [
|
|
227
300
|
{ type: "M", values: [x1, y1] },
|
|
228
301
|
{ type: "L", values: [x2, y2] }
|
|
@@ -231,12 +304,12 @@ export function getPathDataFromEl(el, stringify = false) {
|
|
|
231
304
|
case 'polygon':
|
|
232
305
|
case 'polyline':
|
|
233
306
|
|
|
234
|
-
let points = el.getAttribute('points').
|
|
307
|
+
let points = el.getAttribute('points').split(/,| /).filter(Boolean).map(Number)
|
|
235
308
|
|
|
236
309
|
for (let i = 0; i < points.length; i += 2) {
|
|
237
310
|
pathData.push({
|
|
238
311
|
type: (i === 0 ? "M" : "L"),
|
|
239
|
-
values: [
|
|
312
|
+
values: [points[i], points[i + 1]]
|
|
240
313
|
});
|
|
241
314
|
}
|
|
242
315
|
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
|
|
@@ -4,6 +4,12 @@ export function normalizePoly(pts, {
|
|
|
4
4
|
flatten = false
|
|
5
5
|
} = {}) {
|
|
6
6
|
|
|
7
|
+
// is stringified flat point attribute
|
|
8
|
+
if(typeof pts === 'string' && !isNaN(pts[0])){
|
|
9
|
+
pts = toPointArray(pts.split(/,| /).filter(Boolean).map(Number));
|
|
10
|
+
return pts
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
if (flatten) pts = pts.flat(2);
|
|
8
14
|
let poly = toArray ? polyPtsToArray(pts) : polyArrayToObject(pts)
|
|
9
15
|
return poly
|
|
@@ -29,6 +35,11 @@ export function polyArrayToObject(pts) {
|
|
|
29
35
|
return poly
|
|
30
36
|
}
|
|
31
37
|
|
|
38
|
+
else if(pts.length>3){
|
|
39
|
+
pts = toPointArray(pts)
|
|
40
|
+
return pts
|
|
41
|
+
}
|
|
42
|
+
|
|
32
43
|
return pts.map(pt => { return { x: pt[0], y: pt[1] } })
|
|
33
44
|
}
|
|
34
45
|
|
|
@@ -48,4 +59,13 @@ export function polyPtsToArray(pts) {
|
|
|
48
59
|
|
|
49
60
|
poly = Array.from(pts).map(pt => [pt.x, pt.y])
|
|
50
61
|
return poly
|
|
51
|
-
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// convert flat point value array to point object array
|
|
65
|
+
export function toPointArray(pts) {
|
|
66
|
+
let ptArr = [];
|
|
67
|
+
for (let i = 1, l = pts.length; i < l; i += 2) {
|
|
68
|
+
ptArr.push({ x: pts[i - 1], y: pts[i] });
|
|
69
|
+
}
|
|
70
|
+
return ptArr;
|
|
71
|
+
};
|