Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 344x 344x 344x 92x 344x 252x 252x 344x 92x 92x 344x 241x 241x 344x 344x 344x 1x 1x 7x 7x 1x 1575x 1575x 1x 535x 535x 1x 266x 266x 266x 266x 266x 266x 266x 266x 11642x 11642x 11642x 11642x 266x 5726x 5726x 266x 266x 3479x 3479x 3479x 3479x 3479x 3479x 3479x 3479x 3479x 3479x 1308x 1308x 1310x 1310x 399x 399x 399x 1310x 1308x 1308x 3479x 3479x 190x 190x 190x 190x 190x 190x 190x 190x 3479x 1205x 1205x 1205x 1205x 3479x 8x 8x 8x 8x 8x 8x 8x 8x 8x 3479x 390x 390x 390x 390x 3479x 5x 5x 5x 5x 5x 5x 5x 5x 3479x 3479x 373x 373x 373x 3479x 3479x 3479x 266x 1x 3x 3x 1x 714x 714x 714x 714x 714x 519x 714x 714x 1x 583x 583x 583x 583x 573x 10x 583x 1x 3x 56x 3x 3x 1x 29x 29x 29x 29x 29x 112x 112x 37x 37x 37x 112x 29x 29x 1x 1x 3x 3x 3x 3x 3x 3x 3x 3x 3x 1x 1x 4x 4x 3x 3x 12x 12x 3x 3x 3x 3x 3x 1x 3x 3x 2x 2x 2x 2x 2x 2x 2x 2x 2x 4x 4x 4x 4x 4x 6x 3x 3x 3x 3x 3x 3x 6x 4x 4x 2x 2x 3x 3x 1x 1x 1x | /* * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey * http://juerglehni.com/ & https://puckey.studio/ * * Distributed under the MIT license. See LICENSE file for details. * * All rights reserved. */ // TODO: remove eslint-disable comment and deal with errors over time /* eslint-disable */ import { ref } from '~/globals'; import { Base } from '~/straps'; import { __options } from '~/options'; import { Point } from '~/basic/Point'; import { Size } from '~/basic/Size'; import { Numerical } from '~/util/Numerical'; import { Change } from '~/item/ChangeFlag'; import { Item } from '~/item/Item'; import { CollisionDetection } from '~/util/CollisionDetection'; import { Curve } from './Curve'; import { Segment } from './Segment'; /** * @name PathItem * * @class The PathItem class is the base for any items that describe paths and * offer standardised methods for drawing and path manipulation, such as * {@link Path} and {@link CompoundPath}. * * @extends Item */ export const PathItem = Item.extend( /** @lends PathItem# */ { _class: 'PathItem', _selectBounds: false, _canScaleStroke: true, // Enforce creation of beans, as bean getters have hidden parameters. // See #isClockwise() below. beans: true, initialize: function PathItem() { // Do nothing. }, statics: /** @lends PathItem */ { /** * Creates a path item from the given SVG path-data, determining if the * data describes a plain path or a compound-path with multiple * sub-paths. * * @name PathItem.create * @function * @param {String} pathData the SVG path-data to parse * @return {Path|CompoundPath} the newly created path item */ /** * Creates a path item from the given segments array, determining if the * array describes a plain path or a compound-path with multiple * sub-paths. * * @name PathItem.create * @function * @param {Number[][]} segments the segments array to parse * @return {Path|CompoundPath} the newly created path item */ /** * Creates a path item from the given object, determining if the * contained information describes a plain path or a compound-path with * multiple sub-paths. * * @name PathItem.create * @function * @param {Object} object an object containing the properties describing * the item to be created * @return {Path|CompoundPath} the newly created path item */ create: function (arg) { var data, segments, compound; if (Base.isPlainObject(arg)) { segments = arg.segments; data = arg.pathData; } else if (Array.isArray(arg)) { segments = arg; } else if (typeof arg === 'string') { data = arg; } if (segments) { var first = segments[0]; compound = first && Array.isArray(first[0]); } else if (data) { // If there are multiple moveTo commands or a closePath command // followed by other commands, we have a CompoundPath. compound = (data.match(/m/gi) || []).length > 1 || /z\s*\S+/i.test(data); } var ctor = compound ? ref.CompoundPath : ref.Path; return new ctor(arg); }, }, _asPathItem: function () { // See Item#_asPathItem() return this; }, /** * Specifies whether the path as a whole is oriented clock-wise, by looking * at the path's area. * Note that self-intersecting paths and sub-paths of different orientation * can result in areas that cancel each other out. * * @bean * @type Boolean * @see Path#area * @see CompoundPath#area */ isClockwise: function () { return this.getArea() >= 0; }, setClockwise: function (clockwise) { // Only revers the path if its clockwise orientation is not the same // as what it is now demanded to be. // On-the-fly conversion to boolean: if (this.isClockwise() != (clockwise = !!clockwise)) this.reverse(); }, /** * The path's geometry, formatted as SVG style path data. * * @name PathItem#getPathData * @bean * @type String */ setPathData: function (data) { // NOTE: #getPathData() is defined in CompoundPath / Path // This is a very compact SVG Path Data parser that works both for Path // and CompoundPath. // First split the path data into parts of command-coordinates pairs // Commands are any of these characters: mzlhvcsqta var parts = data && data.match(/[mlhvcsqtaz][^mlhvcsqtaz]*/gi), coords, relative = false, previous, control, current = new Point(), start = new Point(); function getCoord(index, coord) { var val = +coords[index]; if (relative) val += current[coord]; return val; } function getPoint(index) { return new Point(getCoord(index, 'x'), getCoord(index + 1, 'y')); } // First clear the previous content this.clear(); for (var i = 0, l = parts && parts.length; i < l; i++) { var part = parts[i], command = part[0], lower = command.toLowerCase(); // Match all coordinate values coords = part.match(/[+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?/g); var length = coords && coords.length; relative = command === lower; // Fix issues with z in the middle of SVG path data, not followed by // a m command, see #413: if (previous === 'z' && !/[mz]/.test(lower)) this.moveTo(current); switch (lower) { case 'm': case 'l': var move = lower === 'm'; for (var j = 0; j < length; j += 2) { this[move ? 'moveTo' : 'lineTo']((current = getPoint(j))); if (move) { start = current; move = false; } } control = current; break; case 'h': case 'v': var coord = lower === 'h' ? 'x' : 'y'; current = current.clone(); // Clone as we're going to modify it. for (var j = 0; j < length; j++) { current[coord] = getCoord(j, coord); this.lineTo(current); } control = current; break; case 'c': for (var j = 0; j < length; j += 6) { this.cubicCurveTo(getPoint(j), (control = getPoint(j + 2)), (current = getPoint(j + 4))); } break; case 's': // Smooth cubicCurveTo for (var j = 0; j < length; j += 4) { this.cubicCurveTo( /[cs]/.test(previous) ? current.multiply(2).subtract(control) : current, (control = getPoint(j)), (current = getPoint(j + 2)) ); previous = lower; } break; case 'q': for (var j = 0; j < length; j += 4) { this.quadraticCurveTo((control = getPoint(j)), (current = getPoint(j + 2))); } break; case 't': // Smooth quadraticCurveTo for (var j = 0; j < length; j += 2) { this.quadraticCurveTo( (control = /[qt]/.test(previous) ? current.multiply(2).subtract(control) : current), (current = getPoint(j)) ); previous = lower; } break; case 'a': for (var j = 0; j < length; j += 7) { this.arcTo( (current = getPoint(j + 5)), new Size(+coords[j], +coords[j + 1]), +coords[j + 2], +coords[j + 4], +coords[j + 3] ); } break; case 'z': // Merge first and last segment with Numerical.EPSILON tolerance // to address imprecisions in relative SVG data. this.closePath(/*#=*/ Numerical.EPSILON); // Correctly handle relative m commands, see #1101: current = start; break; } previous = lower; } }, _canComposite: function () { // A path with only a fill or a stroke can be directly blended, but if // it has both, it needs to be drawn into a separate canvas first. return !(this.hasFill() && this.hasStroke()); }, _contains: function (point) { // NOTE: point is reverse transformed by _matrix, so we don't need to // apply the matrix here. /*#*/ if (__options.nativeContains || !__options.booleanOperations) { // To compare with native canvas approach: // @ts-expect-error = Expected 3 arguments, but got 2 var ctx = ref.CanvasProvider.getContext(1, 1); // Use dontFinish to tell _draw to only produce geometries for hit-test. this._draw(ctx, new Base({ dontFinish: true })); var res = ctx.isPointInPath(point.x, point.y, this.getFillRule()); ref.CanvasProvider.release(ctx); return res; /*#*/ } else { // !__options.nativeContains && __options.booleanOperations // Check the transformed point against the untransformed (internal) // handle bounds, which is the fastest rough bounding box to calculate // for a quick check before calculating the actual winding. var winding = point.isInside(this.getBounds({ internal: true, handle: true })) ? this._getWinding(point) : {}; // See #1116#issuecomment-243794824 for an explanation of the // winding.onPath check here. return ( winding.onPath || !!(this.getFillRule() === 'evenodd' ? winding.windingL & 1 || winding.windingR & 1 : winding.winding) ); /*#*/ } // !__options.nativeContains && __options.booleanOperations }, /** * {@grouptitle Path Intersections and Locations} * * Returns all intersections between two {@link PathItem} items as an array * of {@link CurveLocation} objects. {@link CompoundPath} items are also * supported. * * @param {PathItem} path the other item to find the intersections with * @param {Function} [include] a callback function that can be used to * filter out undesired locations right while they are collected. When * defined, it shall return {@true to include a location}. * @return {CurveLocation[]} the locations of all intersection between the * paths * @see #getCrossings(path) * @example {@paperscript} // Finding the intersections between two paths * var path = new Path.Rectangle(new Point(30, 25), new Size(50, 50)); * path.strokeColor = 'black'; * * var secondPath = path.clone(); * var intersectionGroup = new Group(); * * function onFrame(event) { * secondPath.rotate(1); * * var intersections = path.getIntersections(secondPath); * intersectionGroup.removeChildren(); * * for (var i = 0; i < intersections.length; i++) { * var intersectionPath = new Path.Circle({ * center: intersections[i].point, * radius: 4, * fillColor: 'red', * parent: intersectionGroup * }); * } * } */ getIntersections: function (path, include, _matrix, _returnFirst) { // NOTE: For self-intersection, path is null. This means you can also // just call path.getIntersections() without an argument to get self // intersections. // NOTE: The hidden argument _matrix is used internally to override the // passed path's transformation matrix. var self = this === path || !path, // self-intersections? matrix1 = this._matrix._orNullIfIdentity(), matrix2 = self ? matrix1 : (_matrix || path._matrix)._orNullIfIdentity(); // First check the bounds of the two paths. If they don't intersect, // we don't need to iterate through their curves. return self || this.getBounds(matrix1).intersects(path.getBounds(matrix2), /*#=*/ Numerical.EPSILON) ? Curve.getIntersections(this.getCurves(), !self && path.getCurves(), include, matrix1, matrix2, _returnFirst) : []; }, /** * Returns all crossings between two {@link PathItem} items as an array of * {@link CurveLocation} objects. {@link CompoundPath} items are also * supported. Crossings are intersections where the paths actually are * crossing each other, as opposed to simply touching. * * @param {PathItem} path the other item to find the crossings with * @return {CurveLocation[]} the locations of all crossings between the * paths * @see #getIntersections(path) */ getCrossings: function (path) { return this.getIntersections(path, function (inter) { return inter.isCrossing(); }); }, /** * Returns the nearest location on the path item to the specified point. * * @param {Point} point the point for which we search the nearest location * @return {CurveLocation} the location on the path that's the closest to * the specified point */ getNearestLocation: function (/* point */) { var point = Point.read(arguments), curves = this.getCurves(), minDist = Infinity, minLoc = null; for (var i = 0, l = curves.length; i < l; i++) { var loc = curves[i].getNearestLocation(point); if (loc._distance < minDist) { minDist = loc._distance; minLoc = loc; } } return minLoc; }, /** * Returns the nearest point on the path item to the specified point. * * @param {Point} point the point for which we search the nearest point * @return {Point} the point on the path that's the closest to the specified * point * * @example {@paperscript height=200} * var star = new Path.Star({ * center: view.center, * points: 10, * radius1: 30, * radius2: 60, * strokeColor: 'black' * }); * * var circle = new Path.Circle({ * center: view.center, * radius: 3, * fillColor: 'red' * }); * * function onMouseMove(event) { * // Get the nearest point from the mouse position * // to the star shaped path: * var nearestPoint = star.getNearestPoint(event.point); * * // Move the red circle to the nearest point: * circle.position = nearestPoint; * } */ getNearestPoint: function (/* point */) { var loc = this.getNearestLocation.apply(this, arguments); return loc ? loc.getPoint() : loc; }, /** * {@grouptitle Path Manipulation} * * Reverses the orientation of the path item. When called on * {@link CompoundPath} items, each of the nested paths is reversed. On * {@link Path} items, the sequence of {@link Path#segments} is reversed. * * @name PathItem#reverse * @function */ /** * Flattens the curves in path items to a sequence of straight lines, by * subdividing them enough times until the specified maximum error is met. * * @name PathItem#flatten * @function * * @param {Number} [flatness=0.25] the maximum error between the flattened * lines and the original curves * * @example {@paperscript} * // Flattening a circle shaped path: * * // Create a circle shaped path at { x: 80, y: 50 } * // with a radius of 35: * var path = new Path.Circle({ * center: [80, 50], * radius: 35 * }); * * // Select the path, so we can inspect its segments: * path.selected = true; * * // Create a copy of the path and move it by 150 points: * var copy = path.clone(); * copy.position.x += 150; * * // Flatten the copied path, with a maximum error of 4 points: * copy.flatten(4); */ // TODO: Write about negative indices, and add an example for ranges. /** * Smooths the path item without changing the amount of segments in the path * or moving the segments' locations, by smoothing and adjusting the angle * and length of the segments' handles based on the position and distance of * neighboring segments. * * Smoothing works both for open paths and closed paths, and can be applied * to the full path, as well as a sub-range of it. If a range is defined * using the `options.from` and `options.to` properties, only the curve * handles inside that range are touched. If one or both limits of the range * are specified in negative indices, the indices are wrapped around the end * of the curve. That way, a smoothing range in a close path can even wrap * around the connection between the last and the first segment. * * Four different smoothing methods are available: * * - `'continuous'` smooths the path item by adjusting its curve handles so * that the first and second derivatives of all involved curves are * continuous across their boundaries. * * This method tends to result in the smoothest results, but does not * allow for further parametrization of the handles. * * - `'asymmetric'` is based on the same principle as `'continuous'` but * uses different factors so that the result is asymmetric. This used to * the only method available until v0.10.0, and is currently still the * default when no method is specified, for reasons of backward * compatibility. It will eventually be removed. * * - `'catmull-rom'` uses the Catmull-Rom spline to smooth the segment. * * The optionally passed factor controls the knot parametrization of the * algorithm: * * - `0.0`: the standard, uniform Catmull-Rom spline * - `0.5`: the centripetal Catmull-Rom spline, guaranteeing no * self-intersections * - `1.0`: the chordal Catmull-Rom spline * * - `'geometric'` use a simple heuristic and empiric geometric method to * smooth the segment's handles. The handles were weighted, meaning that * big differences in distances between the segments will lead to * probably undesired results. * * The optionally passed factor defines the tension parameter (`0...1`), * controlling the amount of smoothing as a factor by which to scale * each handle. * * @name PathItem#smooth * @function * * @option [options.type='asymmetric'] {String} the type of smoothing * method: {@values 'continuous', 'asymmetric', 'catmull-rom', * 'geometric'} * @option options.factor {Number} the factor parameterizing the smoothing * method — default: `0.5` for `'catmull-rom'`, `0.4` for `'geometric'` * @option options.from {Number|Segment|Curve} the segment or curve at which * to start smoothing, if not the full path shall be smoothed * (inclusive). This can either be a segment index, or a segment or * curve object that is part of the path. If the passed number is * negative, the index is wrapped around the end of the path. * @option options.to {Number|Segment|Curve} the segment or curve to which * the handles of the path shall be processed (inclusive). This can * either be a segment index, or a segment or curve object that is part * of the path. If the passed number is negative, the index is wrapped * around the end of the path. * * @param {Object} [options] the smoothing options * @see Segment#smooth([options]) * * @example {@paperscript} * // Smoothing a closed shape: * * // Create a rectangular path with its top-left point at * // {x: 30, y: 25} and a size of {width: 50, height: 50}: * var path = new Path.Rectangle({ * point: [30, 25], * size: [50, 50], * strokeColor: 'black', * }); * * // Select the path, so we can see its handles: * path.fullySelected = true; * * // Create a copy of the path and move it 100 to the right: * var copy = path.clone(); * copy.position.x += 100; * * // Smooth the segments of the copy: * copy.smooth({ type: 'continuous' }); * * @example {@paperscript height=220} * var path = new Path(); * path.strokeColor = 'black'; * * path.add(new Point(30, 50)); * * var y = 5; * var x = 3; * * for (var i = 0; i < 28; i++) { * y *= -1.1; * x *= 1.1; * path.lineBy(x, y); * } * * // Create a copy of the path and move it 100 down: * var copy = path.clone(); * copy.position.y += 120; * * // Select the path, so we can see its handles: * copy.fullySelected = true; * * // Smooth the path using centripetal Catmull-Rom splines: * copy.smooth({ type: 'catmull-rom', factor: 0.5 }); * * @example {@paperscript height=110} * // Smoothing ranges of paths, using segments, curves or indices: * * // Create 5 rectangles, next to each other: * var paths = []; * for (var i = 0; i < 5; i++) { * paths.push(new Path.Rectangle({ * point: [30 + i * 100, 30], * size: [50, 50], * fullySelected: true * })); * } * // Smooth a range, using segments: * paths[1].smooth({ * type: 'continuous', * from: paths[1].segments[0], * to: paths[1].segments[2] * }); * * // Smooth a range, using curves: * paths[2].smooth({ * type: 'continuous', * from: paths[2].curves[0], * to: paths[2].curves[1] * }); * * // Smooth a range, using indices: * paths[3].smooth({ type: 'continuous', from: 0, to: 2 }); * * // Smooth a range, using negative indices: * paths[4].smooth({ type: 'continuous', from: -1, to: 1 }); */ /** * Fits a sequence of as few curves as possible through the path's anchor * points, ignoring the path items's curve-handles, with an allowed maximum * error. When called on {@link CompoundPath} items, each of the nested * paths is simplified. On {@link Path} items, the {@link Path#segments} * array is processed and replaced by the resulting sequence of fitted * curves. * * This method can be used to process and simplify the point data received * from a mouse or touch device. * * @name PathItem#simplify * @function * * @param {Number} [tolerance=2.5] the allowed maximum error when fitting * the curves through the segment points * @return {Boolean} {@true if the method was capable of fitting curves * through the path's segment points} * * @example {@paperscript height=300} * // Click and drag below to draw to draw a line, when you release the * // mouse, the is made smooth using path.simplify(): * * var path; * function onMouseDown(event) { * // If we already made a path before, deselect it: * if (path) { * path.selected = false; * } * * // Create a new path and add the position of the mouse * // as its first segment. Select it, so we can see the * // segment points: * path = new Path({ * segments: [event.point], * strokeColor: 'black', * selected: true * }); * } * * function onMouseDrag(event) { * // On every drag event, add a segment to the path * // at the position of the mouse: * path.add(event.point); * } * * function onMouseUp(event) { * // When the mouse is released, simplify the path: * path.simplify(); * path.selected = true; * } */ /** * Interpolates between the two specified path items and uses the result * as the geometry for this path item. The number of children and * segments in the two paths involved in the operation should be the same. * * @param {PathItem} from the path item defining the geometry when `factor` * is `0` * @param {PathItem} to the path item defining the geometry when `factor` * is `1` * @param {Number} factor the interpolation coefficient, typically between * `0` and `1`, but extrapolation is possible too */ interpolate: function (from, to, factor) { var isPath = !this._children, name = isPath ? '_segments' : '_children', itemsFrom = from[name], itemsTo = to[name], items = this[name]; if (!itemsFrom || !itemsTo || itemsFrom.length !== itemsTo.length) { throw new Error('Invalid operands in interpolate() call: ' + from + ', ' + to); } var current = items.length, length = itemsTo.length; if (current < length) { var ctor = isPath ? Segment : ref.Path; for (var i = current; i < length; i++) { this.add(new ctor()); } } else if (current > length) { this[isPath ? 'removeSegments' : 'removeChildren'](length, current); } // @ts-expect-error = Subsequent variable declarations must have the same type for (var i = 0; i < length; i++) { items[i].interpolate(itemsFrom[i], itemsTo[i], factor); } if (isPath) { this.setClosed(from._closed); this._changed(/*#=*/ Change.GEOMETRY); } }, /** * Compares the geometry of two paths to see if they describe the same * shape, detecting cases where paths start in different segments or even * use different amounts of curves to describe the same shape, as long as * their orientation is the same, and their segments and handles really * result in the same visual appearance of curves. * * @name PathItem#compare * @function * * @param {PathItem} path the path to compare this path's geometry with * @return {Boolean} {@true if two paths describe the same shape} */ compare: function (path) { var ok = false; if (path) { var paths1 = this._children || [this], paths2 = path._children ? path._children.slice() : [path], length1 = paths1.length, length2 = paths2.length, matched = [], count = 0; ok = true; var boundsOverlaps = CollisionDetection.findItemBoundsCollisions(paths1, paths2, Numerical.GEOMETRIC_EPSILON); for (var i1 = length1 - 1; i1 >= 0 && ok; i1--) { var path1 = paths1[i1]; ok = false; var pathBoundsOverlaps = boundsOverlaps[i1]; if (pathBoundsOverlaps) { for (var i2 = pathBoundsOverlaps.length - 1; i2 >= 0 && !ok; i2--) { if (path1.compare(paths2[pathBoundsOverlaps[i2]])) { if (!matched[pathBoundsOverlaps[i2]]) { matched[pathBoundsOverlaps[i2]] = true; count++; } ok = true; } } } } // Each path in path2 needs to be matched at least once. ok = ok && count === length2; } return ok; }, /** * {@grouptitle Postscript Style Drawing Commands} * * On a normal empty {@link Path}, the point is simply added as the path's * first segment. If called on a {@link CompoundPath}, a new {@link Path} is * created as a child and the point is added as its first segment. * * @name PathItem#moveTo * @function * * @param {Point} point the point in which to start the path */ /** * Adds a straight curve to the path, from the the last segment in the path * to the specified point. * * @name PathItem#lineTo * @function * * @param {Point} point the destination point of the newly added straight * curve */ /** * Adds an arc from the position of the last segment in the path, passing * through the specified `through` point, to the specified `to` point, by * adding one or more segments to the path. * * @name PathItem#arcTo * @function * * @param {Point} through the point where the arc should pass through * @param {Point} to the point where the arc should end * * @example {@paperscript} * var path = new Path(); * path.strokeColor = 'black'; * * var firstPoint = new Point(30, 75); * path.add(firstPoint); * * // The point through which we will create the arc: * var throughPoint = new Point(40, 40); * * // The point at which the arc will end: * var toPoint = new Point(130, 75); * * // Draw an arc through 'throughPoint' to 'toPoint' * path.arcTo(throughPoint, toPoint); * * // Add a red circle shaped path at the position of 'throughPoint': * var circle = new Path.Circle(throughPoint, 3); * circle.fillColor = 'red'; * * @example {@paperscript height=300} * // Interactive example. Click and drag in the view below: * * var myPath; * function onMouseDrag(event) { * // If we created a path before, remove it: * if (myPath) { * myPath.remove(); * } * * // Create a new path and add a segment point to it * // at {x: 150, y: 150): * myPath = new Path(); * myPath.add(150, 150); * * // Draw an arc through the position of the mouse to 'toPoint' * var toPoint = new Point(350, 150); * myPath.arcTo(event.point, toPoint); * * // Select the path, so we can see its segments: * myPath.selected = true; * } * * // When the mouse is released, deselect the path * // and fill it with black. * function onMouseUp(event) { * myPath.selected = false; * myPath.fillColor = 'black'; * } */ /** * Adds an arc from the position of the last segment in the path to * the specified point, by adding one or more segments to the path. * * @name PathItem#arcTo * @function * * @param {Point} to the point where the arc should end * @param {Boolean} [clockwise=true] specifies whether the arc should be * drawn in clockwise direction * * @example {@paperscript} * var path = new Path(); * path.strokeColor = 'black'; * * path.add(new Point(30, 75)); * path.arcTo(new Point(130, 75)); * * var path2 = new Path(); * path2.strokeColor = 'red'; * path2.add(new Point(180, 25)); * * // To draw an arc in anticlockwise direction, * // we pass `false` as the second argument to arcTo: * path2.arcTo(new Point(280, 25), false); * * @example {@paperscript height=300} * // Interactive example. Click and drag in the view below: * var myPath; * * // The mouse has to move at least 20 points before * // the next mouse drag event is fired: * tool.minDistance = 20; * * // When the user clicks, create a new path and add * // the current mouse position to it as its first segment: * function onMouseDown(event) { * myPath = new Path(); * myPath.strokeColor = 'black'; * myPath.add(event.point); * } * * // On each mouse drag event, draw an arc to the current * // position of the mouse: * function onMouseDrag(event) { * myPath.arcTo(event.point); * } */ // DOCS: PathItem#arcTo(to, radius, rotation, clockwise, large) /** * Adds a curve from the last segment in the path through the specified * `through` point, to the specified destination point by adding one segment * to the path. * * @name PathItem#curveTo * @function * * @param {Point} through the point through which the curve should pass * @param {Point} to the destination point of the newly added curve * @param {Number} [time=0.5] the curve-time parameter at which the * `through` point is to be located * * @example {@paperscript height=300} * // Interactive example. Move your mouse around the view below: * * var myPath; * function onMouseMove(event) { * // If we created a path before, remove it: * if (myPath) { * myPath.remove(); * } * * // Create a new path and add a segment point to it * // at {x: 150, y: 150): * myPath = new Path(); * myPath.add(150, 150); * * // Draw a curve through the position of the mouse to 'toPoint' * var toPoint = new Point(350, 150); * myPath.curveTo(event.point, toPoint); * * // Select the path, so we can see its segments: * myPath.selected = true; * } */ /** * Adds a cubic bezier curve to the path, from the last segment to the * specified destination point, with the curve itself defined by two * specified handles. * * @name PathItem#cubicCurveTo * @function * * @param {Point} handle1 the location of the first handle of the newly * added curve in absolute coordinates, out of which the relative values * for {@link Segment#handleOut} of its first segment are calculated * @param {Point} handle2 the location of the second handle of the newly * added curve in absolute coordinates, out of which the relative values * for {@link Segment#handleIn} of its second segment are calculated * @param {Point} to the destination point of the newly added curve */ /** * Adds a quadratic bezier curve to the path, from the last segment to the * specified destination point, with the curve itself defined by the * specified handle. * * Note that Paper.js only stores cubic curves, so the handle is actually * converted. * * @name PathItem#quadraticCurveTo * @function * * @param {Point} handle the location of the handle of the newly added * quadratic curve in absolute coordinates, out of which the relative * values for {@link Segment#handleOut} of the resulting cubic curve's * first segment and {@link Segment#handleIn} of its second segment are * calculated * @param {Point} to the destination point of the newly added curve */ /** * Closes the path. When closed, Paper.js connects the first and last * segment of the path with an additional curve. The difference to setting * {@link Path#closed} to `true` is that this will also merge the first * segment with the last if they lie in the same location. * * @name PathItem#closePath * @function * * @see Path#closed */ /** * {@grouptitle Relative Drawing Commands} * * If called on a {@link CompoundPath}, a new {@link Path} is created as a * child and a point is added as its first segment relative to the position * of the last segment of the current path. * * @name PathItem#moveBy * @function * * @param {Point} to */ /** * Adds a straight curve to the path, from the the last segment in the path * to the `to` vector specified relatively to it. * * @name PathItem#lineBy * @function * * @param {Point} point the vector describing the destination of the newly * added straight curve * * @example {@paperscript} * var path = new Path(); * path.strokeColor = 'black'; * * // Add a segment at {x: 50, y: 50} * path.add(25, 25); * * // Add a segment relative to the last segment of the path. * // 50 in x direction and 0 in y direction, becomes {x: 75, y: 25} * path.lineBy(50, 0); * * // 0 in x direction and 50 in y direction, becomes {x: 75, y: 75} * path.lineBy(0, 50); * * @example {@paperscript height=300} * // Drawing a spiral using lineBy: * var path = new Path(); * path.strokeColor = 'black'; * * // Add the first segment at {x: 50, y: 50} * path.add(view.center); * * // Loop 500 times: * for (var i = 0; i < 500; i++) { * // Create a vector with an ever increasing length * // and an angle in increments of 45 degrees * var vector = new Point({ * angle: i * 45, * length: i / 2 * }); * // Add the vector relatively to the last segment point: * path.lineBy(vector); * } * * // Smooth the handles of the path: * path.smooth(); * * // Uncomment the following line and click on 'run' to see * // the construction of the path: * // path.selected = true; */ /** * Adds an arc from the position of the last segment in the path, passing * through the specified `through` vector, to the specified `to` vector, all * specified relatively to it by these given vectors, by adding one or more * segments to the path. * * @name PathItem#arcBy * @function * * @param {Point} through the vector where the arc should pass through * @param {Point} to the vector where the arc should end */ /** * Adds an arc from the position of the last segment in the path to the `to` * vector specified relatively to it, by adding one or more segments to the * path. * * @name PathItem#arcBy * @function * * @param {Point} to the vector where the arc should end * @param {Boolean} [clockwise=true] specifies whether the arc should be * drawn in clockwise direction */ /** * Adds a curve from the last segment in the path through the specified * `through` vector, to the specified `to` vector, all specified relatively * to it by these given vectors, by adding one segment to the path. * * @name PathItem#curveBy * @function * * @param {Point} through the vector through which the curve should pass * @param {Point} to the destination vector of the newly added curve * @param {Number} [time=0.5] the curve-time parameter at which the * `through` point is to be located */ /** * Adds a cubic bezier curve to the path, from the last segment to the * to the specified `to` vector, with the curve itself defined by two * specified handles. * * @name PathItem#cubicCurveBy * @function * * @param {Point} handle1 the location of the first handle of the newly * added curve * @param {Point} handle2 the location of the second handle of the newly * added curve * @param {Point} to the destination point of the newly added curve */ /** * Adds a quadratic bezier curve to the path, from the last segment to the * specified destination point, with the curve itself defined by the * specified handle. * * Note that Paper.js only stores cubic curves, so the handle is actually * converted. * * @name PathItem#quadraticCurveBy * @function * * @param {Point} handle the handle of the newly added quadratic curve out * of which the values for {@link Segment#handleOut} of the resulting * cubic curve's first segment and {@link Segment#handleIn} of its * second segment are calculated * @param {Point} to the destination point of the newly added curve */ } ); ref.PathItem = PathItem; |