├── README.md
├── index.html
└── path-data-polyfill.js
/README.md:
--------------------------------------------------------------------------------
1 | # SVG Break Apart
2 |
3 | Demo of how to break apart a long svg path data into multiple smaller paths in JS (like in InkScape).
4 |
5 | See live example: https://delapouite.github.io/svg-break-apart/
6 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | svg-break-apart
6 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
Composed of two paths: a black background square and a white drawing.
28 |
29 |
33 |
34 |
35 |
36 |
Broken-apart SVG
37 |
The foreground path has been automatically broken apart in several paths with random colors.
38 |
39 |
40 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/path-data-polyfill.js:
--------------------------------------------------------------------------------
1 |
2 | // @info
3 | // Polyfill for SVG 2 getPathData() and setPathData() methods. Based on:
4 | // - SVGPathSeg polyfill by Philip Rogers (MIT License)
5 | // https://github.com/progers/pathseg
6 | // - SVGPathNormalizer by Tadahisa Motooka (MIT License)
7 | // https://github.com/motooka/SVGPathNormalizer/tree/master/src
8 | // - arcToCubicCurves() by Dmitry Baranovskiy (MIT License)
9 | // https://github.com/DmitryBaranovskiy/raphael/blob/v2.1.1/raphael.core.js#L1837
10 | // @author
11 | // Jarosław Foksa
12 | // @license
13 | // MIT License
14 | if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathData) {
15 | let commandsMap = {
16 | "Z":"Z", "M":"M", "L":"L", "C":"C", "Q":"Q", "A":"A", "H":"H", "V":"V", "S":"S", "T":"T",
17 | "z":"Z", "m":"m", "l":"l", "c":"c", "q":"q", "a":"a", "h":"h", "v":"v", "s":"s", "t":"t"
18 | };
19 |
20 | class Source {
21 | constructor(string) {
22 | this._string = string;
23 | this._currentIndex = 0;
24 | this._endIndex = this._string.length;
25 | this._prevCommand = null;
26 |
27 | this._skipOptionalSpaces();
28 | }
29 |
30 | parseSegment() {
31 | let char = this._string[this._currentIndex];
32 | let command = commandsMap[char] ? commandsMap[char] : null;
33 |
34 | if (command === null) {
35 | if (this._prevCommand === null) {
36 | return null;
37 | }
38 |
39 | if (
40 | (char === "+" || char === "-" || char === "." || (char >= "0" && char <= "9")) && this._prevCommand !== "Z"
41 | ) {
42 | if (this._prevCommand === "M") {
43 | command = "L";
44 | }
45 | else if (this._prevCommand === "m") {
46 | command = "l";
47 | }
48 | else {
49 | command = this._prevCommand;
50 | }
51 | }
52 | else {
53 | command = null;
54 | }
55 |
56 | if (command === null) {
57 | return null;
58 | }
59 | }
60 | else {
61 | this._currentIndex += 1;
62 | }
63 |
64 | this._prevCommand = command;
65 |
66 | let values = null;
67 | let cmd = command.toUpperCase();
68 |
69 | if (cmd === "H" || cmd === "V") {
70 | values = [this._parseNumber()];
71 | }
72 | else if (cmd === "M" || cmd === "L" || cmd === "T") {
73 | values = [this._parseNumber(), this._parseNumber()];
74 | }
75 | else if (cmd === "S" || cmd === "Q") {
76 | values = [this._parseNumber(), this._parseNumber(), this._parseNumber(), this._parseNumber()];
77 | }
78 | else if (cmd === "C") {
79 | values = [
80 | this._parseNumber(),
81 | this._parseNumber(),
82 | this._parseNumber(),
83 | this._parseNumber(),
84 | this._parseNumber(),
85 | this._parseNumber()
86 | ];
87 | }
88 | else if (cmd === "A") {
89 | values = [
90 | this._parseNumber(),
91 | this._parseNumber(),
92 | this._parseNumber(),
93 | this._parseArcFlag(),
94 | this._parseArcFlag(),
95 | this._parseNumber(),
96 | this._parseNumber()
97 | ];
98 | }
99 | else if (cmd === "Z") {
100 | this._skipOptionalSpaces();
101 | values = [];
102 | }
103 |
104 | if (values === null || values.indexOf(null) >= 0) {
105 | return null;
106 | }
107 | else {
108 | return {type: command, values: values};
109 | }
110 | }
111 |
112 | hasMoreData() {
113 | return this._currentIndex < this._endIndex;
114 | }
115 |
116 | peekSegmentType() {
117 | let char = this._string[this._currentIndex];
118 | return commandsMap[char] ? commandsMap[char] : null;
119 | }
120 |
121 | initialCommandIsMoveTo() {
122 | if (!this.hasMoreData()) {
123 | return true;
124 | }
125 |
126 | let command = this.peekSegmentType();
127 | return command === "M" || command === "m";
128 | }
129 |
130 | _isCurrentSpace() {
131 | let char = this._string[this._currentIndex];
132 | return char <= " " && (char === " " || char === "\n" || char === "\t" || char === "\r" || char === "\f");
133 | }
134 |
135 | _skipOptionalSpaces() {
136 | while (this._currentIndex < this._endIndex && this._isCurrentSpace()) {
137 | this._currentIndex += 1;
138 | }
139 |
140 | return this._currentIndex < this._endIndex;
141 | }
142 |
143 | _skipOptionalSpacesOrDelimiter() {
144 | if (
145 | this._currentIndex < this._endIndex &&
146 | !this._isCurrentSpace() &&
147 | this._string[this._currentIndex] !== ","
148 | ) {
149 | return false;
150 | }
151 |
152 | if (this._skipOptionalSpaces()) {
153 | if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === ",") {
154 | this._currentIndex += 1;
155 | this._skipOptionalSpaces();
156 | }
157 | }
158 | return this._currentIndex < this._endIndex;
159 | }
160 |
161 | _parseNumber() {
162 | let exponent = 0;
163 | let integer = 0;
164 | let frac = 1;
165 | let decimal = 0;
166 | let sign = 1;
167 | let expsign = 1;
168 | let startIndex = this._currentIndex;
169 |
170 | this._skipOptionalSpaces();
171 |
172 | if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === "+") {
173 | this._currentIndex += 1;
174 | }
175 | else if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === "-") {
176 | this._currentIndex += 1;
177 | sign = -1;
178 | }
179 |
180 | if (
181 | this._currentIndex === this._endIndex ||
182 | (
183 | (this._string[this._currentIndex] < "0" || this._string[this._currentIndex] > "9") &&
184 | this._string[this._currentIndex] !== "."
185 | )
186 | ) {
187 | return null;
188 | }
189 |
190 | let startIntPartIndex = this._currentIndex;
191 |
192 | while (
193 | this._currentIndex < this._endIndex &&
194 | this._string[this._currentIndex] >= "0" &&
195 | this._string[this._currentIndex] <= "9"
196 | ) {
197 | this._currentIndex += 1;
198 | }
199 |
200 | if (this._currentIndex !== startIntPartIndex) {
201 | let scanIntPartIndex = this._currentIndex - 1;
202 | let multiplier = 1;
203 |
204 | while (scanIntPartIndex >= startIntPartIndex) {
205 | integer += multiplier * (this._string[scanIntPartIndex] - "0");
206 | scanIntPartIndex -= 1;
207 | multiplier *= 10;
208 | }
209 | }
210 |
211 | if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === ".") {
212 | this._currentIndex += 1;
213 |
214 | if (
215 | this._currentIndex >= this._endIndex ||
216 | this._string[this._currentIndex] < "0" ||
217 | this._string[this._currentIndex] > "9"
218 | ) {
219 | return null;
220 | }
221 |
222 | while (
223 | this._currentIndex < this._endIndex &&
224 | this._string[this._currentIndex] >= "0" &&
225 | this._string[this._currentIndex] <= "9"
226 | ) {
227 | frac *= 10;
228 | decimal += (this._string.charAt(this._currentIndex) - "0") / frac;
229 | this._currentIndex += 1;
230 | }
231 | }
232 |
233 | if (
234 | this._currentIndex !== startIndex &&
235 | this._currentIndex + 1 < this._endIndex &&
236 | (this._string[this._currentIndex] === "e" || this._string[this._currentIndex] === "E") &&
237 | (this._string[this._currentIndex + 1] !== "x" && this._string[this._currentIndex + 1] !== "m")
238 | ) {
239 | this._currentIndex += 1;
240 |
241 | if (this._string[this._currentIndex] === "+") {
242 | this._currentIndex += 1;
243 | }
244 | else if (this._string[this._currentIndex] === "-") {
245 | this._currentIndex += 1;
246 | expsign = -1;
247 | }
248 |
249 | if (
250 | this._currentIndex >= this._endIndex ||
251 | this._string[this._currentIndex] < "0" ||
252 | this._string[this._currentIndex] > "9"
253 | ) {
254 | return null;
255 | }
256 |
257 | while (
258 | this._currentIndex < this._endIndex &&
259 | this._string[this._currentIndex] >= "0" &&
260 | this._string[this._currentIndex] <= "9"
261 | ) {
262 | exponent *= 10;
263 | exponent += (this._string[this._currentIndex] - "0");
264 | this._currentIndex += 1;
265 | }
266 | }
267 |
268 | let number = integer + decimal;
269 | number *= sign;
270 |
271 | if (exponent) {
272 | number *= Math.pow(10, expsign * exponent);
273 | }
274 |
275 | if (startIndex === this._currentIndex) {
276 | return null;
277 | }
278 |
279 | this._skipOptionalSpacesOrDelimiter();
280 |
281 | return number;
282 | }
283 |
284 | _parseArcFlag() {
285 | if (this._currentIndex >= this._endIndex) {
286 | return null;
287 | }
288 |
289 | let flag = null;
290 | let flagChar = this._string[this._currentIndex];
291 |
292 | this._currentIndex += 1;
293 |
294 | if (flagChar === "0") {
295 | flag = 0;
296 | }
297 | else if (flagChar === "1") {
298 | flag = 1;
299 | }
300 | else {
301 | return null;
302 | }
303 |
304 | this._skipOptionalSpacesOrDelimiter();
305 | return flag;
306 | }
307 | }
308 |
309 | let parsePathDataString = (string) => {
310 | if (!string || string.length === 0) return [];
311 |
312 | let source = new Source(string);
313 | let pathData = [];
314 |
315 | if (source.initialCommandIsMoveTo()) {
316 | while (source.hasMoreData()) {
317 | let pathSeg = source.parseSegment();
318 |
319 | if (pathSeg === null) {
320 | break;
321 | }
322 | else {
323 | pathData.push(pathSeg);
324 | }
325 | }
326 | }
327 |
328 | return pathData;
329 | }
330 |
331 | let setAttribute = SVGPathElement.prototype.setAttribute;
332 | let removeAttribute = SVGPathElement.prototype.removeAttribute;
333 |
334 | let $cachedPathData = Symbol();
335 | let $cachedNormalizedPathData = Symbol();
336 |
337 | // @info
338 | // Get an array of corresponding cubic bezier curve parameters for given arc curve paramters.
339 | let arcToCubicCurves = (x1, y1, x2, y2, r1, r2, angle, largeArcFlag, sweepFlag, _recursive) => {
340 | let degToRad = (degrees) => {
341 | return (Math.PI * degrees) / 180;
342 | };
343 |
344 | let rotate = (x, y, angleRad) => {
345 | let X = x * Math.cos(angleRad) - y * Math.sin(angleRad);
346 | let Y = x * Math.sin(angleRad) + y * Math.cos(angleRad);
347 | return {x: X, y: Y};
348 | };
349 |
350 | let angleRad = degToRad(angle);
351 | let params = [];
352 | let f1, f2, cx, cy;
353 |
354 | if (_recursive) {
355 | f1 = _recursive[0];
356 | f2 = _recursive[1];
357 | cx = _recursive[2];
358 | cy = _recursive[3];
359 | }
360 | else {
361 | let p1 = rotate(x1, y1, -angleRad);
362 | x1 = p1.x;
363 | y1 = p1.y;
364 |
365 | let p2 = rotate(x2, y2, -angleRad);
366 | x2 = p2.x;
367 | y2 = p2.y;
368 |
369 | let x = (x1 - x2) / 2;
370 | let y = (y1 - y2) / 2;
371 | let h = (x * x) / (r1 * r1) + (y * y) / (r2 * r2);
372 |
373 | if (h > 1) {
374 | h = Math.sqrt(h);
375 | r1 = h * r1;
376 | r2 = h * r2;
377 | }
378 |
379 | let sign;
380 |
381 | if (largeArcFlag === sweepFlag) {
382 | sign = -1;
383 | }
384 | else {
385 | sign = 1;
386 | }
387 |
388 | let r1Pow = r1 * r1;
389 | let r2Pow = r2 * r2;
390 |
391 | let left = r1Pow * r2Pow - r1Pow * y * y - r2Pow * x * x;
392 | let right = r1Pow * y * y + r2Pow * x * x;
393 |
394 | let k = sign * Math.sqrt(Math.abs(left/right));
395 |
396 | cx = k * r1 * y / r2 + (x1 + x2) / 2;
397 | cy = k * -r2 * x / r1 + (y1 + y2) / 2;
398 |
399 | f1 = Math.asin(parseFloat(((y1 - cy) / r2).toFixed(9)));
400 | f2 = Math.asin(parseFloat(((y2 - cy) / r2).toFixed(9)));
401 |
402 | if (x1 < cx) {
403 | f1 = Math.PI - f1;
404 | }
405 | if (x2 < cx) {
406 | f2 = Math.PI - f2;
407 | }
408 |
409 | if (f1 < 0) {
410 | f1 = Math.PI * 2 + f1;
411 | }
412 | if (f2 < 0) {
413 | f2 = Math.PI * 2 + f2;
414 | }
415 |
416 | if (sweepFlag && f1 > f2) {
417 | f1 = f1 - Math.PI * 2;
418 | }
419 | if (!sweepFlag && f2 > f1) {
420 | f2 = f2 - Math.PI * 2;
421 | }
422 | }
423 |
424 | let df = f2 - f1;
425 |
426 | if (Math.abs(df) > (Math.PI * 120 / 180)) {
427 | let f2old = f2;
428 | let x2old = x2;
429 | let y2old = y2;
430 |
431 | if (sweepFlag && f2 > f1) {
432 | f2 = f1 + (Math.PI * 120 / 180) * (1);
433 | }
434 | else {
435 | f2 = f1 + (Math.PI * 120 / 180) * (-1);
436 | }
437 |
438 | x2 = cx + r1 * Math.cos(f2);
439 | y2 = cy + r2 * Math.sin(f2);
440 | params = arcToCubicCurves(x2, y2, x2old, y2old, r1, r2, angle, 0, sweepFlag, [f2, f2old, cx, cy]);
441 | }
442 |
443 | df = f2 - f1;
444 |
445 | let c1 = Math.cos(f1);
446 | let s1 = Math.sin(f1);
447 | let c2 = Math.cos(f2);
448 | let s2 = Math.sin(f2);
449 | let t = Math.tan(df / 4);
450 | let hx = 4 / 3 * r1 * t;
451 | let hy = 4 / 3 * r2 * t;
452 |
453 | let m1 = [x1, y1];
454 | let m2 = [x1 + hx * s1, y1 - hy * c1];
455 | let m3 = [x2 + hx * s2, y2 - hy * c2];
456 | let m4 = [x2, y2];
457 |
458 | m2[0] = 2 * m1[0] - m2[0];
459 | m2[1] = 2 * m1[1] - m2[1];
460 |
461 | if (_recursive) {
462 | return [m2, m3, m4].concat(params);
463 | }
464 | else {
465 | params = [m2, m3, m4].concat(params).join().split(",");
466 |
467 | let curves = [];
468 | let curveParams = [];
469 |
470 | params.forEach( function(param, i) {
471 | if (i % 2) {
472 | curveParams.push(rotate(params[i - 1], params[i], angleRad).y);
473 | }
474 | else {
475 | curveParams.push(rotate(params[i], params[i + 1], angleRad).x);
476 | }
477 |
478 | if (curveParams.length === 6) {
479 | curves.push(curveParams);
480 | curveParams = [];
481 | }
482 | });
483 |
484 | return curves;
485 | }
486 | };
487 |
488 | let clonePathData = (pathData) => {
489 | return pathData.map((seg) => {
490 | return {type: seg.type, values: [...seg.values]}
491 | });
492 | };
493 |
494 | // @info
495 | // Takes any path data, returns path data that consists only from absolute commands.
496 | let absolutizePathData = (pathData) => {
497 | let absolutizedPathData = [];
498 |
499 | let currentX = null;
500 | let currentY = null;
501 |
502 | let subpathX = null;
503 | let subpathY = null;
504 |
505 | for (let seg of pathData) {
506 | let type = seg.type;
507 |
508 | if (type === "M") {
509 | let [x, y] = seg.values;
510 |
511 | absolutizedPathData.push({type: "M", values: [x, y]});
512 |
513 | subpathX = x;
514 | subpathY = y;
515 |
516 | currentX = x;
517 | currentY = y;
518 | }
519 |
520 | else if (type === "m") {
521 | let [x, y] = seg.values;
522 |
523 | x += currentX;
524 | y += currentY;
525 |
526 | absolutizedPathData.push({type: "M", values: [x, y]});
527 |
528 | subpathX = x;
529 | subpathY = y;
530 |
531 | currentX = x;
532 | currentY = y;
533 | }
534 |
535 | else if (type === "L") {
536 | let [x, y] = seg.values;
537 |
538 | absolutizedPathData.push({type: "L", values: [x, y]});
539 |
540 | currentX = x;
541 | currentY = y;
542 | }
543 |
544 | else if (type === "l") {
545 | let [x, y] = seg.values;
546 |
547 | x += currentX;
548 | y += currentY;
549 |
550 | absolutizedPathData.push({type: "L", values: [x, y]});
551 |
552 | currentX = x;
553 | currentY = y;
554 | }
555 |
556 | else if (type === "C") {
557 | let [x1, y1, x2, y2, x, y] = seg.values;
558 |
559 | absolutizedPathData.push({type: "C", values: [x1, y1, x2, y2, x, y]});
560 |
561 | currentX = x;
562 | currentY = y;
563 | }
564 |
565 | else if (type === "c") {
566 | let [x1, y1, x2, y2, x, y] = seg.values;
567 |
568 | x1 += currentX;
569 | y1 += currentY;
570 | x2 += currentX;
571 | y2 += currentY;
572 | x += currentX;
573 | y += currentY;
574 |
575 | absolutizedPathData.push({type: "C", values: [x1, y1, x2, y2, x, y]});
576 |
577 | currentX = x;
578 | currentY = y;
579 | }
580 |
581 | else if (type === "Q") {
582 | let [x1, y1, x, y] = seg.values;
583 |
584 | absolutizedPathData.push({type: "Q", values: [x1, y1, x, y]});
585 |
586 | currentX = x;
587 | currentY = y;
588 | }
589 |
590 | else if (type === "q") {
591 | let [x1, y1, x, y] = seg.values;
592 |
593 | x1 += currentX;
594 | y1 += currentY;
595 | x += currentX;
596 | y += currentY;
597 |
598 | absolutizedPathData.push({type: "Q", values: [x1, y1, x, y]});
599 |
600 | currentX = x;
601 | currentY = y;
602 | }
603 |
604 | else if (type === "A") {
605 | let [rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y] = seg.values;
606 |
607 | absolutizedPathData.push({
608 | type: "A",
609 | values: [rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y]
610 | });
611 |
612 | currentX = x;
613 | currentY = y;
614 | }
615 |
616 | else if (type === "a") {
617 | let [rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y] = seg.values;
618 |
619 | x += currentX;
620 | y += currentY;
621 |
622 | absolutizedPathData.push({
623 | type: "A",
624 | values: [rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y]
625 | });
626 |
627 | currentX = x;
628 | currentY = y;
629 | }
630 |
631 | else if (type === "H") {
632 | let [x] = seg.values;
633 | absolutizedPathData.push({type: "H", values: [x]});
634 | currentX = x;
635 | }
636 |
637 | else if (type === "h") {
638 | let [x] = seg.values;
639 | x += currentX;
640 |
641 | absolutizedPathData.push({type: "H", values: [x]});
642 | currentX = x;
643 | }
644 |
645 | else if (type === "V") {
646 | let [y] = seg.values;
647 |
648 | absolutizedPathData.push({type: "V", values: [y]});
649 | currentY = y;
650 | }
651 |
652 | else if (type === "v") {
653 | let [y] = seg.values;
654 | y += currentY;
655 |
656 | absolutizedPathData.push({type: "V", values: [y]});
657 | currentY = y;
658 | }
659 |
660 | else if (type === "S") {
661 | let [x2, y2, x, y] = seg.values;
662 |
663 | absolutizedPathData.push({type: "S", values: [x2, y2, x, y]});
664 |
665 | currentX = x;
666 | currentY = y;
667 | }
668 |
669 | else if (type === "s") {
670 | let [x2, y2, x, y] = seg.values;
671 |
672 | x2 += currentX;
673 | y2 += currentY;
674 | x += currentX;
675 | y += currentY;
676 |
677 | absolutizedPathData.push({type: "S", values: [x2, y2, x, y]});
678 |
679 | currentX = x;
680 | currentY = y;
681 | }
682 |
683 | else if (type === "T") {
684 | let [x, y] = seg.values;
685 |
686 | absolutizedPathData.push({type: "T", values: [x, y]});
687 |
688 | currentX = x;
689 | currentY = y;
690 | }
691 |
692 | else if (type === "t") {
693 | let [x, y] = seg.values;
694 |
695 | x += currentX;
696 | y += currentY;
697 |
698 | absolutizedPathData.push({type: "T", values: [x, y]});
699 |
700 | currentX = x;
701 | currentY = y;
702 | }
703 |
704 | else if (type === "Z" || type === "z") {
705 | absolutizedPathData.push({type: "Z", values: []});
706 |
707 | currentX = subpathX;
708 | currentY = subpathY;
709 | }
710 | }
711 |
712 | return absolutizedPathData;
713 | };
714 |
715 | // @info
716 | // Takes path data that consists only from absolute commands, returns path data that consists only from
717 | // "M", "L", "C" and "Z" commands.
718 | let reducePathData = (pathData) => {
719 | let reducedPathData = [];
720 | let lastType = null;
721 |
722 | let lastControlX = null;
723 | let lastControlY = null;
724 |
725 | let currentX = null;
726 | let currentY = null;
727 |
728 | let subpathX = null;
729 | let subpathY = null;
730 |
731 | for (let seg of pathData) {
732 | if (seg.type === "M") {
733 | let [x, y] = seg.values;
734 |
735 | reducedPathData.push({type: "M", values: [x, y]});
736 |
737 | subpathX = x;
738 | subpathY = y;
739 |
740 | currentX = x;
741 | currentY = y;
742 | }
743 |
744 | else if (seg.type === "C") {
745 | let [x1, y1, x2, y2, x, y] = seg.values;
746 |
747 | reducedPathData.push({type: "C", values: [x1, y1, x2, y2, x, y]});
748 |
749 | lastControlX = x2;
750 | lastControlY = y2;
751 |
752 | currentX = x;
753 | currentY = y;
754 | }
755 |
756 | else if (seg.type === "L") {
757 | let [x, y] = seg.values;
758 |
759 | reducedPathData.push({type: "L", values: [x, y]});
760 |
761 | currentX = x;
762 | currentY = y;
763 | }
764 |
765 | else if (seg.type === "H") {
766 | let [x] = seg.values;
767 |
768 | reducedPathData.push({type: "L", values: [x, currentY]});
769 |
770 | currentX = x;
771 | }
772 |
773 | else if (seg.type === "V") {
774 | let [y] = seg.values;
775 |
776 | reducedPathData.push({type: "L", values: [currentX, y]});
777 |
778 | currentY = y;
779 | }
780 |
781 | else if (seg.type === "S") {
782 | let [x2, y2, x, y] = seg.values;
783 | let cx1, cy1;
784 |
785 | if (lastType === "C" || lastType === "S") {
786 | cx1 = currentX + (currentX - lastControlX);
787 | cy1 = currentY + (currentY - lastControlY);
788 | }
789 | else {
790 | cx1 = currentX;
791 | cy1 = currentY;
792 | }
793 |
794 | reducedPathData.push({type: "C", values: [cx1, cy1, x2, y2, x, y]});
795 |
796 | lastControlX = x2;
797 | lastControlY = y2;
798 |
799 | currentX = x;
800 | currentY = y;
801 | }
802 |
803 | else if (seg.type === "T") {
804 | let [x, y] = seg.values;
805 | let x1, y1;
806 |
807 | if (lastType === "Q" || lastType === "T") {
808 | x1 = currentX + (currentX - lastControlX);
809 | y1 = currentY + (currentY - lastControlY);
810 | }
811 | else {
812 | x1 = currentX;
813 | y1 = currentY;
814 | }
815 |
816 | let cx1 = currentX + 2 * (x1 - currentX) / 3;
817 | let cy1 = currentY + 2 * (y1 - currentY) / 3;
818 | let cx2 = x + 2 * (x1 - x) / 3;
819 | let cy2 = y + 2 * (y1 - y) / 3;
820 |
821 | reducedPathData.push({type: "C", values: [cx1, cy1, cx2, cy2, x, y]});
822 |
823 | lastControlX = x1;
824 | lastControlY = y1;
825 |
826 | currentX = x;
827 | currentY = y;
828 | }
829 |
830 | else if (seg.type === "Q") {
831 | let [x1, y1, x, y] = seg.values;
832 | let cx1 = currentX + 2 * (x1 - currentX) / 3;
833 | let cy1 = currentY + 2 * (y1 - currentY) / 3;
834 | let cx2 = x + 2 * (x1 - x) / 3;
835 | let cy2 = y + 2 * (y1 - y) / 3;
836 |
837 | reducedPathData.push({type: "C", values: [cx1, cy1, cx2, cy2, x, y]});
838 |
839 | lastControlX = x1;
840 | lastControlY = y1;
841 |
842 | currentX = x;
843 | currentY = y;
844 | }
845 |
846 | else if (seg.type === "A") {
847 | let [r1, r2, angle, largeArcFlag, sweepFlag, x, y] = seg.values;
848 |
849 | if (r1 === 0 || r2 === 0) {
850 | reducedPathData.push({type: "C", values: [currentX, currentY, x, y, x, y]});
851 |
852 | currentX = x;
853 | currentY = y;
854 | }
855 | else {
856 | if (currentX !== x || currentY !== y) {
857 | let curves = arcToCubicCurves(currentX, currentY, x, y, r1, r2, angle, largeArcFlag, sweepFlag);
858 |
859 | curves.forEach( function(curve) {
860 | reducedPathData.push({type: "C", values: curve});
861 |
862 | currentX = x;
863 | currentY = y;
864 | });
865 | }
866 | }
867 | }
868 |
869 | else if (seg.type === "Z") {
870 | reducedPathData.push(seg);
871 |
872 | currentX = subpathX;
873 | currentY = subpathY;
874 | }
875 |
876 | lastType = seg.type;
877 | }
878 |
879 | return reducedPathData;
880 | };
881 |
882 | SVGPathElement.prototype.setAttribute = function(name, value) {
883 | if (name === "d") {
884 | this[$cachedPathData] = null;
885 | this[$cachedNormalizedPathData] = null;
886 | }
887 |
888 | setAttribute.call(this, name, value);
889 | };
890 |
891 | SVGPathElement.prototype.removeAttribute = function(name, value) {
892 | if (name === "d") {
893 | this[$cachedPathData] = null;
894 | this[$cachedNormalizedPathData] = null;
895 | }
896 |
897 | removeAttribute.call(this, name);
898 | };
899 |
900 | SVGPathElement.prototype.getPathData = function(options) {
901 | if (options && options.normalize) {
902 | if (this[$cachedNormalizedPathData]) {
903 | return clonePathData(this[$cachedNormalizedPathData]);
904 | }
905 | else {
906 | let pathData;
907 |
908 | if (this[$cachedPathData]) {
909 | pathData = clonePathData(this[$cachedPathData]);
910 | }
911 | else {
912 | pathData = parsePathDataString(this.getAttribute("d") || "");
913 | this[$cachedPathData] = clonePathData(pathData);
914 | }
915 |
916 | let normalizedPathData = reducePathData(absolutizePathData(pathData));
917 | this[$cachedNormalizedPathData] = clonePathData(normalizedPathData);
918 | return normalizedPathData;
919 | }
920 | }
921 | else {
922 | if (this[$cachedPathData]) {
923 | return clonePathData(this[$cachedPathData]);
924 | }
925 | else {
926 | let pathData = parsePathDataString(this.getAttribute("d") || "");
927 | this[$cachedPathData] = clonePathData(pathData);
928 | return pathData;
929 | }
930 | }
931 | };
932 |
933 | SVGPathElement.prototype.setPathData = function(pathData) {
934 | if (pathData.length === 0) {
935 | this.removeAttribute("d");
936 | }
937 | else {
938 | let d = "";
939 |
940 | for (let i = 0, l = pathData.length; i < l; i += 1) {
941 | let seg = pathData[i];
942 |
943 | if (i > 0) {
944 | d += " ";
945 | }
946 |
947 | d += seg.type;
948 |
949 | if (seg.values && seg.values.length > 0) {
950 | d += " " + seg.values.join(" ");
951 | }
952 | }
953 |
954 | this.setAttribute("d", d);
955 | }
956 | };
957 |
958 | SVGRectElement.prototype.getPathData = function(options) {
959 | let x = this.x.baseVal.value;
960 | let y = this.y.baseVal.value;
961 | let width = this.width.baseVal.value;
962 | let height = this.height.baseVal.value;
963 | let rx = this.hasAttribute("rx") ? this.rx.baseVal.value : this.ry.baseVal.value;
964 | let ry = this.hasAttribute("ry") ? this.ry.baseVal.value : this.rx.baseVal.value;
965 |
966 | if (rx > width / 2) {
967 | rx = width / 2;
968 | }
969 |
970 | if (ry > height / 2) {
971 | ry = height / 2;
972 | }
973 |
974 | let pathData = [
975 | {type: "M", values: [x+rx, y]},
976 | {type: "H", values: [x+width-rx]},
977 | {type: "A", values: [rx, ry, 0, 0, 1, x+width, y+ry]},
978 | {type: "V", values: [y+height-ry]},
979 | {type: "A", values: [rx, ry, 0, 0, 1, x+width-rx, y+height]},
980 | {type: "H", values: [x+rx]},
981 | {type: "A", values: [rx, ry, 0, 0, 1, x, y+height-ry]},
982 | {type: "V", values: [y+ry]},
983 | {type: "A", values: [rx, ry, 0, 0, 1, x+rx, y]},
984 | {type: "Z", values: []}
985 | ];
986 |
987 | // Get rid of redundant "A" segs when either rx or ry is 0
988 | pathData = pathData.filter(s => s.type === "A" && (s.values[0] === 0 || s.values[1] === 0) ? false : true);
989 |
990 | if (options && options.normalize === true) {
991 | pathData = reducePathData(pathData);
992 | }
993 |
994 | return pathData;
995 | };
996 |
997 | SVGCircleElement.prototype.getPathData = function(options) {
998 | let cx = this.cx.baseVal.value;
999 | let cy = this.cy.baseVal.value;
1000 | let r = this.r.baseVal.value;
1001 |
1002 | let pathData = [
1003 | { type: "M", values: [cx + r, cy] },
1004 | { type: "A", values: [r, r, 0, 0, 1, cx, cy+r] },
1005 | { type: "A", values: [r, r, 0, 0, 1, cx-r, cy] },
1006 | { type: "A", values: [r, r, 0, 0, 1, cx, cy-r] },
1007 | { type: "A", values: [r, r, 0, 0, 1, cx+r, cy] },
1008 | { type: "Z", values: [] }
1009 | ];
1010 |
1011 | if (options && options.normalize === true) {
1012 | pathData = reducePathData(pathData);
1013 | }
1014 |
1015 | return pathData;
1016 | };
1017 |
1018 | SVGEllipseElement.prototype.getPathData = function(options) {
1019 | let cx = this.cx.baseVal.value;
1020 | let cy = this.cy.baseVal.value;
1021 | let rx = this.rx.baseVal.value;
1022 | let ry = this.ry.baseVal.value;
1023 |
1024 | let pathData = [
1025 | { type: "M", values: [cx + rx, cy] },
1026 | { type: "A", values: [rx, ry, 0, 0, 1, cx, cy+ry] },
1027 | { type: "A", values: [rx, ry, 0, 0, 1, cx-rx, cy] },
1028 | { type: "A", values: [rx, ry, 0, 0, 1, cx, cy-ry] },
1029 | { type: "A", values: [rx, ry, 0, 0, 1, cx+rx, cy] },
1030 | { type: "Z", values: [] }
1031 | ];
1032 |
1033 | if (options && options.normalize === true) {
1034 | pathData = reducePathData(pathData);
1035 | }
1036 |
1037 | return pathData;
1038 | };
1039 |
1040 | SVGLineElement.prototype.getPathData = function() {
1041 | return [
1042 | { type: "M", values: [this.x1.baseVal.value, this.y1.baseVal.value] },
1043 | { type: "L", values: [this.x2.baseVal.value, this.y2.baseVal.value] }
1044 | ];
1045 | };
1046 |
1047 | SVGPolylineElement.prototype.getPathData = function() {
1048 | let pathData = [];
1049 |
1050 | for (let i = 0; i < this.points.numberOfItems; i += 1) {
1051 | let point = this.points.getItem(i);
1052 |
1053 | pathData.push({
1054 | type: (i === 0 ? "M" : "L"),
1055 | values: [point.x, point.y]
1056 | });
1057 | }
1058 |
1059 | return pathData;
1060 | };
1061 |
1062 | SVGPolygonElement.prototype.getPathData = function() {
1063 | let pathData = [];
1064 |
1065 | for (let i = 0; i < this.points.numberOfItems; i += 1) {
1066 | let point = this.points.getItem(i);
1067 |
1068 | pathData.push({
1069 | type: (i === 0 ? "M" : "L"),
1070 | values: [point.x, point.y]
1071 | });
1072 | }
1073 |
1074 | pathData.push({
1075 | type: "Z",
1076 | values: []
1077 | });
1078 |
1079 | return pathData;
1080 | };
1081 | }
1082 |
--------------------------------------------------------------------------------