[6a3a178] | 1 | 'use strict';
|
---|
| 2 |
|
---|
| 3 | const { collectStylesheet, computeStyle } = require('../lib/style.js');
|
---|
| 4 | const { pathElems } = require('./_collections.js');
|
---|
| 5 | const { path2js, js2path } = require('./_path.js');
|
---|
| 6 | const { applyTransforms } = require('./_applyTransforms.js');
|
---|
| 7 | const { cleanupOutData } = require('../lib/svgo/tools');
|
---|
| 8 |
|
---|
| 9 | exports.name = 'convertPathData';
|
---|
| 10 | exports.type = 'visitor';
|
---|
| 11 | exports.active = true;
|
---|
| 12 | exports.description =
|
---|
| 13 | 'optimizes path data: writes in shorter form, applies transformations';
|
---|
| 14 |
|
---|
| 15 | exports.params = {
|
---|
| 16 | applyTransforms: true,
|
---|
| 17 | applyTransformsStroked: true,
|
---|
| 18 | makeArcs: {
|
---|
| 19 | threshold: 2.5, // coefficient of rounding error
|
---|
| 20 | tolerance: 0.5, // percentage of radius
|
---|
| 21 | },
|
---|
| 22 | straightCurves: true,
|
---|
| 23 | lineShorthands: true,
|
---|
| 24 | curveSmoothShorthands: true,
|
---|
| 25 | floatPrecision: 3,
|
---|
| 26 | transformPrecision: 5,
|
---|
| 27 | removeUseless: true,
|
---|
| 28 | collapseRepeated: true,
|
---|
| 29 | utilizeAbsolute: true,
|
---|
| 30 | leadingZero: true,
|
---|
| 31 | negativeExtraSpace: true,
|
---|
| 32 | noSpaceAfterFlags: false, // a20 60 45 0 1 30 20 → a20 60 45 0130 20
|
---|
| 33 | forceAbsolutePath: false,
|
---|
| 34 | };
|
---|
| 35 |
|
---|
| 36 | let roundData;
|
---|
| 37 | let precision;
|
---|
| 38 | let error;
|
---|
| 39 | let arcThreshold;
|
---|
| 40 | let arcTolerance;
|
---|
| 41 |
|
---|
| 42 | /**
|
---|
| 43 | * Convert absolute Path to relative,
|
---|
| 44 | * collapse repeated instructions,
|
---|
| 45 | * detect and convert Lineto shorthands,
|
---|
| 46 | * remove useless instructions like "l0,0",
|
---|
| 47 | * trim useless delimiters and leading zeros,
|
---|
| 48 | * decrease accuracy of floating-point numbers.
|
---|
| 49 | *
|
---|
| 50 | * @see https://www.w3.org/TR/SVG11/paths.html#PathData
|
---|
| 51 | *
|
---|
| 52 | * @param {Object} item current iteration item
|
---|
| 53 | * @param {Object} params plugin params
|
---|
| 54 | * @return {Boolean} if false, item will be filtered out
|
---|
| 55 | *
|
---|
| 56 | * @author Kir Belevich
|
---|
| 57 | */
|
---|
| 58 | exports.fn = (root, params) => {
|
---|
| 59 | const stylesheet = collectStylesheet(root);
|
---|
| 60 | return {
|
---|
| 61 | element: {
|
---|
| 62 | enter: (node) => {
|
---|
| 63 | if (pathElems.includes(node.name) && node.attributes.d != null) {
|
---|
| 64 | const computedStyle = computeStyle(stylesheet, node);
|
---|
| 65 | precision = params.floatPrecision;
|
---|
| 66 | error =
|
---|
| 67 | precision !== false
|
---|
| 68 | ? +Math.pow(0.1, precision).toFixed(precision)
|
---|
| 69 | : 1e-2;
|
---|
| 70 | roundData = precision > 0 && precision < 20 ? strongRound : round;
|
---|
| 71 | if (params.makeArcs) {
|
---|
| 72 | arcThreshold = params.makeArcs.threshold;
|
---|
| 73 | arcTolerance = params.makeArcs.tolerance;
|
---|
| 74 | }
|
---|
| 75 | const hasMarkerMid = computedStyle['marker-mid'] != null;
|
---|
| 76 |
|
---|
| 77 | const maybeHasStroke =
|
---|
| 78 | computedStyle.stroke &&
|
---|
| 79 | (computedStyle.stroke.type === 'dynamic' ||
|
---|
| 80 | computedStyle.stroke.value !== 'none');
|
---|
| 81 | const maybeHasLinecap =
|
---|
| 82 | computedStyle['stroke-linecap'] &&
|
---|
| 83 | (computedStyle['stroke-linecap'].type === 'dynamic' ||
|
---|
| 84 | computedStyle['stroke-linecap'].value !== 'butt');
|
---|
| 85 | const maybeHasStrokeAndLinecap = maybeHasStroke && maybeHasLinecap;
|
---|
| 86 |
|
---|
| 87 | var data = path2js(node);
|
---|
| 88 |
|
---|
| 89 | // TODO: get rid of functions returns
|
---|
| 90 | if (data.length) {
|
---|
| 91 | if (params.applyTransforms) {
|
---|
| 92 | applyTransforms(node, data, params);
|
---|
| 93 | }
|
---|
| 94 |
|
---|
| 95 | convertToRelative(data);
|
---|
| 96 |
|
---|
| 97 | data = filters(data, params, {
|
---|
| 98 | maybeHasStrokeAndLinecap,
|
---|
| 99 | hasMarkerMid,
|
---|
| 100 | });
|
---|
| 101 |
|
---|
| 102 | if (params.utilizeAbsolute) {
|
---|
| 103 | data = convertToMixed(data, params);
|
---|
| 104 | }
|
---|
| 105 |
|
---|
| 106 | js2path(node, data, params);
|
---|
| 107 | }
|
---|
| 108 | }
|
---|
| 109 | },
|
---|
| 110 | },
|
---|
| 111 | };
|
---|
| 112 | };
|
---|
| 113 |
|
---|
| 114 | /**
|
---|
| 115 | * Convert absolute path data coordinates to relative.
|
---|
| 116 | *
|
---|
| 117 | * @param {Array} path input path data
|
---|
| 118 | * @param {Object} params plugin params
|
---|
| 119 | * @return {Array} output path data
|
---|
| 120 | */
|
---|
| 121 | const convertToRelative = (pathData) => {
|
---|
| 122 | let start = [0, 0];
|
---|
| 123 | let cursor = [0, 0];
|
---|
| 124 | let prevCoords = [0, 0];
|
---|
| 125 |
|
---|
| 126 | for (let i = 0; i < pathData.length; i += 1) {
|
---|
| 127 | const pathItem = pathData[i];
|
---|
| 128 | let { command, args } = pathItem;
|
---|
| 129 |
|
---|
| 130 | // moveto (x y)
|
---|
| 131 | if (command === 'm') {
|
---|
| 132 | // update start and cursor
|
---|
| 133 | cursor[0] += args[0];
|
---|
| 134 | cursor[1] += args[1];
|
---|
| 135 | start[0] = cursor[0];
|
---|
| 136 | start[1] = cursor[1];
|
---|
| 137 | }
|
---|
| 138 | if (command === 'M') {
|
---|
| 139 | // M → m
|
---|
| 140 | // skip first moveto
|
---|
| 141 | if (i !== 0) {
|
---|
| 142 | command = 'm';
|
---|
| 143 | }
|
---|
| 144 | args[0] -= cursor[0];
|
---|
| 145 | args[1] -= cursor[1];
|
---|
| 146 | // update start and cursor
|
---|
| 147 | cursor[0] += args[0];
|
---|
| 148 | cursor[1] += args[1];
|
---|
| 149 | start[0] = cursor[0];
|
---|
| 150 | start[1] = cursor[1];
|
---|
| 151 | }
|
---|
| 152 |
|
---|
| 153 | // lineto (x y)
|
---|
| 154 | if (command === 'l') {
|
---|
| 155 | cursor[0] += args[0];
|
---|
| 156 | cursor[1] += args[1];
|
---|
| 157 | }
|
---|
| 158 | if (command === 'L') {
|
---|
| 159 | // L → l
|
---|
| 160 | command = 'l';
|
---|
| 161 | args[0] -= cursor[0];
|
---|
| 162 | args[1] -= cursor[1];
|
---|
| 163 | cursor[0] += args[0];
|
---|
| 164 | cursor[1] += args[1];
|
---|
| 165 | }
|
---|
| 166 |
|
---|
| 167 | // horizontal lineto (x)
|
---|
| 168 | if (command === 'h') {
|
---|
| 169 | cursor[0] += args[0];
|
---|
| 170 | }
|
---|
| 171 | if (command === 'H') {
|
---|
| 172 | // H → h
|
---|
| 173 | command = 'h';
|
---|
| 174 | args[0] -= cursor[0];
|
---|
| 175 | cursor[0] += args[0];
|
---|
| 176 | }
|
---|
| 177 |
|
---|
| 178 | // vertical lineto (y)
|
---|
| 179 | if (command === 'v') {
|
---|
| 180 | cursor[1] += args[0];
|
---|
| 181 | }
|
---|
| 182 | if (command === 'V') {
|
---|
| 183 | // V → v
|
---|
| 184 | command = 'v';
|
---|
| 185 | args[0] -= cursor[1];
|
---|
| 186 | cursor[1] += args[0];
|
---|
| 187 | }
|
---|
| 188 |
|
---|
| 189 | // curveto (x1 y1 x2 y2 x y)
|
---|
| 190 | if (command === 'c') {
|
---|
| 191 | cursor[0] += args[4];
|
---|
| 192 | cursor[1] += args[5];
|
---|
| 193 | }
|
---|
| 194 | if (command === 'C') {
|
---|
| 195 | // C → c
|
---|
| 196 | command = 'c';
|
---|
| 197 | args[0] -= cursor[0];
|
---|
| 198 | args[1] -= cursor[1];
|
---|
| 199 | args[2] -= cursor[0];
|
---|
| 200 | args[3] -= cursor[1];
|
---|
| 201 | args[4] -= cursor[0];
|
---|
| 202 | args[5] -= cursor[1];
|
---|
| 203 | cursor[0] += args[4];
|
---|
| 204 | cursor[1] += args[5];
|
---|
| 205 | }
|
---|
| 206 |
|
---|
| 207 | // smooth curveto (x2 y2 x y)
|
---|
| 208 | if (command === 's') {
|
---|
| 209 | cursor[0] += args[2];
|
---|
| 210 | cursor[1] += args[3];
|
---|
| 211 | }
|
---|
| 212 | if (command === 'S') {
|
---|
| 213 | // S → s
|
---|
| 214 | command = 's';
|
---|
| 215 | args[0] -= cursor[0];
|
---|
| 216 | args[1] -= cursor[1];
|
---|
| 217 | args[2] -= cursor[0];
|
---|
| 218 | args[3] -= cursor[1];
|
---|
| 219 | cursor[0] += args[2];
|
---|
| 220 | cursor[1] += args[3];
|
---|
| 221 | }
|
---|
| 222 |
|
---|
| 223 | // quadratic Bézier curveto (x1 y1 x y)
|
---|
| 224 | if (command === 'q') {
|
---|
| 225 | cursor[0] += args[2];
|
---|
| 226 | cursor[1] += args[3];
|
---|
| 227 | }
|
---|
| 228 | if (command === 'Q') {
|
---|
| 229 | // Q → q
|
---|
| 230 | command = 'q';
|
---|
| 231 | args[0] -= cursor[0];
|
---|
| 232 | args[1] -= cursor[1];
|
---|
| 233 | args[2] -= cursor[0];
|
---|
| 234 | args[3] -= cursor[1];
|
---|
| 235 | cursor[0] += args[2];
|
---|
| 236 | cursor[1] += args[3];
|
---|
| 237 | }
|
---|
| 238 |
|
---|
| 239 | // smooth quadratic Bézier curveto (x y)
|
---|
| 240 | if (command === 't') {
|
---|
| 241 | cursor[0] += args[0];
|
---|
| 242 | cursor[1] += args[1];
|
---|
| 243 | }
|
---|
| 244 | if (command === 'T') {
|
---|
| 245 | // T → t
|
---|
| 246 | command = 't';
|
---|
| 247 | args[0] -= cursor[0];
|
---|
| 248 | args[1] -= cursor[1];
|
---|
| 249 | cursor[0] += args[0];
|
---|
| 250 | cursor[1] += args[1];
|
---|
| 251 | }
|
---|
| 252 |
|
---|
| 253 | // elliptical arc (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
|
---|
| 254 | if (command === 'a') {
|
---|
| 255 | cursor[0] += args[5];
|
---|
| 256 | cursor[1] += args[6];
|
---|
| 257 | }
|
---|
| 258 | if (command === 'A') {
|
---|
| 259 | // A → a
|
---|
| 260 | command = 'a';
|
---|
| 261 | args[5] -= cursor[0];
|
---|
| 262 | args[6] -= cursor[1];
|
---|
| 263 | cursor[0] += args[5];
|
---|
| 264 | cursor[1] += args[6];
|
---|
| 265 | }
|
---|
| 266 |
|
---|
| 267 | // closepath
|
---|
| 268 | if (command === 'Z' || command === 'z') {
|
---|
| 269 | // reset cursor
|
---|
| 270 | cursor[0] = start[0];
|
---|
| 271 | cursor[1] = start[1];
|
---|
| 272 | }
|
---|
| 273 |
|
---|
| 274 | pathItem.command = command;
|
---|
| 275 | pathItem.args = args;
|
---|
| 276 | // store absolute coordinates for later use
|
---|
| 277 | // base should preserve reference from other element
|
---|
| 278 | pathItem.base = prevCoords;
|
---|
| 279 | pathItem.coords = [cursor[0], cursor[1]];
|
---|
| 280 | prevCoords = pathItem.coords;
|
---|
| 281 | }
|
---|
| 282 |
|
---|
| 283 | return pathData;
|
---|
| 284 | };
|
---|
| 285 |
|
---|
| 286 | /**
|
---|
| 287 | * Main filters loop.
|
---|
| 288 | *
|
---|
| 289 | * @param {Array} path input path data
|
---|
| 290 | * @param {Object} params plugin params
|
---|
| 291 | * @return {Array} output path data
|
---|
| 292 | */
|
---|
| 293 | function filters(path, params, { maybeHasStrokeAndLinecap, hasMarkerMid }) {
|
---|
| 294 | var stringify = data2Path.bind(null, params),
|
---|
| 295 | relSubpoint = [0, 0],
|
---|
| 296 | pathBase = [0, 0],
|
---|
| 297 | prev = {};
|
---|
| 298 |
|
---|
| 299 | path = path.filter(function (item, index, path) {
|
---|
| 300 | let command = item.command;
|
---|
| 301 | let data = item.args;
|
---|
| 302 | let next = path[index + 1];
|
---|
| 303 |
|
---|
| 304 | if (command !== 'Z' && command !== 'z') {
|
---|
| 305 | var sdata = data,
|
---|
| 306 | circle;
|
---|
| 307 |
|
---|
| 308 | if (command === 's') {
|
---|
| 309 | sdata = [0, 0].concat(data);
|
---|
| 310 |
|
---|
| 311 | if (command === 'c' || command === 's') {
|
---|
| 312 | var pdata = prev.args,
|
---|
| 313 | n = pdata.length;
|
---|
| 314 |
|
---|
| 315 | // (-x, -y) of the prev tangent point relative to the current point
|
---|
| 316 | sdata[0] = pdata[n - 2] - pdata[n - 4];
|
---|
| 317 | sdata[1] = pdata[n - 1] - pdata[n - 3];
|
---|
| 318 | }
|
---|
| 319 | }
|
---|
| 320 |
|
---|
| 321 | // convert curves to arcs if possible
|
---|
| 322 | if (
|
---|
| 323 | params.makeArcs &&
|
---|
| 324 | (command == 'c' || command == 's') &&
|
---|
| 325 | isConvex(sdata) &&
|
---|
| 326 | (circle = findCircle(sdata))
|
---|
| 327 | ) {
|
---|
| 328 | var r = roundData([circle.radius])[0],
|
---|
| 329 | angle = findArcAngle(sdata, circle),
|
---|
| 330 | sweep = sdata[5] * sdata[0] - sdata[4] * sdata[1] > 0 ? 1 : 0,
|
---|
| 331 | arc = {
|
---|
| 332 | command: 'a',
|
---|
| 333 | args: [r, r, 0, 0, sweep, sdata[4], sdata[5]],
|
---|
| 334 | coords: item.coords.slice(),
|
---|
| 335 | base: item.base,
|
---|
| 336 | },
|
---|
| 337 | output = [arc],
|
---|
| 338 | // relative coordinates to adjust the found circle
|
---|
| 339 | relCenter = [
|
---|
| 340 | circle.center[0] - sdata[4],
|
---|
| 341 | circle.center[1] - sdata[5],
|
---|
| 342 | ],
|
---|
| 343 | relCircle = { center: relCenter, radius: circle.radius },
|
---|
| 344 | arcCurves = [item],
|
---|
| 345 | hasPrev = 0,
|
---|
| 346 | suffix = '',
|
---|
| 347 | nextLonghand;
|
---|
| 348 |
|
---|
| 349 | if (
|
---|
| 350 | (prev.command == 'c' &&
|
---|
| 351 | isConvex(prev.args) &&
|
---|
| 352 | isArcPrev(prev.args, circle)) ||
|
---|
| 353 | (prev.command == 'a' && prev.sdata && isArcPrev(prev.sdata, circle))
|
---|
| 354 | ) {
|
---|
| 355 | arcCurves.unshift(prev);
|
---|
| 356 | arc.base = prev.base;
|
---|
| 357 | arc.args[5] = arc.coords[0] - arc.base[0];
|
---|
| 358 | arc.args[6] = arc.coords[1] - arc.base[1];
|
---|
| 359 | var prevData = prev.command == 'a' ? prev.sdata : prev.args;
|
---|
| 360 | var prevAngle = findArcAngle(prevData, {
|
---|
| 361 | center: [
|
---|
| 362 | prevData[4] + circle.center[0],
|
---|
| 363 | prevData[5] + circle.center[1],
|
---|
| 364 | ],
|
---|
| 365 | radius: circle.radius,
|
---|
| 366 | });
|
---|
| 367 | angle += prevAngle;
|
---|
| 368 | if (angle > Math.PI) arc.args[3] = 1;
|
---|
| 369 | hasPrev = 1;
|
---|
| 370 | }
|
---|
| 371 |
|
---|
| 372 | // check if next curves are fitting the arc
|
---|
| 373 | for (
|
---|
| 374 | var j = index;
|
---|
| 375 | (next = path[++j]) && ~'cs'.indexOf(next.command);
|
---|
| 376 |
|
---|
| 377 | ) {
|
---|
| 378 | var nextData = next.args;
|
---|
| 379 | if (next.command == 's') {
|
---|
| 380 | nextLonghand = makeLonghand(
|
---|
| 381 | { command: 's', args: next.args.slice() },
|
---|
| 382 | path[j - 1].args
|
---|
| 383 | );
|
---|
| 384 | nextData = nextLonghand.args;
|
---|
| 385 | nextLonghand.args = nextData.slice(0, 2);
|
---|
| 386 | suffix = stringify([nextLonghand]);
|
---|
| 387 | }
|
---|
| 388 | if (isConvex(nextData) && isArc(nextData, relCircle)) {
|
---|
| 389 | angle += findArcAngle(nextData, relCircle);
|
---|
| 390 | if (angle - 2 * Math.PI > 1e-3) break; // more than 360°
|
---|
| 391 | if (angle > Math.PI) arc.args[3] = 1;
|
---|
| 392 | arcCurves.push(next);
|
---|
| 393 | if (2 * Math.PI - angle > 1e-3) {
|
---|
| 394 | // less than 360°
|
---|
| 395 | arc.coords = next.coords;
|
---|
| 396 | arc.args[5] = arc.coords[0] - arc.base[0];
|
---|
| 397 | arc.args[6] = arc.coords[1] - arc.base[1];
|
---|
| 398 | } else {
|
---|
| 399 | // full circle, make a half-circle arc and add a second one
|
---|
| 400 | arc.args[5] = 2 * (relCircle.center[0] - nextData[4]);
|
---|
| 401 | arc.args[6] = 2 * (relCircle.center[1] - nextData[5]);
|
---|
| 402 | arc.coords = [
|
---|
| 403 | arc.base[0] + arc.args[5],
|
---|
| 404 | arc.base[1] + arc.args[6],
|
---|
| 405 | ];
|
---|
| 406 | arc = {
|
---|
| 407 | command: 'a',
|
---|
| 408 | args: [
|
---|
| 409 | r,
|
---|
| 410 | r,
|
---|
| 411 | 0,
|
---|
| 412 | 0,
|
---|
| 413 | sweep,
|
---|
| 414 | next.coords[0] - arc.coords[0],
|
---|
| 415 | next.coords[1] - arc.coords[1],
|
---|
| 416 | ],
|
---|
| 417 | coords: next.coords,
|
---|
| 418 | base: arc.coords,
|
---|
| 419 | };
|
---|
| 420 | output.push(arc);
|
---|
| 421 | j++;
|
---|
| 422 | break;
|
---|
| 423 | }
|
---|
| 424 | relCenter[0] -= nextData[4];
|
---|
| 425 | relCenter[1] -= nextData[5];
|
---|
| 426 | } else break;
|
---|
| 427 | }
|
---|
| 428 |
|
---|
| 429 | if ((stringify(output) + suffix).length < stringify(arcCurves).length) {
|
---|
| 430 | if (path[j] && path[j].command == 's') {
|
---|
| 431 | makeLonghand(path[j], path[j - 1].args);
|
---|
| 432 | }
|
---|
| 433 | if (hasPrev) {
|
---|
| 434 | var prevArc = output.shift();
|
---|
| 435 | roundData(prevArc.args);
|
---|
| 436 | relSubpoint[0] += prevArc.args[5] - prev.args[prev.args.length - 2];
|
---|
| 437 | relSubpoint[1] += prevArc.args[6] - prev.args[prev.args.length - 1];
|
---|
| 438 | prev.command = 'a';
|
---|
| 439 | prev.args = prevArc.args;
|
---|
| 440 | item.base = prev.coords = prevArc.coords;
|
---|
| 441 | }
|
---|
| 442 | arc = output.shift();
|
---|
| 443 | if (arcCurves.length == 1) {
|
---|
| 444 | item.sdata = sdata.slice(); // preserve curve data for future checks
|
---|
| 445 | } else if (arcCurves.length - 1 - hasPrev > 0) {
|
---|
| 446 | // filter out consumed next items
|
---|
| 447 | path.splice.apply(
|
---|
| 448 | path,
|
---|
| 449 | [index + 1, arcCurves.length - 1 - hasPrev].concat(output)
|
---|
| 450 | );
|
---|
| 451 | }
|
---|
| 452 | if (!arc) return false;
|
---|
| 453 | command = 'a';
|
---|
| 454 | data = arc.args;
|
---|
| 455 | item.coords = arc.coords;
|
---|
| 456 | }
|
---|
| 457 | }
|
---|
| 458 |
|
---|
| 459 | // Rounding relative coordinates, taking in account accummulating error
|
---|
| 460 | // to get closer to absolute coordinates. Sum of rounded value remains same:
|
---|
| 461 | // l .25 3 .25 2 .25 3 .25 2 -> l .3 3 .2 2 .3 3 .2 2
|
---|
| 462 | if (precision !== false) {
|
---|
| 463 | if (
|
---|
| 464 | command === 'm' ||
|
---|
| 465 | command === 'l' ||
|
---|
| 466 | command === 't' ||
|
---|
| 467 | command === 'q' ||
|
---|
| 468 | command === 's' ||
|
---|
| 469 | command === 'c'
|
---|
| 470 | ) {
|
---|
| 471 | for (var i = data.length; i--; ) {
|
---|
| 472 | data[i] += item.base[i % 2] - relSubpoint[i % 2];
|
---|
| 473 | }
|
---|
| 474 | } else if (command == 'h') {
|
---|
| 475 | data[0] += item.base[0] - relSubpoint[0];
|
---|
| 476 | } else if (command == 'v') {
|
---|
| 477 | data[0] += item.base[1] - relSubpoint[1];
|
---|
| 478 | } else if (command == 'a') {
|
---|
| 479 | data[5] += item.base[0] - relSubpoint[0];
|
---|
| 480 | data[6] += item.base[1] - relSubpoint[1];
|
---|
| 481 | }
|
---|
| 482 | roundData(data);
|
---|
| 483 |
|
---|
| 484 | if (command == 'h') relSubpoint[0] += data[0];
|
---|
| 485 | else if (command == 'v') relSubpoint[1] += data[0];
|
---|
| 486 | else {
|
---|
| 487 | relSubpoint[0] += data[data.length - 2];
|
---|
| 488 | relSubpoint[1] += data[data.length - 1];
|
---|
| 489 | }
|
---|
| 490 | roundData(relSubpoint);
|
---|
| 491 |
|
---|
| 492 | if (command === 'M' || command === 'm') {
|
---|
| 493 | pathBase[0] = relSubpoint[0];
|
---|
| 494 | pathBase[1] = relSubpoint[1];
|
---|
| 495 | }
|
---|
| 496 | }
|
---|
| 497 |
|
---|
| 498 | // convert straight curves into lines segments
|
---|
| 499 | if (params.straightCurves) {
|
---|
| 500 | if (
|
---|
| 501 | (command === 'c' && isCurveStraightLine(data)) ||
|
---|
| 502 | (command === 's' && isCurveStraightLine(sdata))
|
---|
| 503 | ) {
|
---|
| 504 | if (next && next.command == 's') makeLonghand(next, data); // fix up next curve
|
---|
| 505 | command = 'l';
|
---|
| 506 | data = data.slice(-2);
|
---|
| 507 | } else if (command === 'q' && isCurveStraightLine(data)) {
|
---|
| 508 | if (next && next.command == 't') makeLonghand(next, data); // fix up next curve
|
---|
| 509 | command = 'l';
|
---|
| 510 | data = data.slice(-2);
|
---|
| 511 | } else if (
|
---|
| 512 | command === 't' &&
|
---|
| 513 | prev.command !== 'q' &&
|
---|
| 514 | prev.command !== 't'
|
---|
| 515 | ) {
|
---|
| 516 | command = 'l';
|
---|
| 517 | data = data.slice(-2);
|
---|
| 518 | } else if (command === 'a' && (data[0] === 0 || data[1] === 0)) {
|
---|
| 519 | command = 'l';
|
---|
| 520 | data = data.slice(-2);
|
---|
| 521 | }
|
---|
| 522 | }
|
---|
| 523 |
|
---|
| 524 | // horizontal and vertical line shorthands
|
---|
| 525 | // l 50 0 → h 50
|
---|
| 526 | // l 0 50 → v 50
|
---|
| 527 | if (params.lineShorthands && command === 'l') {
|
---|
| 528 | if (data[1] === 0) {
|
---|
| 529 | command = 'h';
|
---|
| 530 | data.pop();
|
---|
| 531 | } else if (data[0] === 0) {
|
---|
| 532 | command = 'v';
|
---|
| 533 | data.shift();
|
---|
| 534 | }
|
---|
| 535 | }
|
---|
| 536 |
|
---|
| 537 | // collapse repeated commands
|
---|
| 538 | // h 20 h 30 -> h 50
|
---|
| 539 | if (
|
---|
| 540 | params.collapseRepeated &&
|
---|
| 541 | hasMarkerMid === false &&
|
---|
| 542 | (command === 'm' || command === 'h' || command === 'v') &&
|
---|
| 543 | prev.command &&
|
---|
| 544 | command == prev.command.toLowerCase() &&
|
---|
| 545 | ((command != 'h' && command != 'v') ||
|
---|
| 546 | prev.args[0] >= 0 == data[0] >= 0)
|
---|
| 547 | ) {
|
---|
| 548 | prev.args[0] += data[0];
|
---|
| 549 | if (command != 'h' && command != 'v') {
|
---|
| 550 | prev.args[1] += data[1];
|
---|
| 551 | }
|
---|
| 552 | prev.coords = item.coords;
|
---|
| 553 | path[index] = prev;
|
---|
| 554 | return false;
|
---|
| 555 | }
|
---|
| 556 |
|
---|
| 557 | // convert curves into smooth shorthands
|
---|
| 558 | if (params.curveSmoothShorthands && prev.command) {
|
---|
| 559 | // curveto
|
---|
| 560 | if (command === 'c') {
|
---|
| 561 | // c + c → c + s
|
---|
| 562 | if (
|
---|
| 563 | prev.command === 'c' &&
|
---|
| 564 | data[0] === -(prev.args[2] - prev.args[4]) &&
|
---|
| 565 | data[1] === -(prev.args[3] - prev.args[5])
|
---|
| 566 | ) {
|
---|
| 567 | command = 's';
|
---|
| 568 | data = data.slice(2);
|
---|
| 569 | }
|
---|
| 570 |
|
---|
| 571 | // s + c → s + s
|
---|
| 572 | else if (
|
---|
| 573 | prev.command === 's' &&
|
---|
| 574 | data[0] === -(prev.args[0] - prev.args[2]) &&
|
---|
| 575 | data[1] === -(prev.args[1] - prev.args[3])
|
---|
| 576 | ) {
|
---|
| 577 | command = 's';
|
---|
| 578 | data = data.slice(2);
|
---|
| 579 | }
|
---|
| 580 |
|
---|
| 581 | // [^cs] + c → [^cs] + s
|
---|
| 582 | else if (
|
---|
| 583 | prev.command !== 'c' &&
|
---|
| 584 | prev.command !== 's' &&
|
---|
| 585 | data[0] === 0 &&
|
---|
| 586 | data[1] === 0
|
---|
| 587 | ) {
|
---|
| 588 | command = 's';
|
---|
| 589 | data = data.slice(2);
|
---|
| 590 | }
|
---|
| 591 | }
|
---|
| 592 |
|
---|
| 593 | // quadratic Bézier curveto
|
---|
| 594 | else if (command === 'q') {
|
---|
| 595 | // q + q → q + t
|
---|
| 596 | if (
|
---|
| 597 | prev.command === 'q' &&
|
---|
| 598 | data[0] === prev.args[2] - prev.args[0] &&
|
---|
| 599 | data[1] === prev.args[3] - prev.args[1]
|
---|
| 600 | ) {
|
---|
| 601 | command = 't';
|
---|
| 602 | data = data.slice(2);
|
---|
| 603 | }
|
---|
| 604 |
|
---|
| 605 | // t + q → t + t
|
---|
| 606 | else if (
|
---|
| 607 | prev.command === 't' &&
|
---|
| 608 | data[2] === prev.args[0] &&
|
---|
| 609 | data[3] === prev.args[1]
|
---|
| 610 | ) {
|
---|
| 611 | command = 't';
|
---|
| 612 | data = data.slice(2);
|
---|
| 613 | }
|
---|
| 614 | }
|
---|
| 615 | }
|
---|
| 616 |
|
---|
| 617 | // remove useless non-first path segments
|
---|
| 618 | if (params.removeUseless && !maybeHasStrokeAndLinecap) {
|
---|
| 619 | // l 0,0 / h 0 / v 0 / q 0,0 0,0 / t 0,0 / c 0,0 0,0 0,0 / s 0,0 0,0
|
---|
| 620 | if (
|
---|
| 621 | (command === 'l' ||
|
---|
| 622 | command === 'h' ||
|
---|
| 623 | command === 'v' ||
|
---|
| 624 | command === 'q' ||
|
---|
| 625 | command === 't' ||
|
---|
| 626 | command === 'c' ||
|
---|
| 627 | command === 's') &&
|
---|
| 628 | data.every(function (i) {
|
---|
| 629 | return i === 0;
|
---|
| 630 | })
|
---|
| 631 | ) {
|
---|
| 632 | path[index] = prev;
|
---|
| 633 | return false;
|
---|
| 634 | }
|
---|
| 635 |
|
---|
| 636 | // a 25,25 -30 0,1 0,0
|
---|
| 637 | if (command === 'a' && data[5] === 0 && data[6] === 0) {
|
---|
| 638 | path[index] = prev;
|
---|
| 639 | return false;
|
---|
| 640 | }
|
---|
| 641 | }
|
---|
| 642 |
|
---|
| 643 | item.command = command;
|
---|
| 644 | item.args = data;
|
---|
| 645 |
|
---|
| 646 | prev = item;
|
---|
| 647 | } else {
|
---|
| 648 | // z resets coordinates
|
---|
| 649 | relSubpoint[0] = pathBase[0];
|
---|
| 650 | relSubpoint[1] = pathBase[1];
|
---|
| 651 | if (prev.command === 'Z' || prev.command === 'z') return false;
|
---|
| 652 | prev = item;
|
---|
| 653 | }
|
---|
| 654 |
|
---|
| 655 | return true;
|
---|
| 656 | });
|
---|
| 657 |
|
---|
| 658 | return path;
|
---|
| 659 | }
|
---|
| 660 |
|
---|
| 661 | /**
|
---|
| 662 | * Writes data in shortest form using absolute or relative coordinates.
|
---|
| 663 | *
|
---|
| 664 | * @param {Array} data input path data
|
---|
| 665 | * @return {Boolean} output
|
---|
| 666 | */
|
---|
| 667 | function convertToMixed(path, params) {
|
---|
| 668 | var prev = path[0];
|
---|
| 669 |
|
---|
| 670 | path = path.filter(function (item, index) {
|
---|
| 671 | if (index == 0) return true;
|
---|
| 672 | if (item.command === 'Z' || item.command === 'z') {
|
---|
| 673 | prev = item;
|
---|
| 674 | return true;
|
---|
| 675 | }
|
---|
| 676 |
|
---|
| 677 | var command = item.command,
|
---|
| 678 | data = item.args,
|
---|
| 679 | adata = data.slice();
|
---|
| 680 |
|
---|
| 681 | if (
|
---|
| 682 | command === 'm' ||
|
---|
| 683 | command === 'l' ||
|
---|
| 684 | command === 't' ||
|
---|
| 685 | command === 'q' ||
|
---|
| 686 | command === 's' ||
|
---|
| 687 | command === 'c'
|
---|
| 688 | ) {
|
---|
| 689 | for (var i = adata.length; i--; ) {
|
---|
| 690 | adata[i] += item.base[i % 2];
|
---|
| 691 | }
|
---|
| 692 | } else if (command == 'h') {
|
---|
| 693 | adata[0] += item.base[0];
|
---|
| 694 | } else if (command == 'v') {
|
---|
| 695 | adata[0] += item.base[1];
|
---|
| 696 | } else if (command == 'a') {
|
---|
| 697 | adata[5] += item.base[0];
|
---|
| 698 | adata[6] += item.base[1];
|
---|
| 699 | }
|
---|
| 700 |
|
---|
| 701 | roundData(adata);
|
---|
| 702 |
|
---|
| 703 | var absoluteDataStr = cleanupOutData(adata, params),
|
---|
| 704 | relativeDataStr = cleanupOutData(data, params);
|
---|
| 705 |
|
---|
| 706 | // Convert to absolute coordinates if it's shorter or forceAbsolutePath is true.
|
---|
| 707 | // v-20 -> V0
|
---|
| 708 | // Don't convert if it fits following previous command.
|
---|
| 709 | // l20 30-10-50 instead of l20 30L20 30
|
---|
| 710 | if (
|
---|
| 711 | params.forceAbsolutePath ||
|
---|
| 712 | (absoluteDataStr.length < relativeDataStr.length &&
|
---|
| 713 | !(
|
---|
| 714 | params.negativeExtraSpace &&
|
---|
| 715 | command == prev.command &&
|
---|
| 716 | prev.command.charCodeAt(0) > 96 &&
|
---|
| 717 | absoluteDataStr.length == relativeDataStr.length - 1 &&
|
---|
| 718 | (data[0] < 0 ||
|
---|
| 719 | (/^0\./.test(data[0]) && prev.args[prev.args.length - 1] % 1))
|
---|
| 720 | ))
|
---|
| 721 | ) {
|
---|
| 722 | item.command = command.toUpperCase();
|
---|
| 723 | item.args = adata;
|
---|
| 724 | }
|
---|
| 725 |
|
---|
| 726 | prev = item;
|
---|
| 727 |
|
---|
| 728 | return true;
|
---|
| 729 | });
|
---|
| 730 |
|
---|
| 731 | return path;
|
---|
| 732 | }
|
---|
| 733 |
|
---|
| 734 | /**
|
---|
| 735 | * Checks if curve is convex. Control points of such a curve must form
|
---|
| 736 | * a convex quadrilateral with diagonals crosspoint inside of it.
|
---|
| 737 | *
|
---|
| 738 | * @param {Array} data input path data
|
---|
| 739 | * @return {Boolean} output
|
---|
| 740 | */
|
---|
| 741 | function isConvex(data) {
|
---|
| 742 | var center = getIntersection([
|
---|
| 743 | 0,
|
---|
| 744 | 0,
|
---|
| 745 | data[2],
|
---|
| 746 | data[3],
|
---|
| 747 | data[0],
|
---|
| 748 | data[1],
|
---|
| 749 | data[4],
|
---|
| 750 | data[5],
|
---|
| 751 | ]);
|
---|
| 752 |
|
---|
| 753 | return (
|
---|
| 754 | center &&
|
---|
| 755 | data[2] < center[0] == center[0] < 0 &&
|
---|
| 756 | data[3] < center[1] == center[1] < 0 &&
|
---|
| 757 | data[4] < center[0] == center[0] < data[0] &&
|
---|
| 758 | data[5] < center[1] == center[1] < data[1]
|
---|
| 759 | );
|
---|
| 760 | }
|
---|
| 761 |
|
---|
| 762 | /**
|
---|
| 763 | * Computes lines equations by two points and returns their intersection point.
|
---|
| 764 | *
|
---|
| 765 | * @param {Array} coords 8 numbers for 4 pairs of coordinates (x,y)
|
---|
| 766 | * @return {Array|undefined} output coordinate of lines' crosspoint
|
---|
| 767 | */
|
---|
| 768 | function getIntersection(coords) {
|
---|
| 769 | // Prev line equation parameters.
|
---|
| 770 | var a1 = coords[1] - coords[3], // y1 - y2
|
---|
| 771 | b1 = coords[2] - coords[0], // x2 - x1
|
---|
| 772 | c1 = coords[0] * coords[3] - coords[2] * coords[1], // x1 * y2 - x2 * y1
|
---|
| 773 | // Next line equation parameters
|
---|
| 774 | a2 = coords[5] - coords[7], // y1 - y2
|
---|
| 775 | b2 = coords[6] - coords[4], // x2 - x1
|
---|
| 776 | c2 = coords[4] * coords[7] - coords[5] * coords[6], // x1 * y2 - x2 * y1
|
---|
| 777 | denom = a1 * b2 - a2 * b1;
|
---|
| 778 |
|
---|
| 779 | if (!denom) return; // parallel lines havn't an intersection
|
---|
| 780 |
|
---|
| 781 | var cross = [(b1 * c2 - b2 * c1) / denom, (a1 * c2 - a2 * c1) / -denom];
|
---|
| 782 | if (
|
---|
| 783 | !isNaN(cross[0]) &&
|
---|
| 784 | !isNaN(cross[1]) &&
|
---|
| 785 | isFinite(cross[0]) &&
|
---|
| 786 | isFinite(cross[1])
|
---|
| 787 | ) {
|
---|
| 788 | return cross;
|
---|
| 789 | }
|
---|
| 790 | }
|
---|
| 791 |
|
---|
| 792 | /**
|
---|
| 793 | * Decrease accuracy of floating-point numbers
|
---|
| 794 | * in path data keeping a specified number of decimals.
|
---|
| 795 | * Smart rounds values like 2.3491 to 2.35 instead of 2.349.
|
---|
| 796 | * Doesn't apply "smartness" if the number precision fits already.
|
---|
| 797 | *
|
---|
| 798 | * @param {Array} data input data array
|
---|
| 799 | * @return {Array} output data array
|
---|
| 800 | */
|
---|
| 801 | function strongRound(data) {
|
---|
| 802 | for (var i = data.length; i-- > 0; ) {
|
---|
| 803 | if (data[i].toFixed(precision) != data[i]) {
|
---|
| 804 | var rounded = +data[i].toFixed(precision - 1);
|
---|
| 805 | data[i] =
|
---|
| 806 | +Math.abs(rounded - data[i]).toFixed(precision + 1) >= error
|
---|
| 807 | ? +data[i].toFixed(precision)
|
---|
| 808 | : rounded;
|
---|
| 809 | }
|
---|
| 810 | }
|
---|
| 811 | return data;
|
---|
| 812 | }
|
---|
| 813 |
|
---|
| 814 | /**
|
---|
| 815 | * Simple rounding function if precision is 0.
|
---|
| 816 | *
|
---|
| 817 | * @param {Array} data input data array
|
---|
| 818 | * @return {Array} output data array
|
---|
| 819 | */
|
---|
| 820 | function round(data) {
|
---|
| 821 | for (var i = data.length; i-- > 0; ) {
|
---|
| 822 | data[i] = Math.round(data[i]);
|
---|
| 823 | }
|
---|
| 824 | return data;
|
---|
| 825 | }
|
---|
| 826 |
|
---|
| 827 | /**
|
---|
| 828 | * Checks if a curve is a straight line by measuring distance
|
---|
| 829 | * from middle points to the line formed by end points.
|
---|
| 830 | *
|
---|
| 831 | * @param {Array} xs array of curve points x-coordinates
|
---|
| 832 | * @param {Array} ys array of curve points y-coordinates
|
---|
| 833 | * @return {Boolean}
|
---|
| 834 | */
|
---|
| 835 |
|
---|
| 836 | function isCurveStraightLine(data) {
|
---|
| 837 | // Get line equation a·x + b·y + c = 0 coefficients a, b (c = 0) by start and end points.
|
---|
| 838 | var i = data.length - 2,
|
---|
| 839 | a = -data[i + 1], // y1 − y2 (y1 = 0)
|
---|
| 840 | b = data[i], // x2 − x1 (x1 = 0)
|
---|
| 841 | d = 1 / (a * a + b * b); // same part for all points
|
---|
| 842 |
|
---|
| 843 | if (i <= 1 || !isFinite(d)) return false; // curve that ends at start point isn't the case
|
---|
| 844 |
|
---|
| 845 | // Distance from point (x0, y0) to the line is sqrt((c − a·x0 − b·y0)² / (a² + b²))
|
---|
| 846 | while ((i -= 2) >= 0) {
|
---|
| 847 | if (Math.sqrt(Math.pow(a * data[i] + b * data[i + 1], 2) * d) > error)
|
---|
| 848 | return false;
|
---|
| 849 | }
|
---|
| 850 |
|
---|
| 851 | return true;
|
---|
| 852 | }
|
---|
| 853 |
|
---|
| 854 | /**
|
---|
| 855 | * Converts next curve from shorthand to full form using the current curve data.
|
---|
| 856 | *
|
---|
| 857 | * @param {Object} item curve to convert
|
---|
| 858 | * @param {Array} data current curve data
|
---|
| 859 | */
|
---|
| 860 |
|
---|
| 861 | function makeLonghand(item, data) {
|
---|
| 862 | switch (item.command) {
|
---|
| 863 | case 's':
|
---|
| 864 | item.command = 'c';
|
---|
| 865 | break;
|
---|
| 866 | case 't':
|
---|
| 867 | item.command = 'q';
|
---|
| 868 | break;
|
---|
| 869 | }
|
---|
| 870 | item.args.unshift(
|
---|
| 871 | data[data.length - 2] - data[data.length - 4],
|
---|
| 872 | data[data.length - 1] - data[data.length - 3]
|
---|
| 873 | );
|
---|
| 874 | return item;
|
---|
| 875 | }
|
---|
| 876 |
|
---|
| 877 | /**
|
---|
| 878 | * Returns distance between two points
|
---|
| 879 | *
|
---|
| 880 | * @param {Array} point1 first point coordinates
|
---|
| 881 | * @param {Array} point2 second point coordinates
|
---|
| 882 | * @return {Number} distance
|
---|
| 883 | */
|
---|
| 884 |
|
---|
| 885 | function getDistance(point1, point2) {
|
---|
| 886 | return Math.hypot(point1[0] - point2[0], point1[1] - point2[1]);
|
---|
| 887 | }
|
---|
| 888 |
|
---|
| 889 | /**
|
---|
| 890 | * Returns coordinates of the curve point corresponding to the certain t
|
---|
| 891 | * a·(1 - t)³·p1 + b·(1 - t)²·t·p2 + c·(1 - t)·t²·p3 + d·t³·p4,
|
---|
| 892 | * where pN are control points and p1 is zero due to relative coordinates.
|
---|
| 893 | *
|
---|
| 894 | * @param {Array} curve array of curve points coordinates
|
---|
| 895 | * @param {Number} t parametric position from 0 to 1
|
---|
| 896 | * @return {Array} Point coordinates
|
---|
| 897 | */
|
---|
| 898 |
|
---|
| 899 | function getCubicBezierPoint(curve, t) {
|
---|
| 900 | var sqrT = t * t,
|
---|
| 901 | cubT = sqrT * t,
|
---|
| 902 | mt = 1 - t,
|
---|
| 903 | sqrMt = mt * mt;
|
---|
| 904 |
|
---|
| 905 | return [
|
---|
| 906 | 3 * sqrMt * t * curve[0] + 3 * mt * sqrT * curve[2] + cubT * curve[4],
|
---|
| 907 | 3 * sqrMt * t * curve[1] + 3 * mt * sqrT * curve[3] + cubT * curve[5],
|
---|
| 908 | ];
|
---|
| 909 | }
|
---|
| 910 |
|
---|
| 911 | /**
|
---|
| 912 | * Finds circle by 3 points of the curve and checks if the curve fits the found circle.
|
---|
| 913 | *
|
---|
| 914 | * @param {Array} curve
|
---|
| 915 | * @return {Object|undefined} circle
|
---|
| 916 | */
|
---|
| 917 |
|
---|
| 918 | function findCircle(curve) {
|
---|
| 919 | var midPoint = getCubicBezierPoint(curve, 1 / 2),
|
---|
| 920 | m1 = [midPoint[0] / 2, midPoint[1] / 2],
|
---|
| 921 | m2 = [(midPoint[0] + curve[4]) / 2, (midPoint[1] + curve[5]) / 2],
|
---|
| 922 | center = getIntersection([
|
---|
| 923 | m1[0],
|
---|
| 924 | m1[1],
|
---|
| 925 | m1[0] + m1[1],
|
---|
| 926 | m1[1] - m1[0],
|
---|
| 927 | m2[0],
|
---|
| 928 | m2[1],
|
---|
| 929 | m2[0] + (m2[1] - midPoint[1]),
|
---|
| 930 | m2[1] - (m2[0] - midPoint[0]),
|
---|
| 931 | ]),
|
---|
| 932 | radius = center && getDistance([0, 0], center),
|
---|
| 933 | tolerance = Math.min(arcThreshold * error, (arcTolerance * radius) / 100);
|
---|
| 934 |
|
---|
| 935 | if (
|
---|
| 936 | center &&
|
---|
| 937 | radius < 1e15 &&
|
---|
| 938 | [1 / 4, 3 / 4].every(function (point) {
|
---|
| 939 | return (
|
---|
| 940 | Math.abs(
|
---|
| 941 | getDistance(getCubicBezierPoint(curve, point), center) - radius
|
---|
| 942 | ) <= tolerance
|
---|
| 943 | );
|
---|
| 944 | })
|
---|
| 945 | )
|
---|
| 946 | return { center: center, radius: radius };
|
---|
| 947 | }
|
---|
| 948 |
|
---|
| 949 | /**
|
---|
| 950 | * Checks if a curve fits the given circle.
|
---|
| 951 | *
|
---|
| 952 | * @param {Object} circle
|
---|
| 953 | * @param {Array} curve
|
---|
| 954 | * @return {Boolean}
|
---|
| 955 | */
|
---|
| 956 |
|
---|
| 957 | function isArc(curve, circle) {
|
---|
| 958 | var tolerance = Math.min(
|
---|
| 959 | arcThreshold * error,
|
---|
| 960 | (arcTolerance * circle.radius) / 100
|
---|
| 961 | );
|
---|
| 962 |
|
---|
| 963 | return [0, 1 / 4, 1 / 2, 3 / 4, 1].every(function (point) {
|
---|
| 964 | return (
|
---|
| 965 | Math.abs(
|
---|
| 966 | getDistance(getCubicBezierPoint(curve, point), circle.center) -
|
---|
| 967 | circle.radius
|
---|
| 968 | ) <= tolerance
|
---|
| 969 | );
|
---|
| 970 | });
|
---|
| 971 | }
|
---|
| 972 |
|
---|
| 973 | /**
|
---|
| 974 | * Checks if a previous curve fits the given circle.
|
---|
| 975 | *
|
---|
| 976 | * @param {Object} circle
|
---|
| 977 | * @param {Array} curve
|
---|
| 978 | * @return {Boolean}
|
---|
| 979 | */
|
---|
| 980 |
|
---|
| 981 | function isArcPrev(curve, circle) {
|
---|
| 982 | return isArc(curve, {
|
---|
| 983 | center: [circle.center[0] + curve[4], circle.center[1] + curve[5]],
|
---|
| 984 | radius: circle.radius,
|
---|
| 985 | });
|
---|
| 986 | }
|
---|
| 987 |
|
---|
| 988 | /**
|
---|
| 989 | * Finds angle of a curve fitting the given arc.
|
---|
| 990 |
|
---|
| 991 | * @param {Array} curve
|
---|
| 992 | * @param {Object} relCircle
|
---|
| 993 | * @return {Number} angle
|
---|
| 994 | */
|
---|
| 995 |
|
---|
| 996 | function findArcAngle(curve, relCircle) {
|
---|
| 997 | var x1 = -relCircle.center[0],
|
---|
| 998 | y1 = -relCircle.center[1],
|
---|
| 999 | x2 = curve[4] - relCircle.center[0],
|
---|
| 1000 | y2 = curve[5] - relCircle.center[1];
|
---|
| 1001 |
|
---|
| 1002 | return Math.acos(
|
---|
| 1003 | (x1 * x2 + y1 * y2) / Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2))
|
---|
| 1004 | );
|
---|
| 1005 | }
|
---|
| 1006 |
|
---|
| 1007 | /**
|
---|
| 1008 | * Converts given path data to string.
|
---|
| 1009 | *
|
---|
| 1010 | * @param {Object} params
|
---|
| 1011 | * @param {Array} pathData
|
---|
| 1012 | * @return {String}
|
---|
| 1013 | */
|
---|
| 1014 |
|
---|
| 1015 | function data2Path(params, pathData) {
|
---|
| 1016 | return pathData.reduce(function (pathString, item) {
|
---|
| 1017 | var strData = '';
|
---|
| 1018 | if (item.args) {
|
---|
| 1019 | strData = cleanupOutData(roundData(item.args.slice()), params);
|
---|
| 1020 | }
|
---|
| 1021 | return pathString + item.command + strData;
|
---|
| 1022 | }, '');
|
---|
| 1023 | }
|
---|