1 | 'use strict';
|
2 |
|
3 | exports.type = 'perItem';
|
4 |
|
5 | exports.active = true;
|
6 |
|
7 | exports.description = 'optimizes path data: writes in shorter form, applies transformations';
|
8 |
|
9 | exports.params = {
|
10 | applyTransforms: true,
|
11 | applyTransformsStroked: true,
|
12 | makeArcs: {
|
13 | threshold: 2.5,
|
14 | tolerance: 0.5
|
15 | },
|
16 | straightCurves: true,
|
17 | lineShorthands: true,
|
18 | curveSmoothShorthands: true,
|
19 | floatPrecision: 3,
|
20 | transformPrecision: 5,
|
21 | removeUseless: true,
|
22 | collapseRepeated: true,
|
23 | utilizeAbsolute: true,
|
24 | leadingZero: true,
|
25 | negativeExtraSpace: true,
|
26 | forceAbsolutePath: false
|
27 | };
|
28 |
|
29 | var pathElems = require('./_collections.js').pathElems,
|
30 | path2js = require('./_path.js').path2js,
|
31 | js2path = require('./_path.js').js2path,
|
32 | applyTransforms = require('./_path.js').applyTransforms,
|
33 | cleanupOutData = require('../lib/svgo/tools').cleanupOutData,
|
34 | roundData,
|
35 | precision,
|
36 | error,
|
37 | arcThreshold,
|
38 | arcTolerance,
|
39 | hasMarkerMid,
|
40 | hasStrokeLinecap;
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 | exports.fn = function(item, params) {
|
59 |
|
60 | if (item.isElem(pathElems) && item.hasAttr('d')) {
|
61 |
|
62 | precision = params.floatPrecision;
|
63 | error = precision !== false ? +Math.pow(.1, precision).toFixed(precision) : 1e-2;
|
64 | roundData = precision > 0 && precision < 20 ? strongRound : round;
|
65 | if (params.makeArcs) {
|
66 | arcThreshold = params.makeArcs.threshold;
|
67 | arcTolerance = params.makeArcs.tolerance;
|
68 | }
|
69 | hasMarkerMid = item.hasAttr('marker-mid');
|
70 |
|
71 | var stroke = item.computedAttr('stroke'),
|
72 | strokeLinecap = item.computedAttr('stroke');
|
73 | hasStrokeLinecap = stroke && stroke != 'none' && strokeLinecap && strokeLinecap != 'butt';
|
74 |
|
75 | var data = path2js(item);
|
76 |
|
77 |
|
78 | if (data.length) {
|
79 | convertToRelative(data);
|
80 |
|
81 | if (params.applyTransforms) {
|
82 | data = applyTransforms(item, data, params);
|
83 | }
|
84 |
|
85 | data = filters(data, params);
|
86 |
|
87 | if (params.utilizeAbsolute) {
|
88 | data = convertToMixed(data, params);
|
89 | }
|
90 |
|
91 | js2path(item, data, params);
|
92 | }
|
93 |
|
94 | }
|
95 |
|
96 | };
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 | function convertToRelative(path) {
|
106 |
|
107 | var point = [0, 0],
|
108 | subpathPoint = [0, 0],
|
109 | baseItem;
|
110 |
|
111 | path.forEach(function(item, index) {
|
112 |
|
113 | var instruction = item.instruction,
|
114 | data = item.data;
|
115 |
|
116 |
|
117 | if (data) {
|
118 |
|
119 |
|
120 |
|
121 | if ('mcslqta'.indexOf(instruction) > -1) {
|
122 |
|
123 | point[0] += data[data.length - 2];
|
124 | point[1] += data[data.length - 1];
|
125 |
|
126 | if (instruction === 'm') {
|
127 | subpathPoint[0] = point[0];
|
128 | subpathPoint[1] = point[1];
|
129 | baseItem = item;
|
130 | }
|
131 |
|
132 | } else if (instruction === 'h') {
|
133 |
|
134 | point[0] += data[0];
|
135 |
|
136 | } else if (instruction === 'v') {
|
137 |
|
138 | point[1] += data[0];
|
139 |
|
140 | }
|
141 |
|
142 |
|
143 |
|
144 |
|
145 | if (instruction === 'M') {
|
146 |
|
147 | if (index > 0) instruction = 'm';
|
148 |
|
149 | data[0] -= point[0];
|
150 | data[1] -= point[1];
|
151 |
|
152 | subpathPoint[0] = point[0] += data[0];
|
153 | subpathPoint[1] = point[1] += data[1];
|
154 |
|
155 | baseItem = item;
|
156 |
|
157 | }
|
158 |
|
159 |
|
160 |
|
161 | else if ('LT'.indexOf(instruction) > -1) {
|
162 |
|
163 | instruction = instruction.toLowerCase();
|
164 |
|
165 |
|
166 |
|
167 | data[0] -= point[0];
|
168 | data[1] -= point[1];
|
169 |
|
170 | point[0] += data[0];
|
171 | point[1] += data[1];
|
172 |
|
173 |
|
174 | } else if (instruction === 'C') {
|
175 |
|
176 | instruction = 'c';
|
177 |
|
178 |
|
179 |
|
180 | data[0] -= point[0];
|
181 | data[1] -= point[1];
|
182 | data[2] -= point[0];
|
183 | data[3] -= point[1];
|
184 | data[4] -= point[0];
|
185 | data[5] -= point[1];
|
186 |
|
187 | point[0] += data[4];
|
188 | point[1] += data[5];
|
189 |
|
190 |
|
191 |
|
192 | } else if ('SQ'.indexOf(instruction) > -1) {
|
193 |
|
194 | instruction = instruction.toLowerCase();
|
195 |
|
196 |
|
197 |
|
198 | data[0] -= point[0];
|
199 | data[1] -= point[1];
|
200 | data[2] -= point[0];
|
201 | data[3] -= point[1];
|
202 |
|
203 | point[0] += data[2];
|
204 | point[1] += data[3];
|
205 |
|
206 |
|
207 | } else if (instruction === 'A') {
|
208 |
|
209 | instruction = 'a';
|
210 |
|
211 |
|
212 |
|
213 | data[5] -= point[0];
|
214 | data[6] -= point[1];
|
215 |
|
216 | point[0] += data[5];
|
217 | point[1] += data[6];
|
218 |
|
219 |
|
220 | } else if (instruction === 'H') {
|
221 |
|
222 | instruction = 'h';
|
223 |
|
224 | data[0] -= point[0];
|
225 |
|
226 | point[0] += data[0];
|
227 |
|
228 |
|
229 | } else if (instruction === 'V') {
|
230 |
|
231 | instruction = 'v';
|
232 |
|
233 | data[0] -= point[1];
|
234 |
|
235 | point[1] += data[0];
|
236 |
|
237 | }
|
238 |
|
239 | item.instruction = instruction;
|
240 | item.data = data;
|
241 |
|
242 |
|
243 | item.coords = point.slice(-2);
|
244 |
|
245 | }
|
246 |
|
247 |
|
248 | else if (instruction == 'z') {
|
249 | if (baseItem) {
|
250 | item.coords = baseItem.coords;
|
251 | }
|
252 | point[0] = subpathPoint[0];
|
253 | point[1] = subpathPoint[1];
|
254 | }
|
255 |
|
256 | item.base = index > 0 ? path[index - 1].coords : [0, 0];
|
257 |
|
258 | });
|
259 |
|
260 | return path;
|
261 |
|
262 | }
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 |
|
271 | function filters(path, params) {
|
272 |
|
273 | var stringify = data2Path.bind(null, params),
|
274 | relSubpoint = [0, 0],
|
275 | pathBase = [0, 0],
|
276 | prev = {};
|
277 |
|
278 | path = path.filter(function(item, index, path) {
|
279 |
|
280 | var instruction = item.instruction,
|
281 | data = item.data,
|
282 | next = path[index + 1];
|
283 |
|
284 | if (data) {
|
285 |
|
286 | var sdata = data,
|
287 | circle;
|
288 |
|
289 | if (instruction === 's') {
|
290 | sdata = [0, 0].concat(data);
|
291 |
|
292 | if ('cs'.indexOf(prev.instruction) > -1) {
|
293 | var pdata = prev.data,
|
294 | n = pdata.length;
|
295 |
|
296 |
|
297 | sdata[0] = pdata[n - 2] - pdata[n - 4];
|
298 | sdata[1] = pdata[n - 1] - pdata[n - 3];
|
299 | }
|
300 |
|
301 | }
|
302 |
|
303 |
|
304 | if (
|
305 | params.makeArcs &&
|
306 | (instruction == 'c' || instruction == 's') &&
|
307 | isConvex(sdata) &&
|
308 | (circle = findCircle(sdata))
|
309 | ) {
|
310 | var r = roundData([circle.radius])[0],
|
311 | angle = findArcAngle(sdata, circle),
|
312 | sweep = sdata[5] * sdata[0] - sdata[4] * sdata[1] > 0 ? 1 : 0,
|
313 | arc = {
|
314 | instruction: 'a',
|
315 | data: [r, r, 0, 0, sweep, sdata[4], sdata[5]],
|
316 | coords: item.coords.slice(),
|
317 | base: item.base
|
318 | },
|
319 | output = [arc],
|
320 |
|
321 | relCenter = [circle.center[0] - sdata[4], circle.center[1] - sdata[5]],
|
322 | relCircle = { center: relCenter, radius: circle.radius },
|
323 | arcCurves = [item],
|
324 | hasPrev = 0,
|
325 | suffix = '',
|
326 | nextLonghand;
|
327 |
|
328 | if (
|
329 | prev.instruction == 'c' && isConvex(prev.data) && isArcPrev(prev.data, circle) ||
|
330 | prev.instruction == 'a' && prev.sdata && isArcPrev(prev.sdata, circle)
|
331 | ) {
|
332 | arcCurves.unshift(prev);
|
333 | arc.base = prev.base;
|
334 | arc.data[5] = arc.coords[0] - arc.base[0];
|
335 | arc.data[6] = arc.coords[1] - arc.base[1];
|
336 | var prevData = prev.instruction == 'a' ? prev.sdata : prev.data;
|
337 | angle += findArcAngle(prevData,
|
338 | {
|
339 | center: [prevData[4] + relCenter[0], prevData[5] + relCenter[1]],
|
340 | radius: circle.radius
|
341 | }
|
342 | );
|
343 | if (angle > Math.PI) arc.data[3] = 1;
|
344 | hasPrev = 1;
|
345 | }
|
346 |
|
347 |
|
348 | for (var j = index; (next = path[++j]) && ~'cs'.indexOf(next.instruction);) {
|
349 | var nextData = next.data;
|
350 | if (next.instruction == 's') {
|
351 | nextLonghand = makeLonghand({instruction: 's', data: next.data.slice() },
|
352 | path[j - 1].data);
|
353 | nextData = nextLonghand.data;
|
354 | nextLonghand.data = nextData.slice(0, 2);
|
355 | suffix = stringify([nextLonghand]);
|
356 | }
|
357 | if (isConvex(nextData) && isArc(nextData, relCircle)) {
|
358 | angle += findArcAngle(nextData, relCircle);
|
359 | if (angle - 2 * Math.PI > 1e-3) break;
|
360 | if (angle > Math.PI) arc.data[3] = 1;
|
361 | arcCurves.push(next);
|
362 | if (2 * Math.PI - angle > 1e-3) {
|
363 | arc.coords = next.coords;
|
364 | arc.data[5] = arc.coords[0] - arc.base[0];
|
365 | arc.data[6] = arc.coords[1] - arc.base[1];
|
366 | } else {
|
367 |
|
368 | arc.data[5] = 2 * (relCircle.center[0] - nextData[4]);
|
369 | arc.data[6] = 2 * (relCircle.center[1] - nextData[5]);
|
370 | arc.coords = [arc.base[0] + arc.data[5], arc.base[1] + arc.data[6]];
|
371 | arc = {
|
372 | instruction: 'a',
|
373 | data: [r, r, 0, 0, sweep,
|
374 | next.coords[0] - arc.coords[0], next.coords[1] - arc.coords[1]],
|
375 | coords: next.coords,
|
376 | base: arc.coords
|
377 | };
|
378 | output.push(arc);
|
379 | j++;
|
380 | break;
|
381 | }
|
382 | relCenter[0] -= nextData[4];
|
383 | relCenter[1] -= nextData[5];
|
384 | } else break;
|
385 | }
|
386 |
|
387 | if ((stringify(output) + suffix).length < stringify(arcCurves).length) {
|
388 | if (path[j] && path[j].instruction == 's') {
|
389 | makeLonghand(path[j], path[j - 1].data);
|
390 | }
|
391 | if (hasPrev) {
|
392 | var prevArc = output.shift();
|
393 | roundData(prevArc.data);
|
394 | relSubpoint[0] += prevArc.data[5] - prev.data[prev.data.length - 2];
|
395 | relSubpoint[1] += prevArc.data[6] - prev.data[prev.data.length - 1];
|
396 | prev.instruction = 'a';
|
397 | prev.data = prevArc.data;
|
398 | item.base = prev.coords = prevArc.coords;
|
399 | }
|
400 | arc = output.shift();
|
401 | if (arcCurves.length == 1) {
|
402 | item.sdata = sdata.slice();
|
403 | } else if (arcCurves.length - 1 - hasPrev > 0) {
|
404 |
|
405 | path.splice.apply(path, [index + 1, arcCurves.length - 1 - hasPrev].concat(output));
|
406 | }
|
407 | if (!arc) return false;
|
408 | instruction = 'a';
|
409 | data = arc.data;
|
410 | item.coords = arc.coords;
|
411 | }
|
412 | }
|
413 |
|
414 |
|
415 |
|
416 |
|
417 | if (precision !== false) {
|
418 | if ('mltqsc'.indexOf(instruction) > -1) {
|
419 | for (var i = data.length; i--;) {
|
420 | data[i] += item.base[i % 2] - relSubpoint[i % 2];
|
421 | }
|
422 | } else if (instruction == 'h') {
|
423 | data[0] += item.base[0] - relSubpoint[0];
|
424 | } else if (instruction == 'v') {
|
425 | data[0] += item.base[1] - relSubpoint[1];
|
426 | } else if (instruction == 'a') {
|
427 | data[5] += item.base[0] - relSubpoint[0];
|
428 | data[6] += item.base[1] - relSubpoint[1];
|
429 | }
|
430 | roundData(data);
|
431 |
|
432 | if (instruction == 'h') relSubpoint[0] += data[0];
|
433 | else if (instruction == 'v') relSubpoint[1] += data[0];
|
434 | else {
|
435 | relSubpoint[0] += data[data.length - 2];
|
436 | relSubpoint[1] += data[data.length - 1];
|
437 | }
|
438 | roundData(relSubpoint);
|
439 |
|
440 | if (instruction.toLowerCase() == 'm') {
|
441 | pathBase[0] = relSubpoint[0];
|
442 | pathBase[1] = relSubpoint[1];
|
443 | }
|
444 | }
|
445 |
|
446 |
|
447 | if (params.straightCurves) {
|
448 |
|
449 | if (
|
450 | instruction === 'c' &&
|
451 | isCurveStraightLine(data) ||
|
452 | instruction === 's' &&
|
453 | isCurveStraightLine(sdata)
|
454 | ) {
|
455 | if (next && next.instruction == 's')
|
456 | makeLonghand(next, data);
|
457 | instruction = 'l';
|
458 | data = data.slice(-2);
|
459 | }
|
460 |
|
461 | else if (
|
462 | instruction === 'q' &&
|
463 | isCurveStraightLine(data)
|
464 | ) {
|
465 | if (next && next.instruction == 't')
|
466 | makeLonghand(next, data);
|
467 | instruction = 'l';
|
468 | data = data.slice(-2);
|
469 | }
|
470 |
|
471 | else if (
|
472 | instruction === 't' &&
|
473 | prev.instruction !== 'q' &&
|
474 | prev.instruction !== 't'
|
475 | ) {
|
476 | instruction = 'l';
|
477 | data = data.slice(-2);
|
478 | }
|
479 |
|
480 | else if (
|
481 | instruction === 'a' &&
|
482 | (data[0] === 0 || data[1] === 0)
|
483 | ) {
|
484 | instruction = 'l';
|
485 | data = data.slice(-2);
|
486 | }
|
487 | }
|
488 |
|
489 |
|
490 |
|
491 |
|
492 | if (
|
493 | params.lineShorthands &&
|
494 | instruction === 'l'
|
495 | ) {
|
496 | if (data[1] === 0) {
|
497 | instruction = 'h';
|
498 | data.pop();
|
499 | } else if (data[0] === 0) {
|
500 | instruction = 'v';
|
501 | data.shift();
|
502 | }
|
503 | }
|
504 |
|
505 |
|
506 |
|
507 | if (
|
508 | params.collapseRepeated &&
|
509 | !hasMarkerMid &&
|
510 | ('mhv'.indexOf(instruction) > -1) &&
|
511 | prev.instruction &&
|
512 | instruction == prev.instruction.toLowerCase() &&
|
513 | (
|
514 | (instruction != 'h' && instruction != 'v') ||
|
515 | (prev.data[0] >= 0) == (item.data[0] >= 0)
|
516 | )) {
|
517 | prev.data[0] += data[0];
|
518 | if (instruction != 'h' && instruction != 'v') {
|
519 | prev.data[1] += data[1];
|
520 | }
|
521 | prev.coords = item.coords;
|
522 | path[index] = prev;
|
523 | return false;
|
524 | }
|
525 |
|
526 |
|
527 | if (params.curveSmoothShorthands && prev.instruction) {
|
528 |
|
529 |
|
530 | if (instruction === 'c') {
|
531 |
|
532 |
|
533 | if (
|
534 | prev.instruction === 'c' &&
|
535 | data[0] === -(prev.data[2] - prev.data[4]) &&
|
536 | data[1] === -(prev.data[3] - prev.data[5])
|
537 | ) {
|
538 | instruction = 's';
|
539 | data = data.slice(2);
|
540 | }
|
541 |
|
542 |
|
543 | else if (
|
544 | prev.instruction === 's' &&
|
545 | data[0] === -(prev.data[0] - prev.data[2]) &&
|
546 | data[1] === -(prev.data[1] - prev.data[3])
|
547 | ) {
|
548 | instruction = 's';
|
549 | data = data.slice(2);
|
550 | }
|
551 |
|
552 |
|
553 | else if (
|
554 | 'cs'.indexOf(prev.instruction) === -1 &&
|
555 | data[0] === 0 &&
|
556 | data[1] === 0
|
557 | ) {
|
558 | instruction = 's';
|
559 | data = data.slice(2);
|
560 | }
|
561 |
|
562 | }
|
563 |
|
564 |
|
565 | else if (instruction === 'q') {
|
566 |
|
567 |
|
568 | if (
|
569 | prev.instruction === 'q' &&
|
570 | data[0] === (prev.data[2] - prev.data[0]) &&
|
571 | data[1] === (prev.data[3] - prev.data[1])
|
572 | ) {
|
573 | instruction = 't';
|
574 | data = data.slice(2);
|
575 | }
|
576 |
|
577 |
|
578 | else if (
|
579 | prev.instruction === 't' &&
|
580 | data[2] === prev.data[0] &&
|
581 | data[3] === prev.data[1]
|
582 | ) {
|
583 | instruction = 't';
|
584 | data = data.slice(2);
|
585 | }
|
586 |
|
587 | }
|
588 |
|
589 | }
|
590 |
|
591 |
|
592 | if (params.removeUseless && !hasStrokeLinecap) {
|
593 |
|
594 |
|
595 | if (
|
596 | (
|
597 | 'lhvqtcs'.indexOf(instruction) > -1
|
598 | ) &&
|
599 | data.every(function(i) { return i === 0; })
|
600 | ) {
|
601 | path[index] = prev;
|
602 | return false;
|
603 | }
|
604 |
|
605 |
|
606 | if (
|
607 | instruction === 'a' &&
|
608 | data[5] === 0 &&
|
609 | data[6] === 0
|
610 | ) {
|
611 | path[index] = prev;
|
612 | return false;
|
613 | }
|
614 |
|
615 | }
|
616 |
|
617 | item.instruction = instruction;
|
618 | item.data = data;
|
619 |
|
620 | prev = item;
|
621 |
|
622 | } else {
|
623 |
|
624 |
|
625 | relSubpoint[0] = pathBase[0];
|
626 | relSubpoint[1] = pathBase[1];
|
627 | if (prev.instruction == 'z') return false;
|
628 | prev = item;
|
629 |
|
630 | }
|
631 |
|
632 | return true;
|
633 |
|
634 | });
|
635 |
|
636 | return path;
|
637 |
|
638 | }
|
639 |
|
640 |
|
641 |
|
642 |
|
643 |
|
644 |
|
645 |
|
646 | function convertToMixed(path, params) {
|
647 |
|
648 | var prev = path[0];
|
649 |
|
650 | path = path.filter(function(item, index) {
|
651 |
|
652 | if (index == 0) return true;
|
653 | if (!item.data) {
|
654 | prev = item;
|
655 | return true;
|
656 | }
|
657 |
|
658 | var instruction = item.instruction,
|
659 | data = item.data,
|
660 | adata = data && data.slice(0);
|
661 |
|
662 | if ('mltqsc'.indexOf(instruction) > -1) {
|
663 | for (var i = adata.length; i--;) {
|
664 | adata[i] += item.base[i % 2];
|
665 | }
|
666 | } else if (instruction == 'h') {
|
667 | adata[0] += item.base[0];
|
668 | } else if (instruction == 'v') {
|
669 | adata[0] += item.base[1];
|
670 | } else if (instruction == 'a') {
|
671 | adata[5] += item.base[0];
|
672 | adata[6] += item.base[1];
|
673 | }
|
674 |
|
675 | roundData(adata);
|
676 |
|
677 | var absoluteDataStr = cleanupOutData(adata, params),
|
678 | relativeDataStr = cleanupOutData(data, params);
|
679 |
|
680 |
|
681 |
|
682 |
|
683 |
|
684 | if (
|
685 | params.forceAbsolutePath || (
|
686 | absoluteDataStr.length < relativeDataStr.length &&
|
687 | !(
|
688 | params.negativeExtraSpace &&
|
689 | instruction == prev.instruction &&
|
690 | prev.instruction.charCodeAt(0) > 96 &&
|
691 | absoluteDataStr.length == relativeDataStr.length - 1 &&
|
692 | (data[0] < 0 || /^0\./.test(data[0]) && prev.data[prev.data.length - 1] % 1)
|
693 | ))
|
694 | ) {
|
695 | item.instruction = instruction.toUpperCase();
|
696 | item.data = adata;
|
697 | }
|
698 |
|
699 | prev = item;
|
700 |
|
701 | return true;
|
702 |
|
703 | });
|
704 |
|
705 | return path;
|
706 |
|
707 | }
|
708 |
|
709 |
|
710 |
|
711 |
|
712 |
|
713 |
|
714 |
|
715 |
|
716 | function isConvex(data) {
|
717 |
|
718 | var center = getIntersection([0, 0, data[2], data[3], data[0], data[1], data[4], data[5]]);
|
719 |
|
720 | return center &&
|
721 | (data[2] < center[0] == center[0] < 0) &&
|
722 | (data[3] < center[1] == center[1] < 0) &&
|
723 | (data[4] < center[0] == center[0] < data[0]) &&
|
724 | (data[5] < center[1] == center[1] < data[1]);
|
725 |
|
726 | }
|
727 |
|
728 |
|
729 |
|
730 |
|
731 |
|
732 |
|
733 |
|
734 | function getIntersection(coords) {
|
735 |
|
736 |
|
737 | var a1 = coords[1] - coords[3],
|
738 | b1 = coords[2] - coords[0],
|
739 | c1 = coords[0] * coords[3] - coords[2] * coords[1],
|
740 |
|
741 |
|
742 | a2 = coords[5] - coords[7],
|
743 | b2 = coords[6] - coords[4],
|
744 | c2 = coords[4] * coords[7] - coords[5] * coords[6],
|
745 | denom = (a1 * b2 - a2 * b1);
|
746 |
|
747 | if (!denom) return;
|
748 |
|
749 | var cross = [
|
750 | (b1 * c2 - b2 * c1) / denom,
|
751 | (a1 * c2 - a2 * c1) / -denom
|
752 | ];
|
753 | if (
|
754 | !isNaN(cross[0]) && !isNaN(cross[1]) &&
|
755 | isFinite(cross[0]) && isFinite(cross[1])
|
756 | ) {
|
757 | return cross;
|
758 | }
|
759 |
|
760 | }
|
761 |
|
762 |
|
763 |
|
764 |
|
765 |
|
766 |
|
767 |
|
768 |
|
769 |
|
770 |
|
771 | function strongRound(data) {
|
772 | for (var i = data.length; i-- > 0;) {
|
773 | if (data[i].toFixed(precision) != data[i]) {
|
774 | var rounded = +data[i].toFixed(precision - 1);
|
775 | data[i] = +Math.abs(rounded - data[i]).toFixed(precision + 1) >= error ?
|
776 | +data[i].toFixed(precision) :
|
777 | rounded;
|
778 | }
|
779 | }
|
780 | return data;
|
781 | }
|
782 |
|
783 |
|
784 |
|
785 |
|
786 |
|
787 |
|
788 |
|
789 | function round(data) {
|
790 | for (var i = data.length; i-- > 0;) {
|
791 | data[i] = Math.round(data[i]);
|
792 | }
|
793 | return data;
|
794 | }
|
795 |
|
796 |
|
797 |
|
798 |
|
799 |
|
800 |
|
801 |
|
802 |
|
803 |
|
804 |
|
805 | function isCurveStraightLine(data) {
|
806 |
|
807 |
|
808 | var i = data.length - 2,
|
809 | a = -data[i + 1],
|
810 | b = data[i],
|
811 | d = 1 / (a * a + b * b);
|
812 |
|
813 | if (i <= 1 || !isFinite(d)) return false;
|
814 |
|
815 |
|
816 | while ((i -= 2) >= 0) {
|
817 | if (Math.sqrt(Math.pow(a * data[i] + b * data[i + 1], 2) * d) > error)
|
818 | return false;
|
819 | }
|
820 |
|
821 | return true;
|
822 |
|
823 | }
|
824 |
|
825 |
|
826 |
|
827 |
|
828 |
|
829 |
|
830 |
|
831 |
|
832 | function makeLonghand(item, data) {
|
833 | switch (item.instruction) {
|
834 | case 's': item.instruction = 'c'; break;
|
835 | case 't': item.instruction = 'q'; break;
|
836 | }
|
837 | item.data.unshift(data[data.length - 2] - data[data.length - 4], data[data.length - 1] - data[data.length - 3]);
|
838 | return item;
|
839 | }
|
840 |
|
841 |
|
842 |
|
843 |
|
844 |
|
845 |
|
846 |
|
847 |
|
848 |
|
849 | function getDistance(point1, point2) {
|
850 | return Math.hypot(point1[0] - point2[0], point1[1] - point2[1]);
|
851 | }
|
852 |
|
853 |
|
854 |
|
855 |
|
856 |
|
857 |
|
858 |
|
859 |
|
860 |
|
861 |
|
862 |
|
863 | function getCubicBezierPoint(curve, t) {
|
864 | var sqrT = t * t,
|
865 | cubT = sqrT * t,
|
866 | mt = 1 - t,
|
867 | sqrMt = mt * mt;
|
868 |
|
869 | return [
|
870 | 3 * sqrMt * t * curve[0] + 3 * mt * sqrT * curve[2] + cubT * curve[4],
|
871 | 3 * sqrMt * t * curve[1] + 3 * mt * sqrT * curve[3] + cubT * curve[5]
|
872 | ];
|
873 | }
|
874 |
|
875 |
|
876 |
|
877 |
|
878 |
|
879 |
|
880 |
|
881 |
|
882 | function findCircle(curve) {
|
883 | var midPoint = getCubicBezierPoint(curve, 1/2),
|
884 | m1 = [midPoint[0] / 2, midPoint[1] / 2],
|
885 | m2 = [(midPoint[0] + curve[4]) / 2, (midPoint[1] + curve[5]) / 2],
|
886 | center = getIntersection([
|
887 | m1[0], m1[1],
|
888 | m1[0] + m1[1], m1[1] - m1[0],
|
889 | m2[0], m2[1],
|
890 | m2[0] + (m2[1] - midPoint[1]), m2[1] - (m2[0] - midPoint[0])
|
891 | ]),
|
892 | radius = center && getDistance([0, 0], center),
|
893 | tolerance = Math.min(arcThreshold * error, arcTolerance * radius / 100);
|
894 |
|
895 | if (center && radius < 1e15 &&
|
896 | [1/4, 3/4].every(function(point) {
|
897 | return Math.abs(getDistance(getCubicBezierPoint(curve, point), center) - radius) <= tolerance;
|
898 | }))
|
899 | return { center: center, radius: radius};
|
900 | }
|
901 |
|
902 |
|
903 |
|
904 |
|
905 |
|
906 |
|
907 |
|
908 |
|
909 |
|
910 | function isArc(curve, circle) {
|
911 | var tolerance = Math.min(arcThreshold * error, arcTolerance * circle.radius / 100);
|
912 |
|
913 | return [0, 1/4, 1/2, 3/4, 1].every(function(point) {
|
914 | return Math.abs(getDistance(getCubicBezierPoint(curve, point), circle.center) - circle.radius) <= tolerance;
|
915 | });
|
916 | }
|
917 |
|
918 |
|
919 |
|
920 |
|
921 |
|
922 |
|
923 |
|
924 |
|
925 |
|
926 | function isArcPrev(curve, circle) {
|
927 | return isArc(curve, {
|
928 | center: [circle.center[0] + curve[4], circle.center[1] + curve[5]],
|
929 | radius: circle.radius
|
930 | });
|
931 | }
|
932 |
|
933 |
|
934 |
|
935 |
|
936 |
|
937 |
|
938 |
|
939 |
|
940 |
|
941 | function findArcAngle(curve, relCircle) {
|
942 | var x1 = -relCircle.center[0],
|
943 | y1 = -relCircle.center[1],
|
944 | x2 = curve[4] - relCircle.center[0],
|
945 | y2 = curve[5] - relCircle.center[1];
|
946 |
|
947 | return Math.acos(
|
948 | (x1 * x2 + y1 * y2) /
|
949 | Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2))
|
950 | );
|
951 | }
|
952 |
|
953 |
|
954 |
|
955 |
|
956 |
|
957 |
|
958 |
|
959 |
|
960 |
|
961 | function data2Path(params, pathData) {
|
962 | return pathData.reduce(function(pathString, item) {
|
963 | return pathString + item.instruction + (item.data ? cleanupOutData(roundData(item.data.slice()), params) : '');
|
964 | }, '');
|
965 | }
|