fabric 4.3.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/fabric.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /* build: `node build.js modules=ALL exclude=gestures,accessors requirejs minifier=uglifyjs` */
2
2
  /*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */
3
3
 
4
- var fabric = fabric || { version: '4.3.0' };
4
+ var fabric = fabric || { version: '4.4.0' };
5
5
  if (typeof exports !== 'undefined') {
6
6
  exports.fabric = fabric;
7
7
  }
@@ -1561,6 +1561,59 @@ fabric.CommonMethods = {
1561
1561
  }).join(' ') + ')';
1562
1562
  },
1563
1563
 
1564
+ /**
1565
+ * given an object and a transform, apply the inverse transform to the object,
1566
+ * this is equivalent to remove from that object that transformation, so that
1567
+ * added in a space with the removed transform, the object will be the same as before.
1568
+ * Removing from an object a transform that scale by 2 is like scaling it by 1/2.
1569
+ * Removing from an object a transfrom that rotate by 30deg is like rotating by 30deg
1570
+ * in the opposite direction.
1571
+ * This util is used to add objects inside transformed groups or nested groups.
1572
+ * @memberOf fabric.util
1573
+ * @param {fabric.Object} object the object you want to transform
1574
+ * @param {Array} transform the destination transform
1575
+ */
1576
+ removeTransformFromObject: function(object, transform) {
1577
+ var inverted = fabric.util.invertTransform(transform),
1578
+ finalTransform = fabric.util.multiplyTransformMatrices(inverted, object.calcOwnMatrix());
1579
+ fabric.util.applyTransformToObject(object, finalTransform);
1580
+ },
1581
+
1582
+ /**
1583
+ * given an object and a transform, apply the transform to the object.
1584
+ * this is equivalent to change the space where the object is drawn.
1585
+ * Adding to an object a transform that scale by 2 is like scaling it by 2.
1586
+ * This is used when removing an object from an active selection for example.
1587
+ * @memberOf fabric.util
1588
+ * @param {fabric.Object} object the object you want to transform
1589
+ * @param {Array} transform the destination transform
1590
+ */
1591
+ addTransformToObject: function(object, transform) {
1592
+ fabric.util.applyTransformToObject(
1593
+ object,
1594
+ fabric.util.multiplyTransformMatrices(transform, object.calcOwnMatrix())
1595
+ );
1596
+ },
1597
+
1598
+ /**
1599
+ * discard an object transform state and apply the one from the matrix.
1600
+ * @memberOf fabric.util
1601
+ * @param {fabric.Object} object the object you want to transform
1602
+ * @param {Array} transform the destination transform
1603
+ */
1604
+ applyTransformToObject: function(object, transform) {
1605
+ var options = fabric.util.qrDecompose(transform),
1606
+ center = new fabric.Point(options.translateX, options.translateY);
1607
+ object.flipX = false;
1608
+ object.flipY = false;
1609
+ object.set('scaleX', options.scaleX);
1610
+ object.set('scaleY', options.scaleY);
1611
+ object.skewX = options.skewX;
1612
+ object.skewY = options.skewY;
1613
+ object.angle = options.angle;
1614
+ object.setPositionByOrigin(center, 'center', 'center');
1615
+ },
1616
+
1564
1617
  /**
1565
1618
  * given a width and height, return the size of the bounding box
1566
1619
  * that can contains the box with width/height with applied transform
@@ -2048,6 +2101,17 @@ fabric.CommonMethods = {
2048
2101
  };
2049
2102
  }
2050
2103
 
2104
+ function getTangentCubicIterator(p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y) {
2105
+ return function (pct) {
2106
+ var invT = 1 - pct,
2107
+ tangentX = (3 * invT * invT * (p2x - p1x)) + (6 * invT * pct * (p3x - p2x)) +
2108
+ (3 * pct * pct * (p4x - p3x)),
2109
+ tangentY = (3 * invT * invT * (p2y - p1y)) + (6 * invT * pct * (p3y - p2y)) +
2110
+ (3 * pct * pct * (p4y - p3y));
2111
+ return Math.atan2(tangentY, tangentX);
2112
+ };
2113
+ }
2114
+
2051
2115
  function QB1(t) {
2052
2116
  return t * t;
2053
2117
  }
@@ -2070,6 +2134,16 @@ fabric.CommonMethods = {
2070
2134
  };
2071
2135
  }
2072
2136
 
2137
+ function getTangentQuadraticIterator(p1x, p1y, p2x, p2y, p3x, p3y) {
2138
+ return function (pct) {
2139
+ var invT = 1 - pct,
2140
+ tangentX = (2 * invT * (p2x - p1x)) + (2 * pct * (p3x - p2x)),
2141
+ tangentY = (2 * invT * (p2y - p1y)) + (2 * pct * (p3y - p2y));
2142
+ return Math.atan2(tangentY, tangentX);
2143
+ };
2144
+ }
2145
+
2146
+
2073
2147
  // this will run over a path segment ( a cubic or quadratic segment) and approximate it
2074
2148
  // with 100 segemnts. This will good enough to calculate the length of the curve
2075
2149
  function pathIterator(iterator, x1, y1) {
@@ -2088,15 +2162,16 @@ fabric.CommonMethods = {
2088
2162
  * The percentage will be then used to find the correct point on the canvas for the path.
2089
2163
  * @param {Array} segInfo fabricJS collection of information on a parsed path
2090
2164
  * @param {Number} distance from starting point, in pixels.
2091
- * @return {Number} length of segment
2165
+ * @return {Object} info object with x and y ( the point on canvas ) and angle, the tangent on that point;
2092
2166
  */
2093
2167
  function findPercentageForDistance(segInfo, distance) {
2094
2168
  var perc = 0, tmpLen = 0, iterator = segInfo.iterator, tempP = { x: segInfo.x, y: segInfo.y },
2095
- p, nextLen, nextStep = 0.01;
2169
+ p, nextLen, nextStep = 0.01, angleFinder = segInfo.angleFinder, lastPerc;
2096
2170
  // nextStep > 0.0001 covers 0.00015625 that 1/64th of 1/100
2097
2171
  // the path
2098
2172
  while (tmpLen < distance && perc <= 1 && nextStep > 0.0001) {
2099
2173
  p = iterator(perc);
2174
+ lastPerc = perc;
2100
2175
  nextLen = calcLineLength(tempP.x, tempP.y, p.x, p.y);
2101
2176
  // compare tmpLen each cycle with distance, decide next perc to test.
2102
2177
  if ((nextLen + tmpLen) > distance) {
@@ -2110,6 +2185,7 @@ fabric.CommonMethods = {
2110
2185
  tmpLen += nextLen;
2111
2186
  }
2112
2187
  }
2188
+ p.angle = angleFinder(lastPerc);
2113
2189
  return p;
2114
2190
  }
2115
2191
 
@@ -2123,7 +2199,7 @@ fabric.CommonMethods = {
2123
2199
  var totalLength = 0, len = path.length, current,
2124
2200
  //x2 and y2 are the coords of segment start
2125
2201
  //x1 and y1 are the coords of the current point
2126
- x1 = 0, y1 = 0, x2 = 0, y2 = 0, info = [], iterator, tempInfo;
2202
+ x1 = 0, y1 = 0, x2 = 0, y2 = 0, info = [], iterator, tempInfo, angleFinder;
2127
2203
  for (var i = 0; i < len; i++) {
2128
2204
  current = path[i];
2129
2205
  tempInfo = {
@@ -2153,7 +2229,18 @@ fabric.CommonMethods = {
2153
2229
  current[5],
2154
2230
  current[6]
2155
2231
  );
2232
+ angleFinder = getTangentCubicIterator(
2233
+ x1,
2234
+ y1,
2235
+ current[1],
2236
+ current[2],
2237
+ current[3],
2238
+ current[4],
2239
+ current[5],
2240
+ current[6]
2241
+ );
2156
2242
  tempInfo.iterator = iterator;
2243
+ tempInfo.angleFinder = angleFinder;
2157
2244
  tempInfo.length = pathIterator(iterator, x1, y1);
2158
2245
  x1 = current[5];
2159
2246
  y1 = current[6];
@@ -2167,7 +2254,16 @@ fabric.CommonMethods = {
2167
2254
  current[3],
2168
2255
  current[4]
2169
2256
  );
2257
+ angleFinder = getTangentQuadraticIterator(
2258
+ x1,
2259
+ y1,
2260
+ current[1],
2261
+ current[2],
2262
+ current[3],
2263
+ current[4]
2264
+ );
2170
2265
  tempInfo.iterator = iterator;
2266
+ tempInfo.angleFinder = angleFinder;
2171
2267
  tempInfo.length = pathIterator(iterator, x1, y1);
2172
2268
  x1 = current[3];
2173
2269
  y1 = current[4];
@@ -2193,29 +2289,33 @@ fabric.CommonMethods = {
2193
2289
  if (!infos) {
2194
2290
  infos = getPathSegmentsInfo(path);
2195
2291
  }
2196
- // var distance = infos[infos.length - 1] * perc;
2197
2292
  var i = 0;
2198
2293
  while ((distance - infos[i].length > 0) && i < (infos.length - 2)) {
2199
2294
  distance -= infos[i].length;
2200
2295
  i++;
2201
2296
  }
2297
+ // var distance = infos[infos.length - 1] * perc;
2202
2298
  var segInfo = infos[i], segPercent = distance / segInfo.length,
2203
- command = segInfo.command, segment = path[i];
2299
+ command = segInfo.command, segment = path[i], info;
2204
2300
 
2205
2301
  switch (command) {
2206
2302
  case 'M':
2207
- return { x: segInfo.x, y: segInfo.y };
2303
+ return { x: segInfo.x, y: segInfo.y, angle: 0 };
2208
2304
  case 'Z':
2209
2305
  case 'z':
2210
- return new fabric.Point(segInfo.x, segInfo.y).lerp(
2306
+ info = new fabric.Point(segInfo.x, segInfo.y).lerp(
2211
2307
  new fabric.Point(segInfo.destX, segInfo.destY),
2212
2308
  segPercent
2213
2309
  );
2310
+ info.angle = Math.atan2(segInfo.destY - segInfo.y, segInfo.destX - segInfo.x);
2311
+ return info;
2214
2312
  case 'L':
2215
- return new fabric.Point(segInfo.x, segInfo.y).lerp(
2313
+ info = new fabric.Point(segInfo.x, segInfo.y).lerp(
2216
2314
  new fabric.Point(segment[1], segment[2]),
2217
2315
  segPercent
2218
2316
  );
2317
+ info.angle = Math.atan2(segment[2] - segInfo.y, segment[1] - segInfo.x);
2318
+ return info;
2219
2319
  case 'C':
2220
2320
  return findPercentageForDistance(segInfo, distance);
2221
2321
  case 'Q':
@@ -6445,7 +6545,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
6445
6545
  * @return {Boolean} true if one flip, but not two.
6446
6546
  */
6447
6547
  function targetHasOneFlip(target) {
6448
- return (target.flipX && !target.flipY) || (!target.flipX && target.flipY);
6548
+ return target.flipX !== target.flipY;
6449
6549
  }
6450
6550
 
6451
6551
  /**
@@ -7022,7 +7122,7 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
7022
7122
  ), xSizeBy2 = xSize / 2, ySizeBy2 = ySize / 2;
7023
7123
  ctx.save();
7024
7124
  ctx.fillStyle = styleOverride.cornerColor || fabricObject.cornerColor;
7025
- ctx.strokeStyle = styleOverride.strokeCornerColor || fabricObject.strokeCornerColor;
7125
+ ctx.strokeStyle = styleOverride.cornerStrokeColor || fabricObject.cornerStrokeColor;
7026
7126
  // this is still wrong
7027
7127
  ctx.lineWidth = 1;
7028
7128
  ctx.translate(left, top);
@@ -8942,7 +9042,10 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
8942
9042
  * @chainable true
8943
9043
  */
8944
9044
  setViewportTransform: function (vpt) {
8945
- var activeObject = this._activeObject, object, i, len;
9045
+ var activeObject = this._activeObject,
9046
+ backgroundObject = this.backgroundImage,
9047
+ overlayObject = this.overlayImage,
9048
+ object, i, len;
8946
9049
  this.viewportTransform = vpt;
8947
9050
  for (i = 0, len = this._objects.length; i < len; i++) {
8948
9051
  object = this._objects[i];
@@ -8951,6 +9054,12 @@ fabric.ElementsParser = function(elements, callback, options, reviver, parsingOp
8951
9054
  if (activeObject) {
8952
9055
  activeObject.setCoords();
8953
9056
  }
9057
+ if (backgroundObject) {
9058
+ backgroundObject.setCoords(true);
9059
+ }
9060
+ if (overlayObject) {
9061
+ overlayObject.setCoords(true);
9062
+ }
8954
9063
  this.calcViewportBoundaries();
8955
9064
  this.renderOnAddRemove && this.requestRenderAll();
8956
9065
  return this;
@@ -10170,6 +10279,15 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10170
10279
  */
10171
10280
  strokeDashArray: null,
10172
10281
 
10282
+ /**
10283
+ * When `true`, the free drawing is limited to the whiteboard size. Default to false.
10284
+ * @type Boolean
10285
+ * @default false
10286
+ */
10287
+
10288
+ limitedToCanvasSize: false,
10289
+
10290
+
10173
10291
  /**
10174
10292
  * Sets brush styles
10175
10293
  * @private
@@ -10234,6 +10352,15 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10234
10352
 
10235
10353
  ctx.shadowColor = '';
10236
10354
  ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0;
10355
+ },
10356
+
10357
+ /**
10358
+ * Check is pointer is outside canvas boundaries
10359
+ * @param {Object} pointer
10360
+ * @private
10361
+ */
10362
+ _isOutSideCanvas: function(pointer) {
10363
+ return pointer.x < 0 || pointer.x > this.canvas.getWidth() || pointer.y < 0 || pointer.y > this.canvas.getHeight();
10237
10364
  }
10238
10365
  });
10239
10366
 
@@ -10296,6 +10423,9 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype
10296
10423
  if (!this.canvas._isMainEvent(options.e)) {
10297
10424
  return;
10298
10425
  }
10426
+ if (this.limitedToCanvasSize === true && this._isOutSideCanvas(pointer)) {
10427
+ return;
10428
+ }
10299
10429
  if (this._captureDrawingPath(pointer) && this._points.length > 1) {
10300
10430
  if (this.needsFullRender()) {
10301
10431
  // redraw curve
@@ -10605,6 +10735,9 @@ fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric
10605
10735
  * @param {Object} pointer
10606
10736
  */
10607
10737
  onMouseMove: function(pointer) {
10738
+ if (this.limitedToCanvasSize === true && this._isOutSideCanvas(pointer)) {
10739
+ return;
10740
+ }
10608
10741
  if (this.needsFullRender()) {
10609
10742
  this.canvas.clearContext(this.canvas.contextTop);
10610
10743
  this.addPoint(pointer);
@@ -10752,6 +10885,9 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric
10752
10885
  * @param {Object} pointer
10753
10886
  */
10754
10887
  onMouseMove: function(pointer) {
10888
+ if (this.limitedToCanvasSize === true && this._isOutSideCanvas(pointer)) {
10889
+ return;
10890
+ }
10755
10891
  this.addSprayChunk(pointer);
10756
10892
  this.render(this.sprayChunkPoints);
10757
10893
  },
@@ -11454,13 +11590,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
11454
11590
  target.render(ctx);
11455
11591
  ctx.restore();
11456
11592
 
11457
- target === this._activeObject && target._renderControls(ctx, {
11458
- hasBorders: false,
11459
- transparentCorners: false
11460
- }, {
11461
- hasBorders: false,
11462
- });
11463
-
11464
11593
  target.selectionBackgroundColor = originalColor;
11465
11594
 
11466
11595
  var isTransparent = fabric.util.isTransparent(
@@ -11722,18 +11851,19 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
11722
11851
  activeObject = this._activeObject,
11723
11852
  aObjects = this.getActiveObjects(),
11724
11853
  activeTarget, activeTargetSubs,
11725
- isTouch = isTouchEvent(e);
11854
+ isTouch = isTouchEvent(e),
11855
+ shouldLookForActive = (aObjects.length > 1 && !skipGroup) || aObjects.length === 1;
11726
11856
 
11727
11857
  // first check current group (if one exists)
11728
11858
  // active group does not check sub targets like normal groups.
11729
11859
  // if active group just exits.
11730
11860
  this.targets = [];
11731
11861
 
11732
- if (aObjects.length > 1 && !skipGroup && activeObject === this._searchPossibleTargets([activeObject], pointer)) {
11862
+ // if we hit the corner of an activeObject, let's return that.
11863
+ if (shouldLookForActive && activeObject._findTargetCorner(pointer, isTouch)) {
11733
11864
  return activeObject;
11734
11865
  }
11735
- // if we hit the corner of an activeObject, let's return that.
11736
- if (aObjects.length === 1 && activeObject._findTargetCorner(pointer, isTouch)) {
11866
+ if (aObjects.length > 1 && !skipGroup && activeObject === this._searchPossibleTargets([activeObject], pointer)) {
11737
11867
  return activeObject;
11738
11868
  }
11739
11869
  if (aObjects.length === 1 &&
@@ -11769,7 +11899,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
11769
11899
  obj.evented &&
11770
11900
  // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html
11771
11901
  // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html
11772
- (obj.containsPoint(pointer) || !!obj._findTargetCorner(pointer))
11902
+ obj.containsPoint(pointer)
11773
11903
  ) {
11774
11904
  if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) {
11775
11905
  var isTransparent = this.isTargetTransparent(obj, globalPointer.x, globalPointer.y);
@@ -12045,38 +12175,50 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
12045
12175
  */
12046
12176
  _fireSelectionEvents: function(oldObjects, e) {
12047
12177
  var somethingChanged = false, objects = this.getActiveObjects(),
12048
- added = [], removed = [], opt = { e: e };
12178
+ added = [], removed = [];
12049
12179
  oldObjects.forEach(function(oldObject) {
12050
12180
  if (objects.indexOf(oldObject) === -1) {
12051
12181
  somethingChanged = true;
12052
- oldObject.fire('deselected', opt);
12182
+ oldObject.fire('deselected', {
12183
+ e: e,
12184
+ target: oldObject
12185
+ });
12053
12186
  removed.push(oldObject);
12054
12187
  }
12055
12188
  });
12056
12189
  objects.forEach(function(object) {
12057
12190
  if (oldObjects.indexOf(object) === -1) {
12058
12191
  somethingChanged = true;
12059
- object.fire('selected', opt);
12192
+ object.fire('selected', {
12193
+ e: e,
12194
+ target: object
12195
+ });
12060
12196
  added.push(object);
12061
12197
  }
12062
12198
  });
12063
12199
  if (oldObjects.length > 0 && objects.length > 0) {
12064
- opt.selected = added;
12065
- opt.deselected = removed;
12066
- // added for backward compatibility
12067
- opt.updated = added[0] || removed[0];
12068
- opt.target = this._activeObject;
12069
- somethingChanged && this.fire('selection:updated', opt);
12200
+ somethingChanged && this.fire('selection:updated', {
12201
+ e: e,
12202
+ selected: added,
12203
+ deselected: removed,
12204
+ // added for backward compatibility
12205
+ // deprecated
12206
+ updated: added[0] || removed[0],
12207
+ target: this._activeObject,
12208
+ });
12070
12209
  }
12071
12210
  else if (objects.length > 0) {
12072
- opt.selected = added;
12073
- // added for backward compatibility
12074
- opt.target = this._activeObject;
12075
- this.fire('selection:created', opt);
12211
+ this.fire('selection:created', {
12212
+ e: e,
12213
+ selected: added,
12214
+ target: this._activeObject,
12215
+ });
12076
12216
  }
12077
12217
  else if (oldObjects.length > 0) {
12078
- opt.deselected = removed;
12079
- this.fire('selection:cleared', opt);
12218
+ this.fire('selection:cleared', {
12219
+ e: e,
12220
+ deselected: removed,
12221
+ });
12080
12222
  }
12081
12223
  },
12082
12224
 
@@ -12095,6 +12237,10 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
12095
12237
  },
12096
12238
 
12097
12239
  /**
12240
+ * This is a private method for now.
12241
+ * This is supposed to be equivalent to setActiveObject but without firing
12242
+ * any event. There is commitment to have this stay this way.
12243
+ * This is the functional part of setActiveObject.
12098
12244
  * @private
12099
12245
  * @param {Object} object to set as active
12100
12246
  * @param {Event} [e] Event (passed along when firing "object:selected")
@@ -12115,6 +12261,13 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
12115
12261
  },
12116
12262
 
12117
12263
  /**
12264
+ * This is a private method for now.
12265
+ * This is supposed to be equivalent to discardActiveObject but without firing
12266
+ * any events. There is commitment to have this stay this way.
12267
+ * This is the functional part of discardActiveObject.
12268
+ * @param {Event} [e] Event (passed along when firing "object:deselected")
12269
+ * @param {Object} object to set as active
12270
+ * @return {Boolean} true if the selection happened
12118
12271
  * @private
12119
12272
  */
12120
12273
  _discardActiveObject: function(e, object) {
@@ -12225,7 +12378,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
12225
12378
  layoutProps.forEach(function(prop) {
12226
12379
  originalValues[prop] = instance[prop];
12227
12380
  });
12228
- this._activeObject.realizeTransform(instance);
12381
+ fabric.util.addTransformToObject(instance, this._activeObject.calcOwnMatrix());
12229
12382
  return originalValues;
12230
12383
  }
12231
12384
  else {
@@ -12727,15 +12880,21 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
12727
12880
  }
12728
12881
  }
12729
12882
  if (target) {
12730
- var corner = target._findTargetCorner(
12731
- this.getPointer(e, true),
12732
- fabric.util.isTouchEvent(e)
12733
- );
12734
- var control = target.controls[corner],
12735
- mouseUpHandler = control && control.getMouseUpHandler(e, target, control);
12736
- if (mouseUpHandler) {
12737
- var pointer = this.getPointer(e);
12738
- mouseUpHandler(e, transform, pointer.x, pointer.y);
12883
+ if (target.selectable && target !== this._activeObject && target.activeOn === 'up') {
12884
+ this.setActiveObject(target, e);
12885
+ shouldRender = true;
12886
+ }
12887
+ else {
12888
+ var corner = target._findTargetCorner(
12889
+ this.getPointer(e, true),
12890
+ fabric.util.isTouchEvent(e)
12891
+ );
12892
+ var control = target.controls[corner],
12893
+ mouseUpHandler = control && control.getMouseUpHandler(e, target, control);
12894
+ if (mouseUpHandler) {
12895
+ var pointer = this.getPointer(e);
12896
+ mouseUpHandler(e, transform, pointer.x, pointer.y);
12897
+ }
12739
12898
  }
12740
12899
  target.isMoving = false;
12741
12900
  }
@@ -12991,7 +13150,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
12991
13150
 
12992
13151
  if (target) {
12993
13152
  var alreadySelected = target === this._activeObject;
12994
- if (target.selectable) {
13153
+ if (target.selectable && target.activeOn === 'down') {
12995
13154
  this.setActiveObject(target, e);
12996
13155
  }
12997
13156
  var corner = target._findTargetCorner(
@@ -13200,7 +13359,6 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
13200
13359
  transform = this._currentTransform;
13201
13360
 
13202
13361
  transform.reset = false;
13203
- transform.target.isMoving = true;
13204
13362
  transform.shiftKey = e.shiftKey;
13205
13363
  transform.altKey = e[this.centeredKey];
13206
13364
 
@@ -13224,6 +13382,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab
13224
13382
  actionPerformed = actionHandler(e, transform, x, y);
13225
13383
  }
13226
13384
  if (action === 'drag' && actionPerformed) {
13385
+ transform.target.isMoving = true;
13227
13386
  this.setCursor(transform.target.moveCursor || this.moveCursor);
13228
13387
  }
13229
13388
  transform.actionPerformed = transform.actionPerformed || actionPerformed;
@@ -14362,6 +14521,17 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
14362
14521
  */
14363
14522
  paintFirst: 'fill',
14364
14523
 
14524
+ /**
14525
+ * When 'down', object is set to active on mousedown/touchstart
14526
+ * When 'up', object is set to active on mouseup/touchend
14527
+ * Experimental. Let's see if this breaks anything before supporting officially
14528
+ * @private
14529
+ * since 4.4.0
14530
+ * @type String
14531
+ * @default 'down'
14532
+ */
14533
+ activeOn: 'down',
14534
+
14365
14535
  /**
14366
14536
  * List of properties to consider when checking if state
14367
14537
  * of an object is changed (fabric.Object#hasStateChanged)
@@ -14630,7 +14800,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
14630
14800
  strokeLineCap: this.strokeLineCap,
14631
14801
  strokeDashOffset: this.strokeDashOffset,
14632
14802
  strokeLineJoin: this.strokeLineJoin,
14633
- // strokeUniform: this.strokeUniform,
14803
+ strokeUniform: this.strokeUniform,
14634
14804
  strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS),
14635
14805
  scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS),
14636
14806
  scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS),
@@ -14782,7 +14952,6 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
14782
14952
  this.group.set('dirty', true);
14783
14953
  }
14784
14954
  }
14785
-
14786
14955
  return this;
14787
14956
  },
14788
14957
 
@@ -19654,17 +19823,27 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19654
19823
  * @chainable
19655
19824
  */
19656
19825
  addWithUpdate: function(object) {
19826
+ var nested = !!this.group;
19657
19827
  this._restoreObjectsState();
19658
19828
  fabric.util.resetObjectTransform(this);
19659
19829
  if (object) {
19830
+ if (nested) {
19831
+ // if this group is inside another group, we need to pre transform the object
19832
+ fabric.util.removeTransformFromObject(object, this.group.calcTransformMatrix());
19833
+ }
19660
19834
  this._objects.push(object);
19661
19835
  object.group = this;
19662
19836
  object._set('canvas', this.canvas);
19663
19837
  }
19664
19838
  this._calcBounds();
19665
19839
  this._updateObjectsCoords();
19666
- this.setCoords();
19667
19840
  this.dirty = true;
19841
+ if (nested) {
19842
+ this.group.addWithUpdate();
19843
+ }
19844
+ else {
19845
+ this.setCoords();
19846
+ }
19668
19847
  return this;
19669
19848
  },
19670
19849
 
@@ -19855,12 +20034,21 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19855
20034
 
19856
20035
  /**
19857
20036
  * Restores original state of each of group objects (original state is that which was before group was created).
20037
+ * if the nested boolean is true, the original state will be restored just for the
20038
+ * first group and not for all the group chain
19858
20039
  * @private
20040
+ * @param {Boolean} nested tell the function to restore object state up to the parent group and not more
19859
20041
  * @return {fabric.Group} thisArg
19860
20042
  * @chainable
19861
20043
  */
19862
20044
  _restoreObjectsState: function() {
19863
- this._objects.forEach(this._restoreObjectState, this);
20045
+ var groupMatrix = this.calcOwnMatrix();
20046
+ this._objects.forEach(function(object) {
20047
+ // instead of using _this = this;
20048
+ fabric.util.addTransformToObject(object, groupMatrix);
20049
+ delete object.group;
20050
+ object.setCoords();
20051
+ });
19864
20052
  return this;
19865
20053
  },
19866
20054
 
@@ -19869,37 +20057,20 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19869
20057
  * i.e. it tells you what would happen if the supplied object was in
19870
20058
  * the group, and then the group was destroyed. It mutates the supplied
19871
20059
  * object.
20060
+ * Warning: this method is not useful anymore, it has been kept to no break the api.
20061
+ * is not used in the fabricJS codebase
20062
+ * this method will be reduced to using the utility.
20063
+ * @private
20064
+ * @deprecated
19872
20065
  * @param {fabric.Object} object
20066
+ * @param {Array} parentMatrix parent transformation
19873
20067
  * @return {fabric.Object} transformedObject
19874
20068
  */
19875
- realizeTransform: function(object) {
19876
- var matrix = object.calcTransformMatrix(),
19877
- options = fabric.util.qrDecompose(matrix),
19878
- center = new fabric.Point(options.translateX, options.translateY);
19879
- object.flipX = false;
19880
- object.flipY = false;
19881
- object.set('scaleX', options.scaleX);
19882
- object.set('scaleY', options.scaleY);
19883
- object.skewX = options.skewX;
19884
- object.skewY = options.skewY;
19885
- object.angle = options.angle;
19886
- object.setPositionByOrigin(center, 'center', 'center');
20069
+ realizeTransform: function(object, parentMatrix) {
20070
+ fabric.util.addTransformToObject(object, parentMatrix);
19887
20071
  return object;
19888
20072
  },
19889
20073
 
19890
- /**
19891
- * Restores original state of a specified object in group
19892
- * @private
19893
- * @param {fabric.Object} object
19894
- * @return {fabric.Group} thisArg
19895
- */
19896
- _restoreObjectState: function(object) {
19897
- this.realizeTransform(object);
19898
- delete object.group;
19899
- object.setCoords();
19900
- return this;
19901
- },
19902
-
19903
20074
  /**
19904
20075
  * Destroys a group (restoring state of its objects)
19905
20076
  * @return {fabric.Group} thisArg
@@ -19972,19 +20143,20 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
19972
20143
  _calcBounds: function(onlyWidthHeight) {
19973
20144
  var aX = [],
19974
20145
  aY = [],
19975
- o, prop,
20146
+ o, prop, coords,
19976
20147
  props = ['tr', 'br', 'bl', 'tl'],
19977
20148
  i = 0, iLen = this._objects.length,
19978
20149
  j, jLen = props.length;
19979
20150
 
19980
20151
  for ( ; i < iLen; ++i) {
19981
20152
  o = this._objects[i];
19982
- o.aCoords = o.calcACoords();
20153
+ coords = o.calcACoords();
19983
20154
  for (j = 0; j < jLen; j++) {
19984
20155
  prop = props[j];
19985
- aX.push(o.aCoords[prop].x);
19986
- aY.push(o.aCoords[prop].y);
20156
+ aX.push(coords[prop].x);
20157
+ aY.push(coords[prop].y);
19987
20158
  }
20159
+ o.aCoords = coords;
19988
20160
  }
19989
20161
 
19990
20162
  this._getBounds(aX, aY, onlyWidthHeight);
@@ -20340,6 +20512,15 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
20340
20512
  */
20341
20513
  stateProperties: fabric.Object.prototype.stateProperties.concat('cropX', 'cropY'),
20342
20514
 
20515
+ /**
20516
+ * List of properties to consider when checking if cache needs refresh
20517
+ * Those properties are checked by statefullCache ON ( or lazy mode if we want ) or from single
20518
+ * calls to Object.set(key, value). If the key is in this list, the object is marked as dirty
20519
+ * and refreshed at the next render
20520
+ * @type Array
20521
+ */
20522
+ cacheProperties: fabric.Object.prototype.cacheProperties.concat('cropX', 'cropY'),
20523
+
20343
20524
  /**
20344
20525
  * key used to retrieve the texture representing this image
20345
20526
  * @since 2.0.0
@@ -20375,7 +20556,11 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
20375
20556
 
20376
20557
  /**
20377
20558
  * Constructor
20378
- * @param {HTMLImageElement | String} element Image element
20559
+ * Image can be initialized with any canvas drawable or a string.
20560
+ * The string should be a url and will be loaded as an image.
20561
+ * Canvas and Image element work out of the box, while videos require extra code to work.
20562
+ * Please check video element events for seeking.
20563
+ * @param {HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | String} element Image element
20379
20564
  * @param {Object} [options] Options object
20380
20565
  * @param {function} [callback] callback function to call after eventual filters applied.
20381
20566
  * @return {fabric.Image} thisArg
@@ -20800,7 +20985,7 @@ fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prot
20800
20985
  sH = min(h * scaleY, elHeight - sY),
20801
20986
  x = -w / 2, y = -h / 2,
20802
20987
  maxDestW = min(w, elWidth / scaleX - cropX),
20803
- maxDestH = min(h, elHeight / scaleX - cropY);
20988
+ maxDestH = min(h, elHeight / scaleY - cropY);
20804
20989
 
20805
20990
  elementToDraw && ctx.drawImage(elementToDraw, sX, sY, sW, sH, x, y, maxDestW, maxDestH);
20806
20991
  },
@@ -25135,9 +25320,9 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
25135
25320
  return;
25136
25321
  }
25137
25322
 
25138
- var styleProps =
25139
- 'fontFamily fontWeight fontSize text underline overline linethrough' +
25140
- ' textAlign fontStyle lineHeight textBackgroundColor charSpacing styles path'.split(' ');
25323
+ var additionalProps =
25324
+ ('fontFamily fontWeight fontSize text underline overline linethrough' +
25325
+ ' textAlign fontStyle lineHeight textBackgroundColor charSpacing styles path').split(' ');
25141
25326
 
25142
25327
  /**
25143
25328
  * Text class
@@ -25297,13 +25482,13 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
25297
25482
  * as well as for history (undo/redo) purposes
25298
25483
  * @type Array
25299
25484
  */
25300
- stateProperties: fabric.Object.prototype.stateProperties.concat(styleProps),
25485
+ stateProperties: fabric.Object.prototype.stateProperties.concat(additionalProps),
25301
25486
 
25302
25487
  /**
25303
25488
  * List of properties to consider when checking if cache needs refresh
25304
25489
  * @type Array
25305
25490
  */
25306
- cacheProperties: fabric.Object.prototype.cacheProperties.concat(styleProps),
25491
+ cacheProperties: fabric.Object.prototype.cacheProperties.concat(additionalProps),
25307
25492
 
25308
25493
  /**
25309
25494
  * When defined, an object is rendered via stroke and this property specifies its color.
@@ -25321,6 +25506,14 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
25321
25506
  */
25322
25507
  shadow: null,
25323
25508
 
25509
+ /**
25510
+ * fabric.Path that the text can follow.
25511
+ * This feature is in BETA.
25512
+ * @type fabric.Path
25513
+ * @default
25514
+ */
25515
+ path: null,
25516
+
25324
25517
  /**
25325
25518
  * @private
25326
25519
  */
@@ -25852,6 +26045,8 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
25852
26045
  if (positionInPath > totalPathLength) {
25853
26046
  positionInPath %= totalPathLength;
25854
26047
  }
26048
+ // it would probably much fater to send all the grapheme position for a line
26049
+ // and calculate path position/angle at once.
25855
26050
  this._setGraphemeOnPath(positionInPath, graphemeInfo, startingPoint);
25856
26051
  }
25857
26052
  lineBounds[i] = graphemeInfo;
@@ -25883,11 +26078,10 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
25883
26078
  path = this.path;
25884
26079
 
25885
26080
  // we are at currentPositionOnPath. we want to know what point on the path is.
25886
- var p1 = fabric.util.getPointOnPath(path.path, centerPosition - 0.1, path.segmentsInfo),
25887
- p2 = fabric.util.getPointOnPath(path.path, centerPosition + 0.1, path.segmentsInfo);
25888
- graphemeInfo.renderLeft = p1.x - startingPoint.x;
25889
- graphemeInfo.renderTop = p1.y - startingPoint.y;
25890
- graphemeInfo.angle = Math.atan2(p2.y - p1.y, p2.x - p1.x);
26081
+ var info = fabric.util.getPointOnPath(path.path, centerPosition, path.segmentsInfo);
26082
+ graphemeInfo.renderLeft = info.x - startingPoint.x;
26083
+ graphemeInfo.renderTop = info.y - startingPoint.y;
26084
+ graphemeInfo.angle = info.angle;
25891
26085
  },
25892
26086
 
25893
26087
  /**
@@ -26427,13 +26621,15 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
26427
26621
  (currentDecoration !== lastDecoration || currentFill !== lastFill || _size !== size || _dy !== dy)
26428
26622
  && boxWidth > 0
26429
26623
  ) {
26430
- lastDecoration && lastFill &&
26624
+ if (lastDecoration && lastFill) {
26625
+ ctx.fillStyle = lastFill;
26431
26626
  ctx.fillRect(
26432
26627
  leftOffset + lineLeftOffset + boxStart,
26433
26628
  top + this.offsets[type] * size + dy,
26434
26629
  boxWidth,
26435
26630
  this.fontSize / 15
26436
26631
  );
26632
+ }
26437
26633
  boxStart = charBox.left;
26438
26634
  boxWidth = charBox.width;
26439
26635
  lastDecoration = currentDecoration;
@@ -26536,9 +26732,11 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
26536
26732
  'textAlign',
26537
26733
  'textBackgroundColor',
26538
26734
  'charSpacing',
26735
+ 'path'
26539
26736
  ].concat(propertiesToInclude);
26540
26737
  var obj = this.callSuper('toObject', additionalProperties);
26541
26738
  obj.styles = clone(this.styles, true);
26739
+ obj.path = this.path && this.path.toObject();
26542
26740
  return obj;
26543
26741
  },
26544
26742
 
@@ -26694,11 +26892,23 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
26694
26892
  * Returns fabric.Text instance from an object representation
26695
26893
  * @static
26696
26894
  * @memberOf fabric.Text
26697
- * @param {Object} object Object to create an instance from
26895
+ * @param {Object} object plain js Object to create an instance from
26698
26896
  * @param {Function} [callback] Callback to invoke when an fabric.Text instance is created
26699
26897
  */
26700
26898
  fabric.Text.fromObject = function(object, callback) {
26701
- return fabric.Object._fromObject('Text', object, callback, 'text');
26899
+ var objectCopy = clone(object), path = object.path;
26900
+ delete objectCopy.path;
26901
+ return fabric.Object._fromObject('Text', objectCopy, function(textInstance) {
26902
+ if (path) {
26903
+ fabric.Object._fromObject('Path', path, function(pathInstance) {
26904
+ textInstance.set('path', pathInstance);
26905
+ callback(textInstance);
26906
+ }, 'path');
26907
+ }
26908
+ else {
26909
+ callback(textInstance);
26910
+ }
26911
+ }, 'text');
26702
26912
  };
26703
26913
 
26704
26914
  fabric.Text.genericFonts = ['sans-serif', 'serif', 'cursive', 'fantasy', 'monospace'];
@@ -27507,8 +27717,9 @@ fabric.Image.filters.BaseFilter.fromObject = function(object, callback) {
27507
27717
  * High level function to know the color of the cursor.
27508
27718
  * the currentChar is the one that precedes the cursor
27509
27719
  * Returns color (fill) of char at the current cursor
27510
- * Unused from the library, is for the end user
27511
- * @return {String} Character color (fill)
27720
+ * if the text object has a pattern or gradient for filler, it will return that.
27721
+ * Unused by the library, is for the end user
27722
+ * @return {String | fabric.Gradient | fabric.Pattern} Character color (fill)
27512
27723
  */
27513
27724
  getCurrentCharColor: function() {
27514
27725
  var cp = this._getCurrentCharIndex();