fabric 4.6.0 → 5.0.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.0.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,117 @@ 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
+ * @property {Function} [options.onChange] Callback; invoked on every value change
3581
+ * @property {Function} [options.onComplete] Callback; invoked when value change is completed
3582
+ * @property {Number} [options.startValue=0] Starting value
3583
+ * @property {Number} [options.endValue=100] Ending value
3584
+ * @property {Number} [options.byValue=100] Value to modify the property by
3585
+ * @property {Function} [options.easing] Easing function
3586
+ * @property {Number} [options.duration=500] Duration of change (in ms)
3587
+ * @property {Function} [options.abort] Additional function with logic. If returns true, animation aborts.
3588
+ *
3589
+ * @typedef {() => void} CancelFunction
3590
+ *
3591
+ * @typedef {Object} AnimationCurrentState
3592
+ * @property {number} currentValue value in range [`startValue`, `endValue`]
3593
+ * @property {number} completionRate value in range [0, 1]
3594
+ * @property {number} durationRate value in range [0, 1]
3595
+ *
3596
+ * @typedef {(AnimationOptions & AnimationCurrentState & { cancel: CancelFunction }} AnimationContext
3597
+ */
3598
+
3599
+ /**
3600
+ * Array holding all running animations
3601
+ * @memberof fabric
3602
+ * @type {AnimationContext[]}
3603
+ */
3604
+ var RUNNING_ANIMATIONS = [];
3605
+ fabric.util.object.extend(RUNNING_ANIMATIONS, {
3606
+
3607
+ /**
3608
+ * cancel all running animations at the next requestAnimFrame
3609
+ * @returns {AnimationContext[]}
3610
+ */
3611
+ cancelAll: function () {
3612
+ var animations = this.splice(0);
3613
+ animations.forEach(function (animation) {
3614
+ animation.cancel();
3615
+ });
3616
+ return animations;
3617
+ },
3618
+
3619
+ /**
3620
+ * cancel all running animations attached to canvas at the next requestAnimFrame
3621
+ * @param {fabric.Canvas} canvas
3622
+ * @returns {AnimationContext[]}
3623
+ */
3624
+ cancelByCanvas: function (canvas) {
3625
+ if (!canvas) {
3626
+ return [];
3627
+ }
3628
+ var cancelled = this.filter(function (animation) {
3629
+ return typeof animation.target === 'object' && animation.target.canvas === canvas;
3630
+ });
3631
+ cancelled.forEach(function (animation) {
3632
+ animation.cancel();
3633
+ });
3634
+ return cancelled;
3635
+ },
3636
+
3637
+ /**
3638
+ * cancel all running animations for target at the next requestAnimFrame
3639
+ * @param {*} target
3640
+ * @returns {AnimationContext[]}
3641
+ */
3642
+ cancelByTarget: function (target) {
3643
+ var cancelled = this.findAnimationsByTarget(target);
3644
+ cancelled.forEach(function (animation) {
3645
+ animation.cancel();
3646
+ });
3647
+ return cancelled;
3648
+ },
3649
+
3650
+ /**
3651
+ *
3652
+ * @param {CancelFunction} cancelFunc the function returned by animate
3653
+ * @returns {number}
3654
+ */
3655
+ findAnimationIndex: function (cancelFunc) {
3656
+ return this.indexOf(this.findAnimation(cancelFunc));
3657
+ },
3658
+
3659
+ /**
3660
+ *
3661
+ * @param {CancelFunction} cancelFunc the function returned by animate
3662
+ * @returns {AnimationContext | undefined} animation's options object
3663
+ */
3664
+ findAnimation: function (cancelFunc) {
3665
+ return this.find(function (animation) {
3666
+ return animation.cancel === cancelFunc;
3667
+ });
3668
+ },
3669
+
3670
+ /**
3671
+ *
3672
+ * @param {*} target the object that is assigned to the target property of the animation context
3673
+ * @returns {AnimationContext[]} array of animation options object associated with target
3674
+ */
3675
+ findAnimationsByTarget: function (target) {
3676
+ if (!target) {
3677
+ return [];
3678
+ }
3679
+ return this.filter(function (animation) {
3680
+ return animation.target === target;
3681
+ });
3682
+ }
3683
+ });
3480
3684
 
3481
3685
  function noop() {
3482
3686
  return false;
@@ -3489,22 +3693,30 @@ fabric.warn = console.warn;
3489
3693
  /**
3490
3694
  * Changes value from one to another within certain period of time, invoking callbacks as value is being changed.
3491
3695
  * @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
3696
+ * @param {AnimationOptions} [options] Animation options
3697
+ * @returns {CancelFunction} cancel function
3502
3698
  */
3503
3699
  function animate(options) {
3504
- var cancel = false;
3505
- requestAnimFrame(function(timestamp) {
3506
- options || (options = { });
3700
+ options || (options = {});
3701
+ var cancel = false,
3702
+ context,
3703
+ removeFromRegistry = function () {
3704
+ var index = fabric.runningAnimations.indexOf(context);
3705
+ return index > -1 && fabric.runningAnimations.splice(index, 1)[0];
3706
+ };
3707
+
3708
+ context = extend(clone(options), {
3709
+ cancel: function () {
3710
+ cancel = true;
3711
+ return removeFromRegistry();
3712
+ },
3713
+ currentValue: 'startValue' in options ? options.startValue : 0,
3714
+ completionRate: 0,
3715
+ durationRate: 0
3716
+ });
3717
+ fabric.runningAnimations.push(context);
3507
3718
 
3719
+ requestAnimFrame(function(timestamp) {
3508
3720
  var start = timestamp || +new Date(),
3509
3721
  duration = options.duration || 500,
3510
3722
  finish = start + duration, time,
@@ -3519,25 +3731,31 @@ fabric.warn = console.warn;
3519
3731
  options.onStart && options.onStart();
3520
3732
 
3521
3733
  (function tick(ticktime) {
3522
- // TODO: move abort call after calculation
3523
- // and pass (current,valuePerc, timePerc) as arguments
3524
3734
  time = ticktime || +new Date();
3525
3735
  var currentTime = time > finish ? duration : (time - start),
3526
3736
  timePerc = currentTime / duration,
3527
3737
  current = easing(currentTime, startValue, byValue, duration),
3528
3738
  valuePerc = Math.abs((current - startValue) / byValue);
3739
+ // update context
3740
+ context.currentValue = current;
3741
+ context.completionRate = valuePerc;
3742
+ context.durationRate = timePerc;
3529
3743
  if (cancel) {
3530
3744
  return;
3531
3745
  }
3532
3746
  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);
3747
+ removeFromRegistry();
3536
3748
  return;
3537
3749
  }
3538
3750
  if (time > finish) {
3751
+ // update context
3752
+ context.currentValue = endValue;
3753
+ context.completionRate = 1;
3754
+ context.durationRate = 1;
3755
+ // execute callbacks
3539
3756
  onChange(endValue, 1, 1);
3540
3757
  onComplete(endValue, 1, 1);
3758
+ removeFromRegistry();
3541
3759
  return;
3542
3760
  }
3543
3761
  else {
@@ -3546,9 +3764,8 @@ fabric.warn = console.warn;
3546
3764
  }
3547
3765
  })(start);
3548
3766
  });
3549
- return function() {
3550
- cancel = true;
3551
- };
3767
+
3768
+ return context.cancel;
3552
3769
  }
3553
3770
 
3554
3771
  var _requestAnimFrame = fabric.window.requestAnimationFrame ||
@@ -3580,6 +3797,7 @@ fabric.warn = console.warn;
3580
3797
  fabric.util.animate = animate;
3581
3798
  fabric.util.requestAnimFrame = requestAnimFrame;
3582
3799
  fabric.util.cancelAnimFrame = cancelAnimFrame;
3800
+ fabric.runningAnimations = RUNNING_ANIMATIONS;
3583
3801
  })();
3584
3802
 
3585
3803
 
@@ -5062,22 +5280,26 @@ fabric.warn = console.warn;
5062
5280
  if (styleContents.trim() === '') {
5063
5281
  continue;
5064
5282
  }
5065
- rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);
5066
- rules = rules.map(function(rule) { return rule.trim(); });
5283
+ // recovers all the rule in this form `body { style code... }`
5284
+ // rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);
5285
+ rules = styleContents.split('}');
5286
+ // remove empty rules.
5287
+ rules = rules.filter(function(rule) { return rule.trim(); });
5288
+ // at this point we have hopefully an array of rules `body { style code... `
5067
5289
  // eslint-disable-next-line no-loop-func
5068
5290
  rules.forEach(function(rule) {
5069
5291
 
5070
- var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/),
5071
- ruleObj = { }, declaration = match[2].trim(),
5072
- propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/);
5292
+ var match = rule.split('{'),
5293
+ ruleObj = { }, declaration = match[1].trim(),
5294
+ propertyValuePairs = declaration.split(';').filter(function(pair) { return pair.trim(); });
5073
5295
 
5074
5296
  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];
5297
+ var pair = propertyValuePairs[i].split(':'),
5298
+ property = pair[0].trim(),
5299
+ value = pair[1].trim();
5078
5300
  ruleObj[property] = value;
5079
5301
  }
5080
- rule = match[1];
5302
+ rule = match[0].trim();
5081
5303
  rule.split(',').forEach(function(_rule) {
5082
5304
  _rule = _rule.replace(/^svg/i, '').trim();
5083
5305
  if (_rule === '') {
@@ -8652,8 +8874,12 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
8652
8874
  imageSmoothingEnabled: true,
8653
8875
 
8654
8876
  /**
8655
- * The transformation (in the format of Canvas transform) which focuses the viewport
8877
+ * The transformation (a Canvas 2D API transform matrix) which focuses the viewport
8656
8878
  * @type Array
8879
+ * @example <caption>Default transform</caption>
8880
+ * canvas.viewportTransform = [1, 0, 0, 1, 0, 0];
8881
+ * @example <caption>Scale by 70% and translate toward bottom-right by 50, without skewing</caption>
8882
+ * canvas.viewportTransform = [0.7, 0, 0, 0.7, 50, 50];
8657
8883
  * @default
8658
8884
  */
8659
8885
  viewportTransform: fabric.iMatrix.concat(),
@@ -8747,7 +8973,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
8747
8973
  * @private
8748
8974
  */
8749
8975
  _isRetinaScaling: function() {
8750
- return (fabric.devicePixelRatio !== 1 && this.enableRetinaScaling);
8976
+ return (fabric.devicePixelRatio > 1 && this.enableRetinaScaling);
8751
8977
  },
8752
8978
 
8753
8979
  /**
@@ -8755,7 +8981,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
8755
8981
  * @return {Number} retinaScaling if applied, otherwise 1;
8756
8982
  */
8757
8983
  getRetinaScaling: function() {
8758
- return this._isRetinaScaling() ? fabric.devicePixelRatio : 1;
8984
+ return this._isRetinaScaling() ? Math.max(1, fabric.devicePixelRatio) : 1;
8759
8985
  },
8760
8986
 
8761
8987
  /**
@@ -9122,7 +9348,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
9122
9348
  }
9123
9349
  }
9124
9350
  if (this._isCurrentlyDrawing) {
9125
- this.freeDrawingBrush && this.freeDrawingBrush._setBrushStyles();
9351
+ this.freeDrawingBrush && this.freeDrawingBrush._setBrushStyles(this.contextTop);
9126
9352
  }
9127
9353
  this._initRetinaScaling();
9128
9354
  this.calcOffset();
@@ -9189,8 +9415,8 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
9189
9415
  },
9190
9416
 
9191
9417
  /**
9192
- * Sets viewport transform of this canvas instance
9193
- * @param {Array} vpt the transform in the form of context.transform
9418
+ * Sets viewport transformation of this canvas instance
9419
+ * @param {Array} vpt a Canvas 2D API transform matrix
9194
9420
  * @return {fabric.Canvas} instance
9195
9421
  * @chainable true
9196
9422
  */
@@ -10277,6 +10503,10 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
10277
10503
  }
10278
10504
  this.forEachObject(function(object) {
10279
10505
  object.dispose && object.dispose();
10506
+ // animation module is still optional
10507
+ if (fabric.runningAnimations) {
10508
+ fabric.runningAnimations.cancelByTarget(object);
10509
+ }
10280
10510
  });
10281
10511
  this._objects = [];
10282
10512
  if (this.backgroundImage && this.backgroundImage.dispose) {
@@ -10291,7 +10521,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
10291
10521
  this.contextContainer = null;
10292
10522
  // restore canvas style
10293
10523
  this.lowerCanvasEl.classList.remove('lower-canvas');
10294
- this.lowerCanvasEl.style = this._originalCanvasStyle;
10524
+ fabric.util.setStyle(this.lowerCanvasEl, this._originalCanvasStyle);
10295
10525
  delete this._originalCanvasStyle;
10296
10526
  // restore canvas size to original size in case retina scaling was applied
10297
10527
  this.lowerCanvasEl.setAttribute('width', this.width);
@@ -10459,9 +10689,9 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10459
10689
  /**
10460
10690
  * Sets brush styles
10461
10691
  * @private
10692
+ * @param {CanvasRenderingContext2D} ctx
10462
10693
  */
10463
- _setBrushStyles: function() {
10464
- var ctx = this.canvas.contextTop;
10694
+ _setBrushStyles: function (ctx) {
10465
10695
  ctx.strokeStyle = this.color;
10466
10696
  ctx.lineWidth = this.width;
10467
10697
  ctx.lineCap = this.strokeLineCap;
@@ -10546,6 +10776,22 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10546
10776
  */
10547
10777
  decimate: 0.4,
10548
10778
 
10779
+ /**
10780
+ * Draws a straight line between last recorded point to current pointer
10781
+ * Used for `shift` functionality
10782
+ *
10783
+ * @type boolean
10784
+ * @default false
10785
+ */
10786
+ drawStraightLine: false,
10787
+
10788
+ /**
10789
+ * The event modifier key that makes the brush draw a straight line.
10790
+ * If `null` or 'none' or any other string that is not a modifier key the feature is disabled.
10791
+ * @type {'altKey' | 'shiftKey' | 'ctrlKey' | 'none' | undefined | null}
10792
+ */
10793
+ straightLineKey: 'shiftKey',
10794
+
10549
10795
  /**
10550
10796
  * Constructor
10551
10797
  * @param {fabric.Canvas} canvas
@@ -10556,6 +10802,10 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10556
10802
  this._points = [];
10557
10803
  },
10558
10804
 
10805
+ needsFullRender: function () {
10806
+ return this.callSuper('needsFullRender') || this._hasStraightLine;
10807
+ },
10808
+
10559
10809
  /**
10560
10810
  * Invoked inside on mouse down and mouse move
10561
10811
  * @param {Object} pointer
@@ -10574,6 +10824,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10574
10824
  if (!this.canvas._isMainEvent(options.e)) {
10575
10825
  return;
10576
10826
  }
10827
+ this.drawStraightLine = options.e[this.straightLineKey];
10577
10828
  this._prepareForDrawing(pointer);
10578
10829
  // capture coordinates immediately
10579
10830
  // this allows to draw dots (when movement never occurs)
@@ -10589,6 +10840,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10589
10840
  if (!this.canvas._isMainEvent(options.e)) {
10590
10841
  return;
10591
10842
  }
10843
+ this.drawStraightLine = options.e[this.straightLineKey];
10592
10844
  if (this.limitedToCanvasSize === true && this._isOutSideCanvas(pointer)) {
10593
10845
  return;
10594
10846
  }
@@ -10621,6 +10873,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10621
10873
  if (!this.canvas._isMainEvent(options.e)) {
10622
10874
  return true;
10623
10875
  }
10876
+ this.drawStraightLine = false;
10624
10877
  this.oldEnd = undefined;
10625
10878
  this._finalizeAndAddPath();
10626
10879
  return false;
@@ -10647,6 +10900,10 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10647
10900
  if (this._points.length > 1 && point.eq(this._points[this._points.length - 1])) {
10648
10901
  return false;
10649
10902
  }
10903
+ if (this.drawStraightLine && this._points.length > 1) {
10904
+ this._hasStraightLine = true;
10905
+ this._points.pop();
10906
+ }
10650
10907
  this._points.push(point);
10651
10908
  return true;
10652
10909
  },
@@ -10657,8 +10914,9 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10657
10914
  */
10658
10915
  _reset: function() {
10659
10916
  this._points = [];
10660
- this._setBrushStyles();
10917
+ this._setBrushStyles(this.canvas.contextTop);
10661
10918
  this._setShadow();
10919
+ this._hasStraightLine = false;
10662
10920
  },
10663
10921
 
10664
10922
  /**
@@ -10673,12 +10931,13 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10673
10931
  /**
10674
10932
  * Draw a smooth path on the topCanvas using quadraticCurveTo
10675
10933
  * @private
10934
+ * @param {CanvasRenderingContext2D} [ctx]
10676
10935
  */
10677
- _render: function() {
10678
- var ctx = this.canvas.contextTop, i, len,
10936
+ _render: function(ctx) {
10937
+ var i, len,
10679
10938
  p1 = this._points[0],
10680
10939
  p2 = this._points[1];
10681
-
10940
+ ctx = ctx || this.canvas.contextTop;
10682
10941
  this._saveAndTransform(ctx);
10683
10942
  ctx.beginPath();
10684
10943
  //if we only have 2 points in the path and they are the same
@@ -11212,17 +11471,19 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
11212
11471
 
11213
11472
  /**
11214
11473
  * Creates "pattern" instance property
11474
+ * @param {CanvasRenderingContext2D} ctx
11215
11475
  */
11216
- getPattern: function() {
11217
- return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), 'repeat');
11476
+ getPattern: function(ctx) {
11477
+ return ctx.createPattern(this.source || this.getPatternSrc(), 'repeat');
11218
11478
  },
11219
11479
 
11220
11480
  /**
11221
11481
  * Sets brush styles
11482
+ * @param {CanvasRenderingContext2D} ctx
11222
11483
  */
11223
- _setBrushStyles: function() {
11224
- this.callSuper('_setBrushStyles');
11225
- this.canvas.contextTop.strokeStyle = this.getPattern();
11484
+ _setBrushStyles: function(ctx) {
11485
+ this.callSuper('_setBrushStyles', ctx);
11486
+ ctx.strokeStyle = this.getPattern(ctx);
11226
11487
  },
11227
11488
 
11228
11489
  /**
@@ -11281,15 +11542,11 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
11281
11542
  * @fires dragover
11282
11543
  * @fires dragenter
11283
11544
  * @fires dragleave
11545
+ * @fires drop:before before drop event. same native event. This is added to handle edge cases
11284
11546
  * @fires drop
11285
11547
  * @fires after:render at the end of the render process, receives the context in the callback
11286
11548
  * @fires before:render at start the render process, receives the context in the callback
11287
11549
  *
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
11550
  */
11294
11551
  fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, /** @lends fabric.Canvas.prototype */ {
11295
11552
 
@@ -11474,13 +11731,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
11474
11731
  */
11475
11732
  freeDrawingCursor: 'crosshair',
11476
11733
 
11477
- /**
11478
- * Cursor value used for rotation point
11479
- * @type String
11480
- * @default
11481
- */
11482
- rotationCursor: 'crosshair',
11483
-
11484
11734
  /**
11485
11735
  * Cursor value used for disabled elements ( corners with disabled action )
11486
11736
  * @type String
@@ -11586,6 +11836,13 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
11586
11836
  */
11587
11837
  targets: [],
11588
11838
 
11839
+ /**
11840
+ * When the option is enabled, PointerEvent is used instead of MouseEvent.
11841
+ * @type Boolean
11842
+ * @default
11843
+ */
11844
+ enablePointerEvents: false,
11845
+
11589
11846
  /**
11590
11847
  * Keep track of the hovered target
11591
11848
  * @type fabric.Object
@@ -11661,6 +11918,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
11661
11918
  }
11662
11919
  if (this.hasLostContext) {
11663
11920
  this.renderTopLayer(this.contextTop);
11921
+ this.hasLostContext = false;
11664
11922
  }
11665
11923
  var canvasToDrawOn = this.contextContainer;
11666
11924
  this.renderCanvas(canvasToDrawOn, this._chooseObjectsToRender());
@@ -12330,17 +12588,12 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
12330
12588
  e: e,
12331
12589
  selected: added,
12332
12590
  deselected: removed,
12333
- // added for backward compatibility
12334
- // deprecated
12335
- updated: added[0] || removed[0],
12336
- target: this._activeObject,
12337
12591
  });
12338
12592
  }
12339
12593
  else if (objects.length > 0) {
12340
12594
  this.fire('selection:created', {
12341
12595
  e: e,
12342
12596
  selected: added,
12343
- target: this._activeObject,
12344
12597
  });
12345
12598
  }
12346
12599
  else if (oldObjects.length > 0) {
@@ -12664,7 +12917,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
12664
12917
  this._onDragOver = this._onDragOver.bind(this);
12665
12918
  this._onDragEnter = this._simpleEventHandler.bind(this, 'dragenter');
12666
12919
  this._onDragLeave = this._simpleEventHandler.bind(this, 'dragleave');
12667
- this._onDrop = this._simpleEventHandler.bind(this, 'drop');
12920
+ this._onDrop = this._onDrop.bind(this);
12668
12921
  this.eventsBound = true;
12669
12922
  },
12670
12923
 
@@ -12776,6 +13029,18 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
12776
13029
  this._fireEnterLeaveEvents(target, e);
12777
13030
  },
12778
13031
 
13032
+ /**
13033
+ * `drop:before` is a an event that allow you to schedule logic
13034
+ * before the `drop` event. Prefer `drop` event always, but if you need
13035
+ * to run some drop-disabling logic on an event, since there is no way
13036
+ * to handle event handlers ordering, use `drop:before`
13037
+ * @param {Event} e
13038
+ */
13039
+ _onDrop: function (e) {
13040
+ this._simpleEventHandler('drop:before', e);
13041
+ return this._simpleEventHandler('drop', e);
13042
+ },
13043
+
12779
13044
  /**
12780
13045
  * @private
12781
13046
  * @param {Event} e Event object fired on mousedown
@@ -13008,25 +13273,34 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
13008
13273
  );
13009
13274
  }
13010
13275
  }
13276
+ var corner, pointer;
13011
13277
  if (target) {
13278
+ corner = target._findTargetCorner(
13279
+ this.getPointer(e, true),
13280
+ fabric.util.isTouchEvent(e)
13281
+ );
13012
13282
  if (target.selectable && target !== this._activeObject && target.activeOn === 'up') {
13013
13283
  this.setActiveObject(target, e);
13014
13284
  shouldRender = true;
13015
13285
  }
13016
13286
  else {
13017
- var corner = target._findTargetCorner(
13018
- this.getPointer(e, true),
13019
- fabric.util.isTouchEvent(e)
13020
- );
13021
13287
  var control = target.controls[corner],
13022
13288
  mouseUpHandler = control && control.getMouseUpHandler(e, target, control);
13023
13289
  if (mouseUpHandler) {
13024
- var pointer = this.getPointer(e);
13290
+ pointer = this.getPointer(e);
13025
13291
  mouseUpHandler(e, transform, pointer.x, pointer.y);
13026
13292
  }
13027
13293
  }
13028
13294
  target.isMoving = false;
13029
13295
  }
13296
+ // if we are ending up a transform on a different control or a new object
13297
+ // fire the original mouse up from the corner that started the transform
13298
+ if (transform && (transform.target !== target || transform.corner !== corner)) {
13299
+ var originalControl = transform.target && transform.target.controls[transform.corner],
13300
+ originalMouseUpHandler = originalControl && originalControl.getMouseUpHandler(e, target, control);
13301
+ pointer = pointer || this.getPointer(e);
13302
+ originalMouseUpHandler && originalMouseUpHandler(e, transform, pointer.x, pointer.y);
13303
+ }
13030
13304
  this._setCursorFromEvent(e, target);
13031
13305
  this._handleEvent(e, 'up', LEFT_CLICK, isClick);
13032
13306
  this._groupSelector = null;
@@ -13108,7 +13382,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
13108
13382
 
13109
13383
  var transform = this._currentTransform,
13110
13384
  target = transform.target,
13111
- eventName,
13112
13385
  options = {
13113
13386
  e: e,
13114
13387
  target: target,
@@ -13123,59 +13396,10 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
13123
13396
  target.setCoords();
13124
13397
 
13125
13398
  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
13399
  this._fire('modified', options);
13133
13400
  }
13134
13401
  },
13135
13402
 
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
13403
  /**
13180
13404
  * @private
13181
13405
  * @param {Event} e Event object fired on mousedown
@@ -14615,6 +14839,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
14615
14839
  /**
14616
14840
  * When `false`, the stoke width will scale with the object.
14617
14841
  * When `true`, the stroke will always match the exact pixel size entered for stroke width.
14842
+ * this Property does not work on Text classes or drawing call that uses strokeText,fillText methods
14618
14843
  * default to false
14619
14844
  * @since 2.6.0
14620
14845
  * @type Boolean
@@ -15265,26 +15490,26 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
15265
15490
  /**
15266
15491
  * Execute the drawing operation for an object clipPath
15267
15492
  * @param {CanvasRenderingContext2D} ctx Context to render on
15493
+ * @param {fabric.Object} clipPath
15268
15494
  */
15269
- drawClipPathOnCache: function(ctx) {
15270
- var path = this.clipPath;
15495
+ drawClipPathOnCache: function(ctx, clipPath) {
15271
15496
  ctx.save();
15272
15497
  // DEBUG: uncomment this line, comment the following
15273
15498
  // ctx.globalAlpha = 0.4
15274
- if (path.inverted) {
15499
+ if (clipPath.inverted) {
15275
15500
  ctx.globalCompositeOperation = 'destination-out';
15276
15501
  }
15277
15502
  else {
15278
15503
  ctx.globalCompositeOperation = 'destination-in';
15279
15504
  }
15280
15505
  //ctx.scale(1 / 2, 1 / 2);
15281
- if (path.absolutePositioned) {
15506
+ if (clipPath.absolutePositioned) {
15282
15507
  var m = fabric.util.invertTransform(this.calcTransformMatrix());
15283
15508
  ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
15284
15509
  }
15285
- path.transform(ctx);
15286
- ctx.scale(1 / path.zoomX, 1 / path.zoomY);
15287
- ctx.drawImage(path._cacheCanvas, -path.cacheTranslationX, -path.cacheTranslationY);
15510
+ clipPath.transform(ctx);
15511
+ ctx.scale(1 / clipPath.zoomX, 1 / clipPath.zoomY);
15512
+ ctx.drawImage(clipPath._cacheCanvas, -clipPath.cacheTranslationX, -clipPath.cacheTranslationY);
15288
15513
  ctx.restore();
15289
15514
  },
15290
15515
 
@@ -15303,22 +15528,26 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
15303
15528
  this._renderBackground(ctx);
15304
15529
  }
15305
15530
  this._render(ctx);
15306
- this._drawClipPath(ctx);
15531
+ this._drawClipPath(ctx, this.clipPath);
15307
15532
  this.fill = originalFill;
15308
15533
  this.stroke = originalStroke;
15309
15534
  },
15310
15535
 
15311
- _drawClipPath: function(ctx) {
15312
- var path = this.clipPath;
15313
- if (!path) { return; }
15536
+ /**
15537
+ * Prepare clipPath state and cache and draw it on instance's cache
15538
+ * @param {CanvasRenderingContext2D} ctx
15539
+ * @param {fabric.Object} clipPath
15540
+ */
15541
+ _drawClipPath: function (ctx, clipPath) {
15542
+ if (!clipPath) { return; }
15314
15543
  // needed to setup a couple of variables
15315
15544
  // path canvas gets overridden with this one.
15316
15545
  // 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);
15546
+ clipPath.canvas = this.canvas;
15547
+ clipPath.shouldCache();
15548
+ clipPath._transformDone = true;
15549
+ clipPath.renderCache({ forClipping: true });
15550
+ this.drawClipPathOnCache(ctx, clipPath);
15322
15551
  },
15323
15552
 
15324
15553
  /**
@@ -15462,6 +15691,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
15462
15691
 
15463
15692
  /**
15464
15693
  * Renders controls and borders for the object
15694
+ * the context here is not transformed
15465
15695
  * @param {CanvasRenderingContext2D} ctx Context to render on
15466
15696
  * @param {Object} [styleOverride] properties to override the object style
15467
15697
  */
@@ -15480,7 +15710,10 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
15480
15710
  if (!this.group) {
15481
15711
  ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
15482
15712
  }
15483
- ctx.rotate(degreesToRadians(options.angle));
15713
+ if (this.flipX) {
15714
+ options.angle -= 180;
15715
+ }
15716
+ ctx.rotate(degreesToRadians(this.group ? options.angle : this.angle));
15484
15717
  if (styleOverride.forActiveSelection || this.group) {
15485
15718
  drawBorders && this.drawBordersInGroup(ctx, options, styleOverride);
15486
15719
  }
@@ -16016,6 +16249,15 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
16016
16249
  if (this.globalCompositeOperation) {
16017
16250
  ctx.globalCompositeOperation = this.globalCompositeOperation;
16018
16251
  }
16252
+ },
16253
+
16254
+ /**
16255
+ * cancel instance's running animations
16256
+ */
16257
+ dispose: function () {
16258
+ if (fabric.runningAnimations) {
16259
+ fabric.runningAnimations.cancelByTarget(this);
16260
+ }
16019
16261
  }
16020
16262
  });
16021
16263
 
@@ -16033,6 +16275,15 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
16033
16275
  */
16034
16276
  fabric.Object.NUM_FRACTION_DIGITS = 2;
16035
16277
 
16278
+ /**
16279
+ * Defines which properties should be enlivened from the object passed to {@link fabric.Object._fromObject}
16280
+ * @static
16281
+ * @memberOf fabric.Object
16282
+ * @constant
16283
+ * @type string[]
16284
+ */
16285
+ fabric.Object.ENLIVEN_PROPS = ['clipPath'];
16286
+
16036
16287
  fabric.Object._fromObject = function(className, object, callback, extraParam) {
16037
16288
  var klass = fabric[className];
16038
16289
  object = clone(object, true);
@@ -16043,8 +16294,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
16043
16294
  if (typeof patterns[1] !== 'undefined') {
16044
16295
  object.stroke = patterns[1];
16045
16296
  }
16046
- fabric.util.enlivenObjects([object.clipPath], function(enlivedProps) {
16047
- object.clipPath = enlivedProps[0];
16297
+ fabric.util.enlivenObjectEnlivables(object, object, function () {
16048
16298
  var instance = extraParam ? new klass(object[extraParam], object) : new klass(object);
16049
16299
  callback && callback(instance);
16050
16300
  });
@@ -16365,7 +16615,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
16365
16615
  /**
16366
16616
  * Describe object's corner position in canvas element coordinates.
16367
16617
  * includes padding. Used of object detection.
16368
- * set and refreshed with setCoords and calcCoords.
16618
+ * set and refreshed with setCoords.
16369
16619
  * @memberOf fabric.Object.prototype
16370
16620
  */
16371
16621
  lineCoords: null,
@@ -16749,21 +16999,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
16749
16999
  return this.scale(value / this.height / boundingRectFactor);
16750
17000
  },
16751
17001
 
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
17002
  calcLineCoords: function() {
16768
17003
  var vpt = this.getViewportTransform(),
16769
17004
  padding = this.padding, angle = degreesToRadians(this.angle),
@@ -16838,7 +17073,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
16838
17073
  * oCoords are used to find the corners
16839
17074
  * aCoords are used to quickly find an object on the canvas
16840
17075
  * 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}
17076
+ * See {@link https://github.com/fabricjs/fabric.js/wiki/When-to-call-setCoords} and {@link http://fabricjs.com/fabric-gotchas}
17077
+ *
16842
17078
  * @param {Boolean} [skipCorners] skip calculation of oCoords.
16843
17079
  * @return {fabric.Object} thisArg
16844
17080
  * @chainable
@@ -16935,23 +17171,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
16935
17171
  return cache.value;
16936
17172
  },
16937
17173
 
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
17174
  /*
16956
17175
  * Calculate object dimensions from its properties
16957
17176
  * @private
@@ -17816,8 +18035,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17816
18035
  * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
17817
18036
  * @param {Function} [callbacks.onComplete] Invoked on completion
17818
18037
  * @param {Function} [callbacks.onChange] Invoked on every step of animation
17819
- * @return {fabric.Canvas} thisArg
17820
- * @chainable
18038
+ * @return {fabric.AnimationContext} context
17821
18039
  */
17822
18040
  fxCenterObjectH: function (object, callbacks) {
17823
18041
  callbacks = callbacks || { };
@@ -17827,7 +18045,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17827
18045
  onChange = callbacks.onChange || empty,
17828
18046
  _this = this;
17829
18047
 
17830
- fabric.util.animate({
18048
+ return fabric.util.animate({
18049
+ target: this,
17831
18050
  startValue: object.left,
17832
18051
  endValue: this.getCenter().left,
17833
18052
  duration: this.FX_DURATION,
@@ -17841,8 +18060,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17841
18060
  onComplete();
17842
18061
  }
17843
18062
  });
17844
-
17845
- return this;
17846
18063
  },
17847
18064
 
17848
18065
  /**
@@ -17851,8 +18068,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17851
18068
  * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
17852
18069
  * @param {Function} [callbacks.onComplete] Invoked on completion
17853
18070
  * @param {Function} [callbacks.onChange] Invoked on every step of animation
17854
- * @return {fabric.Canvas} thisArg
17855
- * @chainable
18071
+ * @return {fabric.AnimationContext} context
17856
18072
  */
17857
18073
  fxCenterObjectV: function (object, callbacks) {
17858
18074
  callbacks = callbacks || { };
@@ -17862,7 +18078,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17862
18078
  onChange = callbacks.onChange || empty,
17863
18079
  _this = this;
17864
18080
 
17865
- fabric.util.animate({
18081
+ return fabric.util.animate({
18082
+ target: this,
17866
18083
  startValue: object.top,
17867
18084
  endValue: this.getCenter().top,
17868
18085
  duration: this.FX_DURATION,
@@ -17876,8 +18093,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17876
18093
  onComplete();
17877
18094
  }
17878
18095
  });
17879
-
17880
- return this;
17881
18096
  },
17882
18097
 
17883
18098
  /**
@@ -17886,8 +18101,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17886
18101
  * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
17887
18102
  * @param {Function} [callbacks.onComplete] Invoked on completion
17888
18103
  * @param {Function} [callbacks.onChange] Invoked on every step of animation
17889
- * @return {fabric.Canvas} thisArg
17890
- * @chainable
18104
+ * @return {fabric.AnimationContext} context
17891
18105
  */
17892
18106
  fxRemove: function (object, callbacks) {
17893
18107
  callbacks = callbacks || { };
@@ -17897,7 +18111,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17897
18111
  onChange = callbacks.onChange || empty,
17898
18112
  _this = this;
17899
18113
 
17900
- fabric.util.animate({
18114
+ return fabric.util.animate({
18115
+ target: this,
17901
18116
  startValue: object.opacity,
17902
18117
  endValue: 0,
17903
18118
  duration: this.FX_DURATION,
@@ -17911,8 +18126,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17911
18126
  onComplete();
17912
18127
  }
17913
18128
  });
17914
-
17915
- return this;
17916
18129
  }
17917
18130
  });
17918
18131
 
@@ -17923,7 +18136,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
17923
18136
  * @param {Number|Object} value Value to animate property to (if string was given first) or options object
17924
18137
  * @return {fabric.Object} thisArg
17925
18138
  * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#animation}
17926
- * @chainable
18139
+ * @return {fabric.AnimationContext | fabric.AnimationContext[]} animation context (or an array if passed multiple properties)
17927
18140
  *
17928
18141
  * As object — multiple properties
17929
18142
  *
@@ -17936,22 +18149,22 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
17936
18149
  * object.animate('left', { duration: ... });
17937
18150
  *
17938
18151
  */
17939
- animate: function() {
18152
+ animate: function () {
17940
18153
  if (arguments[0] && typeof arguments[0] === 'object') {
17941
- var propsToAnimate = [], prop, skipCallbacks;
18154
+ var propsToAnimate = [], prop, skipCallbacks, out = [];
17942
18155
  for (prop in arguments[0]) {
17943
18156
  propsToAnimate.push(prop);
17944
18157
  }
17945
18158
  for (var i = 0, len = propsToAnimate.length; i < len; i++) {
17946
18159
  prop = propsToAnimate[i];
17947
18160
  skipCallbacks = i !== len - 1;
17948
- this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks);
18161
+ out.push(this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks));
17949
18162
  }
18163
+ return out;
17950
18164
  }
17951
18165
  else {
17952
- this._animate.apply(this, arguments);
18166
+ return this._animate.apply(this, arguments);
17953
18167
  }
17954
- return this;
17955
18168
  },
17956
18169
 
17957
18170
  /**
@@ -17999,6 +18212,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
17999
18212
  }
18000
18213
 
18001
18214
  var _options = {
18215
+ target: this,
18002
18216
  startValue: options.from,
18003
18217
  endValue: to,
18004
18218
  byValue: options.by,
@@ -18370,7 +18584,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18370
18584
  'use strict';
18371
18585
 
18372
18586
  var fabric = global.fabric || (global.fabric = { }),
18373
- pi = Math.PI;
18587
+ degreesToRadians = fabric.util.degreesToRadians;
18374
18588
 
18375
18589
  if (fabric.Circle) {
18376
18590
  fabric.warn('fabric.Circle is already defined.');
@@ -18400,22 +18614,20 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18400
18614
  radius: 0,
18401
18615
 
18402
18616
  /**
18403
- * Start angle of the circle, moving clockwise
18404
- * deprecated type, this should be in degree, this was an oversight.
18617
+ * degrees of start of the circle.
18405
18618
  * probably will change to degrees in next major version
18406
- * @type Number
18619
+ * @type Number 0 - 359
18407
18620
  * @default 0
18408
18621
  */
18409
18622
  startAngle: 0,
18410
18623
 
18411
18624
  /**
18412
18625
  * End angle of the circle
18413
- * deprecated type, this should be in degree, this was an oversight.
18414
18626
  * probably will change to degrees in next major version
18415
- * @type Number
18416
- * @default 2Pi
18627
+ * @type Number 1 - 360
18628
+ * @default 360
18417
18629
  */
18418
- endAngle: pi * 2,
18630
+ endAngle: 360,
18419
18631
 
18420
18632
  cacheProperties: fabric.Object.prototype.cacheProperties.concat('radius', 'startAngle', 'endAngle'),
18421
18633
 
@@ -18453,7 +18665,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18453
18665
  */
18454
18666
  _toSVG: function() {
18455
18667
  var svgString, x = 0, y = 0,
18456
- angle = (this.endAngle - this.startAngle) % ( 2 * pi);
18668
+ angle = (this.endAngle - this.startAngle) % 360;
18457
18669
 
18458
18670
  if (angle === 0) {
18459
18671
  svgString = [
@@ -18464,14 +18676,17 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18464
18676
  ];
18465
18677
  }
18466
18678
  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';
18679
+ var start = degreesToRadians(this.startAngle),
18680
+ end = degreesToRadians(this.endAngle),
18681
+ radius = this.radius,
18682
+ startX = fabric.util.cos(start) * radius,
18683
+ startY = fabric.util.sin(start) * radius,
18684
+ endX = fabric.util.cos(end) * radius,
18685
+ endY = fabric.util.sin(end) * radius,
18686
+ largeFlag = angle > 180 ? '1' : '0';
18472
18687
  svgString = [
18473
18688
  '<path d="M ' + startX + ' ' + startY,
18474
- ' A ' + this.radius + ' ' + this.radius,
18689
+ ' A ' + radius + ' ' + radius,
18475
18690
  ' 0 ', +largeFlag + ' 1', ' ' + endX + ' ' + endY,
18476
18691
  '" ', 'COMMON_PARTS', ' />\n'
18477
18692
  ];
@@ -18490,8 +18705,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18490
18705
  0,
18491
18706
  0,
18492
18707
  this.radius,
18493
- this.startAngle,
18494
- this.endAngle, false);
18708
+ degreesToRadians(this.startAngle),
18709
+ degreesToRadians(this.endAngle),
18710
+ false
18711
+ );
18495
18712
  this._renderPaintInOrder(ctx);
18496
18713
  },
18497
18714
 
@@ -19049,7 +19266,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19049
19266
  extend = fabric.util.object.extend,
19050
19267
  min = fabric.util.array.min,
19051
19268
  max = fabric.util.array.max,
19052
- toFixed = fabric.util.toFixed;
19269
+ toFixed = fabric.util.toFixed,
19270
+ projectStrokeOnPoints = fabric.util.projectStrokeOnPoints;
19053
19271
 
19054
19272
  if (fabric.Polyline) {
19055
19273
  fabric.warn('fabric.Polyline is already defined');
@@ -19078,6 +19296,17 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19078
19296
  */
19079
19297
  points: null,
19080
19298
 
19299
+ /**
19300
+ * WARNING: Feature in progress
19301
+ * Calculate the exact bounding box taking in account strokeWidth on acute angles
19302
+ * this will be turned to true by default on fabric 6.0
19303
+ * maybe will be left in as an optimization since calculations may be slow
19304
+ * @deprecated
19305
+ * @type Boolean
19306
+ * @default false
19307
+ */
19308
+ exactBoundingBox: false,
19309
+
19081
19310
  cacheProperties: fabric.Object.prototype.cacheProperties.concat('points'),
19082
19311
 
19083
19312
  /**
@@ -19106,13 +19335,25 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19106
19335
  this._setPositionDimensions(options);
19107
19336
  },
19108
19337
 
19338
+ /**
19339
+ * @private
19340
+ */
19341
+ _projectStrokeOnPoints: function () {
19342
+ return projectStrokeOnPoints(this.points, this, true);
19343
+ },
19344
+
19109
19345
  _setPositionDimensions: function(options) {
19110
- var calcDim = this._calcDimensions(options), correctLeftTop;
19111
- this.width = calcDim.width;
19112
- this.height = calcDim.height;
19346
+ var calcDim = this._calcDimensions(options), correctLeftTop,
19347
+ correctSize = this.exactBoundingBox ? this.strokeWidth : 0;
19348
+ this.width = calcDim.width - correctSize;
19349
+ this.height = calcDim.height - correctSize;
19113
19350
  if (!options.fromSVG) {
19114
19351
  correctLeftTop = this.translateToGivenOrigin(
19115
- { x: calcDim.left - this.strokeWidth / 2, y: calcDim.top - this.strokeWidth / 2 },
19352
+ {
19353
+ // this looks bad, but is one way to keep it optional for now.
19354
+ x: calcDim.left - this.strokeWidth / 2 + correctSize / 2,
19355
+ y: calcDim.top - this.strokeWidth / 2 + correctSize / 2
19356
+ },
19116
19357
  'left',
19117
19358
  'top',
19118
19359
  this.originX,
@@ -19126,8 +19367,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19126
19367
  this.top = options.fromSVG ? calcDim.top : correctLeftTop.y;
19127
19368
  }
19128
19369
  this.pathOffset = {
19129
- x: calcDim.left + this.width / 2,
19130
- y: calcDim.top + this.height / 2
19370
+ x: calcDim.left + this.width / 2 + correctSize / 2,
19371
+ y: calcDim.top + this.height / 2 + correctSize / 2
19131
19372
  };
19132
19373
  },
19133
19374
 
@@ -19143,7 +19384,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19143
19384
  */
19144
19385
  _calcDimensions: function() {
19145
19386
 
19146
- var points = this.points,
19387
+ var points = this.exactBoundingBox ? this._projectStrokeOnPoints() : this.points,
19147
19388
  minX = min(points, 'x') || 0,
19148
19389
  minY = min(points, 'y') || 0,
19149
19390
  maxX = max(points, 'x') || 0,
@@ -19155,7 +19396,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19155
19396
  left: minX,
19156
19397
  top: minY,
19157
19398
  width: width,
19158
- height: height
19399
+ height: height,
19159
19400
  };
19160
19401
  },
19161
19402
 
@@ -19291,7 +19532,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19291
19532
 
19292
19533
  'use strict';
19293
19534
 
19294
- var fabric = global.fabric || (global.fabric = { });
19535
+ var fabric = global.fabric || (global.fabric = {}),
19536
+ projectStrokeOnPoints = fabric.util.projectStrokeOnPoints;
19295
19537
 
19296
19538
  if (fabric.Polygon) {
19297
19539
  fabric.warn('fabric.Polygon is already defined');
@@ -19313,6 +19555,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19313
19555
  */
19314
19556
  type: 'polygon',
19315
19557
 
19558
+ /**
19559
+ * @private
19560
+ */
19561
+ _projectStrokeOnPoints: function () {
19562
+ return projectStrokeOnPoints(this.points, this);
19563
+ },
19564
+
19316
19565
  /**
19317
19566
  * @private
19318
19567
  * @param {CanvasRenderingContext2D} ctx Context to render on
@@ -19370,6 +19619,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19370
19619
  min = fabric.util.array.min,
19371
19620
  max = fabric.util.array.max,
19372
19621
  extend = fabric.util.object.extend,
19622
+ clone = fabric.util.object.clone,
19373
19623
  _toString = Object.prototype.toString,
19374
19624
  toFixed = fabric.util.toFixed;
19375
19625
 
@@ -19411,23 +19661,26 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19411
19661
  * @param {Object} [options] Options object
19412
19662
  * @return {fabric.Path} thisArg
19413
19663
  */
19414
- initialize: function(path, options) {
19415
- options = options || { };
19664
+ initialize: function (path, options) {
19665
+ options = clone(options || {});
19666
+ delete options.path;
19416
19667
  this.callSuper('initialize', options);
19417
- if (!path) {
19418
- path = [];
19419
- }
19668
+ this._setPath(path || [], options);
19669
+ },
19420
19670
 
19671
+ /**
19672
+ * @private
19673
+ * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens)
19674
+ * @param {Object} [options] Options object
19675
+ */
19676
+ _setPath: function (path, options) {
19421
19677
  var fromArray = _toString.call(path) === '[object Array]';
19422
19678
 
19423
19679
  this.path = fabric.util.makePathSimpler(
19424
19680
  fromArray ? path : fabric.util.parsePath(path)
19425
19681
  );
19426
19682
 
19427
- if (!this.path) {
19428
- return;
19429
- }
19430
- fabric.Polyline.prototype._setPositionDimensions.call(this, options);
19683
+ fabric.Polyline.prototype._setPositionDimensions.call(this, options || {});
19431
19684
  },
19432
19685
 
19433
19686
  /**
@@ -20084,7 +20337,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
20084
20337
  for (var i = 0, len = this._objects.length; i < len; i++) {
20085
20338
  this._objects[i].render(ctx);
20086
20339
  }
20087
- this._drawClipPath(ctx);
20340
+ this._drawClipPath(ctx, this.clipPath);
20088
20341
  },
20089
20342
 
20090
20343
  /**
@@ -20130,25 +20383,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
20130
20383
  return this;
20131
20384
  },
20132
20385
 
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
20386
  /**
20153
20387
  * Destroys a group (restoring state of its objects)
20154
20388
  * @return {fabric.Group} thisArg
@@ -20163,6 +20397,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
20163
20397
  return this._restoreObjectsState();
20164
20398
  },
20165
20399
 
20400
+ dispose: function () {
20401
+ this.callSuper('dispose');
20402
+ this.forEachObject(function (object) {
20403
+ object.dispose && object.dispose();
20404
+ });
20405
+ this._objects = [];
20406
+ },
20407
+
20166
20408
  /**
20167
20409
  * make a group an active selection, remove the group from canvas
20168
20410
  * the group has to be on canvas for this to work.
@@ -20326,11 +20568,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
20326
20568
  });
20327
20569
  return;
20328
20570
  }
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;
20571
+ fabric.util.enlivenObjects(objects, function (enlivenedObjects) {
20572
+ var options = fabric.util.object.clone(object, true);
20573
+ delete options.objects;
20574
+ fabric.util.enlivenObjectEnlivables(object, options, function () {
20334
20575
  callback && callback(new fabric.Group(enlivenedObjects, options, true));
20335
20576
  });
20336
20577
  });
@@ -20700,7 +20941,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
20700
20941
  /**
20701
20942
  * Delete textures, reference to elements and eventually JSDOM cleanup
20702
20943
  */
20703
- dispose: function() {
20944
+ dispose: function () {
20945
+ this.callSuper('dispose');
20704
20946
  this.removeTexture(this.cacheKey);
20705
20947
  this.removeTexture(this.cacheKey + '_filtered');
20706
20948
  this._cacheContext = undefined;
@@ -21210,8 +21452,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
21210
21452
  object.filters = filters || [];
21211
21453
  fabric.Image.prototype._initFilters.call(object, [object.resizeFilter], function(resizeFilters) {
21212
21454
  object.resizeFilter = resizeFilters[0];
21213
- fabric.util.enlivenObjects([object.clipPath], function(enlivedProps) {
21214
- object.clipPath = enlivedProps[0];
21455
+ fabric.util.enlivenObjectEnlivables(object, object, function () {
21215
21456
  var image = new fabric.Image(img, object);
21216
21457
  callback(image, false);
21217
21458
  });
@@ -21282,8 +21523,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
21282
21523
  * @chainable
21283
21524
  */
21284
21525
  straighten: function() {
21285
- this.rotate(this._getAngleValueForStraighten());
21286
- return this;
21526
+ return this.rotate(this._getAngleValueForStraighten());
21287
21527
  },
21288
21528
 
21289
21529
  /**
@@ -21292,7 +21532,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
21292
21532
  * @param {Function} [callbacks.onComplete] Invoked on completion
21293
21533
  * @param {Function} [callbacks.onChange] Invoked on every step of animation
21294
21534
  * @return {fabric.Object} thisArg
21295
- * @chainable
21296
21535
  */
21297
21536
  fxStraighten: function(callbacks) {
21298
21537
  callbacks = callbacks || { };
@@ -21302,7 +21541,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
21302
21541
  onChange = callbacks.onChange || empty,
21303
21542
  _this = this;
21304
21543
 
21305
- fabric.util.animate({
21544
+ return fabric.util.animate({
21545
+ target: this,
21306
21546
  startValue: this.get('angle'),
21307
21547
  endValue: this._getAngleValueForStraighten(),
21308
21548
  duration: this.FX_DURATION,
@@ -21315,8 +21555,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
21315
21555
  onComplete();
21316
21556
  },
21317
21557
  });
21318
-
21319
- return this;
21320
21558
  }
21321
21559
  });
21322
21560
 
@@ -21338,13 +21576,11 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
21338
21576
  * Same as {@link fabric.Canvas.prototype.straightenObject}, but animated
21339
21577
  * @param {fabric.Object} object Object to straighten
21340
21578
  * @return {fabric.Canvas} thisArg
21341
- * @chainable
21342
21579
  */
21343
21580
  fxStraightenObject: function (object) {
21344
- object.fxStraighten({
21581
+ return object.fxStraighten({
21345
21582
  onChange: this.requestRenderAllBound
21346
21583
  });
21347
- return this;
21348
21584
  }
21349
21585
  });
21350
21586
 
@@ -25511,7 +25747,7 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
25511
25747
  var additionalProps =
25512
25748
  ('fontFamily fontWeight fontSize text underline overline linethrough' +
25513
25749
  ' textAlign fontStyle lineHeight textBackgroundColor charSpacing styles' +
25514
- ' direction path pathStartOffset pathSide').split(' ');
25750
+ ' direction path pathStartOffset pathSide pathAlign').split(' ');
25515
25751
 
25516
25752
  /**
25517
25753
  * Text class
@@ -25540,7 +25776,8 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
25540
25776
  'styles',
25541
25777
  'path',
25542
25778
  'pathStartOffset',
25543
- 'pathSide'
25779
+ 'pathSide',
25780
+ 'pathAlign'
25544
25781
  ],
25545
25782
 
25546
25783
  /**
@@ -25737,6 +25974,16 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
25737
25974
  */
25738
25975
  pathSide: 'left',
25739
25976
 
25977
+ /**
25978
+ * How text is aligned to the path. This property determines
25979
+ * the perpendicular position of each character relative to the path.
25980
+ * (one of "baseline", "center", "ascender", "descender")
25981
+ * This feature is in BETA, and its behavior may change
25982
+ * @type String
25983
+ * @default
25984
+ */
25985
+ pathAlign: 'baseline',
25986
+
25740
25987
  /**
25741
25988
  * @private
25742
25989
  */
@@ -25880,6 +26127,8 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
25880
26127
  /**
25881
26128
  * Return a context for measurement of text string.
25882
26129
  * if created it gets stored for reuse
26130
+ * this is for internal use, please do not use it
26131
+ * @private
25883
26132
  * @param {String} text Text string
25884
26133
  * @param {Object} [options] Options object
25885
26134
  * @return {fabric.Text} thisArg
@@ -26051,7 +26300,20 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
26051
26300
  * @param {String} [charStyle.fontStyle] Font style (italic|normal)
26052
26301
  */
26053
26302
  _setTextStyles: function(ctx, charStyle, forMeasuring) {
26054
- ctx.textBaseline = 'alphabetic';
26303
+ ctx.textBaseline = 'alphabetical';
26304
+ if (this.path) {
26305
+ switch (this.pathAlign) {
26306
+ case 'center':
26307
+ ctx.textBaseline = 'middle';
26308
+ break;
26309
+ case 'ascender':
26310
+ ctx.textBaseline = 'top';
26311
+ break;
26312
+ case 'descender':
26313
+ ctx.textBaseline = 'bottom';
26314
+ break;
26315
+ }
26316
+ }
26055
26317
  ctx.font = this._getFontDeclaration(charStyle, forMeasuring);
26056
26318
  },
26057
26319
 
@@ -26516,16 +26778,17 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
26516
26778
  path = this.path,
26517
26779
  shortCut = !isJustify && this.charSpacing === 0 && this.isEmptyStyles(lineIndex) && !path,
26518
26780
  isLtr = this.direction === 'ltr', sign = this.direction === 'ltr' ? 1 : -1,
26519
- drawingLeft;
26520
-
26781
+ drawingLeft, currentDirection = ctx.canvas.getAttribute('dir');
26521
26782
  ctx.save();
26783
+ if (currentDirection !== this.direction) {
26784
+ ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
26785
+ ctx.direction = isLtr ? 'ltr' : 'rtl';
26786
+ ctx.textAlign = isLtr ? 'left' : 'right';
26787
+ }
26522
26788
  top -= lineHeight * this._fontSizeFraction / this.lineHeight;
26523
26789
  if (shortCut) {
26524
26790
  // render all the line in one pass without checking
26525
26791
  // 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
26792
  this._renderChar(method, ctx, lineIndex, 0, line.join(''), left, top, lineHeight);
26530
26793
  ctx.restore();
26531
26794
  return;
@@ -26562,9 +26825,6 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
26562
26825
  }
26563
26826
  else {
26564
26827
  drawingLeft = left;
26565
- ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
26566
- ctx.direction = isLtr ? 'ltr' : 'rtl';
26567
- ctx.textAlign = isLtr ? 'left' : 'right';
26568
26828
  this._renderChar(method, ctx, lineIndex, i, charsToRender, drawingLeft, top, lineHeight);
26569
26829
  }
26570
26830
  charsToRender = '';
@@ -26815,19 +27075,12 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
26815
27075
  * @return {Number} Line width
26816
27076
  */
26817
27077
  getLineWidth: function(lineIndex) {
26818
- if (this.__lineWidths[lineIndex]) {
27078
+ if (this.__lineWidths[lineIndex] !== undefined) {
26819
27079
  return this.__lineWidths[lineIndex];
26820
27080
  }
26821
27081
 
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
- }
27082
+ var lineInfo = this.measureLine(lineIndex);
27083
+ var width = lineInfo.width;
26831
27084
  this.__lineWidths[lineIndex] = width;
26832
27085
  return width;
26833
27086
  },
@@ -28924,7 +29177,13 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
28924
29177
  this.insertCharStyleObject(cursorLoc.lineIndex + i, 0, addedLines[i], copiedStyle);
28925
29178
  }
28926
29179
  else if (copiedStyle) {
28927
- this.styles[cursorLoc.lineIndex + i][0] = copiedStyle[0];
29180
+ // this test is required in order to close #6841
29181
+ // when a pasted buffer begins with a newline then
29182
+ // this.styles[cursorLoc.lineIndex + i] and copiedStyle[0]
29183
+ // may be undefined for some reason
29184
+ if (this.styles[cursorLoc.lineIndex + i] && copiedStyle[0]) {
29185
+ this.styles[cursorLoc.lineIndex + i][0] = copiedStyle[0];
29186
+ }
28928
29187
  }
28929
29188
  copiedStyle = copiedStyle && copiedStyle.slice(addedLines[i] + 1);
28930
29189
  }