svgo-v2 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +294 -0
  3. package/bin/svgo +10 -0
  4. package/dist/svgo.browser.js +1 -0
  5. package/lib/css-tools.js +239 -0
  6. package/lib/parser.js +259 -0
  7. package/lib/path.js +347 -0
  8. package/lib/stringifier.js +326 -0
  9. package/lib/style.js +283 -0
  10. package/lib/svgo/coa.js +517 -0
  11. package/lib/svgo/config.js +138 -0
  12. package/lib/svgo/css-class-list.js +72 -0
  13. package/lib/svgo/css-select-adapter.d.ts +2 -0
  14. package/lib/svgo/css-select-adapter.js +120 -0
  15. package/lib/svgo/css-style-declaration.js +232 -0
  16. package/lib/svgo/jsAPI.d.ts +2 -0
  17. package/lib/svgo/jsAPI.js +443 -0
  18. package/lib/svgo/plugins.js +109 -0
  19. package/lib/svgo/tools.js +137 -0
  20. package/lib/svgo-node.js +106 -0
  21. package/lib/svgo.js +83 -0
  22. package/lib/types.ts +172 -0
  23. package/lib/xast.js +102 -0
  24. package/package.json +130 -0
  25. package/plugins/_applyTransforms.js +335 -0
  26. package/plugins/_collections.js +2168 -0
  27. package/plugins/_path.js +816 -0
  28. package/plugins/_transforms.js +379 -0
  29. package/plugins/addAttributesToSVGElement.js +87 -0
  30. package/plugins/addClassesToSVGElement.js +87 -0
  31. package/plugins/cleanupAttrs.js +55 -0
  32. package/plugins/cleanupEnableBackground.js +75 -0
  33. package/plugins/cleanupIDs.js +297 -0
  34. package/plugins/cleanupListOfValues.js +154 -0
  35. package/plugins/cleanupNumericValues.js +113 -0
  36. package/plugins/collapseGroups.js +135 -0
  37. package/plugins/convertColors.js +152 -0
  38. package/plugins/convertEllipseToCircle.js +39 -0
  39. package/plugins/convertPathData.js +1023 -0
  40. package/plugins/convertShapeToPath.js +175 -0
  41. package/plugins/convertStyleToAttrs.js +132 -0
  42. package/plugins/convertTransform.js +432 -0
  43. package/plugins/inlineStyles.js +379 -0
  44. package/plugins/mergePaths.js +104 -0
  45. package/plugins/mergeStyles.js +93 -0
  46. package/plugins/minifyStyles.js +148 -0
  47. package/plugins/moveElemsAttrsToGroup.js +130 -0
  48. package/plugins/moveGroupAttrsToElems.js +62 -0
  49. package/plugins/plugins.js +56 -0
  50. package/plugins/prefixIds.js +241 -0
  51. package/plugins/preset-default.js +80 -0
  52. package/plugins/removeAttributesBySelector.js +99 -0
  53. package/plugins/removeAttrs.js +159 -0
  54. package/plugins/removeComments.js +31 -0
  55. package/plugins/removeDesc.js +41 -0
  56. package/plugins/removeDimensions.js +43 -0
  57. package/plugins/removeDoctype.js +42 -0
  58. package/plugins/removeEditorsNSData.js +68 -0
  59. package/plugins/removeElementsByAttr.js +78 -0
  60. package/plugins/removeEmptyAttrs.js +33 -0
  61. package/plugins/removeEmptyContainers.js +58 -0
  62. package/plugins/removeEmptyText.js +57 -0
  63. package/plugins/removeHiddenElems.js +318 -0
  64. package/plugins/removeMetadata.js +29 -0
  65. package/plugins/removeNonInheritableGroupAttrs.js +38 -0
  66. package/plugins/removeOffCanvasPaths.js +138 -0
  67. package/plugins/removeRasterImages.js +33 -0
  68. package/plugins/removeScriptElement.js +29 -0
  69. package/plugins/removeStyleElement.js +29 -0
  70. package/plugins/removeTitle.js +29 -0
  71. package/plugins/removeUnknownsAndDefaults.js +218 -0
  72. package/plugins/removeUnusedNS.js +61 -0
  73. package/plugins/removeUselessDefs.js +65 -0
  74. package/plugins/removeUselessStrokeAndFill.js +144 -0
  75. package/plugins/removeViewBox.js +51 -0
  76. package/plugins/removeXMLNS.js +30 -0
  77. package/plugins/removeXMLProcInst.js +30 -0
  78. package/plugins/reusePaths.js +113 -0
  79. package/plugins/sortAttrs.js +113 -0
  80. package/plugins/sortDefsChildren.js +60 -0
@@ -0,0 +1,816 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @typedef {import('../lib/types').XastElement} XastElement
5
+ * @typedef {import('../lib/types').PathDataItem} PathDataItem
6
+ */
7
+
8
+ const { parsePathData, stringifyPathData } = require('../lib/path.js');
9
+
10
+ /**
11
+ * @type {[number, number]}
12
+ */
13
+ var prevCtrlPoint;
14
+
15
+ /**
16
+ * Convert path string to JS representation.
17
+ *
18
+ * @type {(path: XastElement) => Array<PathDataItem>}
19
+ */
20
+ const path2js = (path) => {
21
+ // @ts-ignore legacy
22
+ if (path.pathJS) return path.pathJS;
23
+ /**
24
+ * @type {Array<PathDataItem>}
25
+ */
26
+ const pathData = []; // JS representation of the path data
27
+ const newPathData = parsePathData(path.attributes.d);
28
+ for (const { command, args } of newPathData) {
29
+ pathData.push({ command, args });
30
+ }
31
+ // First moveto is actually absolute. Subsequent coordinates were separated above.
32
+ if (pathData.length && pathData[0].command == 'm') {
33
+ pathData[0].command = 'M';
34
+ }
35
+ // @ts-ignore legacy
36
+ path.pathJS = pathData;
37
+ return pathData;
38
+ };
39
+ exports.path2js = path2js;
40
+
41
+ /**
42
+ * Convert relative Path data to absolute.
43
+ *
44
+ * @type {(data: Array<PathDataItem>) => Array<PathDataItem>}
45
+ *
46
+ */
47
+ const convertRelativeToAbsolute = (data) => {
48
+ /**
49
+ * @type {Array<PathDataItem>}
50
+ */
51
+ const newData = [];
52
+ let start = [0, 0];
53
+ let cursor = [0, 0];
54
+
55
+ for (let { command, args } of data) {
56
+ args = args.slice();
57
+
58
+ // moveto (x y)
59
+ if (command === 'm') {
60
+ args[0] += cursor[0];
61
+ args[1] += cursor[1];
62
+ command = 'M';
63
+ }
64
+ if (command === 'M') {
65
+ cursor[0] = args[0];
66
+ cursor[1] = args[1];
67
+ start[0] = cursor[0];
68
+ start[1] = cursor[1];
69
+ }
70
+
71
+ // horizontal lineto (x)
72
+ if (command === 'h') {
73
+ args[0] += cursor[0];
74
+ command = 'H';
75
+ }
76
+ if (command === 'H') {
77
+ cursor[0] = args[0];
78
+ }
79
+
80
+ // vertical lineto (y)
81
+ if (command === 'v') {
82
+ args[0] += cursor[1];
83
+ command = 'V';
84
+ }
85
+ if (command === 'V') {
86
+ cursor[1] = args[0];
87
+ }
88
+
89
+ // lineto (x y)
90
+ if (command === 'l') {
91
+ args[0] += cursor[0];
92
+ args[1] += cursor[1];
93
+ command = 'L';
94
+ }
95
+ if (command === 'L') {
96
+ cursor[0] = args[0];
97
+ cursor[1] = args[1];
98
+ }
99
+
100
+ // curveto (x1 y1 x2 y2 x y)
101
+ if (command === 'c') {
102
+ args[0] += cursor[0];
103
+ args[1] += cursor[1];
104
+ args[2] += cursor[0];
105
+ args[3] += cursor[1];
106
+ args[4] += cursor[0];
107
+ args[5] += cursor[1];
108
+ command = 'C';
109
+ }
110
+ if (command === 'C') {
111
+ cursor[0] = args[4];
112
+ cursor[1] = args[5];
113
+ }
114
+
115
+ // smooth curveto (x2 y2 x y)
116
+ if (command === 's') {
117
+ args[0] += cursor[0];
118
+ args[1] += cursor[1];
119
+ args[2] += cursor[0];
120
+ args[3] += cursor[1];
121
+ command = 'S';
122
+ }
123
+ if (command === 'S') {
124
+ cursor[0] = args[2];
125
+ cursor[1] = args[3];
126
+ }
127
+
128
+ // quadratic Bézier curveto (x1 y1 x y)
129
+ if (command === 'q') {
130
+ args[0] += cursor[0];
131
+ args[1] += cursor[1];
132
+ args[2] += cursor[0];
133
+ args[3] += cursor[1];
134
+ command = 'Q';
135
+ }
136
+ if (command === 'Q') {
137
+ cursor[0] = args[2];
138
+ cursor[1] = args[3];
139
+ }
140
+
141
+ // smooth quadratic Bézier curveto (x y)
142
+ if (command === 't') {
143
+ args[0] += cursor[0];
144
+ args[1] += cursor[1];
145
+ command = 'T';
146
+ }
147
+ if (command === 'T') {
148
+ cursor[0] = args[0];
149
+ cursor[1] = args[1];
150
+ }
151
+
152
+ // elliptical arc (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
153
+ if (command === 'a') {
154
+ args[5] += cursor[0];
155
+ args[6] += cursor[1];
156
+ command = 'A';
157
+ }
158
+ if (command === 'A') {
159
+ cursor[0] = args[5];
160
+ cursor[1] = args[6];
161
+ }
162
+
163
+ // closepath
164
+ if (command === 'z' || command === 'Z') {
165
+ cursor[0] = start[0];
166
+ cursor[1] = start[1];
167
+ command = 'z';
168
+ }
169
+
170
+ newData.push({ command, args });
171
+ }
172
+ return newData;
173
+ };
174
+
175
+ /**
176
+ * @typedef {{ floatPrecision?: number, noSpaceAfterFlags?: boolean }} Js2PathParams
177
+ */
178
+
179
+ /**
180
+ * Convert path array to string.
181
+ *
182
+ * @type {(path: XastElement, data: Array<PathDataItem>, params: Js2PathParams) => void}
183
+ */
184
+ exports.js2path = function (path, data, params) {
185
+ // @ts-ignore legacy
186
+ path.pathJS = data;
187
+
188
+ const pathData = [];
189
+ for (const item of data) {
190
+ // remove moveto commands which are followed by moveto commands
191
+ if (
192
+ pathData.length !== 0 &&
193
+ (item.command === 'M' || item.command === 'm')
194
+ ) {
195
+ const last = pathData[pathData.length - 1];
196
+ if (last.command === 'M' || last.command === 'm') {
197
+ pathData.pop();
198
+ }
199
+ }
200
+ pathData.push({
201
+ command: item.command,
202
+ args: item.args,
203
+ });
204
+ }
205
+
206
+ path.attributes.d = stringifyPathData({
207
+ pathData,
208
+ precision: params.floatPrecision,
209
+ disableSpaceAfterFlags: params.noSpaceAfterFlags,
210
+ });
211
+ };
212
+
213
+ /**
214
+ * @type {(dest: Array<number>, source: Array<number>) => Array<number>}
215
+ */
216
+ function set(dest, source) {
217
+ dest[0] = source[source.length - 2];
218
+ dest[1] = source[source.length - 1];
219
+ return dest;
220
+ }
221
+
222
+ /**
223
+ * Checks if two paths have an intersection by checking convex hulls
224
+ * collision using Gilbert-Johnson-Keerthi distance algorithm
225
+ * https://web.archive.org/web/20180822200027/http://entropyinteractive.com/2011/04/gjk-algorithm/
226
+ *
227
+ * @type {(path1: Array<PathDataItem>, path2: Array<PathDataItem>) => boolean}
228
+ */
229
+ exports.intersects = function (path1, path2) {
230
+ // Collect points of every subpath.
231
+ const points1 = gatherPoints(convertRelativeToAbsolute(path1));
232
+ const points2 = gatherPoints(convertRelativeToAbsolute(path2));
233
+
234
+ // Axis-aligned bounding box check.
235
+ if (
236
+ points1.maxX <= points2.minX ||
237
+ points2.maxX <= points1.minX ||
238
+ points1.maxY <= points2.minY ||
239
+ points2.maxY <= points1.minY ||
240
+ points1.list.every((set1) => {
241
+ return points2.list.every((set2) => {
242
+ return (
243
+ set1.list[set1.maxX][0] <= set2.list[set2.minX][0] ||
244
+ set2.list[set2.maxX][0] <= set1.list[set1.minX][0] ||
245
+ set1.list[set1.maxY][1] <= set2.list[set2.minY][1] ||
246
+ set2.list[set2.maxY][1] <= set1.list[set1.minY][1]
247
+ );
248
+ });
249
+ })
250
+ )
251
+ return false;
252
+
253
+ // Get a convex hull from points of each subpath. Has the most complexity O(n·log n).
254
+ const hullNest1 = points1.list.map(convexHull);
255
+ const hullNest2 = points2.list.map(convexHull);
256
+
257
+ // Check intersection of every subpath of the first path with every subpath of the second.
258
+ return hullNest1.some(function (hull1) {
259
+ if (hull1.list.length < 3) return false;
260
+
261
+ return hullNest2.some(function (hull2) {
262
+ if (hull2.list.length < 3) return false;
263
+
264
+ var simplex = [getSupport(hull1, hull2, [1, 0])], // create the initial simplex
265
+ direction = minus(simplex[0]); // set the direction to point towards the origin
266
+
267
+ var iterations = 1e4; // infinite loop protection, 10 000 iterations is more than enough
268
+ // eslint-disable-next-line no-constant-condition
269
+ while (true) {
270
+ // eslint-disable-next-line no-constant-condition
271
+ if (iterations-- == 0) {
272
+ console.error(
273
+ 'Error: infinite loop while processing mergePaths plugin.'
274
+ );
275
+ return true; // true is the safe value that means “do nothing with paths”
276
+ }
277
+ // add a new point
278
+ simplex.push(getSupport(hull1, hull2, direction));
279
+ // see if the new point was on the correct side of the origin
280
+ if (dot(direction, simplex[simplex.length - 1]) <= 0) return false;
281
+ // process the simplex
282
+ if (processSimplex(simplex, direction)) return true;
283
+ }
284
+ });
285
+ });
286
+
287
+ /**
288
+ * @type {(a: Point, b: Point, direction: Array<number>) => Array<number>}
289
+ */
290
+ function getSupport(a, b, direction) {
291
+ return sub(supportPoint(a, direction), supportPoint(b, minus(direction)));
292
+ }
293
+
294
+ // Computes farthest polygon point in particular direction.
295
+ // Thanks to knowledge of min/max x and y coordinates we can choose a quadrant to search in.
296
+ // Since we're working on convex hull, the dot product is increasing until we find the farthest point.
297
+ /**
298
+ * @type {(polygon: Point, direction: Array<number>) => Array<number>}
299
+ */
300
+ function supportPoint(polygon, direction) {
301
+ var index =
302
+ direction[1] >= 0
303
+ ? direction[0] < 0
304
+ ? polygon.maxY
305
+ : polygon.maxX
306
+ : direction[0] < 0
307
+ ? polygon.minX
308
+ : polygon.minY,
309
+ max = -Infinity,
310
+ value;
311
+ while ((value = dot(polygon.list[index], direction)) > max) {
312
+ max = value;
313
+ index = ++index % polygon.list.length;
314
+ }
315
+ return polygon.list[(index || polygon.list.length) - 1];
316
+ }
317
+ };
318
+
319
+ /**
320
+ * @type {(simplex: Array<Array<number>>, direction: Array<number>) => boolean}
321
+ */
322
+ function processSimplex(simplex, direction) {
323
+ // we only need to handle to 1-simplex and 2-simplex
324
+ if (simplex.length == 2) {
325
+ // 1-simplex
326
+ let a = simplex[1],
327
+ b = simplex[0],
328
+ AO = minus(simplex[1]),
329
+ AB = sub(b, a);
330
+ // AO is in the same direction as AB
331
+ if (dot(AO, AB) > 0) {
332
+ // get the vector perpendicular to AB facing O
333
+ set(direction, orth(AB, a));
334
+ } else {
335
+ set(direction, AO);
336
+ // only A remains in the simplex
337
+ simplex.shift();
338
+ }
339
+ } else {
340
+ // 2-simplex
341
+ let a = simplex[2], // [a, b, c] = simplex
342
+ b = simplex[1],
343
+ c = simplex[0],
344
+ AB = sub(b, a),
345
+ AC = sub(c, a),
346
+ AO = minus(a),
347
+ ACB = orth(AB, AC), // the vector perpendicular to AB facing away from C
348
+ ABC = orth(AC, AB); // the vector perpendicular to AC facing away from B
349
+
350
+ if (dot(ACB, AO) > 0) {
351
+ if (dot(AB, AO) > 0) {
352
+ // region 4
353
+ set(direction, ACB);
354
+ simplex.shift(); // simplex = [b, a]
355
+ } else {
356
+ // region 5
357
+ set(direction, AO);
358
+ simplex.splice(0, 2); // simplex = [a]
359
+ }
360
+ } else if (dot(ABC, AO) > 0) {
361
+ if (dot(AC, AO) > 0) {
362
+ // region 6
363
+ set(direction, ABC);
364
+ simplex.splice(1, 1); // simplex = [c, a]
365
+ } else {
366
+ // region 5 (again)
367
+ set(direction, AO);
368
+ simplex.splice(0, 2); // simplex = [a]
369
+ }
370
+ } // region 7
371
+ else return true;
372
+ }
373
+ return false;
374
+ }
375
+
376
+ /**
377
+ * @type {(v: Array<number>) => Array<number>}
378
+ */
379
+ function minus(v) {
380
+ return [-v[0], -v[1]];
381
+ }
382
+
383
+ /**
384
+ * @type {(v1: Array<number>, v2: Array<number>) => Array<number>}
385
+ */
386
+ function sub(v1, v2) {
387
+ return [v1[0] - v2[0], v1[1] - v2[1]];
388
+ }
389
+
390
+ /**
391
+ * @type {(v1: Array<number>, v2: Array<number>) => number}
392
+ */
393
+ function dot(v1, v2) {
394
+ return v1[0] * v2[0] + v1[1] * v2[1];
395
+ }
396
+
397
+ /**
398
+ * @type {(v1: Array<number>, v2: Array<number>) => Array<number>}
399
+ */
400
+ function orth(v, from) {
401
+ var o = [-v[1], v[0]];
402
+ return dot(o, minus(from)) < 0 ? minus(o) : o;
403
+ }
404
+
405
+ /**
406
+ * @typedef {{
407
+ * list: Array<Array<number>>,
408
+ * minX: number,
409
+ * minY: number,
410
+ * maxX: number,
411
+ * maxY: number
412
+ * }} Point
413
+ */
414
+
415
+ /**
416
+ * @typedef {{
417
+ * list: Array<Point>,
418
+ * minX: number,
419
+ * minY: number,
420
+ * maxX: number,
421
+ * maxY: number
422
+ * }} Points
423
+ */
424
+
425
+ /**
426
+ * @type {(pathData: Array<PathDataItem>) => Points}
427
+ */
428
+ function gatherPoints(pathData) {
429
+ /**
430
+ * @type {Points}
431
+ */
432
+ const points = { list: [], minX: 0, minY: 0, maxX: 0, maxY: 0 };
433
+
434
+ // Writes data about the extreme points on each axle
435
+ /**
436
+ * @type {(path: Point, point: Array<number>) => void}
437
+ */
438
+ const addPoint = (path, point) => {
439
+ if (!path.list.length || point[1] > path.list[path.maxY][1]) {
440
+ path.maxY = path.list.length;
441
+ points.maxY = points.list.length
442
+ ? Math.max(point[1], points.maxY)
443
+ : point[1];
444
+ }
445
+ if (!path.list.length || point[0] > path.list[path.maxX][0]) {
446
+ path.maxX = path.list.length;
447
+ points.maxX = points.list.length
448
+ ? Math.max(point[0], points.maxX)
449
+ : point[0];
450
+ }
451
+ if (!path.list.length || point[1] < path.list[path.minY][1]) {
452
+ path.minY = path.list.length;
453
+ points.minY = points.list.length
454
+ ? Math.min(point[1], points.minY)
455
+ : point[1];
456
+ }
457
+ if (!path.list.length || point[0] < path.list[path.minX][0]) {
458
+ path.minX = path.list.length;
459
+ points.minX = points.list.length
460
+ ? Math.min(point[0], points.minX)
461
+ : point[0];
462
+ }
463
+ path.list.push(point);
464
+ };
465
+
466
+ for (let i = 0; i < pathData.length; i += 1) {
467
+ const pathDataItem = pathData[i];
468
+ let subPath =
469
+ points.list.length === 0
470
+ ? { list: [], minX: 0, minY: 0, maxX: 0, maxY: 0 }
471
+ : points.list[points.list.length - 1];
472
+ let prev = i === 0 ? null : pathData[i - 1];
473
+ let basePoint =
474
+ subPath.list.length === 0 ? null : subPath.list[subPath.list.length - 1];
475
+ let data = pathDataItem.args;
476
+ let ctrlPoint = basePoint;
477
+
478
+ /**
479
+ * @type {(n: number, i: number) => number}
480
+ * TODO fix null hack
481
+ */
482
+ const toAbsolute = (n, i) => n + (basePoint == null ? 0 : basePoint[i % 2]);
483
+
484
+ switch (pathDataItem.command) {
485
+ case 'M':
486
+ subPath = { list: [], minX: 0, minY: 0, maxX: 0, maxY: 0 };
487
+ points.list.push(subPath);
488
+ break;
489
+
490
+ case 'H':
491
+ if (basePoint != null) {
492
+ addPoint(subPath, [data[0], basePoint[1]]);
493
+ }
494
+ break;
495
+
496
+ case 'V':
497
+ if (basePoint != null) {
498
+ addPoint(subPath, [basePoint[0], data[0]]);
499
+ }
500
+ break;
501
+
502
+ case 'Q':
503
+ addPoint(subPath, data.slice(0, 2));
504
+ prevCtrlPoint = [data[2] - data[0], data[3] - data[1]]; // Save control point for shorthand
505
+ break;
506
+
507
+ case 'T':
508
+ if (
509
+ basePoint != null &&
510
+ prev != null &&
511
+ (prev.command == 'Q' || prev.command == 'T')
512
+ ) {
513
+ ctrlPoint = [
514
+ basePoint[0] + prevCtrlPoint[0],
515
+ basePoint[1] + prevCtrlPoint[1],
516
+ ];
517
+ addPoint(subPath, ctrlPoint);
518
+ prevCtrlPoint = [data[0] - ctrlPoint[0], data[1] - ctrlPoint[1]];
519
+ }
520
+ break;
521
+
522
+ case 'C':
523
+ if (basePoint != null) {
524
+ // Approximate quibic Bezier curve with middle points between control points
525
+ addPoint(subPath, [
526
+ 0.5 * (basePoint[0] + data[0]),
527
+ 0.5 * (basePoint[1] + data[1]),
528
+ ]);
529
+ }
530
+ addPoint(subPath, [
531
+ 0.5 * (data[0] + data[2]),
532
+ 0.5 * (data[1] + data[3]),
533
+ ]);
534
+ addPoint(subPath, [
535
+ 0.5 * (data[2] + data[4]),
536
+ 0.5 * (data[3] + data[5]),
537
+ ]);
538
+ prevCtrlPoint = [data[4] - data[2], data[5] - data[3]]; // Save control point for shorthand
539
+ break;
540
+
541
+ case 'S':
542
+ if (
543
+ basePoint != null &&
544
+ prev != null &&
545
+ (prev.command == 'C' || prev.command == 'S')
546
+ ) {
547
+ addPoint(subPath, [
548
+ basePoint[0] + 0.5 * prevCtrlPoint[0],
549
+ basePoint[1] + 0.5 * prevCtrlPoint[1],
550
+ ]);
551
+ ctrlPoint = [
552
+ basePoint[0] + prevCtrlPoint[0],
553
+ basePoint[1] + prevCtrlPoint[1],
554
+ ];
555
+ }
556
+ if (ctrlPoint != null) {
557
+ addPoint(subPath, [
558
+ 0.5 * (ctrlPoint[0] + data[0]),
559
+ 0.5 * (ctrlPoint[1] + data[1]),
560
+ ]);
561
+ }
562
+ addPoint(subPath, [
563
+ 0.5 * (data[0] + data[2]),
564
+ 0.5 * (data[1] + data[3]),
565
+ ]);
566
+ prevCtrlPoint = [data[2] - data[0], data[3] - data[1]];
567
+ break;
568
+
569
+ case 'A':
570
+ if (basePoint != null) {
571
+ // Convert the arc to bezier curves and use the same approximation
572
+ // @ts-ignore no idea what's going on here
573
+ var curves = a2c.apply(0, basePoint.concat(data));
574
+ for (
575
+ var cData;
576
+ (cData = curves.splice(0, 6).map(toAbsolute)).length;
577
+
578
+ ) {
579
+ if (basePoint != null) {
580
+ addPoint(subPath, [
581
+ 0.5 * (basePoint[0] + cData[0]),
582
+ 0.5 * (basePoint[1] + cData[1]),
583
+ ]);
584
+ }
585
+ addPoint(subPath, [
586
+ 0.5 * (cData[0] + cData[2]),
587
+ 0.5 * (cData[1] + cData[3]),
588
+ ]);
589
+ addPoint(subPath, [
590
+ 0.5 * (cData[2] + cData[4]),
591
+ 0.5 * (cData[3] + cData[5]),
592
+ ]);
593
+ if (curves.length) addPoint(subPath, (basePoint = cData.slice(-2)));
594
+ }
595
+ }
596
+ break;
597
+ }
598
+
599
+ // Save final command coordinates
600
+ if (data.length >= 2) addPoint(subPath, data.slice(-2));
601
+ }
602
+
603
+ return points;
604
+ }
605
+
606
+ /**
607
+ * Forms a convex hull from set of points of every subpath using monotone chain convex hull algorithm.
608
+ * https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain
609
+ *
610
+ * @type {(points: Point) => Point}
611
+ */
612
+ function convexHull(points) {
613
+ points.list.sort(function (a, b) {
614
+ return a[0] == b[0] ? a[1] - b[1] : a[0] - b[0];
615
+ });
616
+
617
+ var lower = [],
618
+ minY = 0,
619
+ bottom = 0;
620
+ for (let i = 0; i < points.list.length; i++) {
621
+ while (
622
+ lower.length >= 2 &&
623
+ cross(lower[lower.length - 2], lower[lower.length - 1], points.list[i]) <=
624
+ 0
625
+ ) {
626
+ lower.pop();
627
+ }
628
+ if (points.list[i][1] < points.list[minY][1]) {
629
+ minY = i;
630
+ bottom = lower.length;
631
+ }
632
+ lower.push(points.list[i]);
633
+ }
634
+
635
+ var upper = [],
636
+ maxY = points.list.length - 1,
637
+ top = 0;
638
+ for (let i = points.list.length; i--; ) {
639
+ while (
640
+ upper.length >= 2 &&
641
+ cross(upper[upper.length - 2], upper[upper.length - 1], points.list[i]) <=
642
+ 0
643
+ ) {
644
+ upper.pop();
645
+ }
646
+ if (points.list[i][1] > points.list[maxY][1]) {
647
+ maxY = i;
648
+ top = upper.length;
649
+ }
650
+ upper.push(points.list[i]);
651
+ }
652
+
653
+ // last points are equal to starting points of the other part
654
+ upper.pop();
655
+ lower.pop();
656
+
657
+ const hullList = lower.concat(upper);
658
+
659
+ /**
660
+ * @type {Point}
661
+ */
662
+ const hull = {
663
+ list: hullList,
664
+ minX: 0, // by sorting
665
+ maxX: lower.length,
666
+ minY: bottom,
667
+ maxY: (lower.length + top) % hullList.length,
668
+ };
669
+
670
+ return hull;
671
+ }
672
+
673
+ /**
674
+ * @type {(o: Array<number>, a: Array<number>, b: Array<number>) => number}
675
+ */
676
+ function cross(o, a, b) {
677
+ return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
678
+ }
679
+
680
+ /**
681
+ * Based on code from Snap.svg (Apache 2 license). http://snapsvg.io/
682
+ * Thanks to Dmitry Baranovskiy for his great work!
683
+ *
684
+ * @type {(
685
+ * x1: number,
686
+ * y1: number,
687
+ * rx: number,
688
+ * ry: number,
689
+ * angle: number,
690
+ * large_arc_flag: number,
691
+ * sweep_flag: number,
692
+ * x2: number,
693
+ * y2: number,
694
+ * recursive: Array<number>
695
+ * ) => Array<number>}
696
+ */
697
+ const a2c = (
698
+ x1,
699
+ y1,
700
+ rx,
701
+ ry,
702
+ angle,
703
+ large_arc_flag,
704
+ sweep_flag,
705
+ x2,
706
+ y2,
707
+ recursive
708
+ ) => {
709
+ // for more information of where this Math came from visit:
710
+ // https://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
711
+ const _120 = (Math.PI * 120) / 180;
712
+ const rad = (Math.PI / 180) * (+angle || 0);
713
+ /**
714
+ * @type {Array<number>}
715
+ */
716
+ let res = [];
717
+ /**
718
+ * @type {(x: number, y: number, rad: number) => number}
719
+ */
720
+ const rotateX = (x, y, rad) => {
721
+ return x * Math.cos(rad) - y * Math.sin(rad);
722
+ };
723
+ /**
724
+ * @type {(x: number, y: number, rad: number) => number}
725
+ */
726
+ const rotateY = (x, y, rad) => {
727
+ return x * Math.sin(rad) + y * Math.cos(rad);
728
+ };
729
+ if (!recursive) {
730
+ x1 = rotateX(x1, y1, -rad);
731
+ y1 = rotateY(x1, y1, -rad);
732
+ x2 = rotateX(x2, y2, -rad);
733
+ y2 = rotateY(x2, y2, -rad);
734
+ var x = (x1 - x2) / 2,
735
+ y = (y1 - y2) / 2;
736
+ var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
737
+ if (h > 1) {
738
+ h = Math.sqrt(h);
739
+ rx = h * rx;
740
+ ry = h * ry;
741
+ }
742
+ var rx2 = rx * rx;
743
+ var ry2 = ry * ry;
744
+ var k =
745
+ (large_arc_flag == sweep_flag ? -1 : 1) *
746
+ Math.sqrt(
747
+ Math.abs(
748
+ (rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)
749
+ )
750
+ );
751
+ var cx = (k * rx * y) / ry + (x1 + x2) / 2;
752
+ var cy = (k * -ry * x) / rx + (y1 + y2) / 2;
753
+ var f1 = Math.asin(Number(((y1 - cy) / ry).toFixed(9)));
754
+ var f2 = Math.asin(Number(((y2 - cy) / ry).toFixed(9)));
755
+
756
+ f1 = x1 < cx ? Math.PI - f1 : f1;
757
+ f2 = x2 < cx ? Math.PI - f2 : f2;
758
+ f1 < 0 && (f1 = Math.PI * 2 + f1);
759
+ f2 < 0 && (f2 = Math.PI * 2 + f2);
760
+ if (sweep_flag && f1 > f2) {
761
+ f1 = f1 - Math.PI * 2;
762
+ }
763
+ if (!sweep_flag && f2 > f1) {
764
+ f2 = f2 - Math.PI * 2;
765
+ }
766
+ } else {
767
+ f1 = recursive[0];
768
+ f2 = recursive[1];
769
+ cx = recursive[2];
770
+ cy = recursive[3];
771
+ }
772
+ var df = f2 - f1;
773
+ if (Math.abs(df) > _120) {
774
+ var f2old = f2,
775
+ x2old = x2,
776
+ y2old = y2;
777
+ f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
778
+ x2 = cx + rx * Math.cos(f2);
779
+ y2 = cy + ry * Math.sin(f2);
780
+ res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [
781
+ f2,
782
+ f2old,
783
+ cx,
784
+ cy,
785
+ ]);
786
+ }
787
+ df = f2 - f1;
788
+ var c1 = Math.cos(f1),
789
+ s1 = Math.sin(f1),
790
+ c2 = Math.cos(f2),
791
+ s2 = Math.sin(f2),
792
+ t = Math.tan(df / 4),
793
+ hx = (4 / 3) * rx * t,
794
+ hy = (4 / 3) * ry * t,
795
+ m = [
796
+ -hx * s1,
797
+ hy * c1,
798
+ x2 + hx * s2 - x1,
799
+ y2 - hy * c2 - y1,
800
+ x2 - x1,
801
+ y2 - y1,
802
+ ];
803
+ if (recursive) {
804
+ return m.concat(res);
805
+ } else {
806
+ res = m.concat(res);
807
+ var newres = [];
808
+ for (var i = 0, n = res.length; i < n; i++) {
809
+ newres[i] =
810
+ i % 2
811
+ ? rotateY(res[i - 1], res[i], rad)
812
+ : rotateX(res[i], res[i + 1], rad);
813
+ }
814
+ return newres;
815
+ }
816
+ };