fabric 4.6.0-browser → 5.1.0-browser

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/fabric.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /* build: `node build.js modules=ALL exclude=gestures,accessors,erasing requirejs minifier=uglifyjs` */
2
2
  /*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */
3
3
 
4
- var fabric = fabric || { version: '4.6.0' };
4
+ var fabric = fabric || { version: '5.1.0' };
5
5
  if (typeof exports !== 'undefined') {
6
6
  exports.fabric = fabric;
7
7
  }
@@ -762,6 +762,135 @@ fabric.CommonMethods = {
762
762
  };
763
763
  },
764
764
 
765
+ /**
766
+ * Creates a vetor from points represented as a point
767
+ * @static
768
+ * @memberOf fabric.util
769
+ *
770
+ * @typedef {Object} Point
771
+ * @property {number} x
772
+ * @property {number} y
773
+ *
774
+ * @param {Point} from
775
+ * @param {Point} to
776
+ * @returns {Point} vector
777
+ */
778
+ createVector: function (from, to) {
779
+ return new fabric.Point(to.x - from.x, to.y - from.y);
780
+ },
781
+
782
+ /**
783
+ * Calculates angle between 2 vectors using dot product
784
+ * @static
785
+ * @memberOf fabric.util
786
+ * @param {Point} a
787
+ * @param {Point} b
788
+ * @returns the angle in radian between the vectors
789
+ */
790
+ calcAngleBetweenVectors: function (a, b) {
791
+ return Math.acos((a.x * b.x + a.y * b.y) / (Math.hypot(a.x, a.y) * Math.hypot(b.x, b.y)));
792
+ },
793
+
794
+ /**
795
+ * @static
796
+ * @memberOf fabric.util
797
+ * @param {Point} v
798
+ * @returns {Point} vector representing the unit vector of pointing to the direction of `v`
799
+ */
800
+ getHatVector: function (v) {
801
+ return new fabric.Point(v.x, v.y).multiply(1 / Math.hypot(v.x, v.y));
802
+ },
803
+
804
+ /**
805
+ * @static
806
+ * @memberOf fabric.util
807
+ * @param {Point} A
808
+ * @param {Point} B
809
+ * @param {Point} C
810
+ * @returns {{ vector: Point, angle: number }} vector representing the bisector of A and A's angle
811
+ */
812
+ getBisector: function (A, B, C) {
813
+ var AB = fabric.util.createVector(A, B), AC = fabric.util.createVector(A, C);
814
+ var alpha = fabric.util.calcAngleBetweenVectors(AB, AC);
815
+ // check if alpha is relative to AB->BC
816
+ var ro = fabric.util.calcAngleBetweenVectors(fabric.util.rotateVector(AB, alpha), AC);
817
+ var phi = alpha * (ro === 0 ? 1 : -1) / 2;
818
+ return {
819
+ vector: fabric.util.getHatVector(fabric.util.rotateVector(AB, phi)),
820
+ angle: alpha
821
+ };
822
+ },
823
+
824
+ /**
825
+ * Project stroke width on points returning 2 projections for each point as follows:
826
+ * - `miter`: 2 points corresponding to the outer boundary and the inner boundary of stroke.
827
+ * - `bevel`: 2 points corresponding to the bevel boundaries, tangent to the bisector.
828
+ * - `round`: same as `bevel`
829
+ * Used to calculate object's bounding box
830
+ * @static
831
+ * @memberOf fabric.util
832
+ * @param {Point[]} points
833
+ * @param {Object} options
834
+ * @param {number} options.strokeWidth
835
+ * @param {'miter'|'bevel'|'round'} options.strokeLineJoin
836
+ * @param {number} options.strokeMiterLimit https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-miterlimit
837
+ * @param {boolean} options.strokeUniform
838
+ * @param {number} options.scaleX
839
+ * @param {number} options.scaleY
840
+ * @param {boolean} [openPath] whether the shape is open or not, affects the calculations of the first and last points
841
+ * @returns {fabric.Point[]} array of size 2n/4n of all suspected points
842
+ */
843
+ projectStrokeOnPoints: function (points, options, openPath) {
844
+ var coords = [], s = options.strokeWidth / 2,
845
+ strokeUniformScalar = options.strokeUniform ?
846
+ new fabric.Point(1 / options.scaleX, 1 / options.scaleY) : new fabric.Point(1, 1),
847
+ getStrokeHatVector = function (v) {
848
+ var scalar = s / (Math.hypot(v.x, v.y));
849
+ return new fabric.Point(v.x * scalar * strokeUniformScalar.x, v.y * scalar * strokeUniformScalar.y);
850
+ };
851
+ if (points.length <= 1) {return coords;}
852
+ points.forEach(function (p, index) {
853
+ var A = new fabric.Point(p.x, p.y), B, C;
854
+ if (index === 0) {
855
+ C = points[index + 1];
856
+ B = openPath ? getStrokeHatVector(fabric.util.createVector(C, A)).addEquals(A) : points[points.length - 1];
857
+ }
858
+ else if (index === points.length - 1) {
859
+ B = points[index - 1];
860
+ C = openPath ? getStrokeHatVector(fabric.util.createVector(B, A)).addEquals(A) : points[0];
861
+ }
862
+ else {
863
+ B = points[index - 1];
864
+ C = points[index + 1];
865
+ }
866
+ var bisector = fabric.util.getBisector(A, B, C),
867
+ bisectorVector = bisector.vector,
868
+ alpha = bisector.angle,
869
+ scalar,
870
+ miterVector;
871
+ if (options.strokeLineJoin === 'miter') {
872
+ scalar = -s / Math.sin(alpha / 2);
873
+ miterVector = new fabric.Point(
874
+ bisectorVector.x * scalar * strokeUniformScalar.x,
875
+ bisectorVector.y * scalar * strokeUniformScalar.y
876
+ );
877
+ if (Math.hypot(miterVector.x, miterVector.y) / s <= options.strokeMiterLimit) {
878
+ coords.push(A.add(miterVector));
879
+ coords.push(A.subtract(miterVector));
880
+ return;
881
+ }
882
+ }
883
+ scalar = -s * Math.SQRT2;
884
+ miterVector = new fabric.Point(
885
+ bisectorVector.x * scalar * strokeUniformScalar.x,
886
+ bisectorVector.y * scalar * strokeUniformScalar.y
887
+ );
888
+ coords.push(A.add(miterVector));
889
+ coords.push(A.subtract(miterVector));
890
+ });
891
+ return coords;
892
+ },
893
+
765
894
  /**
766
895
  * Apply transform t to point p
767
896
  * @static
@@ -1074,6 +1203,25 @@ fabric.CommonMethods = {
1074
1203
  });
1075
1204
  },
1076
1205
 
1206
+ /**
1207
+ * Creates corresponding fabric instances residing in an object, e.g. `clipPath`
1208
+ * @see {@link fabric.Object.ENLIVEN_PROPS}
1209
+ * @param {Object} object
1210
+ * @param {Object} [context] assign enlived props to this object (pass null to skip this)
1211
+ * @param {(objects:fabric.Object[]) => void} callback
1212
+ */
1213
+ enlivenObjectEnlivables: function (object, context, callback) {
1214
+ var enlivenProps = fabric.Object.ENLIVEN_PROPS.filter(function (key) { return !!object[key]; });
1215
+ fabric.util.enlivenObjects(enlivenProps.map(function (key) { return object[key]; }), function (enlivedProps) {
1216
+ var objects = {};
1217
+ enlivenProps.forEach(function (key, index) {
1218
+ objects[key] = enlivedProps[index];
1219
+ context && (context[key] = enlivedProps[index]);
1220
+ });
1221
+ callback && callback(objects);
1222
+ });
1223
+ },
1224
+
1077
1225
  /**
1078
1226
  * Create and wait for loading of patterns
1079
1227
  * @static
@@ -1165,49 +1313,6 @@ fabric.CommonMethods = {
1165
1313
  }
1166
1314
  },
1167
1315
 
1168
- /**
1169
- * WARNING: THIS WAS TO SUPPORT OLD BROWSERS. deprecated.
1170
- * WILL BE REMOVED IN FABRIC 5.0
1171
- * Draws a dashed line between two points
1172
- *
1173
- * This method is used to draw dashed line around selection area.
1174
- * See <a href="http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas">dotted stroke in canvas</a>
1175
- *
1176
- * @param {CanvasRenderingContext2D} ctx context
1177
- * @param {Number} x start x coordinate
1178
- * @param {Number} y start y coordinate
1179
- * @param {Number} x2 end x coordinate
1180
- * @param {Number} y2 end y coordinate
1181
- * @param {Array} da dash array pattern
1182
- * @deprecated
1183
- */
1184
- drawDashedLine: function(ctx, x, y, x2, y2, da) {
1185
- var dx = x2 - x,
1186
- dy = y2 - y,
1187
- len = sqrt(dx * dx + dy * dy),
1188
- rot = atan2(dy, dx),
1189
- dc = da.length,
1190
- di = 0,
1191
- draw = true;
1192
-
1193
- ctx.save();
1194
- ctx.translate(x, y);
1195
- ctx.moveTo(0, 0);
1196
- ctx.rotate(rot);
1197
-
1198
- x = 0;
1199
- while (len > x) {
1200
- x += da[di++ % dc];
1201
- if (x > len) {
1202
- x = len;
1203
- }
1204
- ctx[draw ? 'lineTo' : 'moveTo'](x, 0);
1205
- draw = !draw;
1206
- }
1207
-
1208
- ctx.restore();
1209
- },
1210
-
1211
1316
  /**
1212
1317
  * Creates canvas element
1213
1318
  * @static
@@ -1335,7 +1440,7 @@ fabric.CommonMethods = {
1335
1440
  * @param {Boolean} [options.flipX]
1336
1441
  * @param {Boolean} [options.flipY]
1337
1442
  * @param {Number} [options.skewX]
1338
- * @param {Number} [options.skewX]
1443
+ * @param {Number} [options.skewY]
1339
1444
  * @return {Number[]} transform matrix
1340
1445
  */
1341
1446
  calcDimensionsMatrix: function(options) {
@@ -1689,7 +1794,50 @@ fabric.CommonMethods = {
1689
1794
  x: bbox.width,
1690
1795
  y: bbox.height,
1691
1796
  };
1692
- }
1797
+ },
1798
+
1799
+ /**
1800
+ * Merges 2 clip paths into one visually equal clip path
1801
+ *
1802
+ * **IMPORTANT**:\
1803
+ * Does **NOT** clone the arguments, clone them proir if necessary.
1804
+ *
1805
+ * Creates a wrapper (group) that contains one clip path and is clipped by the other so content is kept where both overlap.
1806
+ * Use this method if both the clip paths may have nested clip paths of their own, so assigning one to the other's clip path property is not possible.
1807
+ *
1808
+ * In order to handle the `inverted` property we follow logic described in the following cases:\
1809
+ * **(1)** both clip paths are inverted - the clip paths pass the inverted prop to the wrapper and loose it themselves.\
1810
+ * **(2)** one is inverted and the other isn't - the wrapper shouldn't become inverted and the inverted clip path must clip the non inverted one to produce an identical visual effect.\
1811
+ * **(3)** both clip paths are not inverted - wrapper and clip paths remain unchanged.
1812
+ *
1813
+ * @memberOf fabric.util
1814
+ * @param {fabric.Object} c1
1815
+ * @param {fabric.Object} c2
1816
+ * @returns {fabric.Object} merged clip path
1817
+ */
1818
+ mergeClipPaths: function (c1, c2) {
1819
+ var a = c1, b = c2;
1820
+ if (a.inverted && !b.inverted) {
1821
+ // case (2)
1822
+ a = c2;
1823
+ b = c1;
1824
+ }
1825
+ // `b` becomes `a`'s clip path so we transform `b` to `a` coordinate plane
1826
+ fabric.util.applyTransformToObject(
1827
+ b,
1828
+ fabric.util.multiplyTransformMatrices(
1829
+ fabric.util.invertTransform(a.calcTransformMatrix()),
1830
+ b.calcTransformMatrix()
1831
+ )
1832
+ );
1833
+ // assign the `inverted` prop to the wrapping group
1834
+ var inverted = a.inverted && b.inverted;
1835
+ if (inverted) {
1836
+ // case (1)
1837
+ a.inverted = b.inverted = false;
1838
+ }
1839
+ return new fabric.Group([a], { clipPath: b, inverted: inverted });
1840
+ },
1693
1841
  };
1694
1842
  })(typeof exports !== 'undefined' ? exports : this);
1695
1843
 
@@ -2203,15 +2351,15 @@ fabric.CommonMethods = {
2203
2351
  p, nextLen, nextStep = 0.01, angleFinder = segInfo.angleFinder, lastPerc;
2204
2352
  // nextStep > 0.0001 covers 0.00015625 that 1/64th of 1/100
2205
2353
  // the path
2206
- while (tmpLen < distance && perc <= 1 && nextStep > 0.0001) {
2354
+ while (tmpLen < distance && nextStep > 0.0001) {
2207
2355
  p = iterator(perc);
2208
2356
  lastPerc = perc;
2209
2357
  nextLen = calcLineLength(tempP.x, tempP.y, p.x, p.y);
2210
2358
  // compare tmpLen each cycle with distance, decide next perc to test.
2211
2359
  if ((nextLen + tmpLen) > distance) {
2212
2360
  // we discard this step and we make smaller steps.
2213
- nextStep /= 2;
2214
2361
  perc -= nextStep;
2362
+ nextStep /= 2;
2215
2363
  }
2216
2364
  else {
2217
2365
  tempP = p;
@@ -2507,50 +2655,6 @@ fabric.CommonMethods = {
2507
2655
  });
2508
2656
  }
2509
2657
 
2510
- /**
2511
- * Calculate bounding box of a elliptic-arc
2512
- * @deprecated
2513
- * @param {Number} fx start point of arc
2514
- * @param {Number} fy
2515
- * @param {Number} rx horizontal radius
2516
- * @param {Number} ry vertical radius
2517
- * @param {Number} rot angle of horizontal axis
2518
- * @param {Number} large 1 or 0, whatever the arc is the big or the small on the 2 points
2519
- * @param {Number} sweep 1 or 0, 1 clockwise or counterclockwise direction
2520
- * @param {Number} tx end point of arc
2521
- * @param {Number} ty
2522
- */
2523
- function getBoundsOfArc(fx, fy, rx, ry, rot, large, sweep, tx, ty) {
2524
-
2525
- var fromX = 0, fromY = 0, bound, bounds = [],
2526
- segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot);
2527
-
2528
- for (var i = 0, len = segs.length; i < len; i++) {
2529
- bound = getBoundsOfCurve(fromX, fromY, segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5], segs[i][6]);
2530
- bounds.push({ x: bound[0].x + fx, y: bound[0].y + fy });
2531
- bounds.push({ x: bound[1].x + fx, y: bound[1].y + fy });
2532
- fromX = segs[i][5];
2533
- fromY = segs[i][6];
2534
- }
2535
- return bounds;
2536
- };
2537
-
2538
- /**
2539
- * Draws arc
2540
- * @deprecated
2541
- * @param {CanvasRenderingContext2D} ctx
2542
- * @param {Number} fx
2543
- * @param {Number} fy
2544
- * @param {Array} coords coords of the arc, without the front 'A/a'
2545
- */
2546
- function drawArc(ctx, fx, fy, coords) {
2547
- coords = coords.slice(0).unshift('X'); // command A or a does not matter
2548
- var beziers = fromArcToBeziers(fx, fy, coords);
2549
- beziers.forEach(function(bezier) {
2550
- ctx.bezierCurveTo.apply(ctx, bezier.slice(1));
2551
- });
2552
- };
2553
-
2554
2658
  /**
2555
2659
  * Join path commands to go back to svg format
2556
2660
  * @param {Array} pathData fabricJS parsed path commands
@@ -2566,16 +2670,6 @@ fabric.CommonMethods = {
2566
2670
  fabric.util.getBoundsOfCurve = getBoundsOfCurve;
2567
2671
  fabric.util.getPointOnPath = getPointOnPath;
2568
2672
  fabric.util.transformPath = transformPath;
2569
- /**
2570
- * Typo of `fromArcToBeziers` kept for not breaking the api once corrected.
2571
- * Will be removed in fabric 5.0
2572
- * @deprecated
2573
- */
2574
- fabric.util.fromArcToBeizers = fromArcToBeziers;
2575
- // kept because we do not want to make breaking changes.
2576
- // but useless and deprecated.
2577
- fabric.util.getBoundsOfArc = getBoundsOfArc;
2578
- fabric.util.drawArc = drawArc;
2579
2673
  })();
2580
2674
 
2581
2675
 
@@ -3476,7 +3570,132 @@ fabric.log = console.log;
3476
3570
  fabric.warn = console.warn;
3477
3571
 
3478
3572
 
3479
- (function() {
3573
+ (function () {
3574
+
3575
+ var extend = fabric.util.object.extend,
3576
+ clone = fabric.util.object.clone;
3577
+
3578
+ /**
3579
+ * @typedef {Object} AnimationOptions
3580
+ * Animation of a value or list of values.
3581
+ * When using lists, think of something like this:
3582
+ * fabric.util.animate({
3583
+ * startValue: [1, 2, 3],
3584
+ * endValue: [2, 4, 6],
3585
+ * onChange: function([a, b, c]) {
3586
+ * canvas.zoomToPoint({x: b, y: c}, a)
3587
+ * canvas.renderAll()
3588
+ * }
3589
+ * });
3590
+ * @example
3591
+ * @property {Function} [onChange] Callback; invoked on every value change
3592
+ * @property {Function} [onComplete] Callback; invoked when value change is completed
3593
+ * @example
3594
+ * // Note: startValue, endValue, and byValue must match the type
3595
+ * var animationOptions = { startValue: 0, endValue: 1, byValue: 0.25 }
3596
+ * var animationOptions = { startValue: [0, 1], endValue: [1, 2], byValue: [0.25, 0.25] }
3597
+ * @property {number | number[]} [startValue=0] Starting value
3598
+ * @property {number | number[]} [endValue=100] Ending value
3599
+ * @property {number | number[]} [byValue=100] Value to modify the property by
3600
+ * @property {Function} [easing] Easing function
3601
+ * @property {Number} [duration=500] Duration of change (in ms)
3602
+ * @property {Function} [abort] Additional function with logic. If returns true, animation aborts.
3603
+ *
3604
+ * @typedef {() => void} CancelFunction
3605
+ *
3606
+ * @typedef {Object} AnimationCurrentState
3607
+ * @property {number | number[]} currentValue value in range [`startValue`, `endValue`]
3608
+ * @property {number} completionRate value in range [0, 1]
3609
+ * @property {number} durationRate value in range [0, 1]
3610
+ *
3611
+ * @typedef {(AnimationOptions & AnimationCurrentState & { cancel: CancelFunction }} AnimationContext
3612
+ */
3613
+
3614
+ /**
3615
+ * Array holding all running animations
3616
+ * @memberof fabric
3617
+ * @type {AnimationContext[]}
3618
+ */
3619
+ var RUNNING_ANIMATIONS = [];
3620
+ fabric.util.object.extend(RUNNING_ANIMATIONS, {
3621
+
3622
+ /**
3623
+ * cancel all running animations at the next requestAnimFrame
3624
+ * @returns {AnimationContext[]}
3625
+ */
3626
+ cancelAll: function () {
3627
+ var animations = this.splice(0);
3628
+ animations.forEach(function (animation) {
3629
+ animation.cancel();
3630
+ });
3631
+ return animations;
3632
+ },
3633
+
3634
+ /**
3635
+ * cancel all running animations attached to canvas at the next requestAnimFrame
3636
+ * @param {fabric.Canvas} canvas
3637
+ * @returns {AnimationContext[]}
3638
+ */
3639
+ cancelByCanvas: function (canvas) {
3640
+ if (!canvas) {
3641
+ return [];
3642
+ }
3643
+ var cancelled = this.filter(function (animation) {
3644
+ return typeof animation.target === 'object' && animation.target.canvas === canvas;
3645
+ });
3646
+ cancelled.forEach(function (animation) {
3647
+ animation.cancel();
3648
+ });
3649
+ return cancelled;
3650
+ },
3651
+
3652
+ /**
3653
+ * cancel all running animations for target at the next requestAnimFrame
3654
+ * @param {*} target
3655
+ * @returns {AnimationContext[]}
3656
+ */
3657
+ cancelByTarget: function (target) {
3658
+ var cancelled = this.findAnimationsByTarget(target);
3659
+ cancelled.forEach(function (animation) {
3660
+ animation.cancel();
3661
+ });
3662
+ return cancelled;
3663
+ },
3664
+
3665
+ /**
3666
+ *
3667
+ * @param {CancelFunction} cancelFunc the function returned by animate
3668
+ * @returns {number}
3669
+ */
3670
+ findAnimationIndex: function (cancelFunc) {
3671
+ return this.indexOf(this.findAnimation(cancelFunc));
3672
+ },
3673
+
3674
+ /**
3675
+ *
3676
+ * @param {CancelFunction} cancelFunc the function returned by animate
3677
+ * @returns {AnimationContext | undefined} animation's options object
3678
+ */
3679
+ findAnimation: function (cancelFunc) {
3680
+ return this.find(function (animation) {
3681
+ return animation.cancel === cancelFunc;
3682
+ });
3683
+ },
3684
+
3685
+ /**
3686
+ *
3687
+ * @param {*} target the object that is assigned to the target property of the animation context
3688
+ * @returns {AnimationContext[]} array of animation options object associated with target
3689
+ */
3690
+ findAnimationsByTarget: function (target) {
3691
+ if (!target) {
3692
+ return [];
3693
+ }
3694
+ return this.filter(function (animation) {
3695
+ return animation.target === target;
3696
+ });
3697
+ }
3698
+ });
3480
3699
 
3481
3700
  function noop() {
3482
3701
  return false;
@@ -3489,22 +3708,34 @@ fabric.warn = console.warn;
3489
3708
  /**
3490
3709
  * Changes value from one to another within certain period of time, invoking callbacks as value is being changed.
3491
3710
  * @memberOf fabric.util
3492
- * @param {Object} [options] Animation options
3493
- * @param {Function} [options.onChange] Callback; invoked on every value change
3494
- * @param {Function} [options.onComplete] Callback; invoked when value change is completed
3495
- * @param {Number} [options.startValue=0] Starting value
3496
- * @param {Number} [options.endValue=100] Ending value
3497
- * @param {Number} [options.byValue=100] Value to modify the property by
3498
- * @param {Function} [options.easing] Easing function
3499
- * @param {Number} [options.duration=500] Duration of change (in ms)
3500
- * @param {Function} [options.abort] Additional function with logic. If returns true, onComplete is called.
3501
- * @returns {Function} abort function
3711
+ * @param {AnimationOptions} [options] Animation options
3712
+ * @example
3713
+ * // Note: startValue, endValue, and byValue must match the type
3714
+ * fabric.util.animate({ startValue: 0, endValue: 1, byValue: 0.25 })
3715
+ * fabric.util.animate({ startValue: [0, 1], endValue: [1, 2], byValue: [0.25, 0.25] })
3716
+ * @returns {CancelFunction} cancel function
3502
3717
  */
3503
3718
  function animate(options) {
3504
- var cancel = false;
3505
- requestAnimFrame(function(timestamp) {
3506
- options || (options = { });
3719
+ options || (options = {});
3720
+ var cancel = false,
3721
+ context,
3722
+ removeFromRegistry = function () {
3723
+ var index = fabric.runningAnimations.indexOf(context);
3724
+ return index > -1 && fabric.runningAnimations.splice(index, 1)[0];
3725
+ };
3726
+
3727
+ context = extend(clone(options), {
3728
+ cancel: function () {
3729
+ cancel = true;
3730
+ return removeFromRegistry();
3731
+ },
3732
+ currentValue: 'startValue' in options ? options.startValue : 0,
3733
+ completionRate: 0,
3734
+ durationRate: 0
3735
+ });
3736
+ fabric.runningAnimations.push(context);
3507
3737
 
3738
+ requestAnimFrame(function(timestamp) {
3508
3739
  var start = timestamp || +new Date(),
3509
3740
  duration = options.duration || 500,
3510
3741
  finish = start + duration, time,
@@ -3512,32 +3743,44 @@ fabric.warn = console.warn;
3512
3743
  abort = options.abort || noop,
3513
3744
  onComplete = options.onComplete || noop,
3514
3745
  easing = options.easing || defaultEasing,
3746
+ isMany = 'startValue' in options ? options.startValue.length > 0 : false,
3515
3747
  startValue = 'startValue' in options ? options.startValue : 0,
3516
3748
  endValue = 'endValue' in options ? options.endValue : 100,
3517
- byValue = options.byValue || endValue - startValue;
3749
+ byValue = options.byValue || (isMany ? startValue.map(function(value, i) {
3750
+ return endValue[i] - startValue[i];
3751
+ }) : endValue - startValue);
3518
3752
 
3519
3753
  options.onStart && options.onStart();
3520
3754
 
3521
3755
  (function tick(ticktime) {
3522
- // TODO: move abort call after calculation
3523
- // and pass (current,valuePerc, timePerc) as arguments
3524
3756
  time = ticktime || +new Date();
3525
3757
  var currentTime = time > finish ? duration : (time - start),
3526
3758
  timePerc = currentTime / duration,
3527
- current = easing(currentTime, startValue, byValue, duration),
3528
- valuePerc = Math.abs((current - startValue) / byValue);
3759
+ current = isMany ? startValue.map(function(_value, i) {
3760
+ return easing(currentTime, startValue[i], byValue[i], duration);
3761
+ }) : easing(currentTime, startValue, byValue, duration),
3762
+ valuePerc = isMany ? Math.abs((current[0] - startValue[0]) / byValue[0])
3763
+ : Math.abs((current - startValue) / byValue);
3764
+ // update context
3765
+ context.currentValue = isMany ? current.slice() : current;
3766
+ context.completionRate = valuePerc;
3767
+ context.durationRate = timePerc;
3529
3768
  if (cancel) {
3530
3769
  return;
3531
3770
  }
3532
3771
  if (abort(current, valuePerc, timePerc)) {
3533
- // remove this in 4.0
3534
- // does to even make sense to abort and run onComplete?
3535
- onComplete(endValue, 1, 1);
3772
+ removeFromRegistry();
3536
3773
  return;
3537
3774
  }
3538
3775
  if (time > finish) {
3539
- onChange(endValue, 1, 1);
3776
+ // update context
3777
+ context.currentValue = isMany ? endValue.slice() : endValue;
3778
+ context.completionRate = 1;
3779
+ context.durationRate = 1;
3780
+ // execute callbacks
3781
+ onChange(isMany ? endValue.slice() : endValue, 1, 1);
3540
3782
  onComplete(endValue, 1, 1);
3783
+ removeFromRegistry();
3541
3784
  return;
3542
3785
  }
3543
3786
  else {
@@ -3546,9 +3789,8 @@ fabric.warn = console.warn;
3546
3789
  }
3547
3790
  })(start);
3548
3791
  });
3549
- return function() {
3550
- cancel = true;
3551
- };
3792
+
3793
+ return context.cancel;
3552
3794
  }
3553
3795
 
3554
3796
  var _requestAnimFrame = fabric.window.requestAnimationFrame ||
@@ -3580,6 +3822,7 @@ fabric.warn = console.warn;
3580
3822
  fabric.util.animate = animate;
3581
3823
  fabric.util.requestAnimFrame = requestAnimFrame;
3582
3824
  fabric.util.cancelAnimFrame = cancelAnimFrame;
3825
+ fabric.runningAnimations = RUNNING_ANIMATIONS;
3583
3826
  })();
3584
3827
 
3585
3828
 
@@ -5062,22 +5305,26 @@ fabric.warn = console.warn;
5062
5305
  if (styleContents.trim() === '') {
5063
5306
  continue;
5064
5307
  }
5065
- rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);
5066
- rules = rules.map(function(rule) { return rule.trim(); });
5308
+ // recovers all the rule in this form `body { style code... }`
5309
+ // rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);
5310
+ rules = styleContents.split('}');
5311
+ // remove empty rules.
5312
+ rules = rules.filter(function(rule) { return rule.trim(); });
5313
+ // at this point we have hopefully an array of rules `body { style code... `
5067
5314
  // eslint-disable-next-line no-loop-func
5068
5315
  rules.forEach(function(rule) {
5069
5316
 
5070
- var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/),
5071
- ruleObj = { }, declaration = match[2].trim(),
5072
- propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/);
5317
+ var match = rule.split('{'),
5318
+ ruleObj = { }, declaration = match[1].trim(),
5319
+ propertyValuePairs = declaration.split(';').filter(function(pair) { return pair.trim(); });
5073
5320
 
5074
5321
  for (i = 0, len = propertyValuePairs.length; i < len; i++) {
5075
- var pair = propertyValuePairs[i].split(/\s*:\s*/),
5076
- property = pair[0],
5077
- value = pair[1];
5322
+ var pair = propertyValuePairs[i].split(':'),
5323
+ property = pair[0].trim(),
5324
+ value = pair[1].trim();
5078
5325
  ruleObj[property] = value;
5079
5326
  }
5080
- rule = match[1];
5327
+ rule = match[0].trim();
5081
5328
  rule.split(',').forEach(function(_rule) {
5082
5329
  _rule = _rule.replace(/^svg/i, '').trim();
5083
5330
  if (_rule === '') {
@@ -8652,8 +8899,12 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
8652
8899
  imageSmoothingEnabled: true,
8653
8900
 
8654
8901
  /**
8655
- * The transformation (in the format of Canvas transform) which focuses the viewport
8902
+ * The transformation (a Canvas 2D API transform matrix) which focuses the viewport
8656
8903
  * @type Array
8904
+ * @example <caption>Default transform</caption>
8905
+ * canvas.viewportTransform = [1, 0, 0, 1, 0, 0];
8906
+ * @example <caption>Scale by 70% and translate toward bottom-right by 50, without skewing</caption>
8907
+ * canvas.viewportTransform = [0.7, 0, 0, 0.7, 50, 50];
8657
8908
  * @default
8658
8909
  */
8659
8910
  viewportTransform: fabric.iMatrix.concat(),
@@ -8747,7 +8998,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
8747
8998
  * @private
8748
8999
  */
8749
9000
  _isRetinaScaling: function() {
8750
- return (fabric.devicePixelRatio !== 1 && this.enableRetinaScaling);
9001
+ return (fabric.devicePixelRatio > 1 && this.enableRetinaScaling);
8751
9002
  },
8752
9003
 
8753
9004
  /**
@@ -8755,7 +9006,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
8755
9006
  * @return {Number} retinaScaling if applied, otherwise 1;
8756
9007
  */
8757
9008
  getRetinaScaling: function() {
8758
- return this._isRetinaScaling() ? fabric.devicePixelRatio : 1;
9009
+ return this._isRetinaScaling() ? Math.max(1, fabric.devicePixelRatio) : 1;
8759
9010
  },
8760
9011
 
8761
9012
  /**
@@ -9122,7 +9373,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
9122
9373
  }
9123
9374
  }
9124
9375
  if (this._isCurrentlyDrawing) {
9125
- this.freeDrawingBrush && this.freeDrawingBrush._setBrushStyles();
9376
+ this.freeDrawingBrush && this.freeDrawingBrush._setBrushStyles(this.contextTop);
9126
9377
  }
9127
9378
  this._initRetinaScaling();
9128
9379
  this.calcOffset();
@@ -9189,8 +9440,8 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
9189
9440
  },
9190
9441
 
9191
9442
  /**
9192
- * Sets viewport transform of this canvas instance
9193
- * @param {Array} vpt the transform in the form of context.transform
9443
+ * Sets viewport transformation of this canvas instance
9444
+ * @param {Array} vpt a Canvas 2D API transform matrix
9194
9445
  * @return {fabric.Canvas} instance
9195
9446
  * @chainable true
9196
9447
  */
@@ -10291,7 +10542,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
10291
10542
  this.contextContainer = null;
10292
10543
  // restore canvas style
10293
10544
  this.lowerCanvasEl.classList.remove('lower-canvas');
10294
- this.lowerCanvasEl.style = this._originalCanvasStyle;
10545
+ fabric.util.setStyle(this.lowerCanvasEl, this._originalCanvasStyle);
10295
10546
  delete this._originalCanvasStyle;
10296
10547
  // restore canvas size to original size in case retina scaling was applied
10297
10548
  this.lowerCanvasEl.setAttribute('width', this.width);
@@ -10459,9 +10710,9 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10459
10710
  /**
10460
10711
  * Sets brush styles
10461
10712
  * @private
10713
+ * @param {CanvasRenderingContext2D} ctx
10462
10714
  */
10463
- _setBrushStyles: function() {
10464
- var ctx = this.canvas.contextTop;
10715
+ _setBrushStyles: function (ctx) {
10465
10716
  ctx.strokeStyle = this.color;
10466
10717
  ctx.lineWidth = this.width;
10467
10718
  ctx.lineCap = this.strokeLineCap;
@@ -10546,6 +10797,22 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10546
10797
  */
10547
10798
  decimate: 0.4,
10548
10799
 
10800
+ /**
10801
+ * Draws a straight line between last recorded point to current pointer
10802
+ * Used for `shift` functionality
10803
+ *
10804
+ * @type boolean
10805
+ * @default false
10806
+ */
10807
+ drawStraightLine: false,
10808
+
10809
+ /**
10810
+ * The event modifier key that makes the brush draw a straight line.
10811
+ * If `null` or 'none' or any other string that is not a modifier key the feature is disabled.
10812
+ * @type {'altKey' | 'shiftKey' | 'ctrlKey' | 'none' | undefined | null}
10813
+ */
10814
+ straightLineKey: 'shiftKey',
10815
+
10549
10816
  /**
10550
10817
  * Constructor
10551
10818
  * @param {fabric.Canvas} canvas
@@ -10556,6 +10823,10 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10556
10823
  this._points = [];
10557
10824
  },
10558
10825
 
10826
+ needsFullRender: function () {
10827
+ return this.callSuper('needsFullRender') || this._hasStraightLine;
10828
+ },
10829
+
10559
10830
  /**
10560
10831
  * Invoked inside on mouse down and mouse move
10561
10832
  * @param {Object} pointer
@@ -10574,6 +10845,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10574
10845
  if (!this.canvas._isMainEvent(options.e)) {
10575
10846
  return;
10576
10847
  }
10848
+ this.drawStraightLine = options.e[this.straightLineKey];
10577
10849
  this._prepareForDrawing(pointer);
10578
10850
  // capture coordinates immediately
10579
10851
  // this allows to draw dots (when movement never occurs)
@@ -10589,6 +10861,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10589
10861
  if (!this.canvas._isMainEvent(options.e)) {
10590
10862
  return;
10591
10863
  }
10864
+ this.drawStraightLine = options.e[this.straightLineKey];
10592
10865
  if (this.limitedToCanvasSize === true && this._isOutSideCanvas(pointer)) {
10593
10866
  return;
10594
10867
  }
@@ -10621,6 +10894,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10621
10894
  if (!this.canvas._isMainEvent(options.e)) {
10622
10895
  return true;
10623
10896
  }
10897
+ this.drawStraightLine = false;
10624
10898
  this.oldEnd = undefined;
10625
10899
  this._finalizeAndAddPath();
10626
10900
  return false;
@@ -10647,6 +10921,10 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10647
10921
  if (this._points.length > 1 && point.eq(this._points[this._points.length - 1])) {
10648
10922
  return false;
10649
10923
  }
10924
+ if (this.drawStraightLine && this._points.length > 1) {
10925
+ this._hasStraightLine = true;
10926
+ this._points.pop();
10927
+ }
10650
10928
  this._points.push(point);
10651
10929
  return true;
10652
10930
  },
@@ -10657,8 +10935,9 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10657
10935
  */
10658
10936
  _reset: function() {
10659
10937
  this._points = [];
10660
- this._setBrushStyles();
10938
+ this._setBrushStyles(this.canvas.contextTop);
10661
10939
  this._setShadow();
10940
+ this._hasStraightLine = false;
10662
10941
  },
10663
10942
 
10664
10943
  /**
@@ -10673,12 +10952,13 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10673
10952
  /**
10674
10953
  * Draw a smooth path on the topCanvas using quadraticCurveTo
10675
10954
  * @private
10955
+ * @param {CanvasRenderingContext2D} [ctx]
10676
10956
  */
10677
- _render: function() {
10678
- var ctx = this.canvas.contextTop, i, len,
10957
+ _render: function(ctx) {
10958
+ var i, len,
10679
10959
  p1 = this._points[0],
10680
10960
  p2 = this._points[1];
10681
-
10961
+ ctx = ctx || this.canvas.contextTop;
10682
10962
  this._saveAndTransform(ctx);
10683
10963
  ctx.beginPath();
10684
10964
  //if we only have 2 points in the path and they are the same
@@ -11212,17 +11492,19 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
11212
11492
 
11213
11493
  /**
11214
11494
  * Creates "pattern" instance property
11495
+ * @param {CanvasRenderingContext2D} ctx
11215
11496
  */
11216
- getPattern: function() {
11217
- return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), 'repeat');
11497
+ getPattern: function(ctx) {
11498
+ return ctx.createPattern(this.source || this.getPatternSrc(), 'repeat');
11218
11499
  },
11219
11500
 
11220
11501
  /**
11221
11502
  * Sets brush styles
11503
+ * @param {CanvasRenderingContext2D} ctx
11222
11504
  */
11223
- _setBrushStyles: function() {
11224
- this.callSuper('_setBrushStyles');
11225
- this.canvas.contextTop.strokeStyle = this.getPattern();
11505
+ _setBrushStyles: function(ctx) {
11506
+ this.callSuper('_setBrushStyles', ctx);
11507
+ ctx.strokeStyle = this.getPattern(ctx);
11226
11508
  },
11227
11509
 
11228
11510
  /**
@@ -11281,15 +11563,11 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
11281
11563
  * @fires dragover
11282
11564
  * @fires dragenter
11283
11565
  * @fires dragleave
11566
+ * @fires drop:before before drop event. same native event. This is added to handle edge cases
11284
11567
  * @fires drop
11285
11568
  * @fires after:render at the end of the render process, receives the context in the callback
11286
11569
  * @fires before:render at start the render process, receives the context in the callback
11287
11570
  *
11288
- * the following events are deprecated:
11289
- * @fires object:rotated at the end of a rotation transform
11290
- * @fires object:scaled at the end of a scale transform
11291
- * @fires object:moved at the end of translation transform
11292
- * @fires object:skewed at the end of a skew transform
11293
11571
  */
11294
11572
  fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, /** @lends fabric.Canvas.prototype */ {
11295
11573
 
@@ -11474,13 +11752,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
11474
11752
  */
11475
11753
  freeDrawingCursor: 'crosshair',
11476
11754
 
11477
- /**
11478
- * Cursor value used for rotation point
11479
- * @type String
11480
- * @default
11481
- */
11482
- rotationCursor: 'crosshair',
11483
-
11484
11755
  /**
11485
11756
  * Cursor value used for disabled elements ( corners with disabled action )
11486
11757
  * @type String
@@ -11586,6 +11857,13 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
11586
11857
  */
11587
11858
  targets: [],
11588
11859
 
11860
+ /**
11861
+ * When the option is enabled, PointerEvent is used instead of MouseEvent.
11862
+ * @type Boolean
11863
+ * @default
11864
+ */
11865
+ enablePointerEvents: false,
11866
+
11589
11867
  /**
11590
11868
  * Keep track of the hovered target
11591
11869
  * @type fabric.Object
@@ -11661,6 +11939,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
11661
11939
  }
11662
11940
  if (this.hasLostContext) {
11663
11941
  this.renderTopLayer(this.contextTop);
11942
+ this.hasLostContext = false;
11664
11943
  }
11665
11944
  var canvasToDrawOn = this.contextContainer;
11666
11945
  this.renderCanvas(canvasToDrawOn, this._chooseObjectsToRender());
@@ -12330,17 +12609,12 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
12330
12609
  e: e,
12331
12610
  selected: added,
12332
12611
  deselected: removed,
12333
- // added for backward compatibility
12334
- // deprecated
12335
- updated: added[0] || removed[0],
12336
- target: this._activeObject,
12337
12612
  });
12338
12613
  }
12339
12614
  else if (objects.length > 0) {
12340
12615
  this.fire('selection:created', {
12341
12616
  e: e,
12342
12617
  selected: added,
12343
- target: this._activeObject,
12344
12618
  });
12345
12619
  }
12346
12620
  else if (oldObjects.length > 0) {
@@ -12664,7 +12938,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
12664
12938
  this._onDragOver = this._onDragOver.bind(this);
12665
12939
  this._onDragEnter = this._simpleEventHandler.bind(this, 'dragenter');
12666
12940
  this._onDragLeave = this._simpleEventHandler.bind(this, 'dragleave');
12667
- this._onDrop = this._simpleEventHandler.bind(this, 'drop');
12941
+ this._onDrop = this._onDrop.bind(this);
12668
12942
  this.eventsBound = true;
12669
12943
  },
12670
12944
 
@@ -12776,6 +13050,18 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
12776
13050
  this._fireEnterLeaveEvents(target, e);
12777
13051
  },
12778
13052
 
13053
+ /**
13054
+ * `drop:before` is a an event that allow you to schedule logic
13055
+ * before the `drop` event. Prefer `drop` event always, but if you need
13056
+ * to run some drop-disabling logic on an event, since there is no way
13057
+ * to handle event handlers ordering, use `drop:before`
13058
+ * @param {Event} e
13059
+ */
13060
+ _onDrop: function (e) {
13061
+ this._simpleEventHandler('drop:before', e);
13062
+ return this._simpleEventHandler('drop', e);
13063
+ },
13064
+
12779
13065
  /**
12780
13066
  * @private
12781
13067
  * @param {Event} e Event object fired on mousedown
@@ -13008,25 +13294,34 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
13008
13294
  );
13009
13295
  }
13010
13296
  }
13297
+ var corner, pointer;
13011
13298
  if (target) {
13299
+ corner = target._findTargetCorner(
13300
+ this.getPointer(e, true),
13301
+ fabric.util.isTouchEvent(e)
13302
+ );
13012
13303
  if (target.selectable && target !== this._activeObject && target.activeOn === 'up') {
13013
13304
  this.setActiveObject(target, e);
13014
13305
  shouldRender = true;
13015
13306
  }
13016
13307
  else {
13017
- var corner = target._findTargetCorner(
13018
- this.getPointer(e, true),
13019
- fabric.util.isTouchEvent(e)
13020
- );
13021
13308
  var control = target.controls[corner],
13022
13309
  mouseUpHandler = control && control.getMouseUpHandler(e, target, control);
13023
13310
  if (mouseUpHandler) {
13024
- var pointer = this.getPointer(e);
13311
+ pointer = this.getPointer(e);
13025
13312
  mouseUpHandler(e, transform, pointer.x, pointer.y);
13026
13313
  }
13027
13314
  }
13028
13315
  target.isMoving = false;
13029
13316
  }
13317
+ // if we are ending up a transform on a different control or a new object
13318
+ // fire the original mouse up from the corner that started the transform
13319
+ if (transform && (transform.target !== target || transform.corner !== corner)) {
13320
+ var originalControl = transform.target && transform.target.controls[transform.corner],
13321
+ originalMouseUpHandler = originalControl && originalControl.getMouseUpHandler(e, target, control);
13322
+ pointer = pointer || this.getPointer(e);
13323
+ originalMouseUpHandler && originalMouseUpHandler(e, transform, pointer.x, pointer.y);
13324
+ }
13030
13325
  this._setCursorFromEvent(e, target);
13031
13326
  this._handleEvent(e, 'up', LEFT_CLICK, isClick);
13032
13327
  this._groupSelector = null;
@@ -13108,7 +13403,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
13108
13403
 
13109
13404
  var transform = this._currentTransform,
13110
13405
  target = transform.target,
13111
- eventName,
13112
13406
  options = {
13113
13407
  e: e,
13114
13408
  target: target,
@@ -13123,59 +13417,10 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
13123
13417
  target.setCoords();
13124
13418
 
13125
13419
  if (transform.actionPerformed || (this.stateful && target.hasStateChanged())) {
13126
- if (transform.actionPerformed) {
13127
- // this is not friendly to the new control api.
13128
- // is deprecated.
13129
- eventName = this._addEventOptions(options, transform);
13130
- this._fire(eventName, options);
13131
- }
13132
13420
  this._fire('modified', options);
13133
13421
  }
13134
13422
  },
13135
13423
 
13136
- /**
13137
- * Mutate option object in order to add by property and give back the event name.
13138
- * @private
13139
- * @deprecated since 4.2.0
13140
- * @param {Object} options to mutate
13141
- * @param {Object} transform to inspect action from
13142
- */
13143
- _addEventOptions: function(options, transform) {
13144
- // we can probably add more details at low cost
13145
- // scale change, rotation changes, translation changes
13146
- var eventName, by;
13147
- switch (transform.action) {
13148
- case 'scaleX':
13149
- eventName = 'scaled';
13150
- by = 'x';
13151
- break;
13152
- case 'scaleY':
13153
- eventName = 'scaled';
13154
- by = 'y';
13155
- break;
13156
- case 'skewX':
13157
- eventName = 'skewed';
13158
- by = 'x';
13159
- break;
13160
- case 'skewY':
13161
- eventName = 'skewed';
13162
- by = 'y';
13163
- break;
13164
- case 'scale':
13165
- eventName = 'scaled';
13166
- by = 'equally';
13167
- break;
13168
- case 'rotate':
13169
- eventName = 'rotated';
13170
- break;
13171
- case 'drag':
13172
- eventName = 'moved';
13173
- break;
13174
- }
13175
- options.by = by;
13176
- return eventName;
13177
- },
13178
-
13179
13424
  /**
13180
13425
  * @private
13181
13426
  * @param {Event} e Event object fired on mousedown
@@ -14615,6 +14860,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
14615
14860
  /**
14616
14861
  * When `false`, the stoke width will scale with the object.
14617
14862
  * When `true`, the stroke will always match the exact pixel size entered for stroke width.
14863
+ * this Property does not work on Text classes or drawing call that uses strokeText,fillText methods
14618
14864
  * default to false
14619
14865
  * @since 2.6.0
14620
14866
  * @type Boolean
@@ -15265,26 +15511,26 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
15265
15511
  /**
15266
15512
  * Execute the drawing operation for an object clipPath
15267
15513
  * @param {CanvasRenderingContext2D} ctx Context to render on
15514
+ * @param {fabric.Object} clipPath
15268
15515
  */
15269
- drawClipPathOnCache: function(ctx) {
15270
- var path = this.clipPath;
15516
+ drawClipPathOnCache: function(ctx, clipPath) {
15271
15517
  ctx.save();
15272
15518
  // DEBUG: uncomment this line, comment the following
15273
15519
  // ctx.globalAlpha = 0.4
15274
- if (path.inverted) {
15520
+ if (clipPath.inverted) {
15275
15521
  ctx.globalCompositeOperation = 'destination-out';
15276
15522
  }
15277
15523
  else {
15278
15524
  ctx.globalCompositeOperation = 'destination-in';
15279
15525
  }
15280
15526
  //ctx.scale(1 / 2, 1 / 2);
15281
- if (path.absolutePositioned) {
15527
+ if (clipPath.absolutePositioned) {
15282
15528
  var m = fabric.util.invertTransform(this.calcTransformMatrix());
15283
15529
  ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
15284
15530
  }
15285
- path.transform(ctx);
15286
- ctx.scale(1 / path.zoomX, 1 / path.zoomY);
15287
- ctx.drawImage(path._cacheCanvas, -path.cacheTranslationX, -path.cacheTranslationY);
15531
+ clipPath.transform(ctx);
15532
+ ctx.scale(1 / clipPath.zoomX, 1 / clipPath.zoomY);
15533
+ ctx.drawImage(clipPath._cacheCanvas, -clipPath.cacheTranslationX, -clipPath.cacheTranslationY);
15288
15534
  ctx.restore();
15289
15535
  },
15290
15536
 
@@ -15303,22 +15549,26 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
15303
15549
  this._renderBackground(ctx);
15304
15550
  }
15305
15551
  this._render(ctx);
15306
- this._drawClipPath(ctx);
15552
+ this._drawClipPath(ctx, this.clipPath);
15307
15553
  this.fill = originalFill;
15308
15554
  this.stroke = originalStroke;
15309
15555
  },
15310
15556
 
15311
- _drawClipPath: function(ctx) {
15312
- var path = this.clipPath;
15313
- if (!path) { return; }
15557
+ /**
15558
+ * Prepare clipPath state and cache and draw it on instance's cache
15559
+ * @param {CanvasRenderingContext2D} ctx
15560
+ * @param {fabric.Object} clipPath
15561
+ */
15562
+ _drawClipPath: function (ctx, clipPath) {
15563
+ if (!clipPath) { return; }
15314
15564
  // needed to setup a couple of variables
15315
15565
  // path canvas gets overridden with this one.
15316
15566
  // TODO find a better solution?
15317
- path.canvas = this.canvas;
15318
- path.shouldCache();
15319
- path._transformDone = true;
15320
- path.renderCache({ forClipping: true });
15321
- this.drawClipPathOnCache(ctx);
15567
+ clipPath.canvas = this.canvas;
15568
+ clipPath.shouldCache();
15569
+ clipPath._transformDone = true;
15570
+ clipPath.renderCache({ forClipping: true });
15571
+ this.drawClipPathOnCache(ctx, clipPath);
15322
15572
  },
15323
15573
 
15324
15574
  /**
@@ -15462,6 +15712,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
15462
15712
 
15463
15713
  /**
15464
15714
  * Renders controls and borders for the object
15715
+ * the context here is not transformed
15465
15716
  * @param {CanvasRenderingContext2D} ctx Context to render on
15466
15717
  * @param {Object} [styleOverride] properties to override the object style
15467
15718
  */
@@ -15480,7 +15731,10 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
15480
15731
  if (!this.group) {
15481
15732
  ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
15482
15733
  }
15483
- ctx.rotate(degreesToRadians(options.angle));
15734
+ if (this.flipX) {
15735
+ options.angle -= 180;
15736
+ }
15737
+ ctx.rotate(degreesToRadians(this.group ? options.angle : this.angle));
15484
15738
  if (styleOverride.forActiveSelection || this.group) {
15485
15739
  drawBorders && this.drawBordersInGroup(ctx, options, styleOverride);
15486
15740
  }
@@ -16016,6 +16270,16 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
16016
16270
  if (this.globalCompositeOperation) {
16017
16271
  ctx.globalCompositeOperation = this.globalCompositeOperation;
16018
16272
  }
16273
+ },
16274
+
16275
+ /**
16276
+ * cancel instance's running animations
16277
+ * override if necessary to dispose artifacts such as `clipPath`
16278
+ */
16279
+ dispose: function () {
16280
+ if (fabric.runningAnimations) {
16281
+ fabric.runningAnimations.cancelByTarget(this);
16282
+ }
16019
16283
  }
16020
16284
  });
16021
16285
 
@@ -16033,6 +16297,15 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
16033
16297
  */
16034
16298
  fabric.Object.NUM_FRACTION_DIGITS = 2;
16035
16299
 
16300
+ /**
16301
+ * Defines which properties should be enlivened from the object passed to {@link fabric.Object._fromObject}
16302
+ * @static
16303
+ * @memberOf fabric.Object
16304
+ * @constant
16305
+ * @type string[]
16306
+ */
16307
+ fabric.Object.ENLIVEN_PROPS = ['clipPath'];
16308
+
16036
16309
  fabric.Object._fromObject = function(className, object, callback, extraParam) {
16037
16310
  var klass = fabric[className];
16038
16311
  object = clone(object, true);
@@ -16043,8 +16316,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
16043
16316
  if (typeof patterns[1] !== 'undefined') {
16044
16317
  object.stroke = patterns[1];
16045
16318
  }
16046
- fabric.util.enlivenObjects([object.clipPath], function(enlivedProps) {
16047
- object.clipPath = enlivedProps[0];
16319
+ fabric.util.enlivenObjectEnlivables(object, object, function () {
16048
16320
  var instance = extraParam ? new klass(object[extraParam], object) : new klass(object);
16049
16321
  callback && callback(instance);
16050
16322
  });
@@ -16365,7 +16637,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
16365
16637
  /**
16366
16638
  * Describe object's corner position in canvas element coordinates.
16367
16639
  * includes padding. Used of object detection.
16368
- * set and refreshed with setCoords and calcCoords.
16640
+ * set and refreshed with setCoords.
16369
16641
  * @memberOf fabric.Object.prototype
16370
16642
  */
16371
16643
  lineCoords: null,
@@ -16749,21 +17021,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
16749
17021
  return this.scale(value / this.height / boundingRectFactor);
16750
17022
  },
16751
17023
 
16752
- /**
16753
- * Calculates and returns the .coords of an object.
16754
- * unused by the library, only for the end dev.
16755
- * @return {Object} Object with tl, tr, br, bl ....
16756
- * @chainable
16757
- * @deprecated
16758
- */
16759
- calcCoords: function(absolute) {
16760
- // this is a compatibility function to avoid removing calcCoords now.
16761
- if (absolute) {
16762
- return this.calcACoords();
16763
- }
16764
- return this.calcOCoords();
16765
- },
16766
-
16767
17024
  calcLineCoords: function() {
16768
17025
  var vpt = this.getViewportTransform(),
16769
17026
  padding = this.padding, angle = degreesToRadians(this.angle),
@@ -16838,7 +17095,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
16838
17095
  * oCoords are used to find the corners
16839
17096
  * aCoords are used to quickly find an object on the canvas
16840
17097
  * lineCoords are used to quickly find object during pointer events.
16841
- * See {@link https://github.com/kangax/fabric.js/wiki/When-to-call-setCoords|When-to-call-setCoords}
17098
+ * See {@link https://github.com/fabricjs/fabric.js/wiki/When-to-call-setCoords} and {@link http://fabricjs.com/fabric-gotchas}
17099
+ *
16842
17100
  * @param {Boolean} [skipCorners] skip calculation of oCoords.
16843
17101
  * @return {fabric.Object} thisArg
16844
17102
  * @chainable
@@ -16935,23 +17193,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
16935
17193
  return cache.value;
16936
17194
  },
16937
17195
 
16938
- /*
16939
- * Calculate object dimensions from its properties
16940
- * @private
16941
- * @deprecated since 3.4.0, please use fabric.util._calcDimensionsTransformMatrix
16942
- * not including or including flipX, flipY to emulate the flipping boolean
16943
- * @return {Object} .x width dimension
16944
- * @return {Object} .y height dimension
16945
- */
16946
- _calcDimensionsTransformMatrix: function(skewX, skewY, flipping) {
16947
- return util.calcDimensionsMatrix({
16948
- skewX: skewX,
16949
- skewY: skewY,
16950
- scaleX: this.scaleX * (flipping && this.flipX ? -1 : 1),
16951
- scaleY: this.scaleY * (flipping && this.flipY ? -1 : 1)
16952
- });
16953
- },
16954
-
16955
17196
  /*
16956
17197
  * Calculate object dimensions from its properties
16957
17198
  * @private
@@ -17816,8 +18057,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17816
18057
  * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
17817
18058
  * @param {Function} [callbacks.onComplete] Invoked on completion
17818
18059
  * @param {Function} [callbacks.onChange] Invoked on every step of animation
17819
- * @return {fabric.Canvas} thisArg
17820
- * @chainable
18060
+ * @return {fabric.AnimationContext} context
17821
18061
  */
17822
18062
  fxCenterObjectH: function (object, callbacks) {
17823
18063
  callbacks = callbacks || { };
@@ -17827,7 +18067,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17827
18067
  onChange = callbacks.onChange || empty,
17828
18068
  _this = this;
17829
18069
 
17830
- fabric.util.animate({
18070
+ return fabric.util.animate({
18071
+ target: this,
17831
18072
  startValue: object.left,
17832
18073
  endValue: this.getCenter().left,
17833
18074
  duration: this.FX_DURATION,
@@ -17841,8 +18082,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17841
18082
  onComplete();
17842
18083
  }
17843
18084
  });
17844
-
17845
- return this;
17846
18085
  },
17847
18086
 
17848
18087
  /**
@@ -17851,8 +18090,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17851
18090
  * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
17852
18091
  * @param {Function} [callbacks.onComplete] Invoked on completion
17853
18092
  * @param {Function} [callbacks.onChange] Invoked on every step of animation
17854
- * @return {fabric.Canvas} thisArg
17855
- * @chainable
18093
+ * @return {fabric.AnimationContext} context
17856
18094
  */
17857
18095
  fxCenterObjectV: function (object, callbacks) {
17858
18096
  callbacks = callbacks || { };
@@ -17862,7 +18100,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17862
18100
  onChange = callbacks.onChange || empty,
17863
18101
  _this = this;
17864
18102
 
17865
- fabric.util.animate({
18103
+ return fabric.util.animate({
18104
+ target: this,
17866
18105
  startValue: object.top,
17867
18106
  endValue: this.getCenter().top,
17868
18107
  duration: this.FX_DURATION,
@@ -17876,8 +18115,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17876
18115
  onComplete();
17877
18116
  }
17878
18117
  });
17879
-
17880
- return this;
17881
18118
  },
17882
18119
 
17883
18120
  /**
@@ -17886,8 +18123,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17886
18123
  * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
17887
18124
  * @param {Function} [callbacks.onComplete] Invoked on completion
17888
18125
  * @param {Function} [callbacks.onChange] Invoked on every step of animation
17889
- * @return {fabric.Canvas} thisArg
17890
- * @chainable
18126
+ * @return {fabric.AnimationContext} context
17891
18127
  */
17892
18128
  fxRemove: function (object, callbacks) {
17893
18129
  callbacks = callbacks || { };
@@ -17897,7 +18133,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17897
18133
  onChange = callbacks.onChange || empty,
17898
18134
  _this = this;
17899
18135
 
17900
- fabric.util.animate({
18136
+ return fabric.util.animate({
18137
+ target: this,
17901
18138
  startValue: object.opacity,
17902
18139
  endValue: 0,
17903
18140
  duration: this.FX_DURATION,
@@ -17911,8 +18148,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17911
18148
  onComplete();
17912
18149
  }
17913
18150
  });
17914
-
17915
- return this;
17916
18151
  }
17917
18152
  });
17918
18153
 
@@ -17923,7 +18158,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
17923
18158
  * @param {Number|Object} value Value to animate property to (if string was given first) or options object
17924
18159
  * @return {fabric.Object} thisArg
17925
18160
  * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#animation}
17926
- * @chainable
18161
+ * @return {fabric.AnimationContext | fabric.AnimationContext[]} animation context (or an array if passed multiple properties)
17927
18162
  *
17928
18163
  * As object — multiple properties
17929
18164
  *
@@ -17936,22 +18171,22 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
17936
18171
  * object.animate('left', { duration: ... });
17937
18172
  *
17938
18173
  */
17939
- animate: function() {
18174
+ animate: function () {
17940
18175
  if (arguments[0] && typeof arguments[0] === 'object') {
17941
- var propsToAnimate = [], prop, skipCallbacks;
18176
+ var propsToAnimate = [], prop, skipCallbacks, out = [];
17942
18177
  for (prop in arguments[0]) {
17943
18178
  propsToAnimate.push(prop);
17944
18179
  }
17945
18180
  for (var i = 0, len = propsToAnimate.length; i < len; i++) {
17946
18181
  prop = propsToAnimate[i];
17947
18182
  skipCallbacks = i !== len - 1;
17948
- this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks);
18183
+ out.push(this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks));
17949
18184
  }
18185
+ return out;
17950
18186
  }
17951
18187
  else {
17952
- this._animate.apply(this, arguments);
18188
+ return this._animate.apply(this, arguments);
17953
18189
  }
17954
- return this;
17955
18190
  },
17956
18191
 
17957
18192
  /**
@@ -17999,6 +18234,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
17999
18234
  }
18000
18235
 
18001
18236
  var _options = {
18237
+ target: this,
18002
18238
  startValue: options.from,
18003
18239
  endValue: to,
18004
18240
  byValue: options.by,
@@ -18370,7 +18606,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18370
18606
  'use strict';
18371
18607
 
18372
18608
  var fabric = global.fabric || (global.fabric = { }),
18373
- pi = Math.PI;
18609
+ degreesToRadians = fabric.util.degreesToRadians;
18374
18610
 
18375
18611
  if (fabric.Circle) {
18376
18612
  fabric.warn('fabric.Circle is already defined.');
@@ -18400,22 +18636,20 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18400
18636
  radius: 0,
18401
18637
 
18402
18638
  /**
18403
- * Start angle of the circle, moving clockwise
18404
- * deprecated type, this should be in degree, this was an oversight.
18639
+ * degrees of start of the circle.
18405
18640
  * probably will change to degrees in next major version
18406
- * @type Number
18641
+ * @type Number 0 - 359
18407
18642
  * @default 0
18408
18643
  */
18409
18644
  startAngle: 0,
18410
18645
 
18411
18646
  /**
18412
18647
  * End angle of the circle
18413
- * deprecated type, this should be in degree, this was an oversight.
18414
18648
  * probably will change to degrees in next major version
18415
- * @type Number
18416
- * @default 2Pi
18649
+ * @type Number 1 - 360
18650
+ * @default 360
18417
18651
  */
18418
- endAngle: pi * 2,
18652
+ endAngle: 360,
18419
18653
 
18420
18654
  cacheProperties: fabric.Object.prototype.cacheProperties.concat('radius', 'startAngle', 'endAngle'),
18421
18655
 
@@ -18453,7 +18687,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18453
18687
  */
18454
18688
  _toSVG: function() {
18455
18689
  var svgString, x = 0, y = 0,
18456
- angle = (this.endAngle - this.startAngle) % ( 2 * pi);
18690
+ angle = (this.endAngle - this.startAngle) % 360;
18457
18691
 
18458
18692
  if (angle === 0) {
18459
18693
  svgString = [
@@ -18464,14 +18698,17 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18464
18698
  ];
18465
18699
  }
18466
18700
  else {
18467
- var startX = fabric.util.cos(this.startAngle) * this.radius,
18468
- startY = fabric.util.sin(this.startAngle) * this.radius,
18469
- endX = fabric.util.cos(this.endAngle) * this.radius,
18470
- endY = fabric.util.sin(this.endAngle) * this.radius,
18471
- largeFlag = angle > pi ? '1' : '0';
18701
+ var start = degreesToRadians(this.startAngle),
18702
+ end = degreesToRadians(this.endAngle),
18703
+ radius = this.radius,
18704
+ startX = fabric.util.cos(start) * radius,
18705
+ startY = fabric.util.sin(start) * radius,
18706
+ endX = fabric.util.cos(end) * radius,
18707
+ endY = fabric.util.sin(end) * radius,
18708
+ largeFlag = angle > 180 ? '1' : '0';
18472
18709
  svgString = [
18473
18710
  '<path d="M ' + startX + ' ' + startY,
18474
- ' A ' + this.radius + ' ' + this.radius,
18711
+ ' A ' + radius + ' ' + radius,
18475
18712
  ' 0 ', +largeFlag + ' 1', ' ' + endX + ' ' + endY,
18476
18713
  '" ', 'COMMON_PARTS', ' />\n'
18477
18714
  ];
@@ -18490,8 +18727,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18490
18727
  0,
18491
18728
  0,
18492
18729
  this.radius,
18493
- this.startAngle,
18494
- this.endAngle, false);
18730
+ degreesToRadians(this.startAngle),
18731
+ degreesToRadians(this.endAngle),
18732
+ false
18733
+ );
18495
18734
  this._renderPaintInOrder(ctx);
18496
18735
  },
18497
18736
 
@@ -19049,7 +19288,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19049
19288
  extend = fabric.util.object.extend,
19050
19289
  min = fabric.util.array.min,
19051
19290
  max = fabric.util.array.max,
19052
- toFixed = fabric.util.toFixed;
19291
+ toFixed = fabric.util.toFixed,
19292
+ projectStrokeOnPoints = fabric.util.projectStrokeOnPoints;
19053
19293
 
19054
19294
  if (fabric.Polyline) {
19055
19295
  fabric.warn('fabric.Polyline is already defined');
@@ -19078,6 +19318,17 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19078
19318
  */
19079
19319
  points: null,
19080
19320
 
19321
+ /**
19322
+ * WARNING: Feature in progress
19323
+ * Calculate the exact bounding box taking in account strokeWidth on acute angles
19324
+ * this will be turned to true by default on fabric 6.0
19325
+ * maybe will be left in as an optimization since calculations may be slow
19326
+ * @deprecated
19327
+ * @type Boolean
19328
+ * @default false
19329
+ */
19330
+ exactBoundingBox: false,
19331
+
19081
19332
  cacheProperties: fabric.Object.prototype.cacheProperties.concat('points'),
19082
19333
 
19083
19334
  /**
@@ -19106,13 +19357,25 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19106
19357
  this._setPositionDimensions(options);
19107
19358
  },
19108
19359
 
19360
+ /**
19361
+ * @private
19362
+ */
19363
+ _projectStrokeOnPoints: function () {
19364
+ return projectStrokeOnPoints(this.points, this, true);
19365
+ },
19366
+
19109
19367
  _setPositionDimensions: function(options) {
19110
- var calcDim = this._calcDimensions(options), correctLeftTop;
19111
- this.width = calcDim.width;
19112
- this.height = calcDim.height;
19368
+ var calcDim = this._calcDimensions(options), correctLeftTop,
19369
+ correctSize = this.exactBoundingBox ? this.strokeWidth : 0;
19370
+ this.width = calcDim.width - correctSize;
19371
+ this.height = calcDim.height - correctSize;
19113
19372
  if (!options.fromSVG) {
19114
19373
  correctLeftTop = this.translateToGivenOrigin(
19115
- { x: calcDim.left - this.strokeWidth / 2, y: calcDim.top - this.strokeWidth / 2 },
19374
+ {
19375
+ // this looks bad, but is one way to keep it optional for now.
19376
+ x: calcDim.left - this.strokeWidth / 2 + correctSize / 2,
19377
+ y: calcDim.top - this.strokeWidth / 2 + correctSize / 2
19378
+ },
19116
19379
  'left',
19117
19380
  'top',
19118
19381
  this.originX,
@@ -19126,8 +19389,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19126
19389
  this.top = options.fromSVG ? calcDim.top : correctLeftTop.y;
19127
19390
  }
19128
19391
  this.pathOffset = {
19129
- x: calcDim.left + this.width / 2,
19130
- y: calcDim.top + this.height / 2
19392
+ x: calcDim.left + this.width / 2 + correctSize / 2,
19393
+ y: calcDim.top + this.height / 2 + correctSize / 2
19131
19394
  };
19132
19395
  },
19133
19396
 
@@ -19143,7 +19406,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19143
19406
  */
19144
19407
  _calcDimensions: function() {
19145
19408
 
19146
- var points = this.points,
19409
+ var points = this.exactBoundingBox ? this._projectStrokeOnPoints() : this.points,
19147
19410
  minX = min(points, 'x') || 0,
19148
19411
  minY = min(points, 'y') || 0,
19149
19412
  maxX = max(points, 'x') || 0,
@@ -19155,7 +19418,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19155
19418
  left: minX,
19156
19419
  top: minY,
19157
19420
  width: width,
19158
- height: height
19421
+ height: height,
19159
19422
  };
19160
19423
  },
19161
19424
 
@@ -19291,7 +19554,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19291
19554
 
19292
19555
  'use strict';
19293
19556
 
19294
- var fabric = global.fabric || (global.fabric = { });
19557
+ var fabric = global.fabric || (global.fabric = {}),
19558
+ projectStrokeOnPoints = fabric.util.projectStrokeOnPoints;
19295
19559
 
19296
19560
  if (fabric.Polygon) {
19297
19561
  fabric.warn('fabric.Polygon is already defined');
@@ -19313,6 +19577,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19313
19577
  */
19314
19578
  type: 'polygon',
19315
19579
 
19580
+ /**
19581
+ * @private
19582
+ */
19583
+ _projectStrokeOnPoints: function () {
19584
+ return projectStrokeOnPoints(this.points, this);
19585
+ },
19586
+
19316
19587
  /**
19317
19588
  * @private
19318
19589
  * @param {CanvasRenderingContext2D} ctx Context to render on
@@ -19370,6 +19641,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19370
19641
  min = fabric.util.array.min,
19371
19642
  max = fabric.util.array.max,
19372
19643
  extend = fabric.util.object.extend,
19644
+ clone = fabric.util.object.clone,
19373
19645
  _toString = Object.prototype.toString,
19374
19646
  toFixed = fabric.util.toFixed;
19375
19647
 
@@ -19411,23 +19683,26 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19411
19683
  * @param {Object} [options] Options object
19412
19684
  * @return {fabric.Path} thisArg
19413
19685
  */
19414
- initialize: function(path, options) {
19415
- options = options || { };
19686
+ initialize: function (path, options) {
19687
+ options = clone(options || {});
19688
+ delete options.path;
19416
19689
  this.callSuper('initialize', options);
19417
- if (!path) {
19418
- path = [];
19419
- }
19690
+ this._setPath(path || [], options);
19691
+ },
19420
19692
 
19693
+ /**
19694
+ * @private
19695
+ * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens)
19696
+ * @param {Object} [options] Options object
19697
+ */
19698
+ _setPath: function (path, options) {
19421
19699
  var fromArray = _toString.call(path) === '[object Array]';
19422
19700
 
19423
19701
  this.path = fabric.util.makePathSimpler(
19424
19702
  fromArray ? path : fabric.util.parsePath(path)
19425
19703
  );
19426
19704
 
19427
- if (!this.path) {
19428
- return;
19429
- }
19430
- fabric.Polyline.prototype._setPositionDimensions.call(this, options);
19705
+ fabric.Polyline.prototype._setPositionDimensions.call(this, options || {});
19431
19706
  },
19432
19707
 
19433
19708
  /**
@@ -20084,7 +20359,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
20084
20359
  for (var i = 0, len = this._objects.length; i < len; i++) {
20085
20360
  this._objects[i].render(ctx);
20086
20361
  }
20087
- this._drawClipPath(ctx);
20362
+ this._drawClipPath(ctx, this.clipPath);
20088
20363
  },
20089
20364
 
20090
20365
  /**
@@ -20130,25 +20405,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
20130
20405
  return this;
20131
20406
  },
20132
20407
 
20133
- /**
20134
- * Realises the transform from this group onto the supplied object
20135
- * i.e. it tells you what would happen if the supplied object was in
20136
- * the group, and then the group was destroyed. It mutates the supplied
20137
- * object.
20138
- * Warning: this method is not useful anymore, it has been kept to no break the api.
20139
- * is not used in the fabricJS codebase
20140
- * this method will be reduced to using the utility.
20141
- * @private
20142
- * @deprecated
20143
- * @param {fabric.Object} object
20144
- * @param {Array} parentMatrix parent transformation
20145
- * @return {fabric.Object} transformedObject
20146
- */
20147
- realizeTransform: function(object, parentMatrix) {
20148
- fabric.util.addTransformToObject(object, parentMatrix);
20149
- return object;
20150
- },
20151
-
20152
20408
  /**
20153
20409
  * Destroys a group (restoring state of its objects)
20154
20410
  * @return {fabric.Group} thisArg
@@ -20163,6 +20419,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
20163
20419
  return this._restoreObjectsState();
20164
20420
  },
20165
20421
 
20422
+ dispose: function () {
20423
+ this.callSuper('dispose');
20424
+ this.forEachObject(function (object) {
20425
+ object.dispose && object.dispose();
20426
+ });
20427
+ this._objects = [];
20428
+ },
20429
+
20166
20430
  /**
20167
20431
  * make a group an active selection, remove the group from canvas
20168
20432
  * the group has to be on canvas for this to work.
@@ -20326,11 +20590,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
20326
20590
  });
20327
20591
  return;
20328
20592
  }
20329
- fabric.util.enlivenObjects(objects, function(enlivenedObjects) {
20330
- fabric.util.enlivenObjects([object.clipPath], function(enlivedClipPath) {
20331
- var options = fabric.util.object.clone(object, true);
20332
- options.clipPath = enlivedClipPath[0];
20333
- delete options.objects;
20593
+ fabric.util.enlivenObjects(objects, function (enlivenedObjects) {
20594
+ var options = fabric.util.object.clone(object, true);
20595
+ delete options.objects;
20596
+ fabric.util.enlivenObjectEnlivables(object, options, function () {
20334
20597
  callback && callback(new fabric.Group(enlivenedObjects, options, true));
20335
20598
  });
20336
20599
  });
@@ -20700,7 +20963,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
20700
20963
  /**
20701
20964
  * Delete textures, reference to elements and eventually JSDOM cleanup
20702
20965
  */
20703
- dispose: function() {
20966
+ dispose: function () {
20967
+ this.callSuper('dispose');
20704
20968
  this.removeTexture(this.cacheKey);
20705
20969
  this.removeTexture(this.cacheKey + '_filtered');
20706
20970
  this._cacheContext = undefined;
@@ -21210,8 +21474,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
21210
21474
  object.filters = filters || [];
21211
21475
  fabric.Image.prototype._initFilters.call(object, [object.resizeFilter], function(resizeFilters) {
21212
21476
  object.resizeFilter = resizeFilters[0];
21213
- fabric.util.enlivenObjects([object.clipPath], function(enlivedProps) {
21214
- object.clipPath = enlivedProps[0];
21477
+ fabric.util.enlivenObjectEnlivables(object, object, function () {
21215
21478
  var image = new fabric.Image(img, object);
21216
21479
  callback(image, false);
21217
21480
  });
@@ -21282,8 +21545,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
21282
21545
  * @chainable
21283
21546
  */
21284
21547
  straighten: function() {
21285
- this.rotate(this._getAngleValueForStraighten());
21286
- return this;
21548
+ return this.rotate(this._getAngleValueForStraighten());
21287
21549
  },
21288
21550
 
21289
21551
  /**
@@ -21292,7 +21554,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
21292
21554
  * @param {Function} [callbacks.onComplete] Invoked on completion
21293
21555
  * @param {Function} [callbacks.onChange] Invoked on every step of animation
21294
21556
  * @return {fabric.Object} thisArg
21295
- * @chainable
21296
21557
  */
21297
21558
  fxStraighten: function(callbacks) {
21298
21559
  callbacks = callbacks || { };
@@ -21302,7 +21563,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
21302
21563
  onChange = callbacks.onChange || empty,
21303
21564
  _this = this;
21304
21565
 
21305
- fabric.util.animate({
21566
+ return fabric.util.animate({
21567
+ target: this,
21306
21568
  startValue: this.get('angle'),
21307
21569
  endValue: this._getAngleValueForStraighten(),
21308
21570
  duration: this.FX_DURATION,
@@ -21315,8 +21577,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
21315
21577
  onComplete();
21316
21578
  },
21317
21579
  });
21318
-
21319
- return this;
21320
21580
  }
21321
21581
  });
21322
21582
 
@@ -21338,13 +21598,11 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
21338
21598
  * Same as {@link fabric.Canvas.prototype.straightenObject}, but animated
21339
21599
  * @param {fabric.Object} object Object to straighten
21340
21600
  * @return {fabric.Canvas} thisArg
21341
- * @chainable
21342
21601
  */
21343
21602
  fxStraightenObject: function (object) {
21344
- object.fxStraighten({
21603
+ return object.fxStraighten({
21345
21604
  onChange: this.requestRenderAllBound
21346
21605
  });
21347
- return this;
21348
21606
  }
21349
21607
  });
21350
21608
 
@@ -22245,8 +22503,10 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
22245
22503
  mainParameter: 'matrix',
22246
22504
 
22247
22505
  /**
22248
- * Lock the colormatrix on the color part, skipping alpha, manly for non webgl scenario
22506
+ * Lock the colormatrix on the color part, skipping alpha, mainly for non webgl scenario
22249
22507
  * to save some calculation
22508
+ * @type Boolean
22509
+ * @default true
22250
22510
  */
22251
22511
  colorsOnly: true,
22252
22512
 
@@ -23645,17 +23905,23 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
23645
23905
  /**
23646
23906
  * Color to make the blend operation with. default to a reddish color since black or white
23647
23907
  * gives always strong result.
23908
+ * @type String
23909
+ * @default
23648
23910
  **/
23649
23911
  color: '#F95C63',
23650
23912
 
23651
23913
  /**
23652
23914
  * Blend mode for the filter: one of multiply, add, diff, screen, subtract,
23653
23915
  * darken, lighten, overlay, exclusion, tint.
23916
+ * @type String
23917
+ * @default
23654
23918
  **/
23655
23919
  mode: 'multiply',
23656
23920
 
23657
23921
  /**
23658
23922
  * alpha value. represent the strength of the blend color operation.
23923
+ * @type Number
23924
+ * @default
23659
23925
  **/
23660
23926
  alpha: 1,
23661
23927
 
@@ -23896,8 +24162,9 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
23896
24162
  image: null,
23897
24163
 
23898
24164
  /**
23899
- * Blend mode for the filter: one of multiply, add, diff, screen, subtract,
23900
- * darken, lighten, overlay, exclusion, tint.
24165
+ * Blend mode for the filter (one of "multiply", "mask")
24166
+ * @type String
24167
+ * @default
23901
24168
  **/
23902
24169
  mode: 'multiply',
23903
24170
 
@@ -25033,6 +25300,8 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
25033
25300
  * blur value, in percentage of image dimensions.
25034
25301
  * specific to keep the image blur constant at different resolutions
25035
25302
  * range between 0 and 1.
25303
+ * @type Number
25304
+ * @default
25036
25305
  */
25037
25306
  blur: 0,
25038
25307
 
@@ -25511,7 +25780,7 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
25511
25780
  var additionalProps =
25512
25781
  ('fontFamily fontWeight fontSize text underline overline linethrough' +
25513
25782
  ' textAlign fontStyle lineHeight textBackgroundColor charSpacing styles' +
25514
- ' direction path pathStartOffset pathSide').split(' ');
25783
+ ' direction path pathStartOffset pathSide pathAlign').split(' ');
25515
25784
 
25516
25785
  /**
25517
25786
  * Text class
@@ -25540,7 +25809,8 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
25540
25809
  'styles',
25541
25810
  'path',
25542
25811
  'pathStartOffset',
25543
- 'pathSide'
25812
+ 'pathSide',
25813
+ 'pathAlign'
25544
25814
  ],
25545
25815
 
25546
25816
  /**
@@ -25737,6 +26007,16 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
25737
26007
  */
25738
26008
  pathSide: 'left',
25739
26009
 
26010
+ /**
26011
+ * How text is aligned to the path. This property determines
26012
+ * the perpendicular position of each character relative to the path.
26013
+ * (one of "baseline", "center", "ascender", "descender")
26014
+ * This feature is in BETA, and its behavior may change
26015
+ * @type String
26016
+ * @default
26017
+ */
26018
+ pathAlign: 'baseline',
26019
+
25740
26020
  /**
25741
26021
  * @private
25742
26022
  */
@@ -25880,6 +26160,8 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
25880
26160
  /**
25881
26161
  * Return a context for measurement of text string.
25882
26162
  * if created it gets stored for reuse
26163
+ * this is for internal use, please do not use it
26164
+ * @private
25883
26165
  * @param {String} text Text string
25884
26166
  * @param {Object} [options] Options object
25885
26167
  * @return {fabric.Text} thisArg
@@ -26051,7 +26333,20 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
26051
26333
  * @param {String} [charStyle.fontStyle] Font style (italic|normal)
26052
26334
  */
26053
26335
  _setTextStyles: function(ctx, charStyle, forMeasuring) {
26054
- ctx.textBaseline = 'alphabetic';
26336
+ ctx.textBaseline = 'alphabetical';
26337
+ if (this.path) {
26338
+ switch (this.pathAlign) {
26339
+ case 'center':
26340
+ ctx.textBaseline = 'middle';
26341
+ break;
26342
+ case 'ascender':
26343
+ ctx.textBaseline = 'top';
26344
+ break;
26345
+ case 'descender':
26346
+ ctx.textBaseline = 'bottom';
26347
+ break;
26348
+ }
26349
+ }
26055
26350
  ctx.font = this._getFontDeclaration(charStyle, forMeasuring);
26056
26351
  },
26057
26352
 
@@ -26516,16 +26811,17 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
26516
26811
  path = this.path,
26517
26812
  shortCut = !isJustify && this.charSpacing === 0 && this.isEmptyStyles(lineIndex) && !path,
26518
26813
  isLtr = this.direction === 'ltr', sign = this.direction === 'ltr' ? 1 : -1,
26519
- drawingLeft;
26520
-
26814
+ drawingLeft, currentDirection = ctx.canvas.getAttribute('dir');
26521
26815
  ctx.save();
26816
+ if (currentDirection !== this.direction) {
26817
+ ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
26818
+ ctx.direction = isLtr ? 'ltr' : 'rtl';
26819
+ ctx.textAlign = isLtr ? 'left' : 'right';
26820
+ }
26522
26821
  top -= lineHeight * this._fontSizeFraction / this.lineHeight;
26523
26822
  if (shortCut) {
26524
26823
  // render all the line in one pass without checking
26525
26824
  // drawingLeft = isLtr ? left : left - this.getLineWidth(lineIndex);
26526
- ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
26527
- ctx.direction = isLtr ? 'ltr' : 'rtl';
26528
- ctx.textAlign = isLtr ? 'left' : 'right';
26529
26825
  this._renderChar(method, ctx, lineIndex, 0, line.join(''), left, top, lineHeight);
26530
26826
  ctx.restore();
26531
26827
  return;
@@ -26562,9 +26858,6 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
26562
26858
  }
26563
26859
  else {
26564
26860
  drawingLeft = left;
26565
- ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
26566
- ctx.direction = isLtr ? 'ltr' : 'rtl';
26567
- ctx.textAlign = isLtr ? 'left' : 'right';
26568
26861
  this._renderChar(method, ctx, lineIndex, i, charsToRender, drawingLeft, top, lineHeight);
26569
26862
  }
26570
26863
  charsToRender = '';
@@ -26815,19 +27108,12 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
26815
27108
  * @return {Number} Line width
26816
27109
  */
26817
27110
  getLineWidth: function(lineIndex) {
26818
- if (this.__lineWidths[lineIndex]) {
27111
+ if (this.__lineWidths[lineIndex] !== undefined) {
26819
27112
  return this.__lineWidths[lineIndex];
26820
27113
  }
26821
27114
 
26822
- var width, line = this._textLines[lineIndex], lineInfo;
26823
-
26824
- if (line === '') {
26825
- width = 0;
26826
- }
26827
- else {
26828
- lineInfo = this.measureLine(lineIndex);
26829
- width = lineInfo.width;
26830
- }
27115
+ var lineInfo = this.measureLine(lineIndex);
27116
+ var width = lineInfo.width;
26831
27117
  this.__lineWidths[lineIndex] = width;
26832
27118
  return width;
26833
27119
  },
@@ -28924,7 +29210,13 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
28924
29210
  this.insertCharStyleObject(cursorLoc.lineIndex + i, 0, addedLines[i], copiedStyle);
28925
29211
  }
28926
29212
  else if (copiedStyle) {
28927
- this.styles[cursorLoc.lineIndex + i][0] = copiedStyle[0];
29213
+ // this test is required in order to close #6841
29214
+ // when a pasted buffer begins with a newline then
29215
+ // this.styles[cursorLoc.lineIndex + i] and copiedStyle[0]
29216
+ // may be undefined for some reason
29217
+ if (this.styles[cursorLoc.lineIndex + i] && copiedStyle[0]) {
29218
+ this.styles[cursorLoc.lineIndex + i][0] = copiedStyle[0];
29219
+ }
28928
29220
  }
28929
29221
  copiedStyle = copiedStyle && copiedStyle.slice(addedLines[i] + 1);
28930
29222
  }