fabric 4.5.1-browser → 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.
Files changed (49) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/CONTRIBUTING.md +14 -0
  3. package/HEADER.js +1 -1
  4. package/README.md +3 -6
  5. package/SECURITY.md +5 -0
  6. package/build.js +1 -0
  7. package/dist/fabric.js +1160 -668
  8. package/dist/fabric.min.js +1 -1
  9. package/lib/event.js +7 -3
  10. package/package.json +9 -3
  11. package/publish-next.js +15 -0
  12. package/publish.js +3 -0
  13. package/src/brushes/base_brush.class.js +3 -5
  14. package/src/brushes/pattern_brush.class.js +7 -5
  15. package/src/brushes/pencil_brush.class.js +49 -37
  16. package/src/canvas.class.js +27 -57
  17. package/src/filters/saturate_filter.class.js +9 -1
  18. package/src/filters/vibrance_filter.class.js +122 -0
  19. package/src/mixins/animation.mixin.js +20 -25
  20. package/src/mixins/canvas_events.mixin.js +30 -59
  21. package/src/mixins/canvas_grouping.mixin.js +4 -4
  22. package/src/mixins/collection.mixin.js +11 -2
  23. package/src/mixins/eraser_brush.mixin.js +495 -452
  24. package/src/mixins/itext_behavior.mixin.js +7 -1
  25. package/src/mixins/itext_key_behavior.mixin.js +7 -1
  26. package/src/mixins/object_geometry.mixin.js +5 -35
  27. package/src/mixins/object_interactivity.mixin.js +18 -7
  28. package/src/mixins/object_straightening.mixin.js +4 -9
  29. package/src/mixins/observable.mixin.js +22 -0
  30. package/src/parser.js +13 -9
  31. package/src/shapes/circle.class.js +22 -19
  32. package/src/shapes/ellipse.class.js +2 -2
  33. package/src/shapes/group.class.js +24 -32
  34. package/src/shapes/image.class.js +3 -25
  35. package/src/shapes/itext.class.js +10 -0
  36. package/src/shapes/line.class.js +5 -21
  37. package/src/shapes/object.class.js +67 -32
  38. package/src/shapes/path.class.js +17 -18
  39. package/src/shapes/polygon.class.js +11 -10
  40. package/src/shapes/polyline.class.js +33 -24
  41. package/src/shapes/rect.class.js +0 -18
  42. package/src/shapes/text.class.js +120 -58
  43. package/src/shapes/triangle.class.js +0 -15
  44. package/src/static_canvas.class.js +43 -20
  45. package/src/util/animate.js +149 -16
  46. package/src/util/animate_color.js +2 -1
  47. package/src/util/lang_object.js +5 -1
  48. package/src/util/misc.js +193 -42
  49. package/src/util/path.js +89 -52
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.5.1' };
4
+ var fabric = fabric || { version: '5.0.0' };
5
5
  if (typeof exports !== 'undefined') {
6
6
  exports.fabric = fabric;
7
7
  }
@@ -258,6 +258,27 @@ if (typeof document !== 'undefined' && typeof window !== 'undefined') {
258
258
  return this;
259
259
  }
260
260
 
261
+ function _once(eventName, handler) {
262
+ var _handler = function () {
263
+ handler.apply(this, arguments);
264
+ this.off(eventName, _handler);
265
+ }.bind(this);
266
+ this.on(eventName, _handler);
267
+ }
268
+
269
+ function once(eventName, handler) {
270
+ // one object with key/value pairs was passed
271
+ if (arguments.length === 1) {
272
+ for (var prop in eventName) {
273
+ _once.call(this, prop, eventName[prop]);
274
+ }
275
+ }
276
+ else {
277
+ _once.call(this, eventName, handler);
278
+ }
279
+ return this;
280
+ }
281
+
261
282
  /**
262
283
  * Stops event observing for a particular event handler. Calling this method
263
284
  * without arguments removes all handlers for all events
@@ -326,6 +347,7 @@ if (typeof document !== 'undefined' && typeof window !== 'undefined') {
326
347
  fabric.Observable = {
327
348
  fire: fire,
328
349
  on: on,
350
+ once: once,
329
351
  off: off,
330
352
  };
331
353
  })();
@@ -475,10 +497,19 @@ fabric.Collection = {
475
497
  /**
476
498
  * Returns true if collection contains an object
477
499
  * @param {Object} object Object to check against
500
+ * @param {Boolean} [deep=false] `true` to check all descendants, `false` to check only `_objects`
478
501
  * @return {Boolean} `true` if collection contains an object
479
502
  */
480
- contains: function(object) {
481
- return this._objects.indexOf(object) > -1;
503
+ contains: function (object, deep) {
504
+ if (this._objects.indexOf(object) > -1) {
505
+ return true;
506
+ }
507
+ else if (deep) {
508
+ return this._objects.some(function (obj) {
509
+ return typeof obj.contains === 'function' && obj.contains(object, true);
510
+ });
511
+ }
512
+ return false;
482
513
  },
483
514
 
484
515
  /**
@@ -731,6 +762,135 @@ fabric.CommonMethods = {
731
762
  };
732
763
  },
733
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
+
734
894
  /**
735
895
  * Apply transform t to point p
736
896
  * @static
@@ -1043,6 +1203,25 @@ fabric.CommonMethods = {
1043
1203
  });
1044
1204
  },
1045
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
+
1046
1225
  /**
1047
1226
  * Create and wait for loading of patterns
1048
1227
  * @static
@@ -1134,46 +1313,6 @@ fabric.CommonMethods = {
1134
1313
  }
1135
1314
  },
1136
1315
 
1137
- /**
1138
- * Draws a dashed line between two points
1139
- *
1140
- * This method is used to draw dashed line around selection area.
1141
- * See <a href="http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas">dotted stroke in canvas</a>
1142
- *
1143
- * @param {CanvasRenderingContext2D} ctx context
1144
- * @param {Number} x start x coordinate
1145
- * @param {Number} y start y coordinate
1146
- * @param {Number} x2 end x coordinate
1147
- * @param {Number} y2 end y coordinate
1148
- * @param {Array} da dash array pattern
1149
- */
1150
- drawDashedLine: function(ctx, x, y, x2, y2, da) {
1151
- var dx = x2 - x,
1152
- dy = y2 - y,
1153
- len = sqrt(dx * dx + dy * dy),
1154
- rot = atan2(dy, dx),
1155
- dc = da.length,
1156
- di = 0,
1157
- draw = true;
1158
-
1159
- ctx.save();
1160
- ctx.translate(x, y);
1161
- ctx.moveTo(0, 0);
1162
- ctx.rotate(rot);
1163
-
1164
- x = 0;
1165
- while (len > x) {
1166
- x += da[di++ % dc];
1167
- if (x > len) {
1168
- x = len;
1169
- }
1170
- ctx[draw ? 'lineTo' : 'moveTo'](x, 0);
1171
- draw = !draw;
1172
- }
1173
-
1174
- ctx.restore();
1175
- },
1176
-
1177
1316
  /**
1178
1317
  * Creates canvas element
1179
1318
  * @static
@@ -1301,7 +1440,7 @@ fabric.CommonMethods = {
1301
1440
  * @param {Boolean} [options.flipX]
1302
1441
  * @param {Boolean} [options.flipY]
1303
1442
  * @param {Number} [options.skewX]
1304
- * @param {Number} [options.skewX]
1443
+ * @param {Number} [options.skewY]
1305
1444
  * @return {Number[]} transform matrix
1306
1445
  */
1307
1446
  calcDimensionsMatrix: function(options) {
@@ -1655,7 +1794,50 @@ fabric.CommonMethods = {
1655
1794
  x: bbox.width,
1656
1795
  y: bbox.height,
1657
1796
  };
1658
- }
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
+ },
1659
1841
  };
1660
1842
  })(typeof exports !== 'undefined' ? exports : this);
1661
1843
 
@@ -2148,8 +2330,8 @@ fabric.CommonMethods = {
2148
2330
  // with 100 segemnts. This will good enough to calculate the length of the curve
2149
2331
  function pathIterator(iterator, x1, y1) {
2150
2332
  var tempP = { x: x1, y: y1 }, p, tmpLen = 0, perc;
2151
- for (perc = 0.01; perc <= 1; perc += 0.01) {
2152
- p = iterator(perc);
2333
+ for (perc = 1; perc <= 100; perc += 1) {
2334
+ p = iterator(perc / 100);
2153
2335
  tmpLen += calcLineLength(tempP.x, tempP.y, p.x, p.y);
2154
2336
  tempP = p;
2155
2337
  }
@@ -2169,15 +2351,15 @@ fabric.CommonMethods = {
2169
2351
  p, nextLen, nextStep = 0.01, angleFinder = segInfo.angleFinder, lastPerc;
2170
2352
  // nextStep > 0.0001 covers 0.00015625 that 1/64th of 1/100
2171
2353
  // the path
2172
- while (tmpLen < distance && perc <= 1 && nextStep > 0.0001) {
2354
+ while (tmpLen < distance && nextStep > 0.0001) {
2173
2355
  p = iterator(perc);
2174
2356
  lastPerc = perc;
2175
2357
  nextLen = calcLineLength(tempP.x, tempP.y, p.x, p.y);
2176
2358
  // compare tmpLen each cycle with distance, decide next perc to test.
2177
2359
  if ((nextLen + tmpLen) > distance) {
2178
2360
  // we discard this step and we make smaller steps.
2179
- nextStep /= 2;
2180
2361
  perc -= nextStep;
2362
+ nextStep /= 2;
2181
2363
  }
2182
2364
  else {
2183
2365
  tempP = p;
@@ -2323,6 +2505,18 @@ fabric.CommonMethods = {
2323
2505
  }
2324
2506
  }
2325
2507
 
2508
+ /**
2509
+ *
2510
+ * @param {string} pathString
2511
+ * @return {(string|number)[][]} An array of SVG path commands
2512
+ * @example <caption>Usage</caption>
2513
+ * parsePath('M 3 4 Q 3 5 2 1 4 0 Q 9 12 2 1 4 0') === [
2514
+ * ['M', 3, 4],
2515
+ * ['Q', 3, 5, 2, 1, 4, 0],
2516
+ * ['Q', 9, 12, 2, 1, 4, 0],
2517
+ * ];
2518
+ *
2519
+ */
2326
2520
  function parsePath(pathString) {
2327
2521
  var result = [],
2328
2522
  coords = [],
@@ -2392,65 +2586,90 @@ fabric.CommonMethods = {
2392
2586
  };
2393
2587
 
2394
2588
  /**
2395
- * Calculate bounding box of a elliptic-arc
2396
- * @deprecated
2397
- * @param {Number} fx start point of arc
2398
- * @param {Number} fy
2399
- * @param {Number} rx horizontal radius
2400
- * @param {Number} ry vertical radius
2401
- * @param {Number} rot angle of horizontal axis
2402
- * @param {Number} large 1 or 0, whatever the arc is the big or the small on the 2 points
2403
- * @param {Number} sweep 1 or 0, 1 clockwise or counterclockwise direction
2404
- * @param {Number} tx end point of arc
2405
- * @param {Number} ty
2406
- */
2407
- function getBoundsOfArc(fx, fy, rx, ry, rot, large, sweep, tx, ty) {
2408
-
2409
- var fromX = 0, fromY = 0, bound, bounds = [],
2410
- segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot);
2411
-
2412
- for (var i = 0, len = segs.length; i < len; i++) {
2413
- bound = getBoundsOfCurve(fromX, fromY, segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5], segs[i][6]);
2414
- bounds.push({ x: bound[0].x + fx, y: bound[0].y + fy });
2415
- bounds.push({ x: bound[1].x + fx, y: bound[1].y + fy });
2416
- fromX = segs[i][5];
2417
- fromY = segs[i][6];
2589
+ *
2590
+ * Converts points to a smooth SVG path
2591
+ * @param {{ x: number,y: number }[]} points Array of points
2592
+ * @param {number} [correction] Apply a correction to the path (usually we use `width / 1000`). If value is undefined 0 is used as the correction value.
2593
+ * @return {(string|number)[][]} An array of SVG path commands
2594
+ */
2595
+ function getSmoothPathFromPoints(points, correction) {
2596
+ var path = [], i,
2597
+ p1 = new fabric.Point(points[0].x, points[0].y),
2598
+ p2 = new fabric.Point(points[1].x, points[1].y),
2599
+ len = points.length, multSignX = 1, multSignY = 0, manyPoints = len > 2;
2600
+ correction = correction || 0;
2601
+
2602
+ if (manyPoints) {
2603
+ multSignX = points[2].x < p2.x ? -1 : points[2].x === p2.x ? 0 : 1;
2604
+ multSignY = points[2].y < p2.y ? -1 : points[2].y === p2.y ? 0 : 1;
2605
+ }
2606
+ path.push(['M', p1.x - multSignX * correction, p1.y - multSignY * correction]);
2607
+ for (i = 1; i < len; i++) {
2608
+ if (!p1.eq(p2)) {
2609
+ var midPoint = p1.midPointFrom(p2);
2610
+ // p1 is our bezier control point
2611
+ // midpoint is our endpoint
2612
+ // start point is p(i-1) value.
2613
+ path.push(['Q', p1.x, p1.y, midPoint.x, midPoint.y]);
2614
+ }
2615
+ p1 = points[i];
2616
+ if ((i + 1) < points.length) {
2617
+ p2 = points[i + 1];
2618
+ }
2619
+ }
2620
+ if (manyPoints) {
2621
+ multSignX = p1.x > points[i - 2].x ? 1 : p1.x === points[i - 2].x ? 0 : -1;
2622
+ multSignY = p1.y > points[i - 2].y ? 1 : p1.y === points[i - 2].y ? 0 : -1;
2623
+ }
2624
+ path.push(['L', p1.x + multSignX * correction, p1.y + multSignY * correction]);
2625
+ return path;
2626
+ }
2627
+ /**
2628
+ * Transform a path by transforming each segment.
2629
+ * it has to be a simplified path or it won't work.
2630
+ * WARNING: this depends from pathOffset for correct operation
2631
+ * @param {Array} path fabricJS parsed and simplified path commands
2632
+ * @param {Array} transform matrix that represent the transformation
2633
+ * @param {Object} [pathOffset] the fabric.Path pathOffset
2634
+ * @param {Number} pathOffset.x
2635
+ * @param {Number} pathOffset.y
2636
+ * @returns {Array} the transformed path
2637
+ */
2638
+ function transformPath(path, transform, pathOffset) {
2639
+ if (pathOffset) {
2640
+ transform = fabric.util.multiplyTransformMatrices(
2641
+ transform,
2642
+ [1, 0, 0, 1, -pathOffset.x, -pathOffset.y]
2643
+ );
2418
2644
  }
2419
- return bounds;
2420
- };
2645
+ return path.map(function(pathSegment) {
2646
+ var newSegment = pathSegment.slice(0), point = {};
2647
+ for (var i = 1; i < pathSegment.length - 1; i += 2) {
2648
+ point.x = pathSegment[i];
2649
+ point.y = pathSegment[i + 1];
2650
+ point = fabric.util.transformPoint(point, transform);
2651
+ newSegment[i] = point.x;
2652
+ newSegment[i + 1] = point.y;
2653
+ }
2654
+ return newSegment;
2655
+ });
2656
+ }
2421
2657
 
2422
2658
  /**
2423
- * Draws arc
2424
- * @deprecated
2425
- * @param {CanvasRenderingContext2D} ctx
2426
- * @param {Number} fx
2427
- * @param {Number} fy
2428
- * @param {Array} coords coords of the arc, without the front 'A/a'
2429
- */
2430
- function drawArc(ctx, fx, fy, coords) {
2431
- coords = coords.slice(0).unshift('X'); // command A or a does not matter
2432
- var beziers = fromArcToBeziers(fx, fy, coords);
2433
- beziers.forEach(function(bezier) {
2434
- ctx.bezierCurveTo.apply(ctx, bezier.slice(1));
2435
- });
2659
+ * Join path commands to go back to svg format
2660
+ * @param {Array} pathData fabricJS parsed path commands
2661
+ * @return {String} joined path 'M 0 0 L 20 30'
2662
+ */
2663
+ fabric.util.joinPath = function(pathData) {
2664
+ return pathData.map(function (segment) { return segment.join(' '); }).join(' ');
2436
2665
  };
2437
-
2438
2666
  fabric.util.parsePath = parsePath;
2439
2667
  fabric.util.makePathSimpler = makePathSimpler;
2668
+ fabric.util.getSmoothPathFromPoints = getSmoothPathFromPoints;
2440
2669
  fabric.util.getPathSegmentsInfo = getPathSegmentsInfo;
2441
- fabric.util.fromArcToBeziers = fromArcToBeziers;
2442
- /**
2443
- * Typo of `fromArcToBeziers` kept for not breaking the api once corrected.
2444
- * Will be removed in fabric 5.0
2445
- * @deprecated
2446
- */
2447
- fabric.util.fromArcToBeizers = fromArcToBeziers;
2448
2670
  fabric.util.getBoundsOfCurve = getBoundsOfCurve;
2449
2671
  fabric.util.getPointOnPath = getPointOnPath;
2450
- // kept because we do not want to make breaking changes.
2451
- // but useless and deprecated.
2452
- fabric.util.getBoundsOfArc = getBoundsOfArc;
2453
- fabric.util.drawArc = drawArc;
2672
+ fabric.util.transformPath = transformPath;
2454
2673
  })();
2455
2674
 
2456
2675
 
@@ -2560,6 +2779,7 @@ fabric.CommonMethods = {
2560
2779
  * @memberOf fabric.util.object
2561
2780
  * @param {Object} destination Where to copy to
2562
2781
  * @param {Object} source Where to copy from
2782
+ * @param {Boolean} [deep] Whether to extend nested objects
2563
2783
  * @return {Object}
2564
2784
  */
2565
2785
 
@@ -2605,11 +2825,14 @@ fabric.CommonMethods = {
2605
2825
 
2606
2826
  /**
2607
2827
  * Creates an empty object and copies all enumerable properties of another object to it
2828
+ * This method is mostly for internal use, and not intended for duplicating shapes in canvas.
2608
2829
  * @memberOf fabric.util.object
2609
- * TODO: this function return an empty object if you try to clone null
2610
2830
  * @param {Object} object Object to clone
2831
+ * @param {Boolean} [deep] Whether to clone nested objects
2611
2832
  * @return {Object}
2612
2833
  */
2834
+
2835
+ //TODO: this function return an empty object if you try to clone null
2613
2836
  function clone(object, deep) {
2614
2837
  return extend({ }, object, deep);
2615
2838
  }
@@ -3347,7 +3570,117 @@ fabric.log = console.log;
3347
3570
  fabric.warn = console.warn;
3348
3571
 
3349
3572
 
3350
- (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
+ });
3351
3684
 
3352
3685
  function noop() {
3353
3686
  return false;
@@ -3360,21 +3693,30 @@ fabric.warn = console.warn;
3360
3693
  /**
3361
3694
  * Changes value from one to another within certain period of time, invoking callbacks as value is being changed.
3362
3695
  * @memberOf fabric.util
3363
- * @param {Object} [options] Animation options
3364
- * @param {Function} [options.onChange] Callback; invoked on every value change
3365
- * @param {Function} [options.onComplete] Callback; invoked when value change is completed
3366
- * @param {Number} [options.startValue=0] Starting value
3367
- * @param {Number} [options.endValue=100] Ending value
3368
- * @param {Number} [options.byValue=100] Value to modify the property by
3369
- * @param {Function} [options.easing] Easing function
3370
- * @param {Number} [options.duration=500] Duration of change (in ms)
3371
- * @param {Function} [options.abort] Additional function with logic. If returns true, onComplete is called.
3696
+ * @param {AnimationOptions} [options] Animation options
3697
+ * @returns {CancelFunction} cancel function
3372
3698
  */
3373
3699
  function animate(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
+ };
3374
3707
 
3375
- requestAnimFrame(function(timestamp) {
3376
- options || (options = { });
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);
3377
3718
 
3719
+ requestAnimFrame(function(timestamp) {
3378
3720
  var start = timestamp || +new Date(),
3379
3721
  duration = options.duration || 500,
3380
3722
  finish = start + duration, time,
@@ -3389,20 +3731,31 @@ fabric.warn = console.warn;
3389
3731
  options.onStart && options.onStart();
3390
3732
 
3391
3733
  (function tick(ticktime) {
3392
- // TODO: move abort call after calculation
3393
- // and pass (current,valuePerc, timePerc) as arguments
3394
3734
  time = ticktime || +new Date();
3395
3735
  var currentTime = time > finish ? duration : (time - start),
3396
3736
  timePerc = currentTime / duration,
3397
3737
  current = easing(currentTime, startValue, byValue, duration),
3398
3738
  valuePerc = Math.abs((current - startValue) / byValue);
3399
- if (abort()) {
3400
- onComplete(endValue, 1, 1);
3739
+ // update context
3740
+ context.currentValue = current;
3741
+ context.completionRate = valuePerc;
3742
+ context.durationRate = timePerc;
3743
+ if (cancel) {
3744
+ return;
3745
+ }
3746
+ if (abort(current, valuePerc, timePerc)) {
3747
+ removeFromRegistry();
3401
3748
  return;
3402
3749
  }
3403
3750
  if (time > finish) {
3751
+ // update context
3752
+ context.currentValue = endValue;
3753
+ context.completionRate = 1;
3754
+ context.durationRate = 1;
3755
+ // execute callbacks
3404
3756
  onChange(endValue, 1, 1);
3405
3757
  onComplete(endValue, 1, 1);
3758
+ removeFromRegistry();
3406
3759
  return;
3407
3760
  }
3408
3761
  else {
@@ -3411,6 +3764,8 @@ fabric.warn = console.warn;
3411
3764
  }
3412
3765
  })(start);
3413
3766
  });
3767
+
3768
+ return context.cancel;
3414
3769
  }
3415
3770
 
3416
3771
  var _requestAnimFrame = fabric.window.requestAnimationFrame ||
@@ -3442,6 +3797,7 @@ fabric.warn = console.warn;
3442
3797
  fabric.util.animate = animate;
3443
3798
  fabric.util.requestAnimFrame = requestAnimFrame;
3444
3799
  fabric.util.cancelAnimFrame = cancelAnimFrame;
3800
+ fabric.runningAnimations = RUNNING_ANIMATIONS;
3445
3801
  })();
3446
3802
 
3447
3803
 
@@ -3471,6 +3827,7 @@ fabric.warn = console.warn;
3471
3827
  * @param {Function} [options.onComplete] Callback; invoked when value change is completed
3472
3828
  * @param {Function} [options.colorEasing] Easing function. Note that this function only take two arguments (currentTime, duration). Thus the regular animation easing functions cannot be used.
3473
3829
  * @param {Function} [options.abort] Additional function with logic. If returns true, onComplete is called.
3830
+ * @returns {Function} abort function
3474
3831
  */
3475
3832
  function animateColor(fromColor, toColor, duration, options) {
3476
3833
  var startColor = new fabric.Color(fromColor).getSource(),
@@ -3479,7 +3836,7 @@ fabric.warn = console.warn;
3479
3836
  originalOnChange = options.onChange;
3480
3837
  options = options || {};
3481
3838
 
3482
- fabric.util.animate(fabric.util.object.extend(options, {
3839
+ return fabric.util.animate(fabric.util.object.extend(options, {
3483
3840
  duration: duration || 500,
3484
3841
  startValue: startColor,
3485
3842
  endValue: endColor,
@@ -4923,22 +5280,26 @@ fabric.warn = console.warn;
4923
5280
  if (styleContents.trim() === '') {
4924
5281
  continue;
4925
5282
  }
4926
- rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);
4927
- 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... `
4928
5289
  // eslint-disable-next-line no-loop-func
4929
5290
  rules.forEach(function(rule) {
4930
5291
 
4931
- var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/),
4932
- ruleObj = { }, declaration = match[2].trim(),
4933
- 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(); });
4934
5295
 
4935
5296
  for (i = 0, len = propertyValuePairs.length; i < len; i++) {
4936
- var pair = propertyValuePairs[i].split(/\s*:\s*/),
4937
- property = pair[0],
4938
- value = pair[1];
5297
+ var pair = propertyValuePairs[i].split(':'),
5298
+ property = pair[0].trim(),
5299
+ value = pair[1].trim();
4939
5300
  ruleObj[property] = value;
4940
5301
  }
4941
- rule = match[1];
5302
+ rule = match[0].trim();
4942
5303
  rule.split(',').forEach(function(_rule) {
4943
5304
  _rule = _rule.replace(/^svg/i, '').trim();
4944
5305
  if (_rule === '') {
@@ -8513,8 +8874,12 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
8513
8874
  imageSmoothingEnabled: true,
8514
8875
 
8515
8876
  /**
8516
- * 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
8517
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];
8518
8883
  * @default
8519
8884
  */
8520
8885
  viewportTransform: fabric.iMatrix.concat(),
@@ -8608,7 +8973,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
8608
8973
  * @private
8609
8974
  */
8610
8975
  _isRetinaScaling: function() {
8611
- return (fabric.devicePixelRatio !== 1 && this.enableRetinaScaling);
8976
+ return (fabric.devicePixelRatio > 1 && this.enableRetinaScaling);
8612
8977
  },
8613
8978
 
8614
8979
  /**
@@ -8616,7 +8981,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
8616
8981
  * @return {Number} retinaScaling if applied, otherwise 1;
8617
8982
  */
8618
8983
  getRetinaScaling: function() {
8619
- return this._isRetinaScaling() ? fabric.devicePixelRatio : 1;
8984
+ return this._isRetinaScaling() ? Math.max(1, fabric.devicePixelRatio) : 1;
8620
8985
  },
8621
8986
 
8622
8987
  /**
@@ -8903,7 +9268,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
8903
9268
  }
8904
9269
 
8905
9270
  fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas');
8906
-
9271
+ this._originalCanvasStyle = this.lowerCanvasEl.style;
8907
9272
  if (this.interactive) {
8908
9273
  this._applyCanvasStyle(this.lowerCanvasEl);
8909
9274
  }
@@ -8983,7 +9348,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
8983
9348
  }
8984
9349
  }
8985
9350
  if (this._isCurrentlyDrawing) {
8986
- this.freeDrawingBrush && this.freeDrawingBrush._setBrushStyles();
9351
+ this.freeDrawingBrush && this.freeDrawingBrush._setBrushStyles(this.contextTop);
8987
9352
  }
8988
9353
  this._initRetinaScaling();
8989
9354
  this.calcOffset();
@@ -9050,8 +9415,8 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
9050
9415
  },
9051
9416
 
9052
9417
  /**
9053
- * Sets viewport transform of this canvas instance
9054
- * @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
9055
9420
  * @return {fabric.Canvas} instance
9056
9421
  * @chainable true
9057
9422
  */
@@ -9193,7 +9558,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
9193
9558
  * @chainable
9194
9559
  */
9195
9560
  clear: function () {
9196
- this._objects.length = 0;
9561
+ this.remove.apply(this, this.getObjects());
9197
9562
  this.backgroundImage = null;
9198
9563
  this.overlayImage = null;
9199
9564
  this.backgroundColor = '';
@@ -9539,7 +9904,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
9539
9904
  version: fabric.version,
9540
9905
  objects: this._toObjects(methodName, propertiesToInclude),
9541
9906
  };
9542
- if (clipPath) {
9907
+ if (clipPath && !clipPath.excludeFromExport) {
9543
9908
  data.clipPath = this._toObject(this.clipPath, methodName, propertiesToInclude);
9544
9909
  }
9545
9910
  extend(data, this.__serializeBgOverlay(methodName, propertiesToInclude));
@@ -9582,24 +9947,32 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
9582
9947
  * @private
9583
9948
  */
9584
9949
  __serializeBgOverlay: function(methodName, propertiesToInclude) {
9585
- var data = { }, bgImage = this.backgroundImage, overlay = this.overlayImage;
9950
+ var data = {}, bgImage = this.backgroundImage, overlayImage = this.overlayImage,
9951
+ bgColor = this.backgroundColor, overlayColor = this.overlayColor;
9586
9952
 
9587
- if (this.backgroundColor) {
9588
- data.background = this.backgroundColor.toObject
9589
- ? this.backgroundColor.toObject(propertiesToInclude)
9590
- : this.backgroundColor;
9953
+ if (bgColor && bgColor.toObject) {
9954
+ if (!bgColor.excludeFromExport) {
9955
+ data.background = bgColor.toObject(propertiesToInclude);
9956
+ }
9957
+ }
9958
+ else if (bgColor) {
9959
+ data.background = bgColor;
9591
9960
  }
9592
9961
 
9593
- if (this.overlayColor) {
9594
- data.overlay = this.overlayColor.toObject
9595
- ? this.overlayColor.toObject(propertiesToInclude)
9596
- : this.overlayColor;
9962
+ if (overlayColor && overlayColor.toObject) {
9963
+ if (!overlayColor.excludeFromExport) {
9964
+ data.overlay = overlayColor.toObject(propertiesToInclude);
9965
+ }
9597
9966
  }
9967
+ else if (overlayColor) {
9968
+ data.overlay = overlayColor;
9969
+ }
9970
+
9598
9971
  if (bgImage && !bgImage.excludeFromExport) {
9599
9972
  data.backgroundImage = this._toObject(bgImage, methodName, propertiesToInclude);
9600
9973
  }
9601
- if (overlay && !overlay.excludeFromExport) {
9602
- data.overlayImage = this._toObject(overlay, methodName, propertiesToInclude);
9974
+ if (overlayImage && !overlayImage.excludeFromExport) {
9975
+ data.overlayImage = this._toObject(overlayImage, methodName, propertiesToInclude);
9603
9976
  }
9604
9977
 
9605
9978
  return data;
@@ -10130,6 +10503,10 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
10130
10503
  }
10131
10504
  this.forEachObject(function(object) {
10132
10505
  object.dispose && object.dispose();
10506
+ // animation module is still optional
10507
+ if (fabric.runningAnimations) {
10508
+ fabric.runningAnimations.cancelByTarget(object);
10509
+ }
10133
10510
  });
10134
10511
  this._objects = [];
10135
10512
  if (this.backgroundImage && this.backgroundImage.dispose) {
@@ -10142,6 +10519,13 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
10142
10519
  this.overlayImage = null;
10143
10520
  this._iTextInstances = null;
10144
10521
  this.contextContainer = null;
10522
+ // restore canvas style
10523
+ this.lowerCanvasEl.classList.remove('lower-canvas');
10524
+ fabric.util.setStyle(this.lowerCanvasEl, this._originalCanvasStyle);
10525
+ delete this._originalCanvasStyle;
10526
+ // restore canvas size to original size in case retina scaling was applied
10527
+ this.lowerCanvasEl.setAttribute('width', this.width);
10528
+ this.lowerCanvasEl.setAttribute('height', this.height);
10145
10529
  fabric.util.cleanUpJsdomNode(this.lowerCanvasEl);
10146
10530
  this.lowerCanvasEl = undefined;
10147
10531
  return this;
@@ -10305,17 +10689,15 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10305
10689
  /**
10306
10690
  * Sets brush styles
10307
10691
  * @private
10692
+ * @param {CanvasRenderingContext2D} ctx
10308
10693
  */
10309
- _setBrushStyles: function() {
10310
- var ctx = this.canvas.contextTop;
10694
+ _setBrushStyles: function (ctx) {
10311
10695
  ctx.strokeStyle = this.color;
10312
10696
  ctx.lineWidth = this.width;
10313
10697
  ctx.lineCap = this.strokeLineCap;
10314
10698
  ctx.miterLimit = this.strokeMiterLimit;
10315
10699
  ctx.lineJoin = this.strokeLineJoin;
10316
- if (fabric.StaticCanvas.supports('setLineDash')) {
10317
- ctx.setLineDash(this.strokeDashArray || []);
10318
- }
10700
+ ctx.setLineDash(this.strokeDashArray || []);
10319
10701
  },
10320
10702
 
10321
10703
  /**
@@ -10394,6 +10776,22 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10394
10776
  */
10395
10777
  decimate: 0.4,
10396
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
+
10397
10795
  /**
10398
10796
  * Constructor
10399
10797
  * @param {fabric.Canvas} canvas
@@ -10404,6 +10802,10 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10404
10802
  this._points = [];
10405
10803
  },
10406
10804
 
10805
+ needsFullRender: function () {
10806
+ return this.callSuper('needsFullRender') || this._hasStraightLine;
10807
+ },
10808
+
10407
10809
  /**
10408
10810
  * Invoked inside on mouse down and mouse move
10409
10811
  * @param {Object} pointer
@@ -10422,6 +10824,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10422
10824
  if (!this.canvas._isMainEvent(options.e)) {
10423
10825
  return;
10424
10826
  }
10827
+ this.drawStraightLine = options.e[this.straightLineKey];
10425
10828
  this._prepareForDrawing(pointer);
10426
10829
  // capture coordinates immediately
10427
10830
  // this allows to draw dots (when movement never occurs)
@@ -10437,6 +10840,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10437
10840
  if (!this.canvas._isMainEvent(options.e)) {
10438
10841
  return;
10439
10842
  }
10843
+ this.drawStraightLine = options.e[this.straightLineKey];
10440
10844
  if (this.limitedToCanvasSize === true && this._isOutSideCanvas(pointer)) {
10441
10845
  return;
10442
10846
  }
@@ -10469,6 +10873,7 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10469
10873
  if (!this.canvas._isMainEvent(options.e)) {
10470
10874
  return true;
10471
10875
  }
10876
+ this.drawStraightLine = false;
10472
10877
  this.oldEnd = undefined;
10473
10878
  this._finalizeAndAddPath();
10474
10879
  return false;
@@ -10495,6 +10900,10 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10495
10900
  if (this._points.length > 1 && point.eq(this._points[this._points.length - 1])) {
10496
10901
  return false;
10497
10902
  }
10903
+ if (this.drawStraightLine && this._points.length > 1) {
10904
+ this._hasStraightLine = true;
10905
+ this._points.pop();
10906
+ }
10498
10907
  this._points.push(point);
10499
10908
  return true;
10500
10909
  },
@@ -10505,8 +10914,9 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10505
10914
  */
10506
10915
  _reset: function() {
10507
10916
  this._points = [];
10508
- this._setBrushStyles();
10917
+ this._setBrushStyles(this.canvas.contextTop);
10509
10918
  this._setShadow();
10919
+ this._hasStraightLine = false;
10510
10920
  },
10511
10921
 
10512
10922
  /**
@@ -10521,12 +10931,13 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10521
10931
  /**
10522
10932
  * Draw a smooth path on the topCanvas using quadraticCurveTo
10523
10933
  * @private
10934
+ * @param {CanvasRenderingContext2D} [ctx]
10524
10935
  */
10525
- _render: function() {
10526
- var ctx = this.canvas.contextTop, i, len,
10936
+ _render: function(ctx) {
10937
+ var i, len,
10527
10938
  p1 = this._points[0],
10528
10939
  p2 = this._points[1];
10529
-
10940
+ ctx = ctx || this.canvas.contextTop;
10530
10941
  this._saveAndTransform(ctx);
10531
10942
  ctx.beginPath();
10532
10943
  //if we only have 2 points in the path and they are the same
@@ -10560,43 +10971,26 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10560
10971
  /**
10561
10972
  * Converts points to SVG path
10562
10973
  * @param {Array} points Array of points
10563
- * @return {String} SVG path
10974
+ * @return {(string|number)[][]} SVG path commands
10564
10975
  */
10565
- convertPointsToSVGPath: function(points) {
10566
- var path = [], i, width = this.width / 1000,
10567
- p1 = new fabric.Point(points[0].x, points[0].y),
10568
- p2 = new fabric.Point(points[1].x, points[1].y),
10569
- len = points.length, multSignX = 1, multSignY = 0, manyPoints = len > 2;
10976
+ convertPointsToSVGPath: function (points) {
10977
+ var correction = this.width / 1000;
10978
+ return fabric.util.getSmoothPathFromPoints(points, correction);
10979
+ },
10570
10980
 
10571
- if (manyPoints) {
10572
- multSignX = points[2].x < p2.x ? -1 : points[2].x === p2.x ? 0 : 1;
10573
- multSignY = points[2].y < p2.y ? -1 : points[2].y === p2.y ? 0 : 1;
10574
- }
10575
- path.push('M ', p1.x - multSignX * width, ' ', p1.y - multSignY * width, ' ');
10576
- for (i = 1; i < len; i++) {
10577
- if (!p1.eq(p2)) {
10578
- var midPoint = p1.midPointFrom(p2);
10579
- // p1 is our bezier control point
10580
- // midpoint is our endpoint
10581
- // start point is p(i-1) value.
10582
- path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' ');
10583
- }
10584
- p1 = points[i];
10585
- if ((i + 1) < points.length) {
10586
- p2 = points[i + 1];
10587
- }
10588
- }
10589
- if (manyPoints) {
10590
- multSignX = p1.x > points[i - 2].x ? 1 : p1.x === points[i - 2].x ? 0 : -1;
10591
- multSignY = p1.y > points[i - 2].y ? 1 : p1.y === points[i - 2].y ? 0 : -1;
10592
- }
10593
- path.push('L ', p1.x + multSignX * width, ' ', p1.y + multSignY * width);
10594
- return path;
10981
+ /**
10982
+ * @private
10983
+ * @param {(string|number)[][]} pathData SVG path commands
10984
+ * @returns {boolean}
10985
+ */
10986
+ _isEmptySVGPath: function (pathData) {
10987
+ var pathString = fabric.util.joinPath(pathData);
10988
+ return pathString === 'M 0 0 Q 0 0 0 0 L 0 0';
10595
10989
  },
10596
10990
 
10597
10991
  /**
10598
10992
  * Creates fabric.Path object to add on canvas
10599
- * @param {String} pathData Path data
10993
+ * @param {(string|number)[][]} pathData Path data
10600
10994
  * @return {fabric.Path} Path to add on canvas
10601
10995
  */
10602
10996
  createPath: function(pathData) {
@@ -10653,8 +11047,8 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10653
11047
  if (this.decimate) {
10654
11048
  this._points = this.decimatePoints(this._points, this.decimate);
10655
11049
  }
10656
- var pathData = this.convertPointsToSVGPath(this._points).join('');
10657
- if (pathData === 'M 0 0 Q 0 0 0 0 L 0 0') {
11050
+ var pathData = this.convertPointsToSVGPath(this._points);
11051
+ if (this._isEmptySVGPath(pathData)) {
10658
11052
  // do not create 0 width/height paths, as they are
10659
11053
  // rendered inconsistently across browsers
10660
11054
  // Firefox 4, for example, renders a dot,
@@ -11077,17 +11471,19 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
11077
11471
 
11078
11472
  /**
11079
11473
  * Creates "pattern" instance property
11474
+ * @param {CanvasRenderingContext2D} ctx
11080
11475
  */
11081
- getPattern: function() {
11082
- return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), 'repeat');
11476
+ getPattern: function(ctx) {
11477
+ return ctx.createPattern(this.source || this.getPatternSrc(), 'repeat');
11083
11478
  },
11084
11479
 
11085
11480
  /**
11086
11481
  * Sets brush styles
11482
+ * @param {CanvasRenderingContext2D} ctx
11087
11483
  */
11088
- _setBrushStyles: function() {
11089
- this.callSuper('_setBrushStyles');
11090
- this.canvas.contextTop.strokeStyle = this.getPattern();
11484
+ _setBrushStyles: function(ctx) {
11485
+ this.callSuper('_setBrushStyles', ctx);
11486
+ ctx.strokeStyle = this.getPattern(ctx);
11091
11487
  },
11092
11488
 
11093
11489
  /**
@@ -11111,10 +11507,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
11111
11507
 
11112
11508
  var getPointer = fabric.util.getPointer,
11113
11509
  degreesToRadians = fabric.util.degreesToRadians,
11114
- abs = Math.abs,
11115
- supportLineDash = fabric.StaticCanvas.supports('setLineDash'),
11116
- isTouchEvent = fabric.util.isTouchEvent,
11117
- STROKE_OFFSET = 0.5;
11510
+ isTouchEvent = fabric.util.isTouchEvent;
11118
11511
 
11119
11512
  /**
11120
11513
  * Canvas class
@@ -11149,15 +11542,11 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
11149
11542
  * @fires dragover
11150
11543
  * @fires dragenter
11151
11544
  * @fires dragleave
11545
+ * @fires drop:before before drop event. same native event. This is added to handle edge cases
11152
11546
  * @fires drop
11153
11547
  * @fires after:render at the end of the render process, receives the context in the callback
11154
11548
  * @fires before:render at start the render process, receives the context in the callback
11155
11549
  *
11156
- * the following events are deprecated:
11157
- * @fires object:rotated at the end of a rotation transform
11158
- * @fires object:scaled at the end of a scale transform
11159
- * @fires object:moved at the end of translation transform
11160
- * @fires object:skewed at the end of a skew transform
11161
11550
  */
11162
11551
  fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, /** @lends fabric.Canvas.prototype */ {
11163
11552
 
@@ -11342,13 +11731,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
11342
11731
  */
11343
11732
  freeDrawingCursor: 'crosshair',
11344
11733
 
11345
- /**
11346
- * Cursor value used for rotation point
11347
- * @type String
11348
- * @default
11349
- */
11350
- rotationCursor: 'crosshair',
11351
-
11352
11734
  /**
11353
11735
  * Cursor value used for disabled elements ( corners with disabled action )
11354
11736
  * @type String
@@ -11454,6 +11836,13 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
11454
11836
  */
11455
11837
  targets: [],
11456
11838
 
11839
+ /**
11840
+ * When the option is enabled, PointerEvent is used instead of MouseEvent.
11841
+ * @type Boolean
11842
+ * @default
11843
+ */
11844
+ enablePointerEvents: false,
11845
+
11457
11846
  /**
11458
11847
  * Keep track of the hovered target
11459
11848
  * @type fabric.Object
@@ -11529,6 +11918,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
11529
11918
  }
11530
11919
  if (this.hasLostContext) {
11531
11920
  this.renderTopLayer(this.contextTop);
11921
+ this.hasLostContext = false;
11532
11922
  }
11533
11923
  var canvasToDrawOn = this.contextContainer;
11534
11924
  this.renderCanvas(canvasToDrawOn, this._chooseObjectsToRender());
@@ -11798,21 +12188,20 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
11798
12188
  * @param {CanvasRenderingContext2D} ctx to draw the selection on
11799
12189
  */
11800
12190
  _drawSelection: function (ctx) {
11801
- var groupSelector = this._groupSelector,
11802
- left = groupSelector.left,
11803
- top = groupSelector.top,
11804
- aleft = abs(left),
11805
- atop = abs(top);
12191
+ var selector = this._groupSelector,
12192
+ viewportStart = new fabric.Point(selector.ex, selector.ey),
12193
+ start = fabric.util.transformPoint(viewportStart, this.viewportTransform),
12194
+ viewportExtent = new fabric.Point(selector.ex + selector.left, selector.ey + selector.top),
12195
+ extent = fabric.util.transformPoint(viewportExtent, this.viewportTransform),
12196
+ minX = Math.min(start.x, extent.x),
12197
+ minY = Math.min(start.y, extent.y),
12198
+ maxX = Math.max(start.x, extent.x),
12199
+ maxY = Math.max(start.y, extent.y),
12200
+ strokeOffset = this.selectionLineWidth / 2;
11806
12201
 
11807
12202
  if (this.selectionColor) {
11808
12203
  ctx.fillStyle = this.selectionColor;
11809
-
11810
- ctx.fillRect(
11811
- groupSelector.ex - ((left > 0) ? 0 : -left),
11812
- groupSelector.ey - ((top > 0) ? 0 : -top),
11813
- aleft,
11814
- atop
11815
- );
12204
+ ctx.fillRect(minX, minY, maxX - minX, maxY - minY);
11816
12205
  }
11817
12206
 
11818
12207
  if (!this.selectionLineWidth || !this.selectionBorderColor) {
@@ -11821,31 +12210,13 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
11821
12210
  ctx.lineWidth = this.selectionLineWidth;
11822
12211
  ctx.strokeStyle = this.selectionBorderColor;
11823
12212
 
12213
+ minX += strokeOffset;
12214
+ minY += strokeOffset;
12215
+ maxX -= strokeOffset;
12216
+ maxY -= strokeOffset;
11824
12217
  // selection border
11825
- if (this.selectionDashArray.length > 1 && !supportLineDash) {
11826
-
11827
- var px = groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft),
11828
- py = groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop);
11829
-
11830
- ctx.beginPath();
11831
-
11832
- fabric.util.drawDashedLine(ctx, px, py, px + aleft, py, this.selectionDashArray);
11833
- fabric.util.drawDashedLine(ctx, px, py + atop - 1, px + aleft, py + atop - 1, this.selectionDashArray);
11834
- fabric.util.drawDashedLine(ctx, px, py, px, py + atop, this.selectionDashArray);
11835
- fabric.util.drawDashedLine(ctx, px + aleft - 1, py, px + aleft - 1, py + atop, this.selectionDashArray);
11836
-
11837
- ctx.closePath();
11838
- ctx.stroke();
11839
- }
11840
- else {
11841
- fabric.Object.prototype._setLineDash.call(this, ctx, this.selectionDashArray);
11842
- ctx.strokeRect(
11843
- groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft),
11844
- groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop),
11845
- aleft,
11846
- atop
11847
- );
11848
- }
12218
+ fabric.Object.prototype._setLineDash.call(this, ctx, this.selectionDashArray);
12219
+ ctx.strokeRect(minX, minY, maxX - minX, maxY - minY);
11849
12220
  },
11850
12221
 
11851
12222
  /**
@@ -12217,17 +12588,12 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
12217
12588
  e: e,
12218
12589
  selected: added,
12219
12590
  deselected: removed,
12220
- // added for backward compatibility
12221
- // deprecated
12222
- updated: added[0] || removed[0],
12223
- target: this._activeObject,
12224
12591
  });
12225
12592
  }
12226
12593
  else if (objects.length > 0) {
12227
12594
  this.fire('selection:created', {
12228
12595
  e: e,
12229
12596
  selected: added,
12230
- target: this._activeObject,
12231
12597
  });
12232
12598
  }
12233
12599
  else if (oldObjects.length > 0) {
@@ -12551,7 +12917,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
12551
12917
  this._onDragOver = this._onDragOver.bind(this);
12552
12918
  this._onDragEnter = this._simpleEventHandler.bind(this, 'dragenter');
12553
12919
  this._onDragLeave = this._simpleEventHandler.bind(this, 'dragleave');
12554
- this._onDrop = this._simpleEventHandler.bind(this, 'drop');
12920
+ this._onDrop = this._onDrop.bind(this);
12555
12921
  this.eventsBound = true;
12556
12922
  },
12557
12923
 
@@ -12663,6 +13029,18 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
12663
13029
  this._fireEnterLeaveEvents(target, e);
12664
13030
  },
12665
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
+
12666
13044
  /**
12667
13045
  * @private
12668
13046
  * @param {Event} e Event object fired on mousedown
@@ -12895,25 +13273,34 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
12895
13273
  );
12896
13274
  }
12897
13275
  }
13276
+ var corner, pointer;
12898
13277
  if (target) {
12899
- if (target.selectable && target !== this._activeObject && target.activeOn === 'up') {
12900
- this.setActiveObject(target, e);
13278
+ corner = target._findTargetCorner(
13279
+ this.getPointer(e, true),
13280
+ fabric.util.isTouchEvent(e)
13281
+ );
13282
+ if (target.selectable && target !== this._activeObject && target.activeOn === 'up') {
13283
+ this.setActiveObject(target, e);
12901
13284
  shouldRender = true;
12902
13285
  }
12903
13286
  else {
12904
- var corner = target._findTargetCorner(
12905
- this.getPointer(e, true),
12906
- fabric.util.isTouchEvent(e)
12907
- );
12908
13287
  var control = target.controls[corner],
12909
13288
  mouseUpHandler = control && control.getMouseUpHandler(e, target, control);
12910
13289
  if (mouseUpHandler) {
12911
- var pointer = this.getPointer(e);
13290
+ pointer = this.getPointer(e);
12912
13291
  mouseUpHandler(e, transform, pointer.x, pointer.y);
12913
13292
  }
12914
13293
  }
12915
13294
  target.isMoving = false;
12916
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
+ }
12917
13304
  this._setCursorFromEvent(e, target);
12918
13305
  this._handleEvent(e, 'up', LEFT_CLICK, isClick);
12919
13306
  this._groupSelector = null;
@@ -12995,7 +13382,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
12995
13382
 
12996
13383
  var transform = this._currentTransform,
12997
13384
  target = transform.target,
12998
- eventName,
12999
13385
  options = {
13000
13386
  e: e,
13001
13387
  target: target,
@@ -13010,59 +13396,10 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
13010
13396
  target.setCoords();
13011
13397
 
13012
13398
  if (transform.actionPerformed || (this.stateful && target.hasStateChanged())) {
13013
- if (transform.actionPerformed) {
13014
- // this is not friendly to the new control api.
13015
- // is deprecated.
13016
- eventName = this._addEventOptions(options, transform);
13017
- this._fire(eventName, options);
13018
- }
13019
13399
  this._fire('modified', options);
13020
13400
  }
13021
13401
  },
13022
13402
 
13023
- /**
13024
- * Mutate option object in order to add by property and give back the event name.
13025
- * @private
13026
- * @deprecated since 4.2.0
13027
- * @param {Object} options to mutate
13028
- * @param {Object} transform to inspect action from
13029
- */
13030
- _addEventOptions: function(options, transform) {
13031
- // we can probably add more details at low cost
13032
- // scale change, rotation changes, translation changes
13033
- var eventName, by;
13034
- switch (transform.action) {
13035
- case 'scaleX':
13036
- eventName = 'scaled';
13037
- by = 'x';
13038
- break;
13039
- case 'scaleY':
13040
- eventName = 'scaled';
13041
- by = 'y';
13042
- break;
13043
- case 'skewX':
13044
- eventName = 'skewed';
13045
- by = 'x';
13046
- break;
13047
- case 'skewY':
13048
- eventName = 'skewed';
13049
- by = 'y';
13050
- break;
13051
- case 'scale':
13052
- eventName = 'scaled';
13053
- by = 'equally';
13054
- break;
13055
- case 'rotate':
13056
- eventName = 'rotated';
13057
- break;
13058
- case 'drag':
13059
- eventName = 'moved';
13060
- break;
13061
- }
13062
- options.by = by;
13063
- return eventName;
13064
- },
13065
-
13066
13403
  /**
13067
13404
  * @private
13068
13405
  * @param {Event} e Event object fired on mousedown
@@ -13157,8 +13494,8 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
13157
13494
  if (this.selection && (!target ||
13158
13495
  (!target.selectable && !target.isEditing && target !== this._activeObject))) {
13159
13496
  this._groupSelector = {
13160
- ex: pointer.x,
13161
- ey: pointer.y,
13497
+ ex: this._absolutePointer.x,
13498
+ ey: this._absolutePointer.y,
13162
13499
  top: 0,
13163
13500
  left: 0
13164
13501
  };
@@ -13251,7 +13588,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
13251
13588
 
13252
13589
  // We initially clicked in an empty area, so we draw a box for multiple selection
13253
13590
  if (groupSelector) {
13254
- pointer = this._pointer;
13591
+ pointer = this._absolutePointer;
13255
13592
 
13256
13593
  groupSelector.left = pointer.x - groupSelector.ex;
13257
13594
  groupSelector.top = pointer.y - groupSelector.ey;
@@ -13597,10 +13934,10 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
13597
13934
  continue;
13598
13935
  }
13599
13936
 
13600
- if ((allowIntersect && currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2)) ||
13601
- currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2) ||
13602
- (allowIntersect && currentObject.containsPoint(selectionX1Y1)) ||
13603
- (allowIntersect && currentObject.containsPoint(selectionX2Y2))
13937
+ if ((allowIntersect && currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2, true)) ||
13938
+ currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2, true) ||
13939
+ (allowIntersect && currentObject.containsPoint(selectionX1Y1, null, true)) ||
13940
+ (allowIntersect && currentObject.containsPoint(selectionX2Y2, null, true))
13604
13941
  ) {
13605
13942
  group.push(currentObject);
13606
13943
  // only add one object if it's a click
@@ -13974,7 +14311,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
13974
14311
  toFixed = fabric.util.toFixed,
13975
14312
  capitalize = fabric.util.string.capitalize,
13976
14313
  degreesToRadians = fabric.util.degreesToRadians,
13977
- supportsLineDash = fabric.StaticCanvas.supports('setLineDash'),
13978
14314
  objectCaching = !fabric.isLikelyNode,
13979
14315
  ALIASING_LIMIT = 2;
13980
14316
 
@@ -14503,6 +14839,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
14503
14839
  /**
14504
14840
  * When `false`, the stoke width will scale with the object.
14505
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
14506
14843
  * default to false
14507
14844
  * @since 2.6.0
14508
14845
  * @type Boolean
@@ -14745,6 +15082,12 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
14745
15082
  additionalHeight = height * 0.1;
14746
15083
  }
14747
15084
  }
15085
+ if (this instanceof fabric.Text && this.path) {
15086
+ shouldRedraw = true;
15087
+ shouldResizeCanvas = true;
15088
+ additionalWidth += this.getHeightOfLine(0) * this.zoomX;
15089
+ additionalHeight += this.getHeightOfLine(0) * this.zoomY;
15090
+ }
14748
15091
  if (shouldRedraw) {
14749
15092
  if (shouldResizeCanvas) {
14750
15093
  canvas.width = Math.ceil(width + additionalWidth);
@@ -14834,7 +15177,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
14834
15177
  skewY: toFixed(this.skewY, NUM_FRACTION_DIGITS),
14835
15178
  };
14836
15179
 
14837
- if (this.clipPath) {
15180
+ if (this.clipPath && !this.clipPath.excludeFromExport) {
14838
15181
  object.clipPath = this.clipPath.toObject(propertiesToInclude);
14839
15182
  object.clipPath.inverted = this.clipPath.inverted;
14840
15183
  object.clipPath.absolutePositioned = this.clipPath.absolutePositioned;
@@ -14897,6 +15240,17 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
14897
15240
  * @return {Object} object with scaleX and scaleY properties
14898
15241
  */
14899
15242
  getObjectScaling: function() {
15243
+ // if the object is a top level one, on the canvas, we go for simple aritmetic
15244
+ // otherwise the complex method with angles will return approximations and decimals
15245
+ // and will likely kill the cache when not needed
15246
+ // https://github.com/fabricjs/fabric.js/issues/7157
15247
+ if (!this.group) {
15248
+ return {
15249
+ scaleX: this.scaleX,
15250
+ scaleY: this.scaleY,
15251
+ };
15252
+ }
15253
+ // if we are inside a group total zoom calculation is complex, we defer to generic matrices
14900
15254
  var options = fabric.util.qrDecompose(this.calcTransformMatrix());
14901
15255
  return { scaleX: Math.abs(options.scaleX), scaleY: Math.abs(options.scaleY) };
14902
15256
  },
@@ -15136,26 +15490,26 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
15136
15490
  /**
15137
15491
  * Execute the drawing operation for an object clipPath
15138
15492
  * @param {CanvasRenderingContext2D} ctx Context to render on
15493
+ * @param {fabric.Object} clipPath
15139
15494
  */
15140
- drawClipPathOnCache: function(ctx) {
15141
- var path = this.clipPath;
15495
+ drawClipPathOnCache: function(ctx, clipPath) {
15142
15496
  ctx.save();
15143
15497
  // DEBUG: uncomment this line, comment the following
15144
15498
  // ctx.globalAlpha = 0.4
15145
- if (path.inverted) {
15499
+ if (clipPath.inverted) {
15146
15500
  ctx.globalCompositeOperation = 'destination-out';
15147
15501
  }
15148
15502
  else {
15149
15503
  ctx.globalCompositeOperation = 'destination-in';
15150
15504
  }
15151
15505
  //ctx.scale(1 / 2, 1 / 2);
15152
- if (path.absolutePositioned) {
15506
+ if (clipPath.absolutePositioned) {
15153
15507
  var m = fabric.util.invertTransform(this.calcTransformMatrix());
15154
15508
  ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
15155
15509
  }
15156
- path.transform(ctx);
15157
- ctx.scale(1 / path.zoomX, 1 / path.zoomY);
15158
- 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);
15159
15513
  ctx.restore();
15160
15514
  },
15161
15515
 
@@ -15174,22 +15528,26 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
15174
15528
  this._renderBackground(ctx);
15175
15529
  }
15176
15530
  this._render(ctx);
15177
- this._drawClipPath(ctx);
15531
+ this._drawClipPath(ctx, this.clipPath);
15178
15532
  this.fill = originalFill;
15179
15533
  this.stroke = originalStroke;
15180
15534
  },
15181
15535
 
15182
- _drawClipPath: function(ctx) {
15183
- var path = this.clipPath;
15184
- 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; }
15185
15543
  // needed to setup a couple of variables
15186
15544
  // path canvas gets overridden with this one.
15187
15545
  // TODO find a better solution?
15188
- path.canvas = this.canvas;
15189
- path.shouldCache();
15190
- path._transformDone = true;
15191
- path.renderCache({ forClipping: true });
15192
- 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);
15193
15551
  },
15194
15552
 
15195
15553
  /**
@@ -15319,9 +15677,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
15319
15677
  * Sets line dash
15320
15678
  * @param {CanvasRenderingContext2D} ctx Context to set the dash line on
15321
15679
  * @param {Array} dashArray array representing dashes
15322
- * @param {Function} alternative function to call if browser does not support lineDash
15323
15680
  */
15324
- _setLineDash: function(ctx, dashArray, alternative) {
15681
+ _setLineDash: function(ctx, dashArray) {
15325
15682
  if (!dashArray || dashArray.length === 0) {
15326
15683
  return;
15327
15684
  }
@@ -15329,16 +15686,12 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
15329
15686
  if (1 & dashArray.length) {
15330
15687
  dashArray.push.apply(dashArray, dashArray);
15331
15688
  }
15332
- if (supportsLineDash) {
15333
- ctx.setLineDash(dashArray);
15334
- }
15335
- else {
15336
- alternative && alternative(ctx);
15337
- }
15689
+ ctx.setLineDash(dashArray);
15338
15690
  },
15339
15691
 
15340
15692
  /**
15341
15693
  * Renders controls and borders for the object
15694
+ * the context here is not transformed
15342
15695
  * @param {CanvasRenderingContext2D} ctx Context to render on
15343
15696
  * @param {Object} [styleOverride] properties to override the object style
15344
15697
  */
@@ -15357,12 +15710,14 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
15357
15710
  if (!this.group) {
15358
15711
  ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
15359
15712
  }
15360
- if (styleOverride.forActiveSelection) {
15361
- ctx.rotate(degreesToRadians(options.angle));
15713
+ if (this.flipX) {
15714
+ options.angle -= 180;
15715
+ }
15716
+ ctx.rotate(degreesToRadians(this.group ? options.angle : this.angle));
15717
+ if (styleOverride.forActiveSelection || this.group) {
15362
15718
  drawBorders && this.drawBordersInGroup(ctx, options, styleOverride);
15363
15719
  }
15364
15720
  else {
15365
- ctx.rotate(degreesToRadians(this.angle));
15366
15721
  drawBorders && this.drawBorders(ctx, styleOverride);
15367
15722
  }
15368
15723
  drawControls && this.drawControls(ctx, styleOverride);
@@ -15505,7 +15860,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
15505
15860
  else if (this.strokeUniform) {
15506
15861
  ctx.scale(1 / this.scaleX, 1 / this.scaleY);
15507
15862
  }
15508
- this._setLineDash(ctx, this.strokeDashArray, this._renderDashedStroke);
15863
+ this._setLineDash(ctx, this.strokeDashArray);
15509
15864
  this._setStrokeStyles(ctx, this);
15510
15865
  ctx.stroke();
15511
15866
  ctx.restore();
@@ -15894,6 +16249,15 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
15894
16249
  if (this.globalCompositeOperation) {
15895
16250
  ctx.globalCompositeOperation = this.globalCompositeOperation;
15896
16251
  }
16252
+ },
16253
+
16254
+ /**
16255
+ * cancel instance's running animations
16256
+ */
16257
+ dispose: function () {
16258
+ if (fabric.runningAnimations) {
16259
+ fabric.runningAnimations.cancelByTarget(this);
16260
+ }
15897
16261
  }
15898
16262
  });
15899
16263
 
@@ -15911,6 +16275,15 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
15911
16275
  */
15912
16276
  fabric.Object.NUM_FRACTION_DIGITS = 2;
15913
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
+
15914
16287
  fabric.Object._fromObject = function(className, object, callback, extraParam) {
15915
16288
  var klass = fabric[className];
15916
16289
  object = clone(object, true);
@@ -15921,8 +16294,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
15921
16294
  if (typeof patterns[1] !== 'undefined') {
15922
16295
  object.stroke = patterns[1];
15923
16296
  }
15924
- fabric.util.enlivenObjects([object.clipPath], function(enlivedProps) {
15925
- object.clipPath = enlivedProps[0];
16297
+ fabric.util.enlivenObjectEnlivables(object, object, function () {
15926
16298
  var instance = extraParam ? new klass(object[extraParam], object) : new klass(object);
15927
16299
  callback && callback(instance);
15928
16300
  });
@@ -16243,7 +16615,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
16243
16615
  /**
16244
16616
  * Describe object's corner position in canvas element coordinates.
16245
16617
  * includes padding. Used of object detection.
16246
- * set and refreshed with setCoords and calcCoords.
16618
+ * set and refreshed with setCoords.
16247
16619
  * @memberOf fabric.Object.prototype
16248
16620
  */
16249
16621
  lineCoords: null,
@@ -16627,21 +16999,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
16627
16999
  return this.scale(value / this.height / boundingRectFactor);
16628
17000
  },
16629
17001
 
16630
- /**
16631
- * Calculates and returns the .coords of an object.
16632
- * unused by the library, only for the end dev.
16633
- * @return {Object} Object with tl, tr, br, bl ....
16634
- * @chainable
16635
- * @deprecated
16636
- */
16637
- calcCoords: function(absolute) {
16638
- // this is a compatibility function to avoid removing calcCoords now.
16639
- if (absolute) {
16640
- return this.calcACoords();
16641
- }
16642
- return this.calcOCoords();
16643
- },
16644
-
16645
17002
  calcLineCoords: function() {
16646
17003
  var vpt = this.getViewportTransform(),
16647
17004
  padding = this.padding, angle = degreesToRadians(this.angle),
@@ -16716,7 +17073,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
16716
17073
  * oCoords are used to find the corners
16717
17074
  * aCoords are used to quickly find an object on the canvas
16718
17075
  * lineCoords are used to quickly find object during pointer events.
16719
- * 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
+ *
16720
17078
  * @param {Boolean} [skipCorners] skip calculation of oCoords.
16721
17079
  * @return {fabric.Object} thisArg
16722
17080
  * @chainable
@@ -16813,23 +17171,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
16813
17171
  return cache.value;
16814
17172
  },
16815
17173
 
16816
- /*
16817
- * Calculate object dimensions from its properties
16818
- * @private
16819
- * @deprecated since 3.4.0, please use fabric.util._calcDimensionsTransformMatrix
16820
- * not including or including flipX, flipY to emulate the flipping boolean
16821
- * @return {Object} .x width dimension
16822
- * @return {Object} .y height dimension
16823
- */
16824
- _calcDimensionsTransformMatrix: function(skewX, skewY, flipping) {
16825
- return util.calcDimensionsMatrix({
16826
- skewX: skewX,
16827
- skewY: skewY,
16828
- scaleX: this.scaleX * (flipping && this.flipX ? -1 : 1),
16829
- scaleY: this.scaleY * (flipping && this.flipY ? -1 : 1)
16830
- });
16831
- },
16832
-
16833
17174
  /*
16834
17175
  * Calculate object dimensions from its properties
16835
17176
  * @private
@@ -16858,7 +17199,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
16858
17199
  if (typeof skewY === 'undefined') {
16859
17200
  skewY = this.skewY;
16860
17201
  }
16861
- var dimensions = this._getNonTransformedDimensions(), dimX, dimY,
17202
+ var dimensions, dimX, dimY,
16862
17203
  noSkew = skewX === 0 && skewY === 0;
16863
17204
 
16864
17205
  if (this.strokeUniform) {
@@ -16866,6 +17207,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
16866
17207
  dimY = this.height;
16867
17208
  }
16868
17209
  else {
17210
+ dimensions = this._getNonTransformedDimensions();
16869
17211
  dimX = dimensions.x;
16870
17212
  dimY = dimensions.y;
16871
17213
  }
@@ -17497,7 +17839,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
17497
17839
 
17498
17840
  ctx.save();
17499
17841
  ctx.strokeStyle = styleOverride.borderColor || this.borderColor;
17500
- this._setLineDash(ctx, styleOverride.borderDashArray || this.borderDashArray, null);
17842
+ this._setLineDash(ctx, styleOverride.borderDashArray || this.borderDashArray);
17501
17843
 
17502
17844
  ctx.strokeRect(
17503
17845
  -width / 2,
@@ -17550,7 +17892,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
17550
17892
  height =
17551
17893
  bbox.y + strokeWidth * (strokeUniform ? this.canvas.getZoom() : options.scaleY) + borderScaleFactor;
17552
17894
  ctx.save();
17553
- this._setLineDash(ctx, styleOverride.borderDashArray || this.borderDashArray, null);
17895
+ this._setLineDash(ctx, styleOverride.borderDashArray || this.borderDashArray);
17554
17896
  ctx.strokeStyle = styleOverride.borderColor || this.borderColor;
17555
17897
  ctx.strokeRect(
17556
17898
  -width / 2,
@@ -17575,18 +17917,29 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
17575
17917
  drawControls: function(ctx, styleOverride) {
17576
17918
  styleOverride = styleOverride || {};
17577
17919
  ctx.save();
17578
- ctx.setTransform(this.canvas.getRetinaScaling(), 0, 0, this.canvas.getRetinaScaling(), 0, 0);
17920
+ var retinaScaling = this.canvas.getRetinaScaling(), matrix, p;
17921
+ ctx.setTransform(retinaScaling, 0, 0, retinaScaling, 0, 0);
17579
17922
  ctx.strokeStyle = ctx.fillStyle = styleOverride.cornerColor || this.cornerColor;
17580
17923
  if (!this.transparentCorners) {
17581
17924
  ctx.strokeStyle = styleOverride.cornerStrokeColor || this.cornerStrokeColor;
17582
17925
  }
17583
- this._setLineDash(ctx, styleOverride.cornerDashArray || this.cornerDashArray, null);
17926
+ this._setLineDash(ctx, styleOverride.cornerDashArray || this.cornerDashArray);
17584
17927
  this.setCoords();
17928
+ if (this.group) {
17929
+ // fabricJS does not really support drawing controls inside groups,
17930
+ // this piece of code here helps having at least the control in places.
17931
+ // If an application needs to show some objects as selected because of some UI state
17932
+ // can still call Object._renderControls() on any object they desire, independently of groups.
17933
+ // using no padding, circular controls and hiding the rotating cursor is higly suggested,
17934
+ matrix = this.group.calcTransformMatrix();
17935
+ }
17585
17936
  this.forEachControl(function(control, key, fabricObject) {
17937
+ p = fabricObject.oCoords[key];
17586
17938
  if (control.getVisibility(fabricObject, key)) {
17587
- control.render(ctx,
17588
- fabricObject.oCoords[key].x,
17589
- fabricObject.oCoords[key].y, styleOverride, fabricObject);
17939
+ if (matrix) {
17940
+ p = fabric.util.transformPoint(p, matrix);
17941
+ }
17942
+ control.render(ctx, p.x, p.y, styleOverride, fabricObject);
17590
17943
  }
17591
17944
  });
17592
17945
  ctx.restore();
@@ -17682,8 +18035,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17682
18035
  * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
17683
18036
  * @param {Function} [callbacks.onComplete] Invoked on completion
17684
18037
  * @param {Function} [callbacks.onChange] Invoked on every step of animation
17685
- * @return {fabric.Canvas} thisArg
17686
- * @chainable
18038
+ * @return {fabric.AnimationContext} context
17687
18039
  */
17688
18040
  fxCenterObjectH: function (object, callbacks) {
17689
18041
  callbacks = callbacks || { };
@@ -17693,7 +18045,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17693
18045
  onChange = callbacks.onChange || empty,
17694
18046
  _this = this;
17695
18047
 
17696
- fabric.util.animate({
18048
+ return fabric.util.animate({
18049
+ target: this,
17697
18050
  startValue: object.left,
17698
18051
  endValue: this.getCenter().left,
17699
18052
  duration: this.FX_DURATION,
@@ -17707,8 +18060,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17707
18060
  onComplete();
17708
18061
  }
17709
18062
  });
17710
-
17711
- return this;
17712
18063
  },
17713
18064
 
17714
18065
  /**
@@ -17717,8 +18068,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17717
18068
  * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
17718
18069
  * @param {Function} [callbacks.onComplete] Invoked on completion
17719
18070
  * @param {Function} [callbacks.onChange] Invoked on every step of animation
17720
- * @return {fabric.Canvas} thisArg
17721
- * @chainable
18071
+ * @return {fabric.AnimationContext} context
17722
18072
  */
17723
18073
  fxCenterObjectV: function (object, callbacks) {
17724
18074
  callbacks = callbacks || { };
@@ -17728,7 +18078,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17728
18078
  onChange = callbacks.onChange || empty,
17729
18079
  _this = this;
17730
18080
 
17731
- fabric.util.animate({
18081
+ return fabric.util.animate({
18082
+ target: this,
17732
18083
  startValue: object.top,
17733
18084
  endValue: this.getCenter().top,
17734
18085
  duration: this.FX_DURATION,
@@ -17742,8 +18093,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17742
18093
  onComplete();
17743
18094
  }
17744
18095
  });
17745
-
17746
- return this;
17747
18096
  },
17748
18097
 
17749
18098
  /**
@@ -17752,8 +18101,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17752
18101
  * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
17753
18102
  * @param {Function} [callbacks.onComplete] Invoked on completion
17754
18103
  * @param {Function} [callbacks.onChange] Invoked on every step of animation
17755
- * @return {fabric.Canvas} thisArg
17756
- * @chainable
18104
+ * @return {fabric.AnimationContext} context
17757
18105
  */
17758
18106
  fxRemove: function (object, callbacks) {
17759
18107
  callbacks = callbacks || { };
@@ -17763,7 +18111,8 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17763
18111
  onChange = callbacks.onChange || empty,
17764
18112
  _this = this;
17765
18113
 
17766
- fabric.util.animate({
18114
+ return fabric.util.animate({
18115
+ target: this,
17767
18116
  startValue: object.opacity,
17768
18117
  endValue: 0,
17769
18118
  duration: this.FX_DURATION,
@@ -17777,8 +18126,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
17777
18126
  onComplete();
17778
18127
  }
17779
18128
  });
17780
-
17781
- return this;
17782
18129
  }
17783
18130
  });
17784
18131
 
@@ -17789,7 +18136,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
17789
18136
  * @param {Number|Object} value Value to animate property to (if string was given first) or options object
17790
18137
  * @return {fabric.Object} thisArg
17791
18138
  * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#animation}
17792
- * @chainable
18139
+ * @return {fabric.AnimationContext | fabric.AnimationContext[]} animation context (or an array if passed multiple properties)
17793
18140
  *
17794
18141
  * As object — multiple properties
17795
18142
  *
@@ -17802,22 +18149,22 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
17802
18149
  * object.animate('left', { duration: ... });
17803
18150
  *
17804
18151
  */
17805
- animate: function() {
18152
+ animate: function () {
17806
18153
  if (arguments[0] && typeof arguments[0] === 'object') {
17807
- var propsToAnimate = [], prop, skipCallbacks;
18154
+ var propsToAnimate = [], prop, skipCallbacks, out = [];
17808
18155
  for (prop in arguments[0]) {
17809
18156
  propsToAnimate.push(prop);
17810
18157
  }
17811
18158
  for (var i = 0, len = propsToAnimate.length; i < len; i++) {
17812
18159
  prop = propsToAnimate[i];
17813
18160
  skipCallbacks = i !== len - 1;
17814
- this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks);
18161
+ out.push(this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks));
17815
18162
  }
18163
+ return out;
17816
18164
  }
17817
18165
  else {
17818
- this._animate.apply(this, arguments);
18166
+ return this._animate.apply(this, arguments);
17819
18167
  }
17820
- return this;
17821
18168
  },
17822
18169
 
17823
18170
  /**
@@ -17865,13 +18212,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
17865
18212
  }
17866
18213
 
17867
18214
  var _options = {
18215
+ target: this,
17868
18216
  startValue: options.from,
17869
18217
  endValue: to,
17870
18218
  byValue: options.by,
17871
18219
  easing: options.easing,
17872
18220
  duration: options.duration,
17873
- abort: options.abort && function () {
17874
- return options.abort.call(_this);
18221
+ abort: options.abort && function(value, valueProgress, timeProgress) {
18222
+ return options.abort.call(_this, value, valueProgress, timeProgress);
17875
18223
  },
17876
18224
  onChange: function (value, valueProgress, timeProgress) {
17877
18225
  if (propPair) {
@@ -17896,10 +18244,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
17896
18244
  };
17897
18245
 
17898
18246
  if (propIsColor) {
17899
- fabric.util.animateColor(_options.startValue, _options.endValue, _options.duration, _options);
18247
+ return fabric.util.animateColor(_options.startValue, _options.endValue, _options.duration, _options);
17900
18248
  }
17901
18249
  else {
17902
- fabric.util.animate(_options);
18250
+ return fabric.util.animate(_options);
17903
18251
  }
17904
18252
  }
17905
18253
  });
@@ -17912,8 +18260,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
17912
18260
  var fabric = global.fabric || (global.fabric = { }),
17913
18261
  extend = fabric.util.object.extend,
17914
18262
  clone = fabric.util.object.clone,
17915
- coordProps = { x1: 1, x2: 1, y1: 1, y2: 1 },
17916
- supportsLineDash = fabric.StaticCanvas.supports('setLineDash');
18263
+ coordProps = { x1: 1, x2: 1, y1: 1, y2: 1 };
17917
18264
 
17918
18265
  if (fabric.Line) {
17919
18266
  fabric.warn('fabric.Line is already defined');
@@ -18061,13 +18408,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18061
18408
  _render: function(ctx) {
18062
18409
  ctx.beginPath();
18063
18410
 
18064
- if (!this.strokeDashArray || this.strokeDashArray && supportsLineDash) {
18065
- // move from center (of virtual box) to its left/top corner
18066
- // we can't assume x1, y1 is top left and x2, y2 is bottom right
18067
- var p = this.calcLinePoints();
18068
- ctx.moveTo(p.x1, p.y1);
18069
- ctx.lineTo(p.x2, p.y2);
18070
- }
18411
+
18412
+ var p = this.calcLinePoints();
18413
+ ctx.moveTo(p.x1, p.y1);
18414
+ ctx.lineTo(p.x2, p.y2);
18071
18415
 
18072
18416
  ctx.lineWidth = this.strokeWidth;
18073
18417
 
@@ -18080,18 +18424,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18080
18424
  ctx.strokeStyle = origStrokeStyle;
18081
18425
  },
18082
18426
 
18083
- /**
18084
- * @private
18085
- * @param {CanvasRenderingContext2D} ctx Context to render on
18086
- */
18087
- _renderDashedStroke: function(ctx) {
18088
- var p = this.calcLinePoints();
18089
-
18090
- ctx.beginPath();
18091
- fabric.util.drawDashedLine(ctx, p.x1, p.y1, p.x2, p.y2, this.strokeDashArray);
18092
- ctx.closePath();
18093
- },
18094
-
18095
18427
  /**
18096
18428
  * This function is an helper for svg import. it returns the center of the object in the svg
18097
18429
  * untransformed coordinates
@@ -18252,7 +18584,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18252
18584
  'use strict';
18253
18585
 
18254
18586
  var fabric = global.fabric || (global.fabric = { }),
18255
- pi = Math.PI;
18587
+ degreesToRadians = fabric.util.degreesToRadians;
18256
18588
 
18257
18589
  if (fabric.Circle) {
18258
18590
  fabric.warn('fabric.Circle is already defined.');
@@ -18282,22 +18614,20 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18282
18614
  radius: 0,
18283
18615
 
18284
18616
  /**
18285
- * Start angle of the circle, moving clockwise
18286
- * deprecated type, this should be in degree, this was an oversight.
18617
+ * degrees of start of the circle.
18287
18618
  * probably will change to degrees in next major version
18288
- * @type Number
18619
+ * @type Number 0 - 359
18289
18620
  * @default 0
18290
18621
  */
18291
18622
  startAngle: 0,
18292
18623
 
18293
18624
  /**
18294
18625
  * End angle of the circle
18295
- * deprecated type, this should be in degree, this was an oversight.
18296
18626
  * probably will change to degrees in next major version
18297
- * @type Number
18298
- * @default 2Pi
18627
+ * @type Number 1 - 360
18628
+ * @default 360
18299
18629
  */
18300
- endAngle: pi * 2,
18630
+ endAngle: 360,
18301
18631
 
18302
18632
  cacheProperties: fabric.Object.prototype.cacheProperties.concat('radius', 'startAngle', 'endAngle'),
18303
18633
 
@@ -18335,7 +18665,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18335
18665
  */
18336
18666
  _toSVG: function() {
18337
18667
  var svgString, x = 0, y = 0,
18338
- angle = (this.endAngle - this.startAngle) % ( 2 * pi);
18668
+ angle = (this.endAngle - this.startAngle) % 360;
18339
18669
 
18340
18670
  if (angle === 0) {
18341
18671
  svgString = [
@@ -18346,14 +18676,17 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18346
18676
  ];
18347
18677
  }
18348
18678
  else {
18349
- var startX = fabric.util.cos(this.startAngle) * this.radius,
18350
- startY = fabric.util.sin(this.startAngle) * this.radius,
18351
- endX = fabric.util.cos(this.endAngle) * this.radius,
18352
- endY = fabric.util.sin(this.endAngle) * this.radius,
18353
- 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';
18354
18687
  svgString = [
18355
18688
  '<path d="M ' + startX + ' ' + startY,
18356
- ' A ' + this.radius + ' ' + this.radius,
18689
+ ' A ' + radius + ' ' + radius,
18357
18690
  ' 0 ', +largeFlag + ' 1', ' ' + endX + ' ' + endY,
18358
18691
  '" ', 'COMMON_PARTS', ' />\n'
18359
18692
  ];
@@ -18372,8 +18705,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18372
18705
  0,
18373
18706
  0,
18374
18707
  this.radius,
18375
- this.startAngle,
18376
- this.endAngle, false);
18708
+ degreesToRadians(this.startAngle),
18709
+ degreesToRadians(this.endAngle),
18710
+ false
18711
+ );
18377
18712
  this._renderPaintInOrder(ctx);
18378
18713
  },
18379
18714
 
@@ -18447,10 +18782,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18447
18782
  * @memberOf fabric.Circle
18448
18783
  * @param {Object} object Object to create an instance from
18449
18784
  * @param {function} [callback] invoked with new instance as first argument
18450
- * @return {Object} Instance of fabric.Circle
18785
+ * @return {void}
18451
18786
  */
18452
18787
  fabric.Circle.fromObject = function(object, callback) {
18453
- return fabric.Object._fromObject('Circle', object, callback);
18788
+ fabric.Object._fromObject('Circle', object, callback);
18454
18789
  };
18455
18790
 
18456
18791
  })(typeof exports !== 'undefined' ? exports : this);
@@ -18514,21 +18849,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18514
18849
  this._renderPaintInOrder(ctx);
18515
18850
  },
18516
18851
 
18517
- /**
18518
- * @private
18519
- * @param {CanvasRenderingContext2D} ctx Context to render on
18520
- */
18521
- _renderDashedStroke: function(ctx) {
18522
- var widthBy2 = this.width / 2,
18523
- heightBy2 = this.height / 2;
18524
-
18525
- ctx.beginPath();
18526
- fabric.util.drawDashedLine(ctx, -widthBy2, heightBy2, 0, -heightBy2, this.strokeDashArray);
18527
- fabric.util.drawDashedLine(ctx, 0, -heightBy2, widthBy2, heightBy2, this.strokeDashArray);
18528
- fabric.util.drawDashedLine(ctx, widthBy2, heightBy2, -widthBy2, heightBy2, this.strokeDashArray);
18529
- ctx.closePath();
18530
- },
18531
-
18532
18852
  /* _TO_SVG_START_ */
18533
18853
  /**
18534
18854
  * Returns svg representation of an instance
@@ -18740,10 +19060,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18740
19060
  * @memberOf fabric.Ellipse
18741
19061
  * @param {Object} object Object to create an instance from
18742
19062
  * @param {function} [callback] invoked with new instance as first argument
18743
- * @return {fabric.Ellipse}
19063
+ * @return {void}
18744
19064
  */
18745
19065
  fabric.Ellipse.fromObject = function(object, callback) {
18746
- return fabric.Object._fromObject('Ellipse', object, callback);
19066
+ fabric.Object._fromObject('Ellipse', object, callback);
18747
19067
  };
18748
19068
 
18749
19069
  })(typeof exports !== 'undefined' ? exports : this);
@@ -18862,24 +19182,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18862
19182
  this._renderPaintInOrder(ctx);
18863
19183
  },
18864
19184
 
18865
- /**
18866
- * @private
18867
- * @param {CanvasRenderingContext2D} ctx Context to render on
18868
- */
18869
- _renderDashedStroke: function(ctx) {
18870
- var x = -this.width / 2,
18871
- y = -this.height / 2,
18872
- w = this.width,
18873
- h = this.height;
18874
-
18875
- ctx.beginPath();
18876
- fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray);
18877
- fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray);
18878
- fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray);
18879
- fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray);
18880
- ctx.closePath();
18881
- },
18882
-
18883
19185
  /**
18884
19186
  * Returns object representation of an instance
18885
19187
  * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
@@ -18964,7 +19266,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18964
19266
  extend = fabric.util.object.extend,
18965
19267
  min = fabric.util.array.min,
18966
19268
  max = fabric.util.array.max,
18967
- toFixed = fabric.util.toFixed;
19269
+ toFixed = fabric.util.toFixed,
19270
+ projectStrokeOnPoints = fabric.util.projectStrokeOnPoints;
18968
19271
 
18969
19272
  if (fabric.Polyline) {
18970
19273
  fabric.warn('fabric.Polyline is already defined');
@@ -18993,6 +19296,17 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
18993
19296
  */
18994
19297
  points: null,
18995
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
+
18996
19310
  cacheProperties: fabric.Object.prototype.cacheProperties.concat('points'),
18997
19311
 
18998
19312
  /**
@@ -19021,13 +19335,25 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19021
19335
  this._setPositionDimensions(options);
19022
19336
  },
19023
19337
 
19338
+ /**
19339
+ * @private
19340
+ */
19341
+ _projectStrokeOnPoints: function () {
19342
+ return projectStrokeOnPoints(this.points, this, true);
19343
+ },
19344
+
19024
19345
  _setPositionDimensions: function(options) {
19025
- var calcDim = this._calcDimensions(options), correctLeftTop;
19026
- this.width = calcDim.width;
19027
- 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;
19028
19350
  if (!options.fromSVG) {
19029
19351
  correctLeftTop = this.translateToGivenOrigin(
19030
- { 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
+ },
19031
19357
  'left',
19032
19358
  'top',
19033
19359
  this.originX,
@@ -19041,8 +19367,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19041
19367
  this.top = options.fromSVG ? calcDim.top : correctLeftTop.y;
19042
19368
  }
19043
19369
  this.pathOffset = {
19044
- x: calcDim.left + this.width / 2,
19045
- y: calcDim.top + this.height / 2
19370
+ x: calcDim.left + this.width / 2 + correctSize / 2,
19371
+ y: calcDim.top + this.height / 2 + correctSize / 2
19046
19372
  };
19047
19373
  },
19048
19374
 
@@ -19058,7 +19384,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19058
19384
  */
19059
19385
  _calcDimensions: function() {
19060
19386
 
19061
- var points = this.points,
19387
+ var points = this.exactBoundingBox ? this._projectStrokeOnPoints() : this.points,
19062
19388
  minX = min(points, 'x') || 0,
19063
19389
  minY = min(points, 'y') || 0,
19064
19390
  maxX = max(points, 'x') || 0,
@@ -19070,7 +19396,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19070
19396
  left: minX,
19071
19397
  top: minY,
19072
19398
  width: width,
19073
- height: height
19399
+ height: height,
19074
19400
  };
19075
19401
  },
19076
19402
 
@@ -19144,21 +19470,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19144
19470
  this._renderPaintInOrder(ctx);
19145
19471
  },
19146
19472
 
19147
- /**
19148
- * @private
19149
- * @param {CanvasRenderingContext2D} ctx Context to render on
19150
- */
19151
- _renderDashedStroke: function(ctx) {
19152
- var p1, p2;
19153
-
19154
- ctx.beginPath();
19155
- for (var i = 0, len = this.points.length; i < len; i++) {
19156
- p1 = this.points[i];
19157
- p2 = this.points[i + 1] || p1;
19158
- fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray);
19159
- }
19160
- },
19161
-
19162
19473
  /**
19163
19474
  * Returns complexity of an instance
19164
19475
  * @return {Number} complexity of this instance
@@ -19221,7 +19532,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19221
19532
 
19222
19533
  'use strict';
19223
19534
 
19224
- var fabric = global.fabric || (global.fabric = { });
19535
+ var fabric = global.fabric || (global.fabric = {}),
19536
+ projectStrokeOnPoints = fabric.util.projectStrokeOnPoints;
19225
19537
 
19226
19538
  if (fabric.Polygon) {
19227
19539
  fabric.warn('fabric.Polygon is already defined');
@@ -19243,6 +19555,13 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19243
19555
  */
19244
19556
  type: 'polygon',
19245
19557
 
19558
+ /**
19559
+ * @private
19560
+ */
19561
+ _projectStrokeOnPoints: function () {
19562
+ return projectStrokeOnPoints(this.points, this);
19563
+ },
19564
+
19246
19565
  /**
19247
19566
  * @private
19248
19567
  * @param {CanvasRenderingContext2D} ctx Context to render on
@@ -19255,14 +19574,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19255
19574
  this._renderPaintInOrder(ctx);
19256
19575
  },
19257
19576
 
19258
- /**
19259
- * @private
19260
- * @param {CanvasRenderingContext2D} ctx Context to render on
19261
- */
19262
- _renderDashedStroke: function(ctx) {
19263
- this.callSuper('_renderDashedStroke', ctx);
19264
- ctx.closePath();
19265
- },
19266
19577
  });
19267
19578
 
19268
19579
  /* _FROM_SVG_START_ */
@@ -19291,9 +19602,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19291
19602
  * @memberOf fabric.Polygon
19292
19603
  * @param {Object} object Object to create an instance from
19293
19604
  * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created
19605
+ * @return {void}
19294
19606
  */
19295
19607
  fabric.Polygon.fromObject = function(object, callback) {
19296
- return fabric.Object._fromObject('Polygon', object, callback, 'points');
19608
+ fabric.Object._fromObject('Polygon', object, callback, 'points');
19297
19609
  };
19298
19610
 
19299
19611
  })(typeof exports !== 'undefined' ? exports : this);
@@ -19307,6 +19619,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19307
19619
  min = fabric.util.array.min,
19308
19620
  max = fabric.util.array.max,
19309
19621
  extend = fabric.util.object.extend,
19622
+ clone = fabric.util.object.clone,
19310
19623
  _toString = Object.prototype.toString,
19311
19624
  toFixed = fabric.util.toFixed;
19312
19625
 
@@ -19348,26 +19661,26 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19348
19661
  * @param {Object} [options] Options object
19349
19662
  * @return {fabric.Path} thisArg
19350
19663
  */
19351
- initialize: function(path, options) {
19352
- options = options || { };
19664
+ initialize: function (path, options) {
19665
+ options = clone(options || {});
19666
+ delete options.path;
19353
19667
  this.callSuper('initialize', options);
19354
- if (!path) {
19355
- path = [];
19356
- }
19668
+ this._setPath(path || [], options);
19669
+ },
19357
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) {
19358
19677
  var fromArray = _toString.call(path) === '[object Array]';
19359
19678
 
19360
- this.path = fromArray
19361
- ? fabric.util.makePathSimpler(path)
19362
-
19363
- : fabric.util.makePathSimpler(
19364
- fabric.util.parsePath(path)
19365
- );
19679
+ this.path = fabric.util.makePathSimpler(
19680
+ fromArray ? path : fabric.util.parsePath(path)
19681
+ );
19366
19682
 
19367
- if (!this.path) {
19368
- return;
19369
- }
19370
- fabric.Polyline.prototype._setPositionDimensions.call(this, options);
19683
+ fabric.Polyline.prototype._setPositionDimensions.call(this, options || {});
19371
19684
  },
19372
19685
 
19373
19686
  /**
@@ -19494,9 +19807,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19494
19807
  * of the instance
19495
19808
  */
19496
19809
  _toSVG: function() {
19497
- var path = this.path.map(function(path) {
19498
- return path.join(' ');
19499
- }).join(' ');
19810
+ var path = fabric.util.joinPath(this.path);
19500
19811
  return [
19501
19812
  '<path ', 'COMMON_PARTS',
19502
19813
  'd="', path,
@@ -19923,13 +20234,17 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19923
20234
  */
19924
20235
  toObject: function(propertiesToInclude) {
19925
20236
  var _includeDefaultValues = this.includeDefaultValues;
19926
- var objsToObject = this._objects.map(function(obj) {
19927
- var originalDefaults = obj.includeDefaultValues;
19928
- obj.includeDefaultValues = _includeDefaultValues;
19929
- var _obj = obj.toObject(propertiesToInclude);
19930
- obj.includeDefaultValues = originalDefaults;
19931
- return _obj;
19932
- });
20237
+ var objsToObject = this._objects
20238
+ .filter(function (obj) {
20239
+ return !obj.excludeFromExport;
20240
+ })
20241
+ .map(function (obj) {
20242
+ var originalDefaults = obj.includeDefaultValues;
20243
+ obj.includeDefaultValues = _includeDefaultValues;
20244
+ var _obj = obj.toObject(propertiesToInclude);
20245
+ obj.includeDefaultValues = originalDefaults;
20246
+ return _obj;
20247
+ });
19933
20248
  var obj = fabric.Object.prototype.toObject.call(this, propertiesToInclude);
19934
20249
  obj.objects = objsToObject;
19935
20250
  return obj;
@@ -20022,7 +20337,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
20022
20337
  for (var i = 0, len = this._objects.length; i < len; i++) {
20023
20338
  this._objects[i].render(ctx);
20024
20339
  }
20025
- this._drawClipPath(ctx);
20340
+ this._drawClipPath(ctx, this.clipPath);
20026
20341
  },
20027
20342
 
20028
20343
  /**
@@ -20068,25 +20383,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
20068
20383
  return this;
20069
20384
  },
20070
20385
 
20071
- /**
20072
- * Realises the transform from this group onto the supplied object
20073
- * i.e. it tells you what would happen if the supplied object was in
20074
- * the group, and then the group was destroyed. It mutates the supplied
20075
- * object.
20076
- * Warning: this method is not useful anymore, it has been kept to no break the api.
20077
- * is not used in the fabricJS codebase
20078
- * this method will be reduced to using the utility.
20079
- * @private
20080
- * @deprecated
20081
- * @param {fabric.Object} object
20082
- * @param {Array} parentMatrix parent transformation
20083
- * @return {fabric.Object} transformedObject
20084
- */
20085
- realizeTransform: function(object, parentMatrix) {
20086
- fabric.util.addTransformToObject(object, parentMatrix);
20087
- return object;
20088
- },
20089
-
20090
20386
  /**
20091
20387
  * Destroys a group (restoring state of its objects)
20092
20388
  * @return {fabric.Group} thisArg
@@ -20101,6 +20397,14 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
20101
20397
  return this._restoreObjectsState();
20102
20398
  },
20103
20399
 
20400
+ dispose: function () {
20401
+ this.callSuper('dispose');
20402
+ this.forEachObject(function (object) {
20403
+ object.dispose && object.dispose();
20404
+ });
20405
+ this._objects = [];
20406
+ },
20407
+
20104
20408
  /**
20105
20409
  * make a group an active selection, remove the group from canvas
20106
20410
  * the group has to be on canvas for this to work.
@@ -20264,11 +20568,10 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
20264
20568
  });
20265
20569
  return;
20266
20570
  }
20267
- fabric.util.enlivenObjects(objects, function(enlivenedObjects) {
20268
- fabric.util.enlivenObjects([object.clipPath], function(enlivedClipPath) {
20269
- var options = fabric.util.object.clone(object, true);
20270
- options.clipPath = enlivedClipPath[0];
20271
- 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 () {
20272
20575
  callback && callback(new fabric.Group(enlivenedObjects, options, true));
20273
20576
  });
20274
20577
  });
@@ -20638,7 +20941,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
20638
20941
  /**
20639
20942
  * Delete textures, reference to elements and eventually JSDOM cleanup
20640
20943
  */
20641
- dispose: function() {
20944
+ dispose: function () {
20945
+ this.callSuper('dispose');
20642
20946
  this.removeTexture(this.cacheKey);
20643
20947
  this.removeTexture(this.cacheKey + '_filtered');
20644
20948
  this._cacheContext = undefined;
@@ -20685,28 +20989,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
20685
20989
  ctx.closePath();
20686
20990
  },
20687
20991
 
20688
- /**
20689
- * @private
20690
- * @param {CanvasRenderingContext2D} ctx Context to render on
20691
- */
20692
- _renderDashedStroke: function(ctx) {
20693
- var x = -this.width / 2,
20694
- y = -this.height / 2,
20695
- w = this.width,
20696
- h = this.height;
20697
-
20698
- ctx.save();
20699
- this._setStrokeStyles(ctx, this);
20700
-
20701
- ctx.beginPath();
20702
- fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray);
20703
- fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray);
20704
- fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray);
20705
- fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray);
20706
- ctx.closePath();
20707
- ctx.restore();
20708
- },
20709
-
20710
20992
  /**
20711
20993
  * Returns object representation of an instance
20712
20994
  * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
@@ -21170,8 +21452,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
21170
21452
  object.filters = filters || [];
21171
21453
  fabric.Image.prototype._initFilters.call(object, [object.resizeFilter], function(resizeFilters) {
21172
21454
  object.resizeFilter = resizeFilters[0];
21173
- fabric.util.enlivenObjects([object.clipPath], function(enlivedProps) {
21174
- object.clipPath = enlivedProps[0];
21455
+ fabric.util.enlivenObjectEnlivables(object, object, function () {
21175
21456
  var image = new fabric.Image(img, object);
21176
21457
  callback(image, false);
21177
21458
  });
@@ -21242,8 +21523,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
21242
21523
  * @chainable
21243
21524
  */
21244
21525
  straighten: function() {
21245
- this.rotate(this._getAngleValueForStraighten());
21246
- return this;
21526
+ return this.rotate(this._getAngleValueForStraighten());
21247
21527
  },
21248
21528
 
21249
21529
  /**
@@ -21252,7 +21532,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
21252
21532
  * @param {Function} [callbacks.onComplete] Invoked on completion
21253
21533
  * @param {Function} [callbacks.onChange] Invoked on every step of animation
21254
21534
  * @return {fabric.Object} thisArg
21255
- * @chainable
21256
21535
  */
21257
21536
  fxStraighten: function(callbacks) {
21258
21537
  callbacks = callbacks || { };
@@ -21262,7 +21541,8 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
21262
21541
  onChange = callbacks.onChange || empty,
21263
21542
  _this = this;
21264
21543
 
21265
- fabric.util.animate({
21544
+ return fabric.util.animate({
21545
+ target: this,
21266
21546
  startValue: this.get('angle'),
21267
21547
  endValue: this._getAngleValueForStraighten(),
21268
21548
  duration: this.FX_DURATION,
@@ -21275,8 +21555,6 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
21275
21555
  onComplete();
21276
21556
  },
21277
21557
  });
21278
-
21279
- return this;
21280
21558
  }
21281
21559
  });
21282
21560
 
@@ -21298,13 +21576,11 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
21298
21576
  * Same as {@link fabric.Canvas.prototype.straightenObject}, but animated
21299
21577
  * @param {fabric.Object} object Object to straighten
21300
21578
  * @return {fabric.Canvas} thisArg
21301
- * @chainable
21302
21579
  */
21303
21580
  fxStraightenObject: function (object) {
21304
- object.fxStraighten({
21581
+ return object.fxStraighten({
21305
21582
  onChange: this.requestRenderAllBound
21306
21583
  });
21307
- return this;
21308
21584
  }
21309
21585
  });
21310
21586
 
@@ -24690,7 +24966,7 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
24690
24966
  * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
24691
24967
  * @example
24692
24968
  * var filter = new fabric.Image.filters.Saturation({
24693
- * saturation: 100
24969
+ * saturation: 1
24694
24970
  * });
24695
24971
  * object.filters.push(filter);
24696
24972
  * object.applyFilters();
@@ -24718,6 +24994,14 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
24718
24994
  'gl_FragColor = color;\n' +
24719
24995
  '}',
24720
24996
 
24997
+ /**
24998
+ * Saturation value, from -1 to 1.
24999
+ * Increases/decreases the color saturation.
25000
+ * A value of 0 has no effect.
25001
+ *
25002
+ * @param {Number} saturation
25003
+ * @default
25004
+ */
24721
25005
  saturation: 0,
24722
25006
 
24723
25007
  mainParameter: 'saturation',
@@ -24786,6 +25070,130 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
24786
25070
  })(typeof exports !== 'undefined' ? exports : this);
24787
25071
 
24788
25072
 
25073
+ (function(global) {
25074
+
25075
+ 'use strict';
25076
+
25077
+ var fabric = global.fabric || (global.fabric = { }),
25078
+ filters = fabric.Image.filters,
25079
+ createClass = fabric.util.createClass;
25080
+
25081
+ /**
25082
+ * Vibrance filter class
25083
+ * @class fabric.Image.filters.Vibrance
25084
+ * @memberOf fabric.Image.filters
25085
+ * @extends fabric.Image.filters.BaseFilter
25086
+ * @see {@link fabric.Image.filters.Vibrance#initialize} for constructor definition
25087
+ * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
25088
+ * @example
25089
+ * var filter = new fabric.Image.filters.Vibrance({
25090
+ * vibrance: 1
25091
+ * });
25092
+ * object.filters.push(filter);
25093
+ * object.applyFilters();
25094
+ */
25095
+ filters.Vibrance = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Vibrance.prototype */ {
25096
+
25097
+ /**
25098
+ * Filter type
25099
+ * @param {String} type
25100
+ * @default
25101
+ */
25102
+ type: 'Vibrance',
25103
+
25104
+ fragmentSource: 'precision highp float;\n' +
25105
+ 'uniform sampler2D uTexture;\n' +
25106
+ 'uniform float uVibrance;\n' +
25107
+ 'varying vec2 vTexCoord;\n' +
25108
+ 'void main() {\n' +
25109
+ 'vec4 color = texture2D(uTexture, vTexCoord);\n' +
25110
+ 'float max = max(color.r, max(color.g, color.b));\n' +
25111
+ 'float avg = (color.r + color.g + color.b) / 3.0;\n' +
25112
+ 'float amt = (abs(max - avg) * 2.0) * uVibrance;\n' +
25113
+ 'color.r += max != color.r ? (max - color.r) * amt : 0.00;\n' +
25114
+ 'color.g += max != color.g ? (max - color.g) * amt : 0.00;\n' +
25115
+ 'color.b += max != color.b ? (max - color.b) * amt : 0.00;\n' +
25116
+ 'gl_FragColor = color;\n' +
25117
+ '}',
25118
+
25119
+ /**
25120
+ * Vibrance value, from -1 to 1.
25121
+ * Increases/decreases the saturation of more muted colors with less effect on saturated colors.
25122
+ * A value of 0 has no effect.
25123
+ *
25124
+ * @param {Number} vibrance
25125
+ * @default
25126
+ */
25127
+ vibrance: 0,
25128
+
25129
+ mainParameter: 'vibrance',
25130
+
25131
+ /**
25132
+ * Constructor
25133
+ * @memberOf fabric.Image.filters.Vibrance.prototype
25134
+ * @param {Object} [options] Options object
25135
+ * @param {Number} [options.vibrance=0] Vibrance value for the image (between -1 and 1)
25136
+ */
25137
+
25138
+ /**
25139
+ * Apply the Vibrance operation to a Uint8ClampedArray representing the pixels of an image.
25140
+ *
25141
+ * @param {Object} options
25142
+ * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered.
25143
+ */
25144
+ applyTo2d: function(options) {
25145
+ if (this.vibrance === 0) {
25146
+ return;
25147
+ }
25148
+ var imageData = options.imageData,
25149
+ data = imageData.data, len = data.length,
25150
+ adjust = -this.vibrance, i, max, avg, amt;
25151
+
25152
+ for (i = 0; i < len; i += 4) {
25153
+ max = Math.max(data[i], data[i + 1], data[i + 2]);
25154
+ avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
25155
+ amt = ((Math.abs(max - avg) * 2 / 255) * adjust);
25156
+ data[i] += max !== data[i] ? (max - data[i]) * amt : 0;
25157
+ data[i + 1] += max !== data[i + 1] ? (max - data[i + 1]) * amt : 0;
25158
+ data[i + 2] += max !== data[i + 2] ? (max - data[i + 2]) * amt : 0;
25159
+ }
25160
+ },
25161
+
25162
+ /**
25163
+ * Return WebGL uniform locations for this filter's shader.
25164
+ *
25165
+ * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader.
25166
+ * @param {WebGLShaderProgram} program This filter's compiled shader program.
25167
+ */
25168
+ getUniformLocations: function(gl, program) {
25169
+ return {
25170
+ uVibrance: gl.getUniformLocation(program, 'uVibrance'),
25171
+ };
25172
+ },
25173
+
25174
+ /**
25175
+ * Send data from this filter to its shader program's uniforms.
25176
+ *
25177
+ * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader.
25178
+ * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects
25179
+ */
25180
+ sendUniformData: function(gl, uniformLocations) {
25181
+ gl.uniform1f(uniformLocations.uVibrance, -this.vibrance);
25182
+ },
25183
+ });
25184
+
25185
+ /**
25186
+ * Returns filter instance from an object representation
25187
+ * @static
25188
+ * @param {Object} object Object to create an instance from
25189
+ * @param {Function} [callback] to be invoked after filter creation
25190
+ * @return {fabric.Image.filters.Vibrance} Instance of fabric.Image.filters.Vibrance
25191
+ */
25192
+ fabric.Image.filters.Vibrance.fromObject = fabric.Image.filters.BaseFilter.fromObject;
25193
+
25194
+ })(typeof exports !== 'undefined' ? exports : this);
25195
+
25196
+
24789
25197
  (function(global) {
24790
25198
 
24791
25199
  'use strict';
@@ -25338,7 +25746,8 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
25338
25746
 
25339
25747
  var additionalProps =
25340
25748
  ('fontFamily fontWeight fontSize text underline overline linethrough' +
25341
- ' textAlign fontStyle lineHeight textBackgroundColor charSpacing styles path').split(' ');
25749
+ ' textAlign fontStyle lineHeight textBackgroundColor charSpacing styles' +
25750
+ ' direction path pathStartOffset pathSide pathAlign').split(' ');
25342
25751
 
25343
25752
  /**
25344
25753
  * Text class
@@ -25365,7 +25774,10 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
25365
25774
  'charSpacing',
25366
25775
  'textAlign',
25367
25776
  'styles',
25368
- 'path'
25777
+ 'path',
25778
+ 'pathStartOffset',
25779
+ 'pathSide',
25780
+ 'pathAlign'
25369
25781
  ],
25370
25782
 
25371
25783
  /**
@@ -25523,13 +25935,55 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
25523
25935
  shadow: null,
25524
25936
 
25525
25937
  /**
25526
- * fabric.Path that the text can follow.
25527
- * This feature is in BETA.
25938
+ * fabric.Path that the text should follow.
25939
+ * since 4.6.0 the path will be drawn automatically.
25940
+ * if you want to make the path visible, give it a stroke and strokeWidth or fill value
25941
+ * if you want it to be hidden, assign visible = false to the path.
25942
+ * This feature is in BETA, and SVG import/export is not yet supported.
25528
25943
  * @type fabric.Path
25944
+ * @example
25945
+ * var textPath = new fabric.Text('Text on a path', {
25946
+ * top: 150,
25947
+ * left: 150,
25948
+ * textAlign: 'center',
25949
+ * charSpacing: -50,
25950
+ * path: new fabric.Path('M 0 0 C 50 -100 150 -100 200 0', {
25951
+ * strokeWidth: 1,
25952
+ * visible: false
25953
+ * }),
25954
+ * pathSide: 'left',
25955
+ * pathStartOffset: 0
25956
+ * });
25529
25957
  * @default
25530
25958
  */
25531
25959
  path: null,
25532
25960
 
25961
+ /**
25962
+ * Offset amount for text path starting position
25963
+ * Only used when text has a path
25964
+ * @type Number
25965
+ * @default
25966
+ */
25967
+ pathStartOffset: 0,
25968
+
25969
+ /**
25970
+ * Which side of the path the text should be drawn on.
25971
+ * Only used when text has a path
25972
+ * @type {String} 'left|right'
25973
+ * @default
25974
+ */
25975
+ pathSide: 'left',
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
+
25533
25987
  /**
25534
25988
  * @private
25535
25989
  */
@@ -25673,6 +26127,8 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
25673
26127
  /**
25674
26128
  * Return a context for measurement of text string.
25675
26129
  * if created it gets stored for reuse
26130
+ * this is for internal use, please do not use it
26131
+ * @private
25676
26132
  * @param {String} text Text string
25677
26133
  * @param {Object} [options] Options object
25678
26134
  * @return {fabric.Text} thisArg
@@ -25808,6 +26264,8 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
25808
26264
  * @param {CanvasRenderingContext2D} ctx Context to render on
25809
26265
  */
25810
26266
  _render: function(ctx) {
26267
+ var path = this.path;
26268
+ path && !path.isNotVisible() && path._render(ctx);
25811
26269
  this._setTextStyles(ctx);
25812
26270
  this._renderTextLinesBackground(ctx);
25813
26271
  this._renderTextDecoration(ctx, 'underline');
@@ -25842,7 +26300,20 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
25842
26300
  * @param {String} [charStyle.fontStyle] Font style (italic|normal)
25843
26301
  */
25844
26302
  _setTextStyles: function(ctx, charStyle, forMeasuring) {
25845
- 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
+ }
25846
26317
  ctx.font = this._getFontDeclaration(charStyle, forMeasuring);
25847
26318
  },
25848
26319
 
@@ -26067,29 +26538,15 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
26067
26538
  _measureLine: function(lineIndex) {
26068
26539
  var width = 0, i, grapheme, line = this._textLines[lineIndex], prevGrapheme,
26069
26540
  graphemeInfo, numOfSpaces = 0, lineBounds = new Array(line.length),
26070
- positionInPath = 0, startingPoint, totalPathLength, path = this.path;
26541
+ positionInPath = 0, startingPoint, totalPathLength, path = this.path,
26542
+ reverse = this.pathSide === 'right';
26071
26543
 
26072
26544
  this.__charBounds[lineIndex] = lineBounds;
26073
- if (path) {
26074
- startingPoint = fabric.util.getPointOnPath(path.path, 0, path.segmentsInfo);
26075
- totalPathLength = path.segmentsInfo[path.segmentsInfo.length - 1].length;
26076
- startingPoint.x += path.pathOffset.x;
26077
- startingPoint.y += path.pathOffset.y;
26078
- }
26079
26545
  for (i = 0; i < line.length; i++) {
26080
26546
  grapheme = line[i];
26081
26547
  graphemeInfo = this._getGraphemeBox(grapheme, lineIndex, i, prevGrapheme);
26082
- if (path) {
26083
- if (positionInPath > totalPathLength) {
26084
- positionInPath %= totalPathLength;
26085
- }
26086
- // it would probably much fater to send all the grapheme position for a line
26087
- // and calculate path position/angle at once.
26088
- this._setGraphemeOnPath(positionInPath, graphemeInfo, startingPoint);
26089
- }
26090
26548
  lineBounds[i] = graphemeInfo;
26091
26549
  width += graphemeInfo.kernedWidth;
26092
- positionInPath += graphemeInfo.kernedWidth;
26093
26550
  prevGrapheme = grapheme;
26094
26551
  }
26095
26552
  // this latest bound box represent the last character of the line
@@ -26100,6 +26557,40 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
26100
26557
  kernedWidth: 0,
26101
26558
  height: this.fontSize
26102
26559
  };
26560
+ if (path) {
26561
+ totalPathLength = path.segmentsInfo[path.segmentsInfo.length - 1].length;
26562
+ startingPoint = fabric.util.getPointOnPath(path.path, 0, path.segmentsInfo);
26563
+ startingPoint.x += path.pathOffset.x;
26564
+ startingPoint.y += path.pathOffset.y;
26565
+ switch (this.textAlign) {
26566
+ case 'left':
26567
+ positionInPath = reverse ? (totalPathLength - width) : 0;
26568
+ break;
26569
+ case 'center':
26570
+ positionInPath = (totalPathLength - width) / 2;
26571
+ break;
26572
+ case 'right':
26573
+ positionInPath = reverse ? 0 : (totalPathLength - width);
26574
+ break;
26575
+ //todo - add support for justify
26576
+ }
26577
+ positionInPath += this.pathStartOffset * (reverse ? -1 : 1);
26578
+ for (i = reverse ? line.length - 1 : 0;
26579
+ reverse ? i >= 0 : i < line.length;
26580
+ reverse ? i-- : i++) {
26581
+ graphemeInfo = lineBounds[i];
26582
+ if (positionInPath > totalPathLength) {
26583
+ positionInPath %= totalPathLength;
26584
+ }
26585
+ else if (positionInPath < 0) {
26586
+ positionInPath += totalPathLength;
26587
+ }
26588
+ // it would probably much faster to send all the grapheme position for a line
26589
+ // and calculate path position/angle at once.
26590
+ this._setGraphemeOnPath(positionInPath, graphemeInfo, startingPoint);
26591
+ positionInPath += graphemeInfo.kernedWidth;
26592
+ }
26593
+ }
26103
26594
  return { width: width, numOfSpaces: numOfSpaces };
26104
26595
  },
26105
26596
 
@@ -26119,7 +26610,7 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
26119
26610
  var info = fabric.util.getPointOnPath(path.path, centerPosition, path.segmentsInfo);
26120
26611
  graphemeInfo.renderLeft = info.x - startingPoint.x;
26121
26612
  graphemeInfo.renderTop = info.y - startingPoint.y;
26122
- graphemeInfo.angle = info.angle;
26613
+ graphemeInfo.angle = info.angle + (this.pathSide === 'right' ? Math.PI : 0);
26123
26614
  },
26124
26615
 
26125
26616
  /**
@@ -26287,16 +26778,17 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
26287
26778
  path = this.path,
26288
26779
  shortCut = !isJustify && this.charSpacing === 0 && this.isEmptyStyles(lineIndex) && !path,
26289
26780
  isLtr = this.direction === 'ltr', sign = this.direction === 'ltr' ? 1 : -1,
26290
- drawingLeft;
26291
-
26781
+ drawingLeft, currentDirection = ctx.canvas.getAttribute('dir');
26292
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
+ }
26293
26788
  top -= lineHeight * this._fontSizeFraction / this.lineHeight;
26294
26789
  if (shortCut) {
26295
26790
  // render all the line in one pass without checking
26296
26791
  // drawingLeft = isLtr ? left : left - this.getLineWidth(lineIndex);
26297
- ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
26298
- ctx.direction = isLtr ? 'ltr' : 'rtl';
26299
- ctx.textAlign = isLtr ? 'left' : 'right';
26300
26792
  this._renderChar(method, ctx, lineIndex, 0, line.join(''), left, top, lineHeight);
26301
26793
  ctx.restore();
26302
26794
  return;
@@ -26333,9 +26825,6 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
26333
26825
  }
26334
26826
  else {
26335
26827
  drawingLeft = left;
26336
- ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
26337
- ctx.direction = isLtr ? 'ltr' : 'rtl';
26338
- ctx.textAlign = isLtr ? 'left' : 'right';
26339
26828
  this._renderChar(method, ctx, lineIndex, i, charsToRender, drawingLeft, top, lineHeight);
26340
26829
  }
26341
26830
  charsToRender = '';
@@ -26586,19 +27075,12 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
26586
27075
  * @return {Number} Line width
26587
27076
  */
26588
27077
  getLineWidth: function(lineIndex) {
26589
- if (this.__lineWidths[lineIndex]) {
27078
+ if (this.__lineWidths[lineIndex] !== undefined) {
26590
27079
  return this.__lineWidths[lineIndex];
26591
27080
  }
26592
27081
 
26593
- var width, line = this._textLines[lineIndex], lineInfo;
26594
-
26595
- if (line === '') {
26596
- width = 0;
26597
- }
26598
- else {
26599
- lineInfo = this.measureLine(lineIndex);
26600
- width = lineInfo.width;
26601
- }
27082
+ var lineInfo = this.measureLine(lineIndex);
27083
+ var width = lineInfo.width;
26602
27084
  this.__lineWidths[lineIndex] = width;
26603
27085
  return width;
26604
27086
  },
@@ -26788,25 +27270,13 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
26788
27270
  * @return {Object} Object representation of an instance
26789
27271
  */
26790
27272
  toObject: function(propertiesToInclude) {
26791
- var additionalProperties = [
26792
- 'text',
26793
- 'fontSize',
26794
- 'fontWeight',
26795
- 'fontFamily',
26796
- 'fontStyle',
26797
- 'lineHeight',
26798
- 'underline',
26799
- 'overline',
26800
- 'linethrough',
26801
- 'textAlign',
26802
- 'textBackgroundColor',
26803
- 'charSpacing',
26804
- 'path',
26805
- 'direction',
26806
- ].concat(propertiesToInclude);
26807
- var obj = this.callSuper('toObject', additionalProperties);
27273
+ var allProperties = additionalProps.concat(propertiesToInclude);
27274
+ var obj = this.callSuper('toObject', allProperties);
27275
+ // styles will be overridden with a properly cloned structure
26808
27276
  obj.styles = clone(this.styles, true);
26809
- obj.path = this.path && this.path.toObject();
27277
+ if (obj.path) {
27278
+ obj.path = this.path.toObject();
27279
+ }
26810
27280
  return obj;
26811
27281
  },
26812
27282
 
@@ -27460,6 +27930,16 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
27460
27930
  */
27461
27931
  caching: true,
27462
27932
 
27933
+ /**
27934
+ * DOM container to append the hiddenTextarea.
27935
+ * An alternative to attaching to the document.body.
27936
+ * Useful to reduce laggish redraw of the full document.body tree and
27937
+ * also with modals event capturing that won't let the textarea take focus.
27938
+ * @type HTMLElement
27939
+ * @default
27940
+ */
27941
+ hiddenTextareaContainer: null,
27942
+
27463
27943
  /**
27464
27944
  * @private
27465
27945
  */
@@ -28697,7 +29177,13 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
28697
29177
  this.insertCharStyleObject(cursorLoc.lineIndex + i, 0, addedLines[i], copiedStyle);
28698
29178
  }
28699
29179
  else if (copiedStyle) {
28700
- 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
+ }
28701
29187
  }
28702
29188
  copiedStyle = copiedStyle && copiedStyle.slice(addedLines[i] + 1);
28703
29189
  }
@@ -29061,7 +29547,13 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
29061
29547
  this.hiddenTextarea.style.cssText = 'position: absolute; top: ' + style.top +
29062
29548
  '; left: ' + style.left + '; z-index: -999; opacity: 0; width: 1px; height: 1px; font-size: 1px;' +
29063
29549
  ' paddingーtop: ' + style.fontSize + ';';
29064
- fabric.document.body.appendChild(this.hiddenTextarea);
29550
+
29551
+ if (this.hiddenTextareaContainer) {
29552
+ this.hiddenTextareaContainer.appendChild(this.hiddenTextarea);
29553
+ }
29554
+ else {
29555
+ fabric.document.body.appendChild(this.hiddenTextarea);
29556
+ }
29065
29557
 
29066
29558
  fabric.util.addListener(this.hiddenTextarea, 'keydown', this.onKeyDown.bind(this));
29067
29559
  fabric.util.addListener(this.hiddenTextarea, 'keyup', this.onKeyUp.bind(this));