├── .editorconfig
├── .gitignore
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── build-iife.js
├── index.d.ts
├── index.html
├── index.iife.js
├── index.js
├── index.ts
├── package-lock.json
├── package.json
├── test.js
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | charset = utf-8
7 | end_of_line = lf
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.codeActionsOnSave": {
3 | "source.organizeImports": true
4 | },
5 | "editor.formatOnSave": true,
6 | "typescript.tsdk": "node_modules/typescript/lib"
7 | }
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 null
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # simplify-svg-path
2 |
3 | Extracts `Path#simplify()` from Paper.js.
4 | http://paperjs.org/reference/path/#simplify
5 |
6 | ## Installation & Usage
7 |
8 | ### [npm](https://www.npmjs.com/package/@luncheon/simplify-svg-path)
9 |
10 | ```bash
11 | $ npm i @luncheon/simplify-svg-path
12 | ```
13 |
14 | ```javascript
15 | import simplifySvgPath from '@luncheon/simplify-svg-path'
16 |
17 | const points = [[10, 10], [10, 20], [20, 20]];
18 | const path = simplifySvgPath(points);
19 | // "M10,10c0,3.33333 -2.35702,7.64298 0,10c2.35702,2.35702 6.66667,0 10,0"
20 | ```
21 |
22 | ### CDN ([jsDelivr](https://www.jsdelivr.com/package/npm/@luncheon/simplify-svg-path))
23 |
24 | ```html
25 |
26 |
29 | ```
30 |
31 | ## API
32 |
33 | ```ts
34 | simplifySvgPath(
35 | points: [x: number, y: number][], // `{ x: number, y: number }[]` is also acceptable
36 | {
37 | closed: boolean = false,
38 | tolerance: number = 2.5,
39 | precision: number = 5,
40 | } = {}
41 | ): string
42 |
43 | // SVG path command string such as
44 | // "M10,10c0,3.33333 -2.35702,7.64298 0,10c2.35702,2.35702 6.66667,0 10,0"
45 | ```
46 |
47 | ## Note
48 |
49 | The logic is a copy of Paper.js v0.12.11.
50 | If you like this, please send your thanks and the star to [Paper.js](https://github.com/paperjs/paper.js).
51 |
--------------------------------------------------------------------------------
/build-iife.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import { dirname, resolve } from 'path'
3 | import { fileURLToPath } from 'url'
4 |
5 | const __filename = fileURLToPath(import.meta.url)
6 | const __dirname = dirname(__filename)
7 |
8 | const esm = fs.readFileSync(resolve(__dirname, 'index.js'), 'utf8')
9 | const iife = `var simplifySvgPath=(()=>{${esm.replace('export default', 'return')}})()`
10 | fs.writeFileSync(resolve(__dirname, 'index.iife.js'), iife, 'utf8')
11 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | interface Point {
2 | readonly x: number;
3 | readonly y: number;
4 | }
5 | declare const simplifySvgPath: (points: readonly (readonly [number, number])[] | readonly Point[], options?: {
6 | closed?: boolean;
7 | tolerance?: number;
8 | precision?: number;
9 | }) => string;
10 | export default simplifySvgPath;
11 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | simplify-svg-path demo
94 | Simplifies and smooths SVG <path>s.
95 | Drag to draw. Open the development tools and compare the original path to the simplified path.
96 |
97 |
98 |
139 |
140 |
141 |
142 |
197 |
198 |
199 |
200 |
201 |
202 |
--------------------------------------------------------------------------------
/index.iife.js:
--------------------------------------------------------------------------------
1 | var simplifySvgPath=(()=>{/*
2 | * simplify-svg-path
3 | *
4 | * The logic is a copy of Paper.js v0.12.11.
5 | */
6 | /*
7 | * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
8 | * http://paperjs.org/
9 | *
10 | * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey
11 | * http://juerglehni.com/ & https://puckey.studio/
12 | *
13 | * Distributed under the MIT license. See LICENSE file for details.
14 | *
15 | * All rights reserved.
16 | */
17 | // An Algorithm for Automatically Fitting Digitized Curves
18 | // by Philip J. Schneider
19 | // from "Graphics Gems", Academic Press, 1990
20 | // Modifications and optimizations of original algorithm by Jürg Lehni.
21 | const EPSILON = 1e-12;
22 | const MACHINE_EPSILON = 1.12e-16;
23 | const isMachineZero = (val) => val >= -MACHINE_EPSILON && val <= MACHINE_EPSILON;
24 | // `Math.sqrt(x * x + y * y)` seems to be faster than `Math.hypot(x, y)`
25 | const hypot = (x, y) => Math.sqrt(x * x + y * y);
26 | const point = (x, y) => ({ x, y });
27 | const pointLength = (p) => hypot(p.x, p.y);
28 | const pointNegate = (p) => point(-p.x, -p.y);
29 | const pointAdd = (p1, p2) => point(p1.x + p2.x, p1.y + p2.y);
30 | const pointSubtract = (p1, p2) => point(p1.x - p2.x, p1.y - p2.y);
31 | const pointMultiplyScalar = (p, n) => point(p.x * n, p.y * n);
32 | const pointDot = (p1, p2) => p1.x * p2.x + p1.y * p2.y;
33 | const pointDistance = (p1, p2) => hypot(p1.x - p2.x, p1.y - p2.y);
34 | const pointNormalize = (p, length = 1) => pointMultiplyScalar(p, length / (pointLength(p) || Infinity));
35 | const createSegment = (p, i) => ({ p, i });
36 | const fit = (points, closed, error) => {
37 | // We need to duplicate the first and last segment when simplifying a
38 | // closed path.
39 | if (closed) {
40 | points.unshift(points[points.length - 1]);
41 | points.push(points[1]); // The point previously at index 0 is now 1.
42 | }
43 | const length = points.length;
44 | if (length === 0) {
45 | return [];
46 | }
47 | // To support reducing paths with multiple points in the same place
48 | // to one segment:
49 | const segments = [createSegment(points[0])];
50 | fitCubic(points, segments, error, 0, length - 1,
51 | // Left Tangent
52 | pointSubtract(points[1], points[0]),
53 | // Right Tangent
54 | pointSubtract(points[length - 2], points[length - 1]));
55 | // Remove the duplicated segments for closed paths again.
56 | if (closed) {
57 | segments.shift();
58 | segments.pop();
59 | }
60 | return segments;
61 | };
62 | // Fit a Bezier curve to a (sub)set of digitized points
63 | const fitCubic = (points, segments, error, first, last, tan1, tan2) => {
64 | // Use heuristic if region only has two points in it
65 | if (last - first === 1) {
66 | const pt1 = points[first], pt2 = points[last], dist = pointDistance(pt1, pt2) / 3;
67 | addCurve(segments, [pt1, pointAdd(pt1, pointNormalize(tan1, dist)), pointAdd(pt2, pointNormalize(tan2, dist)), pt2]);
68 | return;
69 | }
70 | // Parameterize points, and attempt to fit curve
71 | const uPrime = chordLengthParameterize(points, first, last);
72 | let maxError = Math.max(error, error * error), split, parametersInOrder = true;
73 | // Try not 4 but 5 iterations
74 | for (let i = 0; i <= 4; i++) {
75 | const curve = generateBezier(points, first, last, uPrime, tan1, tan2);
76 | // Find max deviation of points to fitted curve
77 | const max = findMaxError(points, first, last, curve, uPrime);
78 | if (max.error < error && parametersInOrder) {
79 | addCurve(segments, curve);
80 | return;
81 | }
82 | split = max.index;
83 | // If error not too large, try reparameterization and iteration
84 | if (max.error >= maxError)
85 | break;
86 | parametersInOrder = reparameterize(points, first, last, uPrime, curve);
87 | maxError = max.error;
88 | }
89 | // Fitting failed -- split at max error point and fit recursively
90 | const tanCenter = pointSubtract(points[split - 1], points[split + 1]);
91 | fitCubic(points, segments, error, first, split, tan1, tanCenter);
92 | fitCubic(points, segments, error, split, last, pointNegate(tanCenter), tan2);
93 | };
94 | const addCurve = (segments, curve) => {
95 | const prev = segments[segments.length - 1];
96 | prev.o = pointSubtract(curve[1], curve[0]);
97 | segments.push(createSegment(curve[3], pointSubtract(curve[2], curve[3])));
98 | };
99 | // Use least-squares method to find Bezier control points for region.
100 | const generateBezier = (points, first, last, uPrime, tan1, tan2) => {
101 | const epsilon = /*#=*/ EPSILON, abs = Math.abs, pt1 = points[first], pt2 = points[last],
102 | // Create the C and X matrices
103 | C = [
104 | [0, 0],
105 | [0, 0],
106 | ], X = [0, 0];
107 | for (let i = 0, l = last - first + 1; i < l; i++) {
108 | const u = uPrime[i], t = 1 - u, b = 3 * u * t, b0 = t * t * t, b1 = b * t, b2 = b * u, b3 = u * u * u, a1 = pointNormalize(tan1, b1), a2 = pointNormalize(tan2, b2), tmp = pointSubtract(pointSubtract(points[first + i], pointMultiplyScalar(pt1, b0 + b1)), pointMultiplyScalar(pt2, b2 + b3));
109 | C[0][0] += pointDot(a1, a1);
110 | C[0][1] += pointDot(a1, a2);
111 | // C[1][0] += a1.dot(a2);
112 | C[1][0] = C[0][1];
113 | C[1][1] += pointDot(a2, a2);
114 | X[0] += pointDot(a1, tmp);
115 | X[1] += pointDot(a2, tmp);
116 | }
117 | // Compute the determinants of C and X
118 | const detC0C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1];
119 | let alpha1;
120 | let alpha2;
121 | if (abs(detC0C1) > epsilon) {
122 | // Kramer's rule
123 | const detC0X = C[0][0] * X[1] - C[1][0] * X[0], detXC1 = X[0] * C[1][1] - X[1] * C[0][1];
124 | // Derive alpha values
125 | alpha1 = detXC1 / detC0C1;
126 | alpha2 = detC0X / detC0C1;
127 | }
128 | else {
129 | // Matrix is under-determined, try assuming alpha1 == alpha2
130 | const c0 = C[0][0] + C[0][1], c1 = C[1][0] + C[1][1];
131 | alpha1 = alpha2 = abs(c0) > epsilon ? X[0] / c0 : abs(c1) > epsilon ? X[1] / c1 : 0;
132 | }
133 | // If alpha negative, use the Wu/Barsky heuristic (see text)
134 | // (if alpha is 0, you get coincident control points that lead to
135 | // divide by zero in any subsequent NewtonRaphsonRootFind() call.
136 | const segLength = pointDistance(pt2, pt1), eps = epsilon * segLength;
137 | let handle1, handle2;
138 | if (alpha1 < eps || alpha2 < eps) {
139 | // fall back on standard (probably inaccurate) formula,
140 | // and subdivide further if needed.
141 | alpha1 = alpha2 = segLength / 3;
142 | }
143 | else {
144 | // Check if the found control points are in the right order when
145 | // projected onto the line through pt1 and pt2.
146 | const line = pointSubtract(pt2, pt1);
147 | // Control points 1 and 2 are positioned an alpha distance out
148 | // on the tangent vectors, left and right, respectively
149 | handle1 = pointNormalize(tan1, alpha1);
150 | handle2 = pointNormalize(tan2, alpha2);
151 | if (pointDot(handle1, line) - pointDot(handle2, line) > segLength * segLength) {
152 | // Fall back to the Wu/Barsky heuristic above.
153 | alpha1 = alpha2 = segLength / 3;
154 | handle1 = handle2 = null; // Force recalculation
155 | }
156 | }
157 | // First and last control points of the Bezier curve are
158 | // positioned exactly at the first and last data points
159 | return [pt1, pointAdd(pt1, handle1 || pointNormalize(tan1, alpha1)), pointAdd(pt2, handle2 || pointNormalize(tan2, alpha2)), pt2];
160 | };
161 | // Given set of points and their parameterization, try to find
162 | // a better parameterization.
163 | const reparameterize = (points, first, last, u, curve) => {
164 | for (let i = first; i <= last; i++) {
165 | u[i - first] = findRoot(curve, points[i], u[i - first]);
166 | }
167 | // Detect if the new parameterization has reordered the points.
168 | // In that case, we would fit the points of the path in the wrong order.
169 | for (let i = 1, l = u.length; i < l; i++) {
170 | if (u[i] <= u[i - 1])
171 | return false;
172 | }
173 | return true;
174 | };
175 | // Use Newton-Raphson iteration to find better root.
176 | const findRoot = (curve, point, u) => {
177 | const curve1 = [], curve2 = [];
178 | // Generate control vertices for Q'
179 | for (let i = 0; i <= 2; i++) {
180 | curve1[i] = pointMultiplyScalar(pointSubtract(curve[i + 1], curve[i]), 3);
181 | }
182 | // Generate control vertices for Q''
183 | for (let i = 0; i <= 1; i++) {
184 | curve2[i] = pointMultiplyScalar(pointSubtract(curve1[i + 1], curve1[i]), 2);
185 | }
186 | // Compute Q(u), Q'(u) and Q''(u)
187 | const pt = evaluate(3, curve, u), pt1 = evaluate(2, curve1, u), pt2 = evaluate(1, curve2, u), diff = pointSubtract(pt, point), df = pointDot(pt1, pt1) + pointDot(diff, pt2);
188 | // u = u - f(u) / f'(u)
189 | return isMachineZero(df) ? u : u - pointDot(diff, pt1) / df;
190 | };
191 | // Evaluate a bezier curve at a particular parameter value
192 | const evaluate = (degree, curve, t) => {
193 | // Copy array
194 | const tmp = curve.slice();
195 | // Triangle computation
196 | for (let i = 1; i <= degree; i++) {
197 | for (let j = 0; j <= degree - i; j++) {
198 | tmp[j] = pointAdd(pointMultiplyScalar(tmp[j], 1 - t), pointMultiplyScalar(tmp[j + 1], t));
199 | }
200 | }
201 | return tmp[0];
202 | };
203 | // Assign parameter values to digitized points
204 | // using relative distances between points.
205 | const chordLengthParameterize = (points, first, last) => {
206 | const u = [0];
207 | for (let i = first + 1; i <= last; i++) {
208 | u[i - first] = u[i - first - 1] + pointDistance(points[i], points[i - 1]);
209 | }
210 | for (let i = 1, m = last - first; i <= m; i++) {
211 | u[i] /= u[m];
212 | }
213 | return u;
214 | };
215 | // Find the maximum squared distance of digitized points to fitted curve.
216 | const findMaxError = (points, first, last, curve, u) => {
217 | let index = Math.floor((last - first + 1) / 2), maxDist = 0;
218 | for (let i = first + 1; i < last; i++) {
219 | const P = evaluate(3, curve, u[i - first]);
220 | const v = pointSubtract(P, points[i]);
221 | const dist = v.x * v.x + v.y * v.y; // squared
222 | if (dist >= maxDist) {
223 | maxDist = dist;
224 | index = i;
225 | }
226 | }
227 | return {
228 | error: maxDist,
229 | index: index,
230 | };
231 | };
232 | const getSegmentsPathData = (segments, closed, precision) => {
233 | const length = segments.length;
234 | const precisionMultiplier = 10 ** precision;
235 | const round = precision < 16 ? (n) => Math.round(n * precisionMultiplier) / precisionMultiplier : (n) => n;
236 | const formatPair = (x, y) => round(x) + ',' + round(y);
237 | let first = true;
238 | let prevX, prevY, outX, outY;
239 | const parts = [];
240 | const addSegment = (segment, skipLine) => {
241 | const curX = segment.p.x;
242 | const curY = segment.p.y;
243 | if (first) {
244 | parts.push('M' + formatPair(curX, curY));
245 | first = false;
246 | }
247 | else {
248 | const inX = curX + (segment.i?.x ?? 0);
249 | const inY = curY + (segment.i?.y ?? 0);
250 | if (inX === curX && inY === curY && outX === prevX && outY === prevY) {
251 | // l = relative lineto:
252 | if (!skipLine) {
253 | const dx = curX - prevX;
254 | const dy = curY - prevY;
255 | parts.push(dx === 0 ? 'v' + round(dy) : dy === 0 ? 'h' + round(dx) : 'l' + formatPair(dx, dy));
256 | }
257 | }
258 | else {
259 | // c = relative curveto:
260 | parts.push('c' +
261 | formatPair(outX - prevX, outY - prevY) +
262 | ' ' +
263 | formatPair(inX - prevX, inY - prevY) +
264 | ' ' +
265 | formatPair(curX - prevX, curY - prevY));
266 | }
267 | }
268 | prevX = curX;
269 | prevY = curY;
270 | outX = curX + (segment.o?.x ?? 0);
271 | outY = curY + (segment.o?.y ?? 0);
272 | };
273 | if (!length)
274 | return '';
275 | for (let i = 0; i < length; i++)
276 | addSegment(segments[i]);
277 | // Close path by drawing first segment again
278 | if (closed && length > 0) {
279 | addSegment(segments[0], true);
280 | parts.push('z');
281 | }
282 | return parts.join('');
283 | };
284 | const simplifySvgPath = (points, options = {}) => {
285 | if (points.length === 0) {
286 | return '';
287 | }
288 | return getSegmentsPathData(fit(points.map(typeof points[0].x === 'number' ? (p) => point(p.x, p.y) : (p) => point(p[0], p[1])), options.closed, options.tolerance ?? 2.5), options.closed, options.precision ?? 5);
289 | };
290 | return simplifySvgPath;
291 | })()
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * simplify-svg-path
3 | *
4 | * The logic is a copy of Paper.js v0.12.11.
5 | */
6 | /*
7 | * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
8 | * http://paperjs.org/
9 | *
10 | * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey
11 | * http://juerglehni.com/ & https://puckey.studio/
12 | *
13 | * Distributed under the MIT license. See LICENSE file for details.
14 | *
15 | * All rights reserved.
16 | */
17 | // An Algorithm for Automatically Fitting Digitized Curves
18 | // by Philip J. Schneider
19 | // from "Graphics Gems", Academic Press, 1990
20 | // Modifications and optimizations of original algorithm by Jürg Lehni.
21 | const EPSILON = 1e-12;
22 | const MACHINE_EPSILON = 1.12e-16;
23 | const isMachineZero = (val) => val >= -MACHINE_EPSILON && val <= MACHINE_EPSILON;
24 | // `Math.sqrt(x * x + y * y)` seems to be faster than `Math.hypot(x, y)`
25 | const hypot = (x, y) => Math.sqrt(x * x + y * y);
26 | const point = (x, y) => ({ x, y });
27 | const pointLength = (p) => hypot(p.x, p.y);
28 | const pointNegate = (p) => point(-p.x, -p.y);
29 | const pointAdd = (p1, p2) => point(p1.x + p2.x, p1.y + p2.y);
30 | const pointSubtract = (p1, p2) => point(p1.x - p2.x, p1.y - p2.y);
31 | const pointMultiplyScalar = (p, n) => point(p.x * n, p.y * n);
32 | const pointDot = (p1, p2) => p1.x * p2.x + p1.y * p2.y;
33 | const pointDistance = (p1, p2) => hypot(p1.x - p2.x, p1.y - p2.y);
34 | const pointNormalize = (p, length = 1) => pointMultiplyScalar(p, length / (pointLength(p) || Infinity));
35 | const createSegment = (p, i) => ({ p, i });
36 | const fit = (points, closed, error) => {
37 | // We need to duplicate the first and last segment when simplifying a
38 | // closed path.
39 | if (closed) {
40 | points.unshift(points[points.length - 1]);
41 | points.push(points[1]); // The point previously at index 0 is now 1.
42 | }
43 | const length = points.length;
44 | if (length === 0) {
45 | return [];
46 | }
47 | // To support reducing paths with multiple points in the same place
48 | // to one segment:
49 | const segments = [createSegment(points[0])];
50 | fitCubic(points, segments, error, 0, length - 1,
51 | // Left Tangent
52 | pointSubtract(points[1], points[0]),
53 | // Right Tangent
54 | pointSubtract(points[length - 2], points[length - 1]));
55 | // Remove the duplicated segments for closed paths again.
56 | if (closed) {
57 | segments.shift();
58 | segments.pop();
59 | }
60 | return segments;
61 | };
62 | // Fit a Bezier curve to a (sub)set of digitized points
63 | const fitCubic = (points, segments, error, first, last, tan1, tan2) => {
64 | // Use heuristic if region only has two points in it
65 | if (last - first === 1) {
66 | const pt1 = points[first], pt2 = points[last], dist = pointDistance(pt1, pt2) / 3;
67 | addCurve(segments, [pt1, pointAdd(pt1, pointNormalize(tan1, dist)), pointAdd(pt2, pointNormalize(tan2, dist)), pt2]);
68 | return;
69 | }
70 | // Parameterize points, and attempt to fit curve
71 | const uPrime = chordLengthParameterize(points, first, last);
72 | let maxError = Math.max(error, error * error), split, parametersInOrder = true;
73 | // Try not 4 but 5 iterations
74 | for (let i = 0; i <= 4; i++) {
75 | const curve = generateBezier(points, first, last, uPrime, tan1, tan2);
76 | // Find max deviation of points to fitted curve
77 | const max = findMaxError(points, first, last, curve, uPrime);
78 | if (max.error < error && parametersInOrder) {
79 | addCurve(segments, curve);
80 | return;
81 | }
82 | split = max.index;
83 | // If error not too large, try reparameterization and iteration
84 | if (max.error >= maxError)
85 | break;
86 | parametersInOrder = reparameterize(points, first, last, uPrime, curve);
87 | maxError = max.error;
88 | }
89 | // Fitting failed -- split at max error point and fit recursively
90 | const tanCenter = pointSubtract(points[split - 1], points[split + 1]);
91 | fitCubic(points, segments, error, first, split, tan1, tanCenter);
92 | fitCubic(points, segments, error, split, last, pointNegate(tanCenter), tan2);
93 | };
94 | const addCurve = (segments, curve) => {
95 | const prev = segments[segments.length - 1];
96 | prev.o = pointSubtract(curve[1], curve[0]);
97 | segments.push(createSegment(curve[3], pointSubtract(curve[2], curve[3])));
98 | };
99 | // Use least-squares method to find Bezier control points for region.
100 | const generateBezier = (points, first, last, uPrime, tan1, tan2) => {
101 | const epsilon = /*#=*/ EPSILON, abs = Math.abs, pt1 = points[first], pt2 = points[last],
102 | // Create the C and X matrices
103 | C = [
104 | [0, 0],
105 | [0, 0],
106 | ], X = [0, 0];
107 | for (let i = 0, l = last - first + 1; i < l; i++) {
108 | const u = uPrime[i], t = 1 - u, b = 3 * u * t, b0 = t * t * t, b1 = b * t, b2 = b * u, b3 = u * u * u, a1 = pointNormalize(tan1, b1), a2 = pointNormalize(tan2, b2), tmp = pointSubtract(pointSubtract(points[first + i], pointMultiplyScalar(pt1, b0 + b1)), pointMultiplyScalar(pt2, b2 + b3));
109 | C[0][0] += pointDot(a1, a1);
110 | C[0][1] += pointDot(a1, a2);
111 | // C[1][0] += a1.dot(a2);
112 | C[1][0] = C[0][1];
113 | C[1][1] += pointDot(a2, a2);
114 | X[0] += pointDot(a1, tmp);
115 | X[1] += pointDot(a2, tmp);
116 | }
117 | // Compute the determinants of C and X
118 | const detC0C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1];
119 | let alpha1;
120 | let alpha2;
121 | if (abs(detC0C1) > epsilon) {
122 | // Kramer's rule
123 | const detC0X = C[0][0] * X[1] - C[1][0] * X[0], detXC1 = X[0] * C[1][1] - X[1] * C[0][1];
124 | // Derive alpha values
125 | alpha1 = detXC1 / detC0C1;
126 | alpha2 = detC0X / detC0C1;
127 | }
128 | else {
129 | // Matrix is under-determined, try assuming alpha1 == alpha2
130 | const c0 = C[0][0] + C[0][1], c1 = C[1][0] + C[1][1];
131 | alpha1 = alpha2 = abs(c0) > epsilon ? X[0] / c0 : abs(c1) > epsilon ? X[1] / c1 : 0;
132 | }
133 | // If alpha negative, use the Wu/Barsky heuristic (see text)
134 | // (if alpha is 0, you get coincident control points that lead to
135 | // divide by zero in any subsequent NewtonRaphsonRootFind() call.
136 | const segLength = pointDistance(pt2, pt1), eps = epsilon * segLength;
137 | let handle1, handle2;
138 | if (alpha1 < eps || alpha2 < eps) {
139 | // fall back on standard (probably inaccurate) formula,
140 | // and subdivide further if needed.
141 | alpha1 = alpha2 = segLength / 3;
142 | }
143 | else {
144 | // Check if the found control points are in the right order when
145 | // projected onto the line through pt1 and pt2.
146 | const line = pointSubtract(pt2, pt1);
147 | // Control points 1 and 2 are positioned an alpha distance out
148 | // on the tangent vectors, left and right, respectively
149 | handle1 = pointNormalize(tan1, alpha1);
150 | handle2 = pointNormalize(tan2, alpha2);
151 | if (pointDot(handle1, line) - pointDot(handle2, line) > segLength * segLength) {
152 | // Fall back to the Wu/Barsky heuristic above.
153 | alpha1 = alpha2 = segLength / 3;
154 | handle1 = handle2 = null; // Force recalculation
155 | }
156 | }
157 | // First and last control points of the Bezier curve are
158 | // positioned exactly at the first and last data points
159 | return [pt1, pointAdd(pt1, handle1 || pointNormalize(tan1, alpha1)), pointAdd(pt2, handle2 || pointNormalize(tan2, alpha2)), pt2];
160 | };
161 | // Given set of points and their parameterization, try to find
162 | // a better parameterization.
163 | const reparameterize = (points, first, last, u, curve) => {
164 | for (let i = first; i <= last; i++) {
165 | u[i - first] = findRoot(curve, points[i], u[i - first]);
166 | }
167 | // Detect if the new parameterization has reordered the points.
168 | // In that case, we would fit the points of the path in the wrong order.
169 | for (let i = 1, l = u.length; i < l; i++) {
170 | if (u[i] <= u[i - 1])
171 | return false;
172 | }
173 | return true;
174 | };
175 | // Use Newton-Raphson iteration to find better root.
176 | const findRoot = (curve, point, u) => {
177 | const curve1 = [], curve2 = [];
178 | // Generate control vertices for Q'
179 | for (let i = 0; i <= 2; i++) {
180 | curve1[i] = pointMultiplyScalar(pointSubtract(curve[i + 1], curve[i]), 3);
181 | }
182 | // Generate control vertices for Q''
183 | for (let i = 0; i <= 1; i++) {
184 | curve2[i] = pointMultiplyScalar(pointSubtract(curve1[i + 1], curve1[i]), 2);
185 | }
186 | // Compute Q(u), Q'(u) and Q''(u)
187 | const pt = evaluate(3, curve, u), pt1 = evaluate(2, curve1, u), pt2 = evaluate(1, curve2, u), diff = pointSubtract(pt, point), df = pointDot(pt1, pt1) + pointDot(diff, pt2);
188 | // u = u - f(u) / f'(u)
189 | return isMachineZero(df) ? u : u - pointDot(diff, pt1) / df;
190 | };
191 | // Evaluate a bezier curve at a particular parameter value
192 | const evaluate = (degree, curve, t) => {
193 | // Copy array
194 | const tmp = curve.slice();
195 | // Triangle computation
196 | for (let i = 1; i <= degree; i++) {
197 | for (let j = 0; j <= degree - i; j++) {
198 | tmp[j] = pointAdd(pointMultiplyScalar(tmp[j], 1 - t), pointMultiplyScalar(tmp[j + 1], t));
199 | }
200 | }
201 | return tmp[0];
202 | };
203 | // Assign parameter values to digitized points
204 | // using relative distances between points.
205 | const chordLengthParameterize = (points, first, last) => {
206 | const u = [0];
207 | for (let i = first + 1; i <= last; i++) {
208 | u[i - first] = u[i - first - 1] + pointDistance(points[i], points[i - 1]);
209 | }
210 | for (let i = 1, m = last - first; i <= m; i++) {
211 | u[i] /= u[m];
212 | }
213 | return u;
214 | };
215 | // Find the maximum squared distance of digitized points to fitted curve.
216 | const findMaxError = (points, first, last, curve, u) => {
217 | let index = Math.floor((last - first + 1) / 2), maxDist = 0;
218 | for (let i = first + 1; i < last; i++) {
219 | const P = evaluate(3, curve, u[i - first]);
220 | const v = pointSubtract(P, points[i]);
221 | const dist = v.x * v.x + v.y * v.y; // squared
222 | if (dist >= maxDist) {
223 | maxDist = dist;
224 | index = i;
225 | }
226 | }
227 | return {
228 | error: maxDist,
229 | index: index,
230 | };
231 | };
232 | const getSegmentsPathData = (segments, closed, precision) => {
233 | const length = segments.length;
234 | const precisionMultiplier = 10 ** precision;
235 | const round = precision < 16 ? (n) => Math.round(n * precisionMultiplier) / precisionMultiplier : (n) => n;
236 | const formatPair = (x, y) => round(x) + ',' + round(y);
237 | let first = true;
238 | let prevX, prevY, outX, outY;
239 | const parts = [];
240 | const addSegment = (segment, skipLine) => {
241 | const curX = segment.p.x;
242 | const curY = segment.p.y;
243 | if (first) {
244 | parts.push('M' + formatPair(curX, curY));
245 | first = false;
246 | }
247 | else {
248 | const inX = curX + (segment.i?.x ?? 0);
249 | const inY = curY + (segment.i?.y ?? 0);
250 | if (inX === curX && inY === curY && outX === prevX && outY === prevY) {
251 | // l = relative lineto:
252 | if (!skipLine) {
253 | const dx = curX - prevX;
254 | const dy = curY - prevY;
255 | parts.push(dx === 0 ? 'v' + round(dy) : dy === 0 ? 'h' + round(dx) : 'l' + formatPair(dx, dy));
256 | }
257 | }
258 | else {
259 | // c = relative curveto:
260 | parts.push('c' +
261 | formatPair(outX - prevX, outY - prevY) +
262 | ' ' +
263 | formatPair(inX - prevX, inY - prevY) +
264 | ' ' +
265 | formatPair(curX - prevX, curY - prevY));
266 | }
267 | }
268 | prevX = curX;
269 | prevY = curY;
270 | outX = curX + (segment.o?.x ?? 0);
271 | outY = curY + (segment.o?.y ?? 0);
272 | };
273 | if (!length)
274 | return '';
275 | for (let i = 0; i < length; i++)
276 | addSegment(segments[i]);
277 | // Close path by drawing first segment again
278 | if (closed && length > 0) {
279 | addSegment(segments[0], true);
280 | parts.push('z');
281 | }
282 | return parts.join('');
283 | };
284 | const simplifySvgPath = (points, options = {}) => {
285 | if (points.length === 0) {
286 | return '';
287 | }
288 | return getSegmentsPathData(fit(points.map(typeof points[0].x === 'number' ? (p) => point(p.x, p.y) : (p) => point(p[0], p[1])), options.closed, options.tolerance ?? 2.5), options.closed, options.precision ?? 5);
289 | };
290 | export default simplifySvgPath;
291 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * simplify-svg-path
3 | *
4 | * The logic is a copy of Paper.js v0.12.11.
5 | */
6 |
7 | /*
8 | * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
9 | * http://paperjs.org/
10 | *
11 | * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey
12 | * http://juerglehni.com/ & https://puckey.studio/
13 | *
14 | * Distributed under the MIT license. See LICENSE file for details.
15 | *
16 | * All rights reserved.
17 | */
18 |
19 | // An Algorithm for Automatically Fitting Digitized Curves
20 | // by Philip J. Schneider
21 | // from "Graphics Gems", Academic Press, 1990
22 | // Modifications and optimizations of original algorithm by Jürg Lehni.
23 | const EPSILON = 1e-12
24 | const MACHINE_EPSILON = 1.12e-16
25 | const isMachineZero = (val: number) => val >= -MACHINE_EPSILON && val <= MACHINE_EPSILON
26 |
27 | // `Math.sqrt(x * x + y * y)` seems to be faster than `Math.hypot(x, y)`
28 | const hypot = (x: number, y: number) => Math.sqrt(x * x + y * y)
29 |
30 | interface Point {
31 | readonly x: number
32 | readonly y: number
33 | }
34 | const point = (x: number, y: number) => ({ x, y })
35 | const pointLength = (p: Point) => hypot(p.x, p.y)
36 | const pointNegate = (p: Point) => point(-p.x, -p.y)
37 | const pointAdd = (p1: Point, p2: Point) => point(p1.x + p2.x, p1.y + p2.y)
38 | const pointSubtract = (p1: Point, p2: Point) => point(p1.x - p2.x, p1.y - p2.y)
39 | const pointMultiplyScalar = (p: Point, n: number) => point(p.x * n, p.y * n)
40 | const pointDot = (p1: Point, p2: Point) => p1.x * p2.x + p1.y * p2.y
41 | const pointDistance = (p1: Point, p2: Point) => hypot(p1.x - p2.x, p1.y - p2.y)
42 | const pointNormalize = (p: Point, length = 1) => pointMultiplyScalar(p, length / (pointLength(p) || Infinity))
43 |
44 | interface Segment {
45 | readonly p: Point
46 | readonly i?: Point // handleIn
47 | o?: Point // handleOut
48 | }
49 | const createSegment = (p: Point, i?: Point) => ({ p, i })
50 |
51 | const fit = (points: Point[], closed: unknown, error: number) => {
52 | // We need to duplicate the first and last segment when simplifying a
53 | // closed path.
54 | if (closed) {
55 | points.unshift(points[points.length - 1])
56 | points.push(points[1]) // The point previously at index 0 is now 1.
57 | }
58 | const length = points.length
59 | if (length === 0) {
60 | return []
61 | }
62 | // To support reducing paths with multiple points in the same place
63 | // to one segment:
64 | const segments = [createSegment(points[0])]
65 | fitCubic(
66 | points,
67 | segments,
68 | error,
69 | 0,
70 | length - 1,
71 | // Left Tangent
72 | pointSubtract(points[1], points[0]),
73 | // Right Tangent
74 | pointSubtract(points[length - 2], points[length - 1]),
75 | )
76 | // Remove the duplicated segments for closed paths again.
77 | if (closed) {
78 | segments.shift()
79 | segments.pop()
80 | }
81 | return segments
82 | }
83 |
84 | // Fit a Bezier curve to a (sub)set of digitized points
85 | const fitCubic = (points: readonly Point[], segments: Segment[], error: number, first: number, last: number, tan1: Point, tan2: Point) => {
86 | // Use heuristic if region only has two points in it
87 | if (last - first === 1) {
88 | const pt1 = points[first],
89 | pt2 = points[last],
90 | dist = pointDistance(pt1, pt2) / 3
91 | addCurve(segments, [pt1, pointAdd(pt1, pointNormalize(tan1, dist)), pointAdd(pt2, pointNormalize(tan2, dist)), pt2])
92 | return
93 | }
94 | // Parameterize points, and attempt to fit curve
95 | const uPrime = chordLengthParameterize(points, first, last)
96 | let maxError = Math.max(error, error * error),
97 | split: number,
98 | parametersInOrder = true
99 | // Try not 4 but 5 iterations
100 | for (let i = 0; i <= 4; i++) {
101 | const curve = generateBezier(points, first, last, uPrime, tan1, tan2)
102 | // Find max deviation of points to fitted curve
103 | const max = findMaxError(points, first, last, curve, uPrime)
104 | if (max.error < error && parametersInOrder) {
105 | addCurve(segments, curve)
106 | return
107 | }
108 | split = max.index
109 | // If error not too large, try reparameterization and iteration
110 | if (max.error >= maxError) break
111 | parametersInOrder = reparameterize(points, first, last, uPrime, curve)
112 | maxError = max.error
113 | }
114 | // Fitting failed -- split at max error point and fit recursively
115 | const tanCenter = pointSubtract(points[split! - 1], points[split! + 1])
116 | fitCubic(points, segments, error, first, split!, tan1, tanCenter)
117 | fitCubic(points, segments, error, split!, last, pointNegate(tanCenter), tan2)
118 | }
119 |
120 | const addCurve = (segments: Segment[], curve: readonly Point[]) => {
121 | const prev = segments[segments.length - 1]
122 | prev.o = pointSubtract(curve[1], curve[0])
123 | segments.push(createSegment(curve[3], pointSubtract(curve[2], curve[3])))
124 | }
125 |
126 | // Use least-squares method to find Bezier control points for region.
127 | const generateBezier = (points: readonly Point[], first: number, last: number, uPrime: readonly number[], tan1: Point, tan2: Point) => {
128 | const epsilon = /*#=*/ EPSILON,
129 | abs = Math.abs,
130 | pt1 = points[first],
131 | pt2 = points[last],
132 | // Create the C and X matrices
133 | C = [
134 | [0, 0],
135 | [0, 0],
136 | ],
137 | X = [0, 0]
138 |
139 | for (let i = 0, l = last - first + 1; i < l; i++) {
140 | const u = uPrime[i],
141 | t = 1 - u,
142 | b = 3 * u * t,
143 | b0 = t * t * t,
144 | b1 = b * t,
145 | b2 = b * u,
146 | b3 = u * u * u,
147 | a1 = pointNormalize(tan1, b1),
148 | a2 = pointNormalize(tan2, b2),
149 | tmp = pointSubtract(pointSubtract(points[first + i], pointMultiplyScalar(pt1, b0 + b1)), pointMultiplyScalar(pt2, b2 + b3))
150 | C[0][0] += pointDot(a1, a1)
151 | C[0][1] += pointDot(a1, a2)
152 | // C[1][0] += a1.dot(a2);
153 | C[1][0] = C[0][1]
154 | C[1][1] += pointDot(a2, a2)
155 | X[0] += pointDot(a1, tmp)
156 | X[1] += pointDot(a2, tmp)
157 | }
158 |
159 | // Compute the determinants of C and X
160 | const detC0C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1]
161 | let alpha1
162 | let alpha2
163 | if (abs(detC0C1) > epsilon) {
164 | // Kramer's rule
165 | const detC0X = C[0][0] * X[1] - C[1][0] * X[0],
166 | detXC1 = X[0] * C[1][1] - X[1] * C[0][1]
167 | // Derive alpha values
168 | alpha1 = detXC1 / detC0C1
169 | alpha2 = detC0X / detC0C1
170 | } else {
171 | // Matrix is under-determined, try assuming alpha1 == alpha2
172 | const c0 = C[0][0] + C[0][1],
173 | c1 = C[1][0] + C[1][1]
174 | alpha1 = alpha2 = abs(c0) > epsilon ? X[0] / c0 : abs(c1) > epsilon ? X[1] / c1 : 0
175 | }
176 |
177 | // If alpha negative, use the Wu/Barsky heuristic (see text)
178 | // (if alpha is 0, you get coincident control points that lead to
179 | // divide by zero in any subsequent NewtonRaphsonRootFind() call.
180 | const segLength = pointDistance(pt2, pt1),
181 | eps = epsilon * segLength
182 | let handle1, handle2
183 | if (alpha1 < eps || alpha2 < eps) {
184 | // fall back on standard (probably inaccurate) formula,
185 | // and subdivide further if needed.
186 | alpha1 = alpha2 = segLength / 3
187 | } else {
188 | // Check if the found control points are in the right order when
189 | // projected onto the line through pt1 and pt2.
190 | const line = pointSubtract(pt2, pt1)
191 | // Control points 1 and 2 are positioned an alpha distance out
192 | // on the tangent vectors, left and right, respectively
193 | handle1 = pointNormalize(tan1, alpha1)
194 | handle2 = pointNormalize(tan2, alpha2)
195 | if (pointDot(handle1, line) - pointDot(handle2, line) > segLength * segLength) {
196 | // Fall back to the Wu/Barsky heuristic above.
197 | alpha1 = alpha2 = segLength / 3
198 | handle1 = handle2 = null // Force recalculation
199 | }
200 | }
201 |
202 | // First and last control points of the Bezier curve are
203 | // positioned exactly at the first and last data points
204 | return [pt1, pointAdd(pt1, handle1 || pointNormalize(tan1, alpha1)), pointAdd(pt2, handle2 || pointNormalize(tan2, alpha2)), pt2]
205 | }
206 |
207 | // Given set of points and their parameterization, try to find
208 | // a better parameterization.
209 | const reparameterize = (points: readonly Point[], first: number, last: number, u: number[], curve: Point[]) => {
210 | for (let i = first; i <= last; i++) {
211 | u[i - first] = findRoot(curve, points[i], u[i - first])
212 | }
213 | // Detect if the new parameterization has reordered the points.
214 | // In that case, we would fit the points of the path in the wrong order.
215 | for (let i = 1, l = u.length; i < l; i++) {
216 | if (u[i] <= u[i - 1]) return false
217 | }
218 | return true
219 | }
220 |
221 | // Use Newton-Raphson iteration to find better root.
222 | const findRoot = (curve: readonly Point[], point: Point, u: number) => {
223 | const curve1 = [],
224 | curve2 = []
225 | // Generate control vertices for Q'
226 | for (let i = 0; i <= 2; i++) {
227 | curve1[i] = pointMultiplyScalar(pointSubtract(curve[i + 1], curve[i]), 3)
228 | }
229 | // Generate control vertices for Q''
230 | for (let i = 0; i <= 1; i++) {
231 | curve2[i] = pointMultiplyScalar(pointSubtract(curve1[i + 1], curve1[i]), 2)
232 | }
233 | // Compute Q(u), Q'(u) and Q''(u)
234 | const pt = evaluate(3, curve, u),
235 | pt1 = evaluate(2, curve1, u),
236 | pt2 = evaluate(1, curve2, u),
237 | diff = pointSubtract(pt, point),
238 | df = pointDot(pt1, pt1) + pointDot(diff, pt2)
239 | // u = u - f(u) / f'(u)
240 | return isMachineZero(df) ? u : u - pointDot(diff, pt1) / df
241 | }
242 |
243 | // Evaluate a bezier curve at a particular parameter value
244 | const evaluate = (degree: number, curve: readonly Point[], t: number) => {
245 | // Copy array
246 | const tmp = curve.slice()
247 | // Triangle computation
248 | for (let i = 1; i <= degree; i++) {
249 | for (let j = 0; j <= degree - i; j++) {
250 | tmp[j] = pointAdd(pointMultiplyScalar(tmp[j], 1 - t), pointMultiplyScalar(tmp[j + 1], t))
251 | }
252 | }
253 | return tmp[0]
254 | }
255 |
256 | // Assign parameter values to digitized points
257 | // using relative distances between points.
258 | const chordLengthParameterize = (points: readonly Point[], first: number, last: number) => {
259 | const u = [0]
260 | for (let i = first + 1; i <= last; i++) {
261 | u[i - first] = u[i - first - 1] + pointDistance(points[i], points[i - 1])
262 | }
263 | for (let i = 1, m = last - first; i <= m; i++) {
264 | u[i] /= u[m]
265 | }
266 | return u
267 | }
268 |
269 | // Find the maximum squared distance of digitized points to fitted curve.
270 | const findMaxError = (points: readonly Point[], first: number, last: number, curve: Point[], u: number[]) => {
271 | let index = Math.floor((last - first + 1) / 2),
272 | maxDist = 0
273 | for (let i = first + 1; i < last; i++) {
274 | const P = evaluate(3, curve, u[i - first])
275 | const v = pointSubtract(P, points[i])
276 | const dist = v.x * v.x + v.y * v.y // squared
277 | if (dist >= maxDist) {
278 | maxDist = dist
279 | index = i
280 | }
281 | }
282 | return {
283 | error: maxDist,
284 | index: index,
285 | }
286 | }
287 |
288 | const getSegmentsPathData = (segments: Segment[], closed: unknown, precision: number) => {
289 | const length = segments.length
290 | const precisionMultiplier = 10 ** precision
291 | const round = precision < 16 ? (n: number) => Math.round(n * precisionMultiplier) / precisionMultiplier : (n: number) => n
292 | const formatPair = (x: number, y: number) => round(x) + ',' + round(y)
293 | let first = true
294 | let prevX: number, prevY: number, outX: number, outY: number
295 | const parts: string[] = []
296 |
297 | const addSegment = (segment: Segment, skipLine?: boolean) => {
298 | const curX = segment.p.x
299 | const curY = segment.p.y
300 | if (first) {
301 | parts.push('M' + formatPair(curX, curY))
302 | first = false
303 | } else {
304 | const inX = curX + (segment.i?.x ?? 0)
305 | const inY = curY + (segment.i?.y ?? 0)
306 | if (inX === curX && inY === curY && outX === prevX && outY === prevY) {
307 | // l = relative lineto:
308 | if (!skipLine) {
309 | const dx = curX - prevX
310 | const dy = curY - prevY
311 | parts.push(dx === 0 ? 'v' + round(dy) : dy === 0 ? 'h' + round(dx) : 'l' + formatPair(dx, dy))
312 | }
313 | } else {
314 | // c = relative curveto:
315 | parts.push(
316 | 'c' +
317 | formatPair(outX - prevX, outY - prevY) +
318 | ' ' +
319 | formatPair(inX - prevX, inY - prevY) +
320 | ' ' +
321 | formatPair(curX - prevX, curY - prevY),
322 | )
323 | }
324 | }
325 | prevX = curX
326 | prevY = curY
327 | outX = curX + (segment.o?.x ?? 0)
328 | outY = curY + (segment.o?.y ?? 0)
329 | }
330 |
331 | if (!length) return ''
332 |
333 | for (let i = 0; i < length; i++) addSegment(segments[i])
334 | // Close path by drawing first segment again
335 | if (closed && length > 0) {
336 | addSegment(segments[0], true)
337 | parts.push('z')
338 | }
339 | return parts.join('')
340 | }
341 |
342 | const simplifySvgPath = (
343 | points: readonly (readonly [number, number])[] | readonly Point[],
344 | options: { closed?: boolean; tolerance?: number; precision?: number } = {},
345 | ) => {
346 | if (points.length === 0) {
347 | return ''
348 | }
349 | return getSegmentsPathData(
350 | fit(
351 | points.map(typeof (points[0] as { readonly x: number }).x === 'number' ? (p: any) => point(p.x, p.y) : (p: any) => point(p[0], p[1])),
352 | options.closed,
353 | options.tolerance ?? 2.5,
354 | ),
355 | options.closed,
356 | options.precision ?? 5,
357 | )
358 | }
359 |
360 | export default simplifySvgPath
361 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@luncheon/simplify-svg-path",
3 | "version": "0.2.0",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "@luncheon/simplify-svg-path",
9 | "version": "0.2.0",
10 | "license": "MIT",
11 | "devDependencies": {
12 | "jsdom": "^20.0.3",
13 | "paper": "^0.12.17",
14 | "prettier": "^2.8.0",
15 | "typescript": "^4.9.3"
16 | }
17 | },
18 | "node_modules/@tootallnate/once": {
19 | "version": "2.0.0",
20 | "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
21 | "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
22 | "dev": true,
23 | "engines": {
24 | "node": ">= 10"
25 | }
26 | },
27 | "node_modules/abab": {
28 | "version": "2.0.6",
29 | "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
30 | "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==",
31 | "dev": true
32 | },
33 | "node_modules/acorn": {
34 | "version": "8.8.1",
35 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
36 | "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==",
37 | "dev": true,
38 | "bin": {
39 | "acorn": "bin/acorn"
40 | },
41 | "engines": {
42 | "node": ">=0.4.0"
43 | }
44 | },
45 | "node_modules/acorn-globals": {
46 | "version": "7.0.1",
47 | "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
48 | "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
49 | "dev": true,
50 | "dependencies": {
51 | "acorn": "^8.1.0",
52 | "acorn-walk": "^8.0.2"
53 | }
54 | },
55 | "node_modules/acorn-walk": {
56 | "version": "8.2.0",
57 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
58 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
59 | "dev": true,
60 | "engines": {
61 | "node": ">=0.4.0"
62 | }
63 | },
64 | "node_modules/agent-base": {
65 | "version": "6.0.2",
66 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
67 | "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
68 | "dev": true,
69 | "dependencies": {
70 | "debug": "4"
71 | },
72 | "engines": {
73 | "node": ">= 6.0.0"
74 | }
75 | },
76 | "node_modules/asynckit": {
77 | "version": "0.4.0",
78 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
79 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
80 | "dev": true
81 | },
82 | "node_modules/combined-stream": {
83 | "version": "1.0.8",
84 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
85 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
86 | "dev": true,
87 | "dependencies": {
88 | "delayed-stream": "~1.0.0"
89 | },
90 | "engines": {
91 | "node": ">= 0.8"
92 | }
93 | },
94 | "node_modules/cssom": {
95 | "version": "0.5.0",
96 | "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
97 | "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==",
98 | "dev": true
99 | },
100 | "node_modules/cssstyle": {
101 | "version": "2.3.0",
102 | "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
103 | "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
104 | "dev": true,
105 | "dependencies": {
106 | "cssom": "~0.3.6"
107 | },
108 | "engines": {
109 | "node": ">=8"
110 | }
111 | },
112 | "node_modules/cssstyle/node_modules/cssom": {
113 | "version": "0.3.8",
114 | "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
115 | "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
116 | "dev": true
117 | },
118 | "node_modules/data-urls": {
119 | "version": "3.0.2",
120 | "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
121 | "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==",
122 | "dev": true,
123 | "dependencies": {
124 | "abab": "^2.0.6",
125 | "whatwg-mimetype": "^3.0.0",
126 | "whatwg-url": "^11.0.0"
127 | },
128 | "engines": {
129 | "node": ">=12"
130 | }
131 | },
132 | "node_modules/debug": {
133 | "version": "4.3.4",
134 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
135 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
136 | "dev": true,
137 | "dependencies": {
138 | "ms": "2.1.2"
139 | },
140 | "engines": {
141 | "node": ">=6.0"
142 | },
143 | "peerDependenciesMeta": {
144 | "supports-color": {
145 | "optional": true
146 | }
147 | }
148 | },
149 | "node_modules/decimal.js": {
150 | "version": "10.4.2",
151 | "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.2.tgz",
152 | "integrity": "sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA==",
153 | "dev": true
154 | },
155 | "node_modules/deep-is": {
156 | "version": "0.1.4",
157 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
158 | "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
159 | "dev": true
160 | },
161 | "node_modules/delayed-stream": {
162 | "version": "1.0.0",
163 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
164 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
165 | "dev": true,
166 | "engines": {
167 | "node": ">=0.4.0"
168 | }
169 | },
170 | "node_modules/domexception": {
171 | "version": "4.0.0",
172 | "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
173 | "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==",
174 | "dev": true,
175 | "dependencies": {
176 | "webidl-conversions": "^7.0.0"
177 | },
178 | "engines": {
179 | "node": ">=12"
180 | }
181 | },
182 | "node_modules/entities": {
183 | "version": "4.4.0",
184 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz",
185 | "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==",
186 | "dev": true,
187 | "engines": {
188 | "node": ">=0.12"
189 | },
190 | "funding": {
191 | "url": "https://github.com/fb55/entities?sponsor=1"
192 | }
193 | },
194 | "node_modules/escodegen": {
195 | "version": "2.0.0",
196 | "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz",
197 | "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==",
198 | "dev": true,
199 | "dependencies": {
200 | "esprima": "^4.0.1",
201 | "estraverse": "^5.2.0",
202 | "esutils": "^2.0.2",
203 | "optionator": "^0.8.1"
204 | },
205 | "bin": {
206 | "escodegen": "bin/escodegen.js",
207 | "esgenerate": "bin/esgenerate.js"
208 | },
209 | "engines": {
210 | "node": ">=6.0"
211 | },
212 | "optionalDependencies": {
213 | "source-map": "~0.6.1"
214 | }
215 | },
216 | "node_modules/esprima": {
217 | "version": "4.0.1",
218 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
219 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
220 | "dev": true,
221 | "bin": {
222 | "esparse": "bin/esparse.js",
223 | "esvalidate": "bin/esvalidate.js"
224 | },
225 | "engines": {
226 | "node": ">=4"
227 | }
228 | },
229 | "node_modules/estraverse": {
230 | "version": "5.3.0",
231 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
232 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
233 | "dev": true,
234 | "engines": {
235 | "node": ">=4.0"
236 | }
237 | },
238 | "node_modules/esutils": {
239 | "version": "2.0.3",
240 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
241 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
242 | "dev": true,
243 | "engines": {
244 | "node": ">=0.10.0"
245 | }
246 | },
247 | "node_modules/fast-levenshtein": {
248 | "version": "2.0.6",
249 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
250 | "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
251 | "dev": true
252 | },
253 | "node_modules/form-data": {
254 | "version": "4.0.0",
255 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
256 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
257 | "dev": true,
258 | "dependencies": {
259 | "asynckit": "^0.4.0",
260 | "combined-stream": "^1.0.8",
261 | "mime-types": "^2.1.12"
262 | },
263 | "engines": {
264 | "node": ">= 6"
265 | }
266 | },
267 | "node_modules/html-encoding-sniffer": {
268 | "version": "3.0.0",
269 | "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
270 | "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
271 | "dev": true,
272 | "dependencies": {
273 | "whatwg-encoding": "^2.0.0"
274 | },
275 | "engines": {
276 | "node": ">=12"
277 | }
278 | },
279 | "node_modules/http-proxy-agent": {
280 | "version": "5.0.0",
281 | "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
282 | "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
283 | "dev": true,
284 | "dependencies": {
285 | "@tootallnate/once": "2",
286 | "agent-base": "6",
287 | "debug": "4"
288 | },
289 | "engines": {
290 | "node": ">= 6"
291 | }
292 | },
293 | "node_modules/https-proxy-agent": {
294 | "version": "5.0.1",
295 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
296 | "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
297 | "dev": true,
298 | "dependencies": {
299 | "agent-base": "6",
300 | "debug": "4"
301 | },
302 | "engines": {
303 | "node": ">= 6"
304 | }
305 | },
306 | "node_modules/iconv-lite": {
307 | "version": "0.6.3",
308 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
309 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
310 | "dev": true,
311 | "dependencies": {
312 | "safer-buffer": ">= 2.1.2 < 3.0.0"
313 | },
314 | "engines": {
315 | "node": ">=0.10.0"
316 | }
317 | },
318 | "node_modules/is-potential-custom-element-name": {
319 | "version": "1.0.1",
320 | "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
321 | "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
322 | "dev": true
323 | },
324 | "node_modules/jsdom": {
325 | "version": "20.0.3",
326 | "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz",
327 | "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==",
328 | "dev": true,
329 | "dependencies": {
330 | "abab": "^2.0.6",
331 | "acorn": "^8.8.1",
332 | "acorn-globals": "^7.0.0",
333 | "cssom": "^0.5.0",
334 | "cssstyle": "^2.3.0",
335 | "data-urls": "^3.0.2",
336 | "decimal.js": "^10.4.2",
337 | "domexception": "^4.0.0",
338 | "escodegen": "^2.0.0",
339 | "form-data": "^4.0.0",
340 | "html-encoding-sniffer": "^3.0.0",
341 | "http-proxy-agent": "^5.0.0",
342 | "https-proxy-agent": "^5.0.1",
343 | "is-potential-custom-element-name": "^1.0.1",
344 | "nwsapi": "^2.2.2",
345 | "parse5": "^7.1.1",
346 | "saxes": "^6.0.0",
347 | "symbol-tree": "^3.2.4",
348 | "tough-cookie": "^4.1.2",
349 | "w3c-xmlserializer": "^4.0.0",
350 | "webidl-conversions": "^7.0.0",
351 | "whatwg-encoding": "^2.0.0",
352 | "whatwg-mimetype": "^3.0.0",
353 | "whatwg-url": "^11.0.0",
354 | "ws": "^8.11.0",
355 | "xml-name-validator": "^4.0.0"
356 | },
357 | "engines": {
358 | "node": ">=14"
359 | },
360 | "peerDependencies": {
361 | "canvas": "^2.5.0"
362 | },
363 | "peerDependenciesMeta": {
364 | "canvas": {
365 | "optional": true
366 | }
367 | }
368 | },
369 | "node_modules/levn": {
370 | "version": "0.3.0",
371 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
372 | "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
373 | "dev": true,
374 | "dependencies": {
375 | "prelude-ls": "~1.1.2",
376 | "type-check": "~0.3.2"
377 | },
378 | "engines": {
379 | "node": ">= 0.8.0"
380 | }
381 | },
382 | "node_modules/mime-db": {
383 | "version": "1.52.0",
384 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
385 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
386 | "dev": true,
387 | "engines": {
388 | "node": ">= 0.6"
389 | }
390 | },
391 | "node_modules/mime-types": {
392 | "version": "2.1.35",
393 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
394 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
395 | "dev": true,
396 | "dependencies": {
397 | "mime-db": "1.52.0"
398 | },
399 | "engines": {
400 | "node": ">= 0.6"
401 | }
402 | },
403 | "node_modules/ms": {
404 | "version": "2.1.2",
405 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
406 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
407 | "dev": true
408 | },
409 | "node_modules/nwsapi": {
410 | "version": "2.2.2",
411 | "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz",
412 | "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==",
413 | "dev": true
414 | },
415 | "node_modules/optionator": {
416 | "version": "0.8.3",
417 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
418 | "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
419 | "dev": true,
420 | "dependencies": {
421 | "deep-is": "~0.1.3",
422 | "fast-levenshtein": "~2.0.6",
423 | "levn": "~0.3.0",
424 | "prelude-ls": "~1.1.2",
425 | "type-check": "~0.3.2",
426 | "word-wrap": "~1.2.3"
427 | },
428 | "engines": {
429 | "node": ">= 0.8.0"
430 | }
431 | },
432 | "node_modules/paper": {
433 | "version": "0.12.17",
434 | "resolved": "https://registry.npmjs.org/paper/-/paper-0.12.17.tgz",
435 | "integrity": "sha512-oCe+e1C2w8hKIcGoAqUjD0GGxGPv+itrRXlEFUmp3H8tY/NTnHOkYgpJFPGw6OJ8Q1Wa6+RgzlY7Dx/2WWHtkA==",
436 | "dev": true,
437 | "engines": {
438 | "node": ">=8.0.0"
439 | }
440 | },
441 | "node_modules/parse5": {
442 | "version": "7.1.2",
443 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
444 | "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
445 | "dev": true,
446 | "dependencies": {
447 | "entities": "^4.4.0"
448 | },
449 | "funding": {
450 | "url": "https://github.com/inikulin/parse5?sponsor=1"
451 | }
452 | },
453 | "node_modules/prelude-ls": {
454 | "version": "1.1.2",
455 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
456 | "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==",
457 | "dev": true,
458 | "engines": {
459 | "node": ">= 0.8.0"
460 | }
461 | },
462 | "node_modules/prettier": {
463 | "version": "2.8.0",
464 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.0.tgz",
465 | "integrity": "sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA==",
466 | "dev": true,
467 | "bin": {
468 | "prettier": "bin-prettier.js"
469 | },
470 | "engines": {
471 | "node": ">=10.13.0"
472 | },
473 | "funding": {
474 | "url": "https://github.com/prettier/prettier?sponsor=1"
475 | }
476 | },
477 | "node_modules/psl": {
478 | "version": "1.9.0",
479 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
480 | "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
481 | "dev": true
482 | },
483 | "node_modules/punycode": {
484 | "version": "2.1.1",
485 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
486 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
487 | "dev": true,
488 | "engines": {
489 | "node": ">=6"
490 | }
491 | },
492 | "node_modules/querystringify": {
493 | "version": "2.2.0",
494 | "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
495 | "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
496 | "dev": true
497 | },
498 | "node_modules/requires-port": {
499 | "version": "1.0.0",
500 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
501 | "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
502 | "dev": true
503 | },
504 | "node_modules/safer-buffer": {
505 | "version": "2.1.2",
506 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
507 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
508 | "dev": true
509 | },
510 | "node_modules/saxes": {
511 | "version": "6.0.0",
512 | "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
513 | "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
514 | "dev": true,
515 | "dependencies": {
516 | "xmlchars": "^2.2.0"
517 | },
518 | "engines": {
519 | "node": ">=v12.22.7"
520 | }
521 | },
522 | "node_modules/source-map": {
523 | "version": "0.6.1",
524 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
525 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
526 | "dev": true,
527 | "optional": true,
528 | "engines": {
529 | "node": ">=0.10.0"
530 | }
531 | },
532 | "node_modules/symbol-tree": {
533 | "version": "3.2.4",
534 | "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
535 | "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
536 | "dev": true
537 | },
538 | "node_modules/tough-cookie": {
539 | "version": "4.1.2",
540 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz",
541 | "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==",
542 | "dev": true,
543 | "dependencies": {
544 | "psl": "^1.1.33",
545 | "punycode": "^2.1.1",
546 | "universalify": "^0.2.0",
547 | "url-parse": "^1.5.3"
548 | },
549 | "engines": {
550 | "node": ">=6"
551 | }
552 | },
553 | "node_modules/tr46": {
554 | "version": "3.0.0",
555 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
556 | "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
557 | "dev": true,
558 | "dependencies": {
559 | "punycode": "^2.1.1"
560 | },
561 | "engines": {
562 | "node": ">=12"
563 | }
564 | },
565 | "node_modules/type-check": {
566 | "version": "0.3.2",
567 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
568 | "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==",
569 | "dev": true,
570 | "dependencies": {
571 | "prelude-ls": "~1.1.2"
572 | },
573 | "engines": {
574 | "node": ">= 0.8.0"
575 | }
576 | },
577 | "node_modules/typescript": {
578 | "version": "4.9.3",
579 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz",
580 | "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==",
581 | "dev": true,
582 | "bin": {
583 | "tsc": "bin/tsc",
584 | "tsserver": "bin/tsserver"
585 | },
586 | "engines": {
587 | "node": ">=4.2.0"
588 | }
589 | },
590 | "node_modules/universalify": {
591 | "version": "0.2.0",
592 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
593 | "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
594 | "dev": true,
595 | "engines": {
596 | "node": ">= 4.0.0"
597 | }
598 | },
599 | "node_modules/url-parse": {
600 | "version": "1.5.10",
601 | "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
602 | "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
603 | "dev": true,
604 | "dependencies": {
605 | "querystringify": "^2.1.1",
606 | "requires-port": "^1.0.0"
607 | }
608 | },
609 | "node_modules/w3c-xmlserializer": {
610 | "version": "4.0.0",
611 | "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
612 | "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==",
613 | "dev": true,
614 | "dependencies": {
615 | "xml-name-validator": "^4.0.0"
616 | },
617 | "engines": {
618 | "node": ">=14"
619 | }
620 | },
621 | "node_modules/webidl-conversions": {
622 | "version": "7.0.0",
623 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
624 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
625 | "dev": true,
626 | "engines": {
627 | "node": ">=12"
628 | }
629 | },
630 | "node_modules/whatwg-encoding": {
631 | "version": "2.0.0",
632 | "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
633 | "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
634 | "dev": true,
635 | "dependencies": {
636 | "iconv-lite": "0.6.3"
637 | },
638 | "engines": {
639 | "node": ">=12"
640 | }
641 | },
642 | "node_modules/whatwg-mimetype": {
643 | "version": "3.0.0",
644 | "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
645 | "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
646 | "dev": true,
647 | "engines": {
648 | "node": ">=12"
649 | }
650 | },
651 | "node_modules/whatwg-url": {
652 | "version": "11.0.0",
653 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
654 | "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
655 | "dev": true,
656 | "dependencies": {
657 | "tr46": "^3.0.0",
658 | "webidl-conversions": "^7.0.0"
659 | },
660 | "engines": {
661 | "node": ">=12"
662 | }
663 | },
664 | "node_modules/word-wrap": {
665 | "version": "1.2.3",
666 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
667 | "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
668 | "dev": true,
669 | "engines": {
670 | "node": ">=0.10.0"
671 | }
672 | },
673 | "node_modules/ws": {
674 | "version": "8.11.0",
675 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
676 | "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
677 | "dev": true,
678 | "engines": {
679 | "node": ">=10.0.0"
680 | },
681 | "peerDependencies": {
682 | "bufferutil": "^4.0.1",
683 | "utf-8-validate": "^5.0.2"
684 | },
685 | "peerDependenciesMeta": {
686 | "bufferutil": {
687 | "optional": true
688 | },
689 | "utf-8-validate": {
690 | "optional": true
691 | }
692 | }
693 | },
694 | "node_modules/xml-name-validator": {
695 | "version": "4.0.0",
696 | "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
697 | "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
698 | "dev": true,
699 | "engines": {
700 | "node": ">=12"
701 | }
702 | },
703 | "node_modules/xmlchars": {
704 | "version": "2.2.0",
705 | "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
706 | "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
707 | "dev": true
708 | }
709 | },
710 | "dependencies": {
711 | "@tootallnate/once": {
712 | "version": "2.0.0",
713 | "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
714 | "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
715 | "dev": true
716 | },
717 | "abab": {
718 | "version": "2.0.6",
719 | "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
720 | "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==",
721 | "dev": true
722 | },
723 | "acorn": {
724 | "version": "8.8.1",
725 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
726 | "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==",
727 | "dev": true
728 | },
729 | "acorn-globals": {
730 | "version": "7.0.1",
731 | "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
732 | "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
733 | "dev": true,
734 | "requires": {
735 | "acorn": "^8.1.0",
736 | "acorn-walk": "^8.0.2"
737 | }
738 | },
739 | "acorn-walk": {
740 | "version": "8.2.0",
741 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
742 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
743 | "dev": true
744 | },
745 | "agent-base": {
746 | "version": "6.0.2",
747 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
748 | "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
749 | "dev": true,
750 | "requires": {
751 | "debug": "4"
752 | }
753 | },
754 | "asynckit": {
755 | "version": "0.4.0",
756 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
757 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
758 | "dev": true
759 | },
760 | "combined-stream": {
761 | "version": "1.0.8",
762 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
763 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
764 | "dev": true,
765 | "requires": {
766 | "delayed-stream": "~1.0.0"
767 | }
768 | },
769 | "cssom": {
770 | "version": "0.5.0",
771 | "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
772 | "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==",
773 | "dev": true
774 | },
775 | "cssstyle": {
776 | "version": "2.3.0",
777 | "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
778 | "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
779 | "dev": true,
780 | "requires": {
781 | "cssom": "~0.3.6"
782 | },
783 | "dependencies": {
784 | "cssom": {
785 | "version": "0.3.8",
786 | "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
787 | "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
788 | "dev": true
789 | }
790 | }
791 | },
792 | "data-urls": {
793 | "version": "3.0.2",
794 | "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
795 | "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==",
796 | "dev": true,
797 | "requires": {
798 | "abab": "^2.0.6",
799 | "whatwg-mimetype": "^3.0.0",
800 | "whatwg-url": "^11.0.0"
801 | }
802 | },
803 | "debug": {
804 | "version": "4.3.4",
805 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
806 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
807 | "dev": true,
808 | "requires": {
809 | "ms": "2.1.2"
810 | }
811 | },
812 | "decimal.js": {
813 | "version": "10.4.2",
814 | "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.2.tgz",
815 | "integrity": "sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA==",
816 | "dev": true
817 | },
818 | "deep-is": {
819 | "version": "0.1.4",
820 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
821 | "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
822 | "dev": true
823 | },
824 | "delayed-stream": {
825 | "version": "1.0.0",
826 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
827 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
828 | "dev": true
829 | },
830 | "domexception": {
831 | "version": "4.0.0",
832 | "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
833 | "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==",
834 | "dev": true,
835 | "requires": {
836 | "webidl-conversions": "^7.0.0"
837 | }
838 | },
839 | "entities": {
840 | "version": "4.4.0",
841 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz",
842 | "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==",
843 | "dev": true
844 | },
845 | "escodegen": {
846 | "version": "2.0.0",
847 | "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz",
848 | "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==",
849 | "dev": true,
850 | "requires": {
851 | "esprima": "^4.0.1",
852 | "estraverse": "^5.2.0",
853 | "esutils": "^2.0.2",
854 | "optionator": "^0.8.1",
855 | "source-map": "~0.6.1"
856 | }
857 | },
858 | "esprima": {
859 | "version": "4.0.1",
860 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
861 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
862 | "dev": true
863 | },
864 | "estraverse": {
865 | "version": "5.3.0",
866 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
867 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
868 | "dev": true
869 | },
870 | "esutils": {
871 | "version": "2.0.3",
872 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
873 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
874 | "dev": true
875 | },
876 | "fast-levenshtein": {
877 | "version": "2.0.6",
878 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
879 | "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
880 | "dev": true
881 | },
882 | "form-data": {
883 | "version": "4.0.0",
884 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
885 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
886 | "dev": true,
887 | "requires": {
888 | "asynckit": "^0.4.0",
889 | "combined-stream": "^1.0.8",
890 | "mime-types": "^2.1.12"
891 | }
892 | },
893 | "html-encoding-sniffer": {
894 | "version": "3.0.0",
895 | "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
896 | "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
897 | "dev": true,
898 | "requires": {
899 | "whatwg-encoding": "^2.0.0"
900 | }
901 | },
902 | "http-proxy-agent": {
903 | "version": "5.0.0",
904 | "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
905 | "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
906 | "dev": true,
907 | "requires": {
908 | "@tootallnate/once": "2",
909 | "agent-base": "6",
910 | "debug": "4"
911 | }
912 | },
913 | "https-proxy-agent": {
914 | "version": "5.0.1",
915 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
916 | "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
917 | "dev": true,
918 | "requires": {
919 | "agent-base": "6",
920 | "debug": "4"
921 | }
922 | },
923 | "iconv-lite": {
924 | "version": "0.6.3",
925 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
926 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
927 | "dev": true,
928 | "requires": {
929 | "safer-buffer": ">= 2.1.2 < 3.0.0"
930 | }
931 | },
932 | "is-potential-custom-element-name": {
933 | "version": "1.0.1",
934 | "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
935 | "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
936 | "dev": true
937 | },
938 | "jsdom": {
939 | "version": "20.0.3",
940 | "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz",
941 | "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==",
942 | "dev": true,
943 | "requires": {
944 | "abab": "^2.0.6",
945 | "acorn": "^8.8.1",
946 | "acorn-globals": "^7.0.0",
947 | "cssom": "^0.5.0",
948 | "cssstyle": "^2.3.0",
949 | "data-urls": "^3.0.2",
950 | "decimal.js": "^10.4.2",
951 | "domexception": "^4.0.0",
952 | "escodegen": "^2.0.0",
953 | "form-data": "^4.0.0",
954 | "html-encoding-sniffer": "^3.0.0",
955 | "http-proxy-agent": "^5.0.0",
956 | "https-proxy-agent": "^5.0.1",
957 | "is-potential-custom-element-name": "^1.0.1",
958 | "nwsapi": "^2.2.2",
959 | "parse5": "^7.1.1",
960 | "saxes": "^6.0.0",
961 | "symbol-tree": "^3.2.4",
962 | "tough-cookie": "^4.1.2",
963 | "w3c-xmlserializer": "^4.0.0",
964 | "webidl-conversions": "^7.0.0",
965 | "whatwg-encoding": "^2.0.0",
966 | "whatwg-mimetype": "^3.0.0",
967 | "whatwg-url": "^11.0.0",
968 | "ws": "^8.11.0",
969 | "xml-name-validator": "^4.0.0"
970 | }
971 | },
972 | "levn": {
973 | "version": "0.3.0",
974 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
975 | "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
976 | "dev": true,
977 | "requires": {
978 | "prelude-ls": "~1.1.2",
979 | "type-check": "~0.3.2"
980 | }
981 | },
982 | "mime-db": {
983 | "version": "1.52.0",
984 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
985 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
986 | "dev": true
987 | },
988 | "mime-types": {
989 | "version": "2.1.35",
990 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
991 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
992 | "dev": true,
993 | "requires": {
994 | "mime-db": "1.52.0"
995 | }
996 | },
997 | "ms": {
998 | "version": "2.1.2",
999 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
1000 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
1001 | "dev": true
1002 | },
1003 | "nwsapi": {
1004 | "version": "2.2.2",
1005 | "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz",
1006 | "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==",
1007 | "dev": true
1008 | },
1009 | "optionator": {
1010 | "version": "0.8.3",
1011 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
1012 | "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
1013 | "dev": true,
1014 | "requires": {
1015 | "deep-is": "~0.1.3",
1016 | "fast-levenshtein": "~2.0.6",
1017 | "levn": "~0.3.0",
1018 | "prelude-ls": "~1.1.2",
1019 | "type-check": "~0.3.2",
1020 | "word-wrap": "~1.2.3"
1021 | }
1022 | },
1023 | "paper": {
1024 | "version": "0.12.17",
1025 | "resolved": "https://registry.npmjs.org/paper/-/paper-0.12.17.tgz",
1026 | "integrity": "sha512-oCe+e1C2w8hKIcGoAqUjD0GGxGPv+itrRXlEFUmp3H8tY/NTnHOkYgpJFPGw6OJ8Q1Wa6+RgzlY7Dx/2WWHtkA==",
1027 | "dev": true
1028 | },
1029 | "parse5": {
1030 | "version": "7.1.2",
1031 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
1032 | "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
1033 | "dev": true,
1034 | "requires": {
1035 | "entities": "^4.4.0"
1036 | }
1037 | },
1038 | "prelude-ls": {
1039 | "version": "1.1.2",
1040 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
1041 | "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==",
1042 | "dev": true
1043 | },
1044 | "prettier": {
1045 | "version": "2.8.0",
1046 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.0.tgz",
1047 | "integrity": "sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA==",
1048 | "dev": true
1049 | },
1050 | "psl": {
1051 | "version": "1.9.0",
1052 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
1053 | "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
1054 | "dev": true
1055 | },
1056 | "punycode": {
1057 | "version": "2.1.1",
1058 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
1059 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
1060 | "dev": true
1061 | },
1062 | "querystringify": {
1063 | "version": "2.2.0",
1064 | "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
1065 | "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
1066 | "dev": true
1067 | },
1068 | "requires-port": {
1069 | "version": "1.0.0",
1070 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
1071 | "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
1072 | "dev": true
1073 | },
1074 | "safer-buffer": {
1075 | "version": "2.1.2",
1076 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1077 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1078 | "dev": true
1079 | },
1080 | "saxes": {
1081 | "version": "6.0.0",
1082 | "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
1083 | "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
1084 | "dev": true,
1085 | "requires": {
1086 | "xmlchars": "^2.2.0"
1087 | }
1088 | },
1089 | "source-map": {
1090 | "version": "0.6.1",
1091 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
1092 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
1093 | "dev": true,
1094 | "optional": true
1095 | },
1096 | "symbol-tree": {
1097 | "version": "3.2.4",
1098 | "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
1099 | "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
1100 | "dev": true
1101 | },
1102 | "tough-cookie": {
1103 | "version": "4.1.2",
1104 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz",
1105 | "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==",
1106 | "dev": true,
1107 | "requires": {
1108 | "psl": "^1.1.33",
1109 | "punycode": "^2.1.1",
1110 | "universalify": "^0.2.0",
1111 | "url-parse": "^1.5.3"
1112 | }
1113 | },
1114 | "tr46": {
1115 | "version": "3.0.0",
1116 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
1117 | "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
1118 | "dev": true,
1119 | "requires": {
1120 | "punycode": "^2.1.1"
1121 | }
1122 | },
1123 | "type-check": {
1124 | "version": "0.3.2",
1125 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
1126 | "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==",
1127 | "dev": true,
1128 | "requires": {
1129 | "prelude-ls": "~1.1.2"
1130 | }
1131 | },
1132 | "typescript": {
1133 | "version": "4.9.3",
1134 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz",
1135 | "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==",
1136 | "dev": true
1137 | },
1138 | "universalify": {
1139 | "version": "0.2.0",
1140 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
1141 | "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
1142 | "dev": true
1143 | },
1144 | "url-parse": {
1145 | "version": "1.5.10",
1146 | "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
1147 | "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
1148 | "dev": true,
1149 | "requires": {
1150 | "querystringify": "^2.1.1",
1151 | "requires-port": "^1.0.0"
1152 | }
1153 | },
1154 | "w3c-xmlserializer": {
1155 | "version": "4.0.0",
1156 | "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
1157 | "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==",
1158 | "dev": true,
1159 | "requires": {
1160 | "xml-name-validator": "^4.0.0"
1161 | }
1162 | },
1163 | "webidl-conversions": {
1164 | "version": "7.0.0",
1165 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
1166 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
1167 | "dev": true
1168 | },
1169 | "whatwg-encoding": {
1170 | "version": "2.0.0",
1171 | "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
1172 | "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
1173 | "dev": true,
1174 | "requires": {
1175 | "iconv-lite": "0.6.3"
1176 | }
1177 | },
1178 | "whatwg-mimetype": {
1179 | "version": "3.0.0",
1180 | "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
1181 | "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
1182 | "dev": true
1183 | },
1184 | "whatwg-url": {
1185 | "version": "11.0.0",
1186 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
1187 | "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
1188 | "dev": true,
1189 | "requires": {
1190 | "tr46": "^3.0.0",
1191 | "webidl-conversions": "^7.0.0"
1192 | }
1193 | },
1194 | "word-wrap": {
1195 | "version": "1.2.3",
1196 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
1197 | "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
1198 | "dev": true
1199 | },
1200 | "ws": {
1201 | "version": "8.11.0",
1202 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
1203 | "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
1204 | "dev": true,
1205 | "requires": {}
1206 | },
1207 | "xml-name-validator": {
1208 | "version": "4.0.0",
1209 | "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
1210 | "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
1211 | "dev": true
1212 | },
1213 | "xmlchars": {
1214 | "version": "2.2.0",
1215 | "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
1216 | "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
1217 | "dev": true
1218 | }
1219 | }
1220 | }
1221 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@luncheon/simplify-svg-path",
3 | "version": "0.2.0",
4 | "description": "Extracts Path#simplify() from Paper.js.",
5 | "license": "MIT",
6 | "repository": "luncheon/simplify-svg-path",
7 | "files": [
8 | "index.js",
9 | "index.d.ts",
10 | "index.iife.js"
11 | ],
12 | "type": "module",
13 | "main": "index.js",
14 | "jsdelivr": "index.iife.js",
15 | "unpkg": "index.iife.js",
16 | "keywords": [
17 | "paper",
18 | "paper.js",
19 | "svg",
20 | "path"
21 | ],
22 | "prettier": {
23 | "printWidth": 140,
24 | "endOfLine": "lf",
25 | "singleQuote": true,
26 | "trailingComma": "all",
27 | "semi": false,
28 | "arrowParens": "avoid"
29 | },
30 | "scripts": {
31 | "build": "tsc -p . && node build-iife.js && npm t",
32 | "test": "node test.js"
33 | },
34 | "devDependencies": {
35 | "jsdom": "^20.0.3",
36 | "paper": "^0.12.17",
37 | "prettier": "^2.8.0",
38 | "typescript": "^4.9.3"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert'
2 | import Paper from 'paper'
3 | import simplifySvgPath from './index.js'
4 |
5 | new Paper.Project()
6 |
7 | const simplifySvgPathByPaper = (segments, { closed, tolerance, precision }) => {
8 | const path = new Paper.Path(segments)
9 | closed && path.closePath()
10 | path.simplify(tolerance)
11 | return path.getPathData(undefined, precision)
12 | }
13 |
14 | let actualTime = 0
15 | let expectedTime = 0
16 |
17 | for (let i = 0; i < 100; i++) {
18 | const points = []
19 | for (let i = 0; i < 1000; i++) {
20 | points.push([Math.random() * 100, Math.random() * 100])
21 | }
22 | const options = {
23 | closed: Math.random() < 0.5,
24 | tolerance: Math.random() * 5 || 2.5,
25 | precision: i % 4,
26 | }
27 | const now1 = performance.now()
28 | const actual = simplifySvgPath(points, options)
29 | const now2 = performance.now()
30 | const expected = simplifySvgPathByPaper(points, options)
31 | const now3 = performance.now()
32 | actualTime += now2 - now1
33 | expectedTime += now3 - now2
34 | assert.strictEqual(
35 | actual,
36 | expected,
37 | `simplified path does not equal to Paper.js. closed: ${options.closed}, tolerance: ${options.tolerance}, precision: ${options.precision}`,
38 | )
39 | }
40 |
41 | console.log('passed', actualTime, '[ms]', Math.round((actualTime / expectedTime) * 10000) / 100, '%')
42 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2020",
4 | "module": "es2015",
5 | "moduleResolution": "node",
6 | "strict": true,
7 | "noFallthroughCasesInSwitch": true,
8 | "noUnusedLocals": true,
9 | "noUnusedParameters": true,
10 | "newLine": "LF",
11 | "declaration": true,
12 | "outDir": "."
13 | },
14 | "files": ["index.ts"]
15 | }
16 |
--------------------------------------------------------------------------------