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
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { deg2rad, inch2cm, inch2pt, rad2Deg, root2 } from "../constants";
|
|
2
|
+
import { hex2Rgb, hsl2Rgb, rgba2Hex } from "./convert_colors";
|
|
3
|
+
import { autoRound } from "./rounding";
|
|
4
|
+
import { horizontalProps, verticalProps } from "./svg-styles-to-attributes-const";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export function svgElUnitsToPixel(el, {
|
|
8
|
+
width = 0,
|
|
9
|
+
height = 0,
|
|
10
|
+
fontSize = 16,
|
|
11
|
+
dpi = 96,
|
|
12
|
+
autoRoundValues = false,
|
|
13
|
+
decimals = -1,
|
|
14
|
+
} = {}) {
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
let attributes = [...el.attributes];
|
|
18
|
+
let attNames = attributes.map(att => att.name)
|
|
19
|
+
let attValues = attributes.map(att => att.nodeValue)
|
|
20
|
+
//console.log('attributes', attributes);
|
|
21
|
+
|
|
22
|
+
let isSquare = width === height;
|
|
23
|
+
|
|
24
|
+
let atts = {}
|
|
25
|
+
attNames.forEach((att, i) => {
|
|
26
|
+
let isHorizontal = horizontalProps.includes(att)
|
|
27
|
+
let isVertical = verticalProps.includes(att)
|
|
28
|
+
let normalizedDiagonal = !isSquare && att === 'r' ? true : false
|
|
29
|
+
let attValue = attValues[i];
|
|
30
|
+
//console.log(att, isHorizontal, isVertical, autoRoundValues);
|
|
31
|
+
let val = normalizeUnits(attValue, { isHorizontal, isVertical, width, height, normalizedDiagonal, autoRoundValues })
|
|
32
|
+
atts[att] = val;
|
|
33
|
+
|
|
34
|
+
// apply
|
|
35
|
+
el.setAttribute(att, val)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
return atts;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
// convert real life units to pixels
|
|
43
|
+
export function normalizeUnits(value = null, {
|
|
44
|
+
unit = null,
|
|
45
|
+
width = 0,
|
|
46
|
+
height = 0,
|
|
47
|
+
decimals = -1,
|
|
48
|
+
isHorizontal = false,
|
|
49
|
+
isVertical = false,
|
|
50
|
+
autoRoundValues = false,
|
|
51
|
+
dpi = 96,
|
|
52
|
+
fontSize = 16,
|
|
53
|
+
normalizedDiagonal = false,
|
|
54
|
+
} = {}) {
|
|
55
|
+
|
|
56
|
+
// only required for circle r normalization when height!=width
|
|
57
|
+
normalizedDiagonal = width === height ? false : normalizedDiagonal;
|
|
58
|
+
|
|
59
|
+
let type = typeof value;
|
|
60
|
+
if (!value) return value;
|
|
61
|
+
|
|
62
|
+
// check if value is string
|
|
63
|
+
let isNum = type === 'number' ? true : isNumericValue(value)
|
|
64
|
+
let isArray = type === 'string' ? value.split(/,| /).length > 1 : false;
|
|
65
|
+
let isFunction = type === 'string' ? value.includes('(') : false;
|
|
66
|
+
//console.log(isArray);
|
|
67
|
+
if (!isNum || isArray || isFunction) return value
|
|
68
|
+
|
|
69
|
+
// check unit if not specified
|
|
70
|
+
unit = !unit ? getUnit(value) : unit;
|
|
71
|
+
|
|
72
|
+
let val = parseFloat(value);
|
|
73
|
+
let scale = 1;
|
|
74
|
+
let scaleRoot = Math.sqrt(width * width + height * height) / root2
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
// no unit - already pixes/user unit
|
|
78
|
+
if (!unit) {
|
|
79
|
+
return val;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
switch (unit) {
|
|
83
|
+
case "%":
|
|
84
|
+
if (width && isHorizontal) {
|
|
85
|
+
scale = width / 100;
|
|
86
|
+
}
|
|
87
|
+
else if (height && isVertical) {
|
|
88
|
+
scale = height / 100;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
scale = normalizedDiagonal ? scaleRoot / 100 : 1;
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
|
|
95
|
+
case "rad":
|
|
96
|
+
scale = rad2Deg;
|
|
97
|
+
break;
|
|
98
|
+
case "turn":
|
|
99
|
+
scale = 360;
|
|
100
|
+
break;
|
|
101
|
+
|
|
102
|
+
case "in":
|
|
103
|
+
scale = dpi;
|
|
104
|
+
break;
|
|
105
|
+
|
|
106
|
+
case "pt":
|
|
107
|
+
// 1/72
|
|
108
|
+
scale = dpi * inch2pt;
|
|
109
|
+
break;
|
|
110
|
+
|
|
111
|
+
case "pc":
|
|
112
|
+
// 1/6
|
|
113
|
+
scale = dpi * 0.16666667;
|
|
114
|
+
break;
|
|
115
|
+
|
|
116
|
+
case "cm":
|
|
117
|
+
// 1/2.54
|
|
118
|
+
scale = inch2cm * dpi;
|
|
119
|
+
break;
|
|
120
|
+
case "mm":
|
|
121
|
+
//scale = ((1 / 2.54) * dpi) * 0.1;
|
|
122
|
+
scale = inch2cm * dpi * 0.1
|
|
123
|
+
break;
|
|
124
|
+
|
|
125
|
+
// has anyone ever used it?
|
|
126
|
+
case "Q":
|
|
127
|
+
scale = inch2cm * dpi * 0.025;
|
|
128
|
+
break;
|
|
129
|
+
|
|
130
|
+
// just a default approximation
|
|
131
|
+
case "em":
|
|
132
|
+
case "rem":
|
|
133
|
+
scale = fontSize;
|
|
134
|
+
break;
|
|
135
|
+
default:
|
|
136
|
+
scale = 1;
|
|
137
|
+
}
|
|
138
|
+
let valuePx = val * scale;
|
|
139
|
+
if (autoRoundValues) valuePx = autoRound(valuePx);
|
|
140
|
+
else if (decimals > -1) valuePx = +valuePx.toFixed(decimals);
|
|
141
|
+
|
|
142
|
+
//console.log('valuePx', valuePx);
|
|
143
|
+
return valuePx;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
export function getUnit(val) {
|
|
148
|
+
if (!val || !isNaN(val)) return '';
|
|
149
|
+
val = val.replace(/\+|\-/g, '');
|
|
150
|
+
let unit = val.match(/[^\d|.]+/g)[0];
|
|
151
|
+
return unit;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function isNumericValue(val = '') {
|
|
155
|
+
// is number
|
|
156
|
+
if (!isNaN(val)) return true;
|
|
157
|
+
// parse with unit
|
|
158
|
+
return !isNaN(parseFloat(val))
|
|
159
|
+
}
|
package/src/svgii/geometry.js
CHANGED
|
@@ -3,9 +3,11 @@ import {abs, acos, asin, atan, atan2, ceil, cos, exp, floor,
|
|
|
3
3
|
log, max, min, pow, random, round, sin, sqrt, tan, PI} from '/.constants.js';
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { rad2Deg } from "../constants";
|
|
6
|
+
import { rad2Deg, root2 } from "../constants";
|
|
7
|
+
//import { getPolyBBox } from "./geometry_bbox";
|
|
7
8
|
import { renderPoint } from "./visualize";
|
|
8
9
|
|
|
10
|
+
|
|
9
11
|
export const {
|
|
10
12
|
abs, acos, asin, atan, atan2, ceil, cos, exp, floor,
|
|
11
13
|
log, max, min, pow, random, round, sin, sqrt, tan, PI
|
|
@@ -127,6 +129,15 @@ export function checkLineIntersection(p1 = null, p2 = null, p3 = null, p4 = null
|
|
|
127
129
|
return false
|
|
128
130
|
}
|
|
129
131
|
|
|
132
|
+
// coinciding line points
|
|
133
|
+
if (
|
|
134
|
+
(p1.x === p2.x && p1.y === p2.y) ||
|
|
135
|
+
(p3.x === p4.x && p3.y === p4.y)
|
|
136
|
+
) {
|
|
137
|
+
return false
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
130
141
|
try {
|
|
131
142
|
denominator = ((p4.y - p3.y) * (p2.x - p1.x)) - ((p4.x - p3.x) * (p2.y - p1.y));
|
|
132
143
|
|
|
@@ -171,6 +182,8 @@ export function checkLineIntersection(p1 = null, p2 = null, p3 = null, p4 = null
|
|
|
171
182
|
return false;
|
|
172
183
|
}
|
|
173
184
|
|
|
185
|
+
|
|
186
|
+
|
|
174
187
|
// if line1 and line2 are segments, they intersect if both of the above are true
|
|
175
188
|
//console.log('inter', intersectionPoint)
|
|
176
189
|
return intersectionPoint;
|
|
@@ -192,7 +205,7 @@ export function isPointInPolygon(pt, polygon, bb, skipBB = false) {
|
|
|
192
205
|
}
|
|
193
206
|
}
|
|
194
207
|
|
|
195
|
-
let l=polygon.length;
|
|
208
|
+
let l = polygon.length;
|
|
196
209
|
for (let i = l - 1, j = 0; j < l; i = j, j++) {
|
|
197
210
|
const A = polygon[i];
|
|
198
211
|
const B = polygon[j];
|
|
@@ -204,7 +217,7 @@ export function isPointInPolygon(pt, polygon, bb, skipBB = false) {
|
|
|
204
217
|
/**
|
|
205
218
|
* if pt inside the vertical range filter out "ray pass vertex" problem
|
|
206
219
|
* by treating the line a little lower
|
|
207
|
-
*/
|
|
220
|
+
*/
|
|
208
221
|
if ((pt.y == A.y && B.y >= A.y) || (pt.y == B.y && A.y >= B.y)) continue;
|
|
209
222
|
// calc cross product `ptA X ptB`, pt lays on left side of AB if c > 0
|
|
210
223
|
const c = (A.x - pt.x) * (B.y - pt.y) - (B.x - pt.x) * (A.y - pt.y);
|
|
@@ -1273,7 +1286,7 @@ export function intersectLines(p1, p2, p3, p4) {
|
|
|
1273
1286
|
*/
|
|
1274
1287
|
export function getDistance(p1, p2, isArray = false) {
|
|
1275
1288
|
//if(Array.isArray(p1)) isArray = true;
|
|
1276
|
-
|
|
1289
|
+
|
|
1277
1290
|
//console.log(p1, p2);
|
|
1278
1291
|
let dx = isArray ? p2[0] - p1[0] : (p2.x - p1.x);
|
|
1279
1292
|
let dy = isArray ? p2[1] - p1[1] : (p2.y - p1.y);
|
|
@@ -1388,6 +1401,13 @@ export function reducePoints(points, maxPoints = 48) {
|
|
|
1388
1401
|
}
|
|
1389
1402
|
|
|
1390
1403
|
|
|
1404
|
+
export function getElementTransform(parent, el) {
|
|
1405
|
+
if (!parent || !el) return { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }
|
|
1406
|
+
let matrix = parent.getScreenCTM().inverse().multiply(el.getScreenCTM());
|
|
1407
|
+
return matrix
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
|
|
1391
1411
|
export function mirrorCpts(cpt2_0, pt0, cpt2, pt1, outgoing = true, t = 0.666) {
|
|
1392
1412
|
|
|
1393
1413
|
// hypotenuse angle
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
|
|
2
2
|
//import { splitSubpaths } from "./convert_segments";
|
|
3
|
+
import { getElementAtts } from "../svg-getAttributes";
|
|
3
4
|
import { pointAtT, svgArcToCenterParam, getBezierExtremeT, getArcExtemes, getDistance, interpolate, getPointOnEllipse } from "./geometry";
|
|
5
|
+
//import { parsePathDataNormalized } from "./pathData_convert";
|
|
4
6
|
import { renderPoint } from "./visualize";
|
|
5
7
|
//import {arcToBezier} from'./pathData_convert';
|
|
6
8
|
|
|
7
|
-
|
|
8
9
|
/**
|
|
9
10
|
* calculate polygon bbox
|
|
10
11
|
*/
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { getElementAtts } from "../svg-getAttributes";
|
|
2
|
+
import { getPathDataPoly, getPolyBBox } from "./geometry_bbox";
|
|
3
|
+
import { parsePathDataNormalized } from "./pathData_convert";
|
|
4
|
+
import { normalizePoly } from "./poly_normalize";
|
|
5
|
+
|
|
6
|
+
export function getElBBox(el){
|
|
7
|
+
|
|
8
|
+
let type=el.nodeName.toLowerCase()
|
|
9
|
+
let atts = getElementAtts(el);
|
|
10
|
+
let bb = {x:0, y:0, width:0, height:0}
|
|
11
|
+
let pts = [];
|
|
12
|
+
|
|
13
|
+
switch(type){
|
|
14
|
+
case 'path':
|
|
15
|
+
let pathData = parsePathDataNormalized(atts.d)
|
|
16
|
+
bb=getPolyBBox(getPathDataPoly(pathData))
|
|
17
|
+
|
|
18
|
+
break;
|
|
19
|
+
case 'rect':
|
|
20
|
+
bb = {x:atts.x||0, y:atts.y||0, width:atts.width, height:atts.height}
|
|
21
|
+
break;
|
|
22
|
+
case 'circle':
|
|
23
|
+
let diameter = atts.r*2
|
|
24
|
+
bb = {x:atts.cx-atts.r, y:atts.cy-atts.r, width:diameter, height:diameter}
|
|
25
|
+
break;
|
|
26
|
+
case 'ellipse':
|
|
27
|
+
bb = {x:atts.cx-atts.rx, y:atts.cy-atts.ry, width:atts.rx*2, height:atts.ry*2}
|
|
28
|
+
break;
|
|
29
|
+
|
|
30
|
+
case 'line':
|
|
31
|
+
pts = [{x:atts.x1, y:atts.y1}, {x:atts.x2, y:atts.y2}]
|
|
32
|
+
bb = getPolyBBox(pts)
|
|
33
|
+
break;
|
|
34
|
+
|
|
35
|
+
case 'polyline':
|
|
36
|
+
case 'polygon':
|
|
37
|
+
pts = normalizePoly(atts.points);
|
|
38
|
+
bb = getPolyBBox(pts)
|
|
39
|
+
break;
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
//console.log('bb', bb, type, el);
|
|
43
|
+
|
|
44
|
+
return bb;
|
|
45
|
+
}
|
|
46
|
+
|
|
@@ -55,10 +55,10 @@ export function analyzePathData(pathData = [], {
|
|
|
55
55
|
let len = pathData.length;
|
|
56
56
|
|
|
57
57
|
// threshold for corner angles: 10 deg
|
|
58
|
-
let thresholdCorner = Math.PI * 2 / 360 * 10
|
|
58
|
+
//let thresholdCorner = Math.PI * 2 / 360 * 10
|
|
59
59
|
|
|
60
60
|
// define angle threshold for semi extremes
|
|
61
|
-
let thresholdAngle = detectSemiExtremes ? 0.01 : 0.05
|
|
61
|
+
//let thresholdAngle = detectSemiExtremes ? 0.01 : 0.05
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
for (let c = 2; len && c <= len; c++) {
|
|
@@ -86,7 +86,7 @@ export function analyzePathData(pathData = [], {
|
|
|
86
86
|
// check flatness of command
|
|
87
87
|
let toleranceFlat = 0.01;
|
|
88
88
|
let thresholdLength = dimA * 0.1
|
|
89
|
-
let threshold = thresholdLength*0.01
|
|
89
|
+
let threshold = thresholdLength * 0.01
|
|
90
90
|
let areaThresh = squareDist * toleranceFlat;
|
|
91
91
|
let isFlat = Math.abs(cptArea) < areaThresh;
|
|
92
92
|
|
|
@@ -109,14 +109,29 @@ export function analyzePathData(pathData = [], {
|
|
|
109
109
|
let dx = type === 'C' ? Math.abs(com.cp2.x - com.p.x) : Math.abs(com.cp1.x - com.p.x)
|
|
110
110
|
let dy = type === 'C' ? Math.abs(com.cp2.y - com.p.y) : Math.abs(com.cp1.y - com.p.y)
|
|
111
111
|
|
|
112
|
-
let horizontal = (dy === 0 || dy
|
|
113
|
-
let vertical = (dx === 0 || dx
|
|
112
|
+
//let horizontal = (dy === 0 || dy<=threshold ) && dx > 0
|
|
113
|
+
//let vertical = (dx === 0 || dx<=threshold ) && dy > 0
|
|
114
|
+
let horizontal = (dy === 0 || dy <= threshold) && dx > 0
|
|
115
|
+
let vertical = (dx === 0 || dx <= threshold) && dy > 0
|
|
116
|
+
|
|
114
117
|
|
|
115
118
|
if (horizontal || vertical) {
|
|
116
119
|
hasExtremes = true;
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
// is extreme relative to bounding box
|
|
123
|
+
|
|
124
|
+
// (cp1.x===p0.x && cp1.y!==p0.y ) ||
|
|
125
|
+
if ((cp1.x === p0.x && cp1.y !== p0.y) || (cp1.y === p0.y && cp1.x !== p0.x)) {
|
|
126
|
+
//hasExtremes = true;
|
|
127
|
+
//renderPoint(markers, p0 )
|
|
128
|
+
//p0.extreme = true
|
|
129
|
+
//let comP = pathDataProps[pathDataProps.length-1]
|
|
130
|
+
pathDataProps[pathDataProps.length - 1].extreme = true
|
|
131
|
+
//console.log(comP);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
120
135
|
if ((p.x === left || p.y === top || p.x === right || p.y === bottom)) {
|
|
121
136
|
hasExtremes = true;
|
|
122
137
|
}
|
|
@@ -126,6 +141,7 @@ export function analyzePathData(pathData = [], {
|
|
|
126
141
|
let couldHaveExtremes = bezierhasExtreme(null, commandPts)
|
|
127
142
|
if (couldHaveExtremes) {
|
|
128
143
|
let tArr = getTatAngles(commandPts)
|
|
144
|
+
|
|
129
145
|
if (tArr.length && (tArr[0] > 0.2)) {
|
|
130
146
|
hasExtremes = true;
|
|
131
147
|
}
|
|
@@ -181,24 +197,28 @@ export function analyzePathData(pathData = [], {
|
|
|
181
197
|
|
|
182
198
|
let signChange2 = (areaCpt < 0 && com.cptArea > 0) || (areaCpt > 0 && com.cptArea < 0) ? true : false;
|
|
183
199
|
|
|
184
|
-
let isCorner
|
|
200
|
+
let isCorner = !isFlat && signChange2;
|
|
185
201
|
if (isCorner) com.corner = true;
|
|
186
202
|
}
|
|
187
203
|
}
|
|
188
204
|
|
|
189
|
-
|
|
190
|
-
|
|
205
|
+
pathDataProps.push(com)
|
|
206
|
+
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
//debug = true;
|
|
211
|
+
|
|
212
|
+
if (debug) {
|
|
213
|
+
pathDataProps.forEach(com=>{
|
|
191
214
|
//if (com.semiExtreme) renderPoint(markers, com.p, 'blue', '2%', '0.5')
|
|
192
215
|
if (com.directionChange) renderPoint(markers, com.p, 'orange', '1.5%', '0.5')
|
|
193
216
|
if (com.corner) renderPoint(markers, com.p, 'magenta', '1.5%', '0.5')
|
|
194
217
|
if (com.extreme) renderPoint(markers, com.p, 'cyan', '1%', '0.5')
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
pathDataProps.push(com)
|
|
199
|
-
|
|
218
|
+
})
|
|
200
219
|
}
|
|
201
220
|
|
|
221
|
+
|
|
202
222
|
//pathDataProps.push(comLast)
|
|
203
223
|
|
|
204
224
|
|
|
@@ -316,7 +336,7 @@ export function getPathDataVerbose(pathData, {
|
|
|
316
336
|
}
|
|
317
337
|
}
|
|
318
338
|
|
|
319
|
-
else if (type === 'A') {
|
|
339
|
+
else if (type === 'A' && addArcParams) {
|
|
320
340
|
let { rx, ry, cx, cy, startAngle, endAngle, deltaAngle } = svgArcToCenterParam(p0.x, p0.y, ...values)
|
|
321
341
|
com.cx = cx
|
|
322
342
|
com.cy = cy
|