svg-path-simplify 0.0.1 → 0.0.2
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/README.md +28 -1
- package/dist/svg-path-simplify.esm.js +4040 -0
- package/dist/svg-path-simplify.esm.min.js +1 -0
- package/dist/svg-path-simplify.js +4065 -0
- package/dist/svg-path-simplify.min.js +1 -0
- package/dist/svg-path-simplify.node.js +4062 -0
- package/dist/svg-path-simplify.node.min.js +1 -0
- package/index.html +222 -0
- package/package.json +2 -2
- package/src/constants.js +4 -0
- package/src/index.js +18 -3
- package/src/pathData_simplify_cubic.js +324 -0
- package/src/pathData_simplify_cubic_arr.js +50 -0
- package/src/pathData_simplify_cubic_extrapolate.js +220 -0
- package/src/pathSimplify-main.js +294 -0
- package/src/svgii/...parse.js +402 -0
- package/src/svgii/geometry.js +1096 -0
- package/src/svgii/geometry_area.js +265 -0
- package/src/svgii/geometry_bbox.js +223 -0
- package/src/svgii/pathData_analyze.js +896 -0
- package/src/svgii/pathData_convert.js +1180 -0
- package/src/svgii/pathData_parse.js +487 -0
- package/src/svgii/pathData_remove_collinear.js +85 -0
- package/src/svgii/pathData_remove_zerolength.js +28 -0
- package/src/svgii/pathData_reorder.js +204 -0
- package/src/svgii/pathData_reverse.js +124 -0
- package/src/svgii/pathData_scale.js +42 -0
- package/src/svgii/pathData_split.js +449 -0
- package/src/svgii/pathData_stringify.js +146 -0
- package/src/svgii/pathData_toPolygon.js +92 -0
- package/src/svgii/pathdata_cleanup.js +363 -0
- package/src/svgii/poly_analyze.js +172 -0
- package/src/svgii/poly_to_pathdata.js +185 -0
- package/src/svgii/rounding.js +154 -0
- package/src/svgii/simplify.js +248 -0
- package/src/svgii/simplify_bezier.js +470 -0
- package/src/svgii/simplify_linetos.js +93 -0
- package/src/svgii/simplify_polygon.js +135 -0
- package/src/svgii/stringify.js +103 -0
- package/src/svgii/svg_cleanup.js +80 -0
- package/src/svgii/visualize.js +317 -0
- package/LICENSE +0 -21
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* Chord-Length Parameterization
|
|
4
|
+
* based on
|
|
5
|
+
* https://francoisromain.medium.com/smooth-a-svg-path-with-cubic-bezier-curves-e37b49d46c74
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { checkLineIntersection, interpolate, mirrorCpts } from "./geometry";
|
|
9
|
+
import { getPolyBBox } from "./geometry_bbox";
|
|
10
|
+
import { isClosedPolygon } from "./poly_analyze";
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// Render the svg <path> element
|
|
14
|
+
export function getCurvePathData(pts, t = 0.666, closed = 'auto', keepCorners = true) {
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
//auto detect closed polygon
|
|
18
|
+
if(closed==='auto'){
|
|
19
|
+
closed = isClosedPolygon(pts)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
// append first 2 pts for closed paths
|
|
24
|
+
if (closed) {
|
|
25
|
+
pts = pts.concat(pts.slice(0, 2));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
// Position of a control point
|
|
30
|
+
const controlPoint = (pt1, pt0, pt2, reverse = false, t = 0.666) => {
|
|
31
|
+
|
|
32
|
+
let p = pt0 || pt1;
|
|
33
|
+
let n = pt2 || pt1;
|
|
34
|
+
|
|
35
|
+
let dx = n.x - p.x
|
|
36
|
+
let dy = n.y - p.y
|
|
37
|
+
let sign = reverse ? -1 : 1;
|
|
38
|
+
|
|
39
|
+
let cp0 = {
|
|
40
|
+
x: pt1.x + dx * sign,
|
|
41
|
+
y: pt1.y + dy * sign
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
let t2 = 0.1 / (1 - t * 0.5)
|
|
46
|
+
let cp = interpolate(pt1, cp0, t2)
|
|
47
|
+
|
|
48
|
+
return cp;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
// collect smoothed pathData
|
|
53
|
+
let pathData = [];
|
|
54
|
+
pathData.push({ type: "M", values: [pts[0].x, pts[0].y], p0:{x:pts[0].x, y:pts[0].y} });
|
|
55
|
+
|
|
56
|
+
let cp2_0 = pts[0];
|
|
57
|
+
let l = pts.length;
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
for (let i = 1; i < l; i++) {
|
|
61
|
+
|
|
62
|
+
let drawLine = false;
|
|
63
|
+
let ptPrev = i > 1 ? pts[i - 2] : pts[l - 1];
|
|
64
|
+
let ptNext = i < l - 1 ? pts[i + 1] : pts[0];
|
|
65
|
+
//console.log(ptPrev, ptNext);
|
|
66
|
+
|
|
67
|
+
let pt0 = pts[i - 1];
|
|
68
|
+
let pt1 = pts[i];
|
|
69
|
+
let cp1 = controlPoint(pt0, ptPrev, pt1, false, t);
|
|
70
|
+
let cp2 = controlPoint(pt1, pt0, ptNext, true, t);
|
|
71
|
+
|
|
72
|
+
let {isExtreme, isCorner,directionChange} = pt1;
|
|
73
|
+
|
|
74
|
+
// get cp vector intersections
|
|
75
|
+
let cpI = checkLineIntersection(pt0, cp1, pt1, cp2, false);
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
// harmonize cpts
|
|
79
|
+
if (cpI) {
|
|
80
|
+
let { left, top, right, bottom, width, height } = getPolyBBox([pt0, pt1]);
|
|
81
|
+
let outside = cpI ? (cpI.x < left || cpI.x > right || cpI.y < top || cpI.y > bottom) : false;
|
|
82
|
+
|
|
83
|
+
// adjust/harmonize control points
|
|
84
|
+
if (!outside) {
|
|
85
|
+
cp1 = interpolate(pt0, cpI, t)
|
|
86
|
+
cp2 = interpolate(pt1, cpI, t)
|
|
87
|
+
} else {
|
|
88
|
+
|
|
89
|
+
// check exact cp self intersections
|
|
90
|
+
let cpI2 = checkLineIntersection(pt0, cp1, pt1, cp2, true);
|
|
91
|
+
|
|
92
|
+
// control points are diverging - connction between cps and start/end point
|
|
93
|
+
let interH = checkLineIntersection(pt0, pt1, cp1, cp2, true);
|
|
94
|
+
|
|
95
|
+
cpI = !interH ? cpI : (cpI2 ? cpI2 : null)
|
|
96
|
+
|
|
97
|
+
//&& i < l - 3
|
|
98
|
+
if (cpI ) {
|
|
99
|
+
cp1 = interpolate(pt0, cpI, t)
|
|
100
|
+
cp2 = interpolate(pt1, cpI, t)
|
|
101
|
+
//renderPoint(svg, cpI, 'magenta')
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
if (keepCorners) {
|
|
109
|
+
|
|
110
|
+
// mirror cpts
|
|
111
|
+
if ((pt1.isCorner && !pt0.isCorner) || (!pt1.isCorner && pt0.isCorner)) {
|
|
112
|
+
let outgoing = !pt1.isCorner && pt0.isCorner;
|
|
113
|
+
|
|
114
|
+
let cps = mirrorCpts(cp2_0, pt0, cp2, pt1, outgoing, t);
|
|
115
|
+
let cp1_2 = cps.cp1
|
|
116
|
+
let cp2_2 = cps.cp2
|
|
117
|
+
|
|
118
|
+
cp1 = cp1_2;
|
|
119
|
+
cp2 = cp2_2;
|
|
120
|
+
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// withdraw cpts for sharp corners - tag as lineto
|
|
124
|
+
else if ((pt1.isCorner && pt0.isCorner)) {
|
|
125
|
+
|
|
126
|
+
cp1 = { x: pt0.x, y: pt0.y };
|
|
127
|
+
cp2 = { x: pt1.x, y: pt1.y };
|
|
128
|
+
drawLine = true
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
// update last cp2
|
|
135
|
+
cp2_0 = cp2;
|
|
136
|
+
|
|
137
|
+
let com = { type: "C", values: [cp1.x, cp1.y, cp2.x, cp2.y, pt1.x, pt1.y],
|
|
138
|
+
drawLine,
|
|
139
|
+
// add properties for chunk based simplification
|
|
140
|
+
isExtreme, isCorner,directionChange
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
let values = com.values
|
|
145
|
+
com.p0 = pt0
|
|
146
|
+
com.cp1 = {x:values[0], y:values[1]}
|
|
147
|
+
com.cp2 = {x:values[2], y:values[3]}
|
|
148
|
+
com.p = {x:values[4], y:values[5]}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
pathData.push(com);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// copy last commands 1st controlpoint to first curveto
|
|
155
|
+
if (closed) {
|
|
156
|
+
let comLast = pathData[pathData.length - 1];
|
|
157
|
+
let valuesLastC = comLast.values;
|
|
158
|
+
let valuesFirstC = pathData[1].values;
|
|
159
|
+
|
|
160
|
+
pathData[1].type = 'C'
|
|
161
|
+
pathData[1].values = [valuesLastC[0], valuesLastC[1], ...valuesFirstC.slice(2)]
|
|
162
|
+
let values0 = pathData[0].values
|
|
163
|
+
let values = pathData[1].values
|
|
164
|
+
pathData[1].p0 = {x:values0[0], y:values0[1]}
|
|
165
|
+
pathData[1].cp1 = {x:values[0], y:values[1]}
|
|
166
|
+
pathData[1].cp2 = {x:values[2], y:values[3]}
|
|
167
|
+
pathData[1].p = {x:values[4], y:values[5]}
|
|
168
|
+
|
|
169
|
+
// delete last curveto
|
|
170
|
+
pathData = pathData.slice(0, pathData.length - 1);
|
|
171
|
+
pathData.push({ type: 'z', values: [] })
|
|
172
|
+
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// convert flat curves to linetos
|
|
176
|
+
pathData.forEach((com, i) => {
|
|
177
|
+
if (com.drawLine) {
|
|
178
|
+
pathData[i].type = 'L'
|
|
179
|
+
pathData[i].values = com.values.slice(-2);
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
//console.log(pathData);
|
|
184
|
+
return pathData;
|
|
185
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* detect suitable floating point accuracy
|
|
4
|
+
* for further rounding/optimizations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getDistAv } from "./geometry";
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export function detectAccuracy(pathData) {
|
|
11
|
+
|
|
12
|
+
// Reference first MoveTo command (M)
|
|
13
|
+
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
14
|
+
let p0 = M
|
|
15
|
+
let p = M
|
|
16
|
+
pathData[0].decimals = 0
|
|
17
|
+
let lastDec = 0;
|
|
18
|
+
let maxDecimals = 0
|
|
19
|
+
let minDim = Infinity
|
|
20
|
+
let maxDim = 0
|
|
21
|
+
|
|
22
|
+
//console.log('detectAccuracy');
|
|
23
|
+
|
|
24
|
+
// add average distances
|
|
25
|
+
for (let i = 0, len = pathData.length; i < len; i++) {
|
|
26
|
+
let com = pathData[i];
|
|
27
|
+
let { type, values } = com;
|
|
28
|
+
|
|
29
|
+
let lastVals = values.length ? values.slice(-2) : [M.x, M.y];
|
|
30
|
+
p={x:lastVals[0], y:lastVals[1]}
|
|
31
|
+
|
|
32
|
+
// use existing averave dimension value or calculate
|
|
33
|
+
let dimA = com.dimA ? +com.dimA.toFixed(8) : type!=='M' ? +getDistAv(p0, p).toFixed(8) : 0
|
|
34
|
+
//let dimA = +getDistAv(p0, p).toFixed(8)
|
|
35
|
+
//console.log('dimA', dimA, com.dimA, type);
|
|
36
|
+
|
|
37
|
+
if(dimA && dimA<minDim) minDim = dimA;
|
|
38
|
+
//if(dimA && dimA>maxDim) maxDim = dimA;
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
if(type==='M'){
|
|
42
|
+
M=p;
|
|
43
|
+
}
|
|
44
|
+
p0 = p;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//minDim = +minDim.toFixed(8)
|
|
48
|
+
let decimalsAuto = Math.floor(50 / minDim).toString().length
|
|
49
|
+
//console.log('!!!minDim', minDim, 'maxDim', maxDim, decimalsAuto);
|
|
50
|
+
|
|
51
|
+
// clamp
|
|
52
|
+
return Math.min(Math.max(0, decimalsAuto), 8)
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
export function detectAccuracy_back(pathData) {
|
|
61
|
+
|
|
62
|
+
// Reference first MoveTo command (M)
|
|
63
|
+
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
64
|
+
let p0 = { ...M };
|
|
65
|
+
pathData[0].decimals = 0
|
|
66
|
+
let lastDec = 0;
|
|
67
|
+
let maxDecimals = 0
|
|
68
|
+
|
|
69
|
+
for (let i = 1, len = pathData.length; i < len; i++) {
|
|
70
|
+
let com = pathData[i];
|
|
71
|
+
let { type, values } = com;
|
|
72
|
+
|
|
73
|
+
let lastVals = values.length ? values.slice(-2) : [M.x, M.y];
|
|
74
|
+
let lastX = lastVals[0];
|
|
75
|
+
let lastY = lastVals[1];
|
|
76
|
+
|
|
77
|
+
if (type === 'Z' || type === 'z') {
|
|
78
|
+
lastX = M.x;
|
|
79
|
+
lastY = M.y;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let w = Math.abs(p0.x - lastX);
|
|
83
|
+
let h = Math.abs(p0.y - lastY);
|
|
84
|
+
let dimA = (w + h) / 2 || 0;
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
// Determine decimal places dynamically
|
|
88
|
+
let decimals = (type !== 'Z' && type !== 'z') ? Math.ceil((1 / dimA)).toString().length + 1 : 0;
|
|
89
|
+
|
|
90
|
+
//console.log(type, dimA, decimals);
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if (dimA === 0) {
|
|
94
|
+
//console.log('zero length');
|
|
95
|
+
decimals = lastDec;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
else if (decimals && dimA < 0.5) {
|
|
99
|
+
decimals++
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
//console.log('dimA', type, dimA, decimals);
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
// Update previous coordinates
|
|
106
|
+
p0 = { x: lastX, y: lastY };
|
|
107
|
+
|
|
108
|
+
// Track MoveTo for closing paths
|
|
109
|
+
if (type === 'M') {
|
|
110
|
+
M = { x: values[0], y: values[1] };
|
|
111
|
+
com.decimals = decimals;
|
|
112
|
+
} else {
|
|
113
|
+
|
|
114
|
+
// Store ideal precision for next pass
|
|
115
|
+
com.decimals = decimals;
|
|
116
|
+
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
maxDecimals = decimals > maxDecimals ? decimals : maxDecimals;
|
|
120
|
+
lastDec = decimals;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// set max decimal for M
|
|
124
|
+
return maxDecimals
|
|
125
|
+
//pathData[0].decimals = maxDecimals
|
|
126
|
+
//return pathData
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* round path data
|
|
132
|
+
* either by explicit decimal value or
|
|
133
|
+
* based on suggested accuracy in path data
|
|
134
|
+
*/
|
|
135
|
+
export function roundPathData(pathData, decimals = -1) {
|
|
136
|
+
// has recommended decimals
|
|
137
|
+
let hasDecimal = decimals == 'auto' && pathData[0].hasOwnProperty('decimals') ? true : false;
|
|
138
|
+
//console.log('decimals', decimals, hasDecimal);
|
|
139
|
+
|
|
140
|
+
for(let c=0, len=pathData.length; c<len; c++){
|
|
141
|
+
let com=pathData[c];
|
|
142
|
+
let {type, values} = com
|
|
143
|
+
|
|
144
|
+
if (decimals >-1 || hasDecimal) {
|
|
145
|
+
decimals = hasDecimal ? com.decimals : decimals;
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
//console.log('decimals', type, decimals);
|
|
149
|
+
pathData[c].values = com.values.map(val=>{return val ? +val.toFixed(decimals) : val });
|
|
150
|
+
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
return pathData;
|
|
154
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* split path data into chunks
|
|
4
|
+
* to detect subsequent cubic segments
|
|
5
|
+
* that could be combined
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
//import { splitSubpaths, shiftSvgStartingPoint } from "./convert_segments";
|
|
9
|
+
import { shiftSvgStartingPoint } from "./pathData_reorder.js";
|
|
10
|
+
import { splitSubpaths, getPathDataPlusChunks } from './pathData_split.js';
|
|
11
|
+
|
|
12
|
+
import { getAngle, bezierhasExtreme, getPathDataVertices } from "./geometry";
|
|
13
|
+
import { renderPoint, renderPath } from "./visualize";
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
//import { optimizeStartingPoints } from './cleanup.js';
|
|
17
|
+
//import { getPathDataVertices, getPointOnEllipse, pointAtT, checkLineIntersection, getDistance, interpolate } from './geometry.js';
|
|
18
|
+
|
|
19
|
+
import { getPolygonArea, getPathArea, getRelativeAreaDiff } from './geometry_area.js';
|
|
20
|
+
import { getPathDataBBox, getPolyBBox } from './geometry_bbox.js';
|
|
21
|
+
|
|
22
|
+
import { optimizeStartingPoints, cleanUpPathData } from './pathdata_cleanup.js';
|
|
23
|
+
|
|
24
|
+
import { pathDataArcsToCubics, pathDataQuadraticToCubic, quadratic2Cubic, pathDataToRelative, pathDataToAbsolute, pathDataToLonghands, pathDataToShorthands, pathDataToQuadratic, cubicToQuad, arcToBezier, pathDataToVerbose, convertArrayPathData, revertPathDataToArray, combineArcs, replaceCubicsByArcs } from './pathData_convert.js';
|
|
25
|
+
|
|
26
|
+
import {unitePolygon} from './simplify_polygon.js';
|
|
27
|
+
|
|
28
|
+
import { simplifyBezierSequence } from './simplify_bezier.js';
|
|
29
|
+
//import { simplifyBezierSequence } from './simplify_bezier_back16_working.js';
|
|
30
|
+
//import { simplifyBezierSequence } from './simplify_bezier_back17_working.js';
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
import { simplifyLinetoSequence } from './simplify_linetos.js';
|
|
35
|
+
import { analyzePathData } from "./pathData_anylyse.js";
|
|
36
|
+
import { scalePathData } from "./pathData_scale.js";
|
|
37
|
+
//import { analyzePathData } from "./pathData_anylyse_back1.js";
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
export function simplifyPathData(pathData, tolerance = 3, keepDetails = true, forceCubic = false, cubicToArc = true, multipass = false, debug = false) {
|
|
42
|
+
|
|
43
|
+
///devcomment
|
|
44
|
+
|
|
45
|
+
//console.log('forceCubic simplifyPathData', forceCubic);
|
|
46
|
+
|
|
47
|
+
// unoptimized area
|
|
48
|
+
let area0 = getPathArea(pathData);
|
|
49
|
+
|
|
50
|
+
// get bbox for adjustment scaling
|
|
51
|
+
let bb = getPathDataBBox(pathData);
|
|
52
|
+
//console.log('bb', bb);
|
|
53
|
+
|
|
54
|
+
let dimA = (bb.width + bb.height) / 2;
|
|
55
|
+
let scale = dimA < 10 ? 100 / dimA : 1;
|
|
56
|
+
|
|
57
|
+
// scale small paths
|
|
58
|
+
if (scale != 1) pathData = scalePathData(pathData, scale, scale)
|
|
59
|
+
|
|
60
|
+
// remove zero length commands and shift starting point
|
|
61
|
+
let addExtremes = true;
|
|
62
|
+
addExtremes = false;
|
|
63
|
+
|
|
64
|
+
let removeFinalLineto = false
|
|
65
|
+
let startToTop = true;
|
|
66
|
+
//tolerance = 5;
|
|
67
|
+
|
|
68
|
+
// show chunks
|
|
69
|
+
//debug = true
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* optimize starting point
|
|
73
|
+
* remove zero length segments
|
|
74
|
+
*/
|
|
75
|
+
pathData = cleanUpPathData(pathData, addExtremes, removeFinalLineto, startToTop, debug)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
// get verbose pathdata properties
|
|
79
|
+
let pathDataPlus = analyzePathData(pathData);
|
|
80
|
+
|
|
81
|
+
// add chunks to path object
|
|
82
|
+
let pathDataPlusChunks = getPathDataPlusChunks(pathDataPlus, debug);
|
|
83
|
+
|
|
84
|
+
// create simplified pathData
|
|
85
|
+
let pathDataSimple = [];
|
|
86
|
+
|
|
87
|
+
// loop sup path
|
|
88
|
+
for (let s = 0, l = pathDataPlusChunks.length; l && s < l; s++) {
|
|
89
|
+
let sub = pathDataPlusChunks[s];
|
|
90
|
+
let { chunks, dimA, area } = sub;
|
|
91
|
+
|
|
92
|
+
let thresh = dimA * 0.1
|
|
93
|
+
let len = chunks.length;
|
|
94
|
+
let simplified;
|
|
95
|
+
//console.log('sub', chunks);
|
|
96
|
+
|
|
97
|
+
//forceCubic = true
|
|
98
|
+
|
|
99
|
+
for (let i = 0; i < len; i++) {
|
|
100
|
+
let chunk = chunks[i];
|
|
101
|
+
let type = chunk[0].type;
|
|
102
|
+
|
|
103
|
+
// try to convert cubic to quadratic
|
|
104
|
+
|
|
105
|
+
//forceCubic = true
|
|
106
|
+
|
|
107
|
+
if (!forceCubic && chunk.length === 1 && type === 'C') {
|
|
108
|
+
simplified = simplifyBezierSequence(chunk);
|
|
109
|
+
pathDataSimple.push(...simplified);
|
|
110
|
+
//console.log('simplified cubic to quadratic', simplified);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// nothing to combine
|
|
115
|
+
if (chunk.length < 2) {
|
|
116
|
+
pathDataSimple.push(...chunk);
|
|
117
|
+
//console.log('simple',chunk );
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// simplify linetos
|
|
122
|
+
if (type === 'L' && chunk.length > 1) {
|
|
123
|
+
//simplified = simplifyLinetoSequence(chunk, thresh);
|
|
124
|
+
//console.log('lineto');
|
|
125
|
+
simplified = simplifyLinetoSequence(chunk);
|
|
126
|
+
pathDataSimple.push(...simplified);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Béziers
|
|
130
|
+
else if (chunk.length > 1 && (type === 'C' || type === 'Q')) {
|
|
131
|
+
//console.log('hasCubics');
|
|
132
|
+
if (chunk.length) {
|
|
133
|
+
|
|
134
|
+
multipass = false
|
|
135
|
+
//multipass = true
|
|
136
|
+
|
|
137
|
+
let directionChange = chunk[0].directionChange;
|
|
138
|
+
//directionChange = false
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* prevent too aggressive simplification
|
|
142
|
+
* e.g for quadratic glyphs
|
|
143
|
+
* by splitting large chunks in two
|
|
144
|
+
*/
|
|
145
|
+
//keepDetails = false
|
|
146
|
+
|
|
147
|
+
//(directionChange && chunk.length > 4) || (!directionChange && chunk.length > 4)
|
|
148
|
+
if (keepDetails && (chunk.length > 4) && !multipass) {
|
|
149
|
+
let split = Math.ceil((chunk.length - 1) / 2)
|
|
150
|
+
let chunk1 = chunk.slice(0, split)
|
|
151
|
+
let chunk2 = chunk.slice(split)
|
|
152
|
+
//console.log('chunk:', chunk);
|
|
153
|
+
//renderPoint(svg1,chunk[0].p0, 'magenta' )
|
|
154
|
+
|
|
155
|
+
//console.log('forceCubic keepDetails', forceCubic);
|
|
156
|
+
let simplified1 = simplifyBezierSequence(chunk1, tolerance, keepDetails, forceCubic);
|
|
157
|
+
let simplified2 = simplifyBezierSequence(chunk2, tolerance, keepDetails, forceCubic);
|
|
158
|
+
|
|
159
|
+
pathDataSimple.push(...simplified1, ...simplified2);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
else {
|
|
163
|
+
simplified = simplifyBezierSequence(chunk, tolerance, keepDetails, forceCubic);
|
|
164
|
+
pathDataSimple.push(...simplified);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// No match, keep original commands
|
|
170
|
+
else {
|
|
171
|
+
//chunk.forEach(com => pathDataSimple.push({ type: com.type, values: com.values }));
|
|
172
|
+
pathDataSimple.push(...chunk);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* try to replace cubics
|
|
180
|
+
* to arcs
|
|
181
|
+
*/
|
|
182
|
+
//cubicToArc = false;
|
|
183
|
+
if (cubicToArc) {
|
|
184
|
+
//console.log();
|
|
185
|
+
pathDataSimple = replaceCubicsByArcs(pathDataSimple, tolerance * 0.5);
|
|
186
|
+
|
|
187
|
+
// combine adjacent arcs
|
|
188
|
+
pathDataSimple = combineArcs(pathDataSimple);
|
|
189
|
+
|
|
190
|
+
console.log('arcs', pathDataSimple);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
// rescale small paths
|
|
195
|
+
if (scale != 1) pathDataSimple = scalePathData(pathDataSimple, 1 / scale, 1 / scale)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* final area check
|
|
200
|
+
* fallback to original if difference is too large
|
|
201
|
+
*/
|
|
202
|
+
/*
|
|
203
|
+
let areaS = getPathArea(pathDataSimple);
|
|
204
|
+
let areaDiff = getRelativeAreaDiff(area0, areaS)
|
|
205
|
+
|
|
206
|
+
if (areaDiff > tolerance) {
|
|
207
|
+
//pathDataSimple = pathData;
|
|
208
|
+
//console.log('take original', pathDataSimple);
|
|
209
|
+
}
|
|
210
|
+
*/
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* final optimization
|
|
215
|
+
* simplify adjacent linetos
|
|
216
|
+
* optimize start points
|
|
217
|
+
* we done it before
|
|
218
|
+
* but we need to apply this again to
|
|
219
|
+
* avoid unnecessary close linetos
|
|
220
|
+
*/
|
|
221
|
+
|
|
222
|
+
// prefer first lineto to allow implicit closing linetos by "Z"
|
|
223
|
+
removeFinalLineto = true;
|
|
224
|
+
startToTop = false;
|
|
225
|
+
addExtremes = false;
|
|
226
|
+
debug = false;
|
|
227
|
+
|
|
228
|
+
pathDataSimple = cleanUpPathData(pathDataSimple, addExtremes, removeFinalLineto, startToTop, debug)
|
|
229
|
+
console.log('pathDataSimple post', pathDataSimple);
|
|
230
|
+
|
|
231
|
+
return pathDataSimple;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
|