fabric 4.6.0 → 5.0.0-browser

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,7 +13,7 @@
13
13
  var additionalProps =
14
14
  ('fontFamily fontWeight fontSize text underline overline linethrough' +
15
15
  ' textAlign fontStyle lineHeight textBackgroundColor charSpacing styles' +
16
- ' direction path pathStartOffset pathSide').split(' ');
16
+ ' direction path pathStartOffset pathSide pathAlign').split(' ');
17
17
 
18
18
  /**
19
19
  * Text class
@@ -42,7 +42,8 @@
42
42
  'styles',
43
43
  'path',
44
44
  'pathStartOffset',
45
- 'pathSide'
45
+ 'pathSide',
46
+ 'pathAlign'
46
47
  ],
47
48
 
48
49
  /**
@@ -239,6 +240,16 @@
239
240
  */
240
241
  pathSide: 'left',
241
242
 
243
+ /**
244
+ * How text is aligned to the path. This property determines
245
+ * the perpendicular position of each character relative to the path.
246
+ * (one of "baseline", "center", "ascender", "descender")
247
+ * This feature is in BETA, and its behavior may change
248
+ * @type String
249
+ * @default
250
+ */
251
+ pathAlign: 'baseline',
252
+
242
253
  /**
243
254
  * @private
244
255
  */
@@ -382,6 +393,8 @@
382
393
  /**
383
394
  * Return a context for measurement of text string.
384
395
  * if created it gets stored for reuse
396
+ * this is for internal use, please do not use it
397
+ * @private
385
398
  * @param {String} text Text string
386
399
  * @param {Object} [options] Options object
387
400
  * @return {fabric.Text} thisArg
@@ -553,7 +566,20 @@
553
566
  * @param {String} [charStyle.fontStyle] Font style (italic|normal)
554
567
  */
555
568
  _setTextStyles: function(ctx, charStyle, forMeasuring) {
556
- ctx.textBaseline = 'alphabetic';
569
+ ctx.textBaseline = 'alphabetical';
570
+ if (this.path) {
571
+ switch (this.pathAlign) {
572
+ case 'center':
573
+ ctx.textBaseline = 'middle';
574
+ break;
575
+ case 'ascender':
576
+ ctx.textBaseline = 'top';
577
+ break;
578
+ case 'descender':
579
+ ctx.textBaseline = 'bottom';
580
+ break;
581
+ }
582
+ }
557
583
  ctx.font = this._getFontDeclaration(charStyle, forMeasuring);
558
584
  },
559
585
 
@@ -1018,16 +1044,17 @@
1018
1044
  path = this.path,
1019
1045
  shortCut = !isJustify && this.charSpacing === 0 && this.isEmptyStyles(lineIndex) && !path,
1020
1046
  isLtr = this.direction === 'ltr', sign = this.direction === 'ltr' ? 1 : -1,
1021
- drawingLeft;
1022
-
1047
+ drawingLeft, currentDirection = ctx.canvas.getAttribute('dir');
1023
1048
  ctx.save();
1049
+ if (currentDirection !== this.direction) {
1050
+ ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
1051
+ ctx.direction = isLtr ? 'ltr' : 'rtl';
1052
+ ctx.textAlign = isLtr ? 'left' : 'right';
1053
+ }
1024
1054
  top -= lineHeight * this._fontSizeFraction / this.lineHeight;
1025
1055
  if (shortCut) {
1026
1056
  // render all the line in one pass without checking
1027
1057
  // drawingLeft = isLtr ? left : left - this.getLineWidth(lineIndex);
1028
- ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
1029
- ctx.direction = isLtr ? 'ltr' : 'rtl';
1030
- ctx.textAlign = isLtr ? 'left' : 'right';
1031
1058
  this._renderChar(method, ctx, lineIndex, 0, line.join(''), left, top, lineHeight);
1032
1059
  ctx.restore();
1033
1060
  return;
@@ -1064,9 +1091,6 @@
1064
1091
  }
1065
1092
  else {
1066
1093
  drawingLeft = left;
1067
- ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl');
1068
- ctx.direction = isLtr ? 'ltr' : 'rtl';
1069
- ctx.textAlign = isLtr ? 'left' : 'right';
1070
1094
  this._renderChar(method, ctx, lineIndex, i, charsToRender, drawingLeft, top, lineHeight);
1071
1095
  }
1072
1096
  charsToRender = '';
@@ -1317,19 +1341,12 @@
1317
1341
  * @return {Number} Line width
1318
1342
  */
1319
1343
  getLineWidth: function(lineIndex) {
1320
- if (this.__lineWidths[lineIndex]) {
1344
+ if (this.__lineWidths[lineIndex] !== undefined) {
1321
1345
  return this.__lineWidths[lineIndex];
1322
1346
  }
1323
1347
 
1324
- var width, line = this._textLines[lineIndex], lineInfo;
1325
-
1326
- if (line === '') {
1327
- width = 0;
1328
- }
1329
- else {
1330
- lineInfo = this.measureLine(lineIndex);
1331
- width = lineInfo.width;
1332
- }
1348
+ var lineInfo = this.measureLine(lineIndex);
1349
+ var width = lineInfo.width;
1333
1350
  this.__lineWidths[lineIndex] = width;
1334
1351
  return width;
1335
1352
  },
@@ -133,8 +133,12 @@
133
133
  imageSmoothingEnabled: true,
134
134
 
135
135
  /**
136
- * The transformation (in the format of Canvas transform) which focuses the viewport
136
+ * The transformation (a Canvas 2D API transform matrix) which focuses the viewport
137
137
  * @type Array
138
+ * @example <caption>Default transform</caption>
139
+ * canvas.viewportTransform = [1, 0, 0, 1, 0, 0];
140
+ * @example <caption>Scale by 70% and translate toward bottom-right by 50, without skewing</caption>
141
+ * canvas.viewportTransform = [0.7, 0, 0, 0.7, 50, 50];
138
142
  * @default
139
143
  */
140
144
  viewportTransform: fabric.iMatrix.concat(),
@@ -228,7 +232,7 @@
228
232
  * @private
229
233
  */
230
234
  _isRetinaScaling: function() {
231
- return (fabric.devicePixelRatio !== 1 && this.enableRetinaScaling);
235
+ return (fabric.devicePixelRatio > 1 && this.enableRetinaScaling);
232
236
  },
233
237
 
234
238
  /**
@@ -236,7 +240,7 @@
236
240
  * @return {Number} retinaScaling if applied, otherwise 1;
237
241
  */
238
242
  getRetinaScaling: function() {
239
- return this._isRetinaScaling() ? fabric.devicePixelRatio : 1;
243
+ return this._isRetinaScaling() ? Math.max(1, fabric.devicePixelRatio) : 1;
240
244
  },
241
245
 
242
246
  /**
@@ -603,7 +607,7 @@
603
607
  }
604
608
  }
605
609
  if (this._isCurrentlyDrawing) {
606
- this.freeDrawingBrush && this.freeDrawingBrush._setBrushStyles();
610
+ this.freeDrawingBrush && this.freeDrawingBrush._setBrushStyles(this.contextTop);
607
611
  }
608
612
  this._initRetinaScaling();
609
613
  this.calcOffset();
@@ -670,8 +674,8 @@
670
674
  },
671
675
 
672
676
  /**
673
- * Sets viewport transform of this canvas instance
674
- * @param {Array} vpt the transform in the form of context.transform
677
+ * Sets viewport transformation of this canvas instance
678
+ * @param {Array} vpt a Canvas 2D API transform matrix
675
679
  * @return {fabric.Canvas} instance
676
680
  * @chainable true
677
681
  */
@@ -1758,6 +1762,10 @@
1758
1762
  }
1759
1763
  this.forEachObject(function(object) {
1760
1764
  object.dispose && object.dispose();
1765
+ // animation module is still optional
1766
+ if (fabric.runningAnimations) {
1767
+ fabric.runningAnimations.cancelByTarget(object);
1768
+ }
1761
1769
  });
1762
1770
  this._objects = [];
1763
1771
  if (this.backgroundImage && this.backgroundImage.dispose) {
@@ -1772,7 +1780,7 @@
1772
1780
  this.contextContainer = null;
1773
1781
  // restore canvas style
1774
1782
  this.lowerCanvasEl.classList.remove('lower-canvas');
1775
- this.lowerCanvasEl.style = this._originalCanvasStyle;
1783
+ fabric.util.setStyle(this.lowerCanvasEl, this._originalCanvasStyle);
1776
1784
  delete this._originalCanvasStyle;
1777
1785
  // restore canvas size to original size in case retina scaling was applied
1778
1786
  this.lowerCanvasEl.setAttribute('width', this.width);
@@ -1,4 +1,114 @@
1
- (function() {
1
+ (function () {
2
+
3
+ var extend = fabric.util.object.extend,
4
+ clone = fabric.util.object.clone;
5
+
6
+ /**
7
+ * @typedef {Object} AnimationOptions
8
+ * @property {Function} [options.onChange] Callback; invoked on every value change
9
+ * @property {Function} [options.onComplete] Callback; invoked when value change is completed
10
+ * @property {Number} [options.startValue=0] Starting value
11
+ * @property {Number} [options.endValue=100] Ending value
12
+ * @property {Number} [options.byValue=100] Value to modify the property by
13
+ * @property {Function} [options.easing] Easing function
14
+ * @property {Number} [options.duration=500] Duration of change (in ms)
15
+ * @property {Function} [options.abort] Additional function with logic. If returns true, animation aborts.
16
+ *
17
+ * @typedef {() => void} CancelFunction
18
+ *
19
+ * @typedef {Object} AnimationCurrentState
20
+ * @property {number} currentValue value in range [`startValue`, `endValue`]
21
+ * @property {number} completionRate value in range [0, 1]
22
+ * @property {number} durationRate value in range [0, 1]
23
+ *
24
+ * @typedef {(AnimationOptions & AnimationCurrentState & { cancel: CancelFunction }} AnimationContext
25
+ */
26
+
27
+ /**
28
+ * Array holding all running animations
29
+ * @memberof fabric
30
+ * @type {AnimationContext[]}
31
+ */
32
+ var RUNNING_ANIMATIONS = [];
33
+ fabric.util.object.extend(RUNNING_ANIMATIONS, {
34
+
35
+ /**
36
+ * cancel all running animations at the next requestAnimFrame
37
+ * @returns {AnimationContext[]}
38
+ */
39
+ cancelAll: function () {
40
+ var animations = this.splice(0);
41
+ animations.forEach(function (animation) {
42
+ animation.cancel();
43
+ });
44
+ return animations;
45
+ },
46
+
47
+ /**
48
+ * cancel all running animations attached to canvas at the next requestAnimFrame
49
+ * @param {fabric.Canvas} canvas
50
+ * @returns {AnimationContext[]}
51
+ */
52
+ cancelByCanvas: function (canvas) {
53
+ if (!canvas) {
54
+ return [];
55
+ }
56
+ var cancelled = this.filter(function (animation) {
57
+ return typeof animation.target === 'object' && animation.target.canvas === canvas;
58
+ });
59
+ cancelled.forEach(function (animation) {
60
+ animation.cancel();
61
+ });
62
+ return cancelled;
63
+ },
64
+
65
+ /**
66
+ * cancel all running animations for target at the next requestAnimFrame
67
+ * @param {*} target
68
+ * @returns {AnimationContext[]}
69
+ */
70
+ cancelByTarget: function (target) {
71
+ var cancelled = this.findAnimationsByTarget(target);
72
+ cancelled.forEach(function (animation) {
73
+ animation.cancel();
74
+ });
75
+ return cancelled;
76
+ },
77
+
78
+ /**
79
+ *
80
+ * @param {CancelFunction} cancelFunc the function returned by animate
81
+ * @returns {number}
82
+ */
83
+ findAnimationIndex: function (cancelFunc) {
84
+ return this.indexOf(this.findAnimation(cancelFunc));
85
+ },
86
+
87
+ /**
88
+ *
89
+ * @param {CancelFunction} cancelFunc the function returned by animate
90
+ * @returns {AnimationContext | undefined} animation's options object
91
+ */
92
+ findAnimation: function (cancelFunc) {
93
+ return this.find(function (animation) {
94
+ return animation.cancel === cancelFunc;
95
+ });
96
+ },
97
+
98
+ /**
99
+ *
100
+ * @param {*} target the object that is assigned to the target property of the animation context
101
+ * @returns {AnimationContext[]} array of animation options object associated with target
102
+ */
103
+ findAnimationsByTarget: function (target) {
104
+ if (!target) {
105
+ return [];
106
+ }
107
+ return this.filter(function (animation) {
108
+ return animation.target === target;
109
+ });
110
+ }
111
+ });
2
112
 
3
113
  function noop() {
4
114
  return false;
@@ -11,22 +121,30 @@
11
121
  /**
12
122
  * Changes value from one to another within certain period of time, invoking callbacks as value is being changed.
13
123
  * @memberOf fabric.util
14
- * @param {Object} [options] Animation options
15
- * @param {Function} [options.onChange] Callback; invoked on every value change
16
- * @param {Function} [options.onComplete] Callback; invoked when value change is completed
17
- * @param {Number} [options.startValue=0] Starting value
18
- * @param {Number} [options.endValue=100] Ending value
19
- * @param {Number} [options.byValue=100] Value to modify the property by
20
- * @param {Function} [options.easing] Easing function
21
- * @param {Number} [options.duration=500] Duration of change (in ms)
22
- * @param {Function} [options.abort] Additional function with logic. If returns true, onComplete is called.
23
- * @returns {Function} abort function
124
+ * @param {AnimationOptions} [options] Animation options
125
+ * @returns {CancelFunction} cancel function
24
126
  */
25
127
  function animate(options) {
26
- var cancel = false;
27
- requestAnimFrame(function(timestamp) {
28
- options || (options = { });
128
+ options || (options = {});
129
+ var cancel = false,
130
+ context,
131
+ removeFromRegistry = function () {
132
+ var index = fabric.runningAnimations.indexOf(context);
133
+ return index > -1 && fabric.runningAnimations.splice(index, 1)[0];
134
+ };
29
135
 
136
+ context = extend(clone(options), {
137
+ cancel: function () {
138
+ cancel = true;
139
+ return removeFromRegistry();
140
+ },
141
+ currentValue: 'startValue' in options ? options.startValue : 0,
142
+ completionRate: 0,
143
+ durationRate: 0
144
+ });
145
+ fabric.runningAnimations.push(context);
146
+
147
+ requestAnimFrame(function(timestamp) {
30
148
  var start = timestamp || +new Date(),
31
149
  duration = options.duration || 500,
32
150
  finish = start + duration, time,
@@ -41,25 +159,31 @@
41
159
  options.onStart && options.onStart();
42
160
 
43
161
  (function tick(ticktime) {
44
- // TODO: move abort call after calculation
45
- // and pass (current,valuePerc, timePerc) as arguments
46
162
  time = ticktime || +new Date();
47
163
  var currentTime = time > finish ? duration : (time - start),
48
164
  timePerc = currentTime / duration,
49
165
  current = easing(currentTime, startValue, byValue, duration),
50
166
  valuePerc = Math.abs((current - startValue) / byValue);
167
+ // update context
168
+ context.currentValue = current;
169
+ context.completionRate = valuePerc;
170
+ context.durationRate = timePerc;
51
171
  if (cancel) {
52
172
  return;
53
173
  }
54
174
  if (abort(current, valuePerc, timePerc)) {
55
- // remove this in 4.0
56
- // does to even make sense to abort and run onComplete?
57
- onComplete(endValue, 1, 1);
175
+ removeFromRegistry();
58
176
  return;
59
177
  }
60
178
  if (time > finish) {
179
+ // update context
180
+ context.currentValue = endValue;
181
+ context.completionRate = 1;
182
+ context.durationRate = 1;
183
+ // execute callbacks
61
184
  onChange(endValue, 1, 1);
62
185
  onComplete(endValue, 1, 1);
186
+ removeFromRegistry();
63
187
  return;
64
188
  }
65
189
  else {
@@ -68,9 +192,8 @@
68
192
  }
69
193
  })(start);
70
194
  });
71
- return function() {
72
- cancel = true;
73
- };
195
+
196
+ return context.cancel;
74
197
  }
75
198
 
76
199
  var _requestAnimFrame = fabric.window.requestAnimationFrame ||
@@ -102,4 +225,5 @@
102
225
  fabric.util.animate = animate;
103
226
  fabric.util.requestAnimFrame = requestAnimFrame;
104
227
  fabric.util.cancelAnimFrame = cancelAnimFrame;
228
+ fabric.runningAnimations = RUNNING_ANIMATIONS;
105
229
  })();
package/src/util/misc.js CHANGED
@@ -139,6 +139,135 @@
139
139
  };
140
140
  },
141
141
 
142
+ /**
143
+ * Creates a vetor from points represented as a point
144
+ * @static
145
+ * @memberOf fabric.util
146
+ *
147
+ * @typedef {Object} Point
148
+ * @property {number} x
149
+ * @property {number} y
150
+ *
151
+ * @param {Point} from
152
+ * @param {Point} to
153
+ * @returns {Point} vector
154
+ */
155
+ createVector: function (from, to) {
156
+ return new fabric.Point(to.x - from.x, to.y - from.y);
157
+ },
158
+
159
+ /**
160
+ * Calculates angle between 2 vectors using dot product
161
+ * @static
162
+ * @memberOf fabric.util
163
+ * @param {Point} a
164
+ * @param {Point} b
165
+ * @returns the angle in radian between the vectors
166
+ */
167
+ calcAngleBetweenVectors: function (a, b) {
168
+ return Math.acos((a.x * b.x + a.y * b.y) / (Math.hypot(a.x, a.y) * Math.hypot(b.x, b.y)));
169
+ },
170
+
171
+ /**
172
+ * @static
173
+ * @memberOf fabric.util
174
+ * @param {Point} v
175
+ * @returns {Point} vector representing the unit vector of pointing to the direction of `v`
176
+ */
177
+ getHatVector: function (v) {
178
+ return new fabric.Point(v.x, v.y).multiply(1 / Math.hypot(v.x, v.y));
179
+ },
180
+
181
+ /**
182
+ * @static
183
+ * @memberOf fabric.util
184
+ * @param {Point} A
185
+ * @param {Point} B
186
+ * @param {Point} C
187
+ * @returns {{ vector: Point, angle: number }} vector representing the bisector of A and A's angle
188
+ */
189
+ getBisector: function (A, B, C) {
190
+ var AB = fabric.util.createVector(A, B), AC = fabric.util.createVector(A, C);
191
+ var alpha = fabric.util.calcAngleBetweenVectors(AB, AC);
192
+ // check if alpha is relative to AB->BC
193
+ var ro = fabric.util.calcAngleBetweenVectors(fabric.util.rotateVector(AB, alpha), AC);
194
+ var phi = alpha * (ro === 0 ? 1 : -1) / 2;
195
+ return {
196
+ vector: fabric.util.getHatVector(fabric.util.rotateVector(AB, phi)),
197
+ angle: alpha
198
+ };
199
+ },
200
+
201
+ /**
202
+ * Project stroke width on points returning 2 projections for each point as follows:
203
+ * - `miter`: 2 points corresponding to the outer boundary and the inner boundary of stroke.
204
+ * - `bevel`: 2 points corresponding to the bevel boundaries, tangent to the bisector.
205
+ * - `round`: same as `bevel`
206
+ * Used to calculate object's bounding box
207
+ * @static
208
+ * @memberOf fabric.util
209
+ * @param {Point[]} points
210
+ * @param {Object} options
211
+ * @param {number} options.strokeWidth
212
+ * @param {'miter'|'bevel'|'round'} options.strokeLineJoin
213
+ * @param {number} options.strokeMiterLimit https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-miterlimit
214
+ * @param {boolean} options.strokeUniform
215
+ * @param {number} options.scaleX
216
+ * @param {number} options.scaleY
217
+ * @param {boolean} [openPath] whether the shape is open or not, affects the calculations of the first and last points
218
+ * @returns {fabric.Point[]} array of size 2n/4n of all suspected points
219
+ */
220
+ projectStrokeOnPoints: function (points, options, openPath) {
221
+ var coords = [], s = options.strokeWidth / 2,
222
+ strokeUniformScalar = options.strokeUniform ?
223
+ new fabric.Point(1 / options.scaleX, 1 / options.scaleY) : new fabric.Point(1, 1),
224
+ getStrokeHatVector = function (v) {
225
+ var scalar = s / (Math.hypot(v.x, v.y));
226
+ return new fabric.Point(v.x * scalar * strokeUniformScalar.x, v.y * scalar * strokeUniformScalar.y);
227
+ };
228
+ if (points.length <= 1) {return coords;}
229
+ points.forEach(function (p, index) {
230
+ var A = new fabric.Point(p.x, p.y), B, C;
231
+ if (index === 0) {
232
+ C = points[index + 1];
233
+ B = openPath ? getStrokeHatVector(fabric.util.createVector(C, A)).addEquals(A) : points[points.length - 1];
234
+ }
235
+ else if (index === points.length - 1) {
236
+ B = points[index - 1];
237
+ C = openPath ? getStrokeHatVector(fabric.util.createVector(B, A)).addEquals(A) : points[0];
238
+ }
239
+ else {
240
+ B = points[index - 1];
241
+ C = points[index + 1];
242
+ }
243
+ var bisector = fabric.util.getBisector(A, B, C),
244
+ bisectorVector = bisector.vector,
245
+ alpha = bisector.angle,
246
+ scalar,
247
+ miterVector;
248
+ if (options.strokeLineJoin === 'miter') {
249
+ scalar = -s / Math.sin(alpha / 2);
250
+ miterVector = new fabric.Point(
251
+ bisectorVector.x * scalar * strokeUniformScalar.x,
252
+ bisectorVector.y * scalar * strokeUniformScalar.y
253
+ );
254
+ if (Math.hypot(miterVector.x, miterVector.y) / s <= options.strokeMiterLimit) {
255
+ coords.push(A.add(miterVector));
256
+ coords.push(A.subtract(miterVector));
257
+ return;
258
+ }
259
+ }
260
+ scalar = -s * Math.SQRT2;
261
+ miterVector = new fabric.Point(
262
+ bisectorVector.x * scalar * strokeUniformScalar.x,
263
+ bisectorVector.y * scalar * strokeUniformScalar.y
264
+ );
265
+ coords.push(A.add(miterVector));
266
+ coords.push(A.subtract(miterVector));
267
+ });
268
+ return coords;
269
+ },
270
+
142
271
  /**
143
272
  * Apply transform t to point p
144
273
  * @static
@@ -451,6 +580,25 @@
451
580
  });
452
581
  },
453
582
 
583
+ /**
584
+ * Creates corresponding fabric instances residing in an object, e.g. `clipPath`
585
+ * @see {@link fabric.Object.ENLIVEN_PROPS}
586
+ * @param {Object} object
587
+ * @param {Object} [context] assign enlived props to this object (pass null to skip this)
588
+ * @param {(objects:fabric.Object[]) => void} callback
589
+ */
590
+ enlivenObjectEnlivables: function (object, context, callback) {
591
+ var enlivenProps = fabric.Object.ENLIVEN_PROPS.filter(function (key) { return !!object[key]; });
592
+ fabric.util.enlivenObjects(enlivenProps.map(function (key) { return object[key]; }), function (enlivedProps) {
593
+ var objects = {};
594
+ enlivenProps.forEach(function (key, index) {
595
+ objects[key] = enlivedProps[index];
596
+ context && (context[key] = enlivedProps[index]);
597
+ });
598
+ callback && callback(objects);
599
+ });
600
+ },
601
+
454
602
  /**
455
603
  * Create and wait for loading of patterns
456
604
  * @static
@@ -542,49 +690,6 @@
542
690
  }
543
691
  },
544
692
 
545
- /**
546
- * WARNING: THIS WAS TO SUPPORT OLD BROWSERS. deprecated.
547
- * WILL BE REMOVED IN FABRIC 5.0
548
- * Draws a dashed line between two points
549
- *
550
- * This method is used to draw dashed line around selection area.
551
- * See <a href="http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas">dotted stroke in canvas</a>
552
- *
553
- * @param {CanvasRenderingContext2D} ctx context
554
- * @param {Number} x start x coordinate
555
- * @param {Number} y start y coordinate
556
- * @param {Number} x2 end x coordinate
557
- * @param {Number} y2 end y coordinate
558
- * @param {Array} da dash array pattern
559
- * @deprecated
560
- */
561
- drawDashedLine: function(ctx, x, y, x2, y2, da) {
562
- var dx = x2 - x,
563
- dy = y2 - y,
564
- len = sqrt(dx * dx + dy * dy),
565
- rot = atan2(dy, dx),
566
- dc = da.length,
567
- di = 0,
568
- draw = true;
569
-
570
- ctx.save();
571
- ctx.translate(x, y);
572
- ctx.moveTo(0, 0);
573
- ctx.rotate(rot);
574
-
575
- x = 0;
576
- while (len > x) {
577
- x += da[di++ % dc];
578
- if (x > len) {
579
- x = len;
580
- }
581
- ctx[draw ? 'lineTo' : 'moveTo'](x, 0);
582
- draw = !draw;
583
- }
584
-
585
- ctx.restore();
586
- },
587
-
588
693
  /**
589
694
  * Creates canvas element
590
695
  * @static
@@ -712,7 +817,7 @@
712
817
  * @param {Boolean} [options.flipX]
713
818
  * @param {Boolean} [options.flipY]
714
819
  * @param {Number} [options.skewX]
715
- * @param {Number} [options.skewX]
820
+ * @param {Number} [options.skewY]
716
821
  * @return {Number[]} transform matrix
717
822
  */
718
823
  calcDimensionsMatrix: function(options) {
@@ -1066,6 +1171,49 @@
1066
1171
  x: bbox.width,
1067
1172
  y: bbox.height,
1068
1173
  };
1069
- }
1174
+ },
1175
+
1176
+ /**
1177
+ * Merges 2 clip paths into one visually equal clip path
1178
+ *
1179
+ * **IMPORTANT**:\
1180
+ * Does **NOT** clone the arguments, clone them proir if necessary.
1181
+ *
1182
+ * Creates a wrapper (group) that contains one clip path and is clipped by the other so content is kept where both overlap.
1183
+ * 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.
1184
+ *
1185
+ * In order to handle the `inverted` property we follow logic described in the following cases:\
1186
+ * **(1)** both clip paths are inverted - the clip paths pass the inverted prop to the wrapper and loose it themselves.\
1187
+ * **(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.\
1188
+ * **(3)** both clip paths are not inverted - wrapper and clip paths remain unchanged.
1189
+ *
1190
+ * @memberOf fabric.util
1191
+ * @param {fabric.Object} c1
1192
+ * @param {fabric.Object} c2
1193
+ * @returns {fabric.Object} merged clip path
1194
+ */
1195
+ mergeClipPaths: function (c1, c2) {
1196
+ var a = c1, b = c2;
1197
+ if (a.inverted && !b.inverted) {
1198
+ // case (2)
1199
+ a = c2;
1200
+ b = c1;
1201
+ }
1202
+ // `b` becomes `a`'s clip path so we transform `b` to `a` coordinate plane
1203
+ fabric.util.applyTransformToObject(
1204
+ b,
1205
+ fabric.util.multiplyTransformMatrices(
1206
+ fabric.util.invertTransform(a.calcTransformMatrix()),
1207
+ b.calcTransformMatrix()
1208
+ )
1209
+ );
1210
+ // assign the `inverted` prop to the wrapping group
1211
+ var inverted = a.inverted && b.inverted;
1212
+ if (inverted) {
1213
+ // case (1)
1214
+ a.inverted = b.inverted = false;
1215
+ }
1216
+ return new fabric.Group([a], { clipPath: b, inverted: inverted });
1217
+ },
1070
1218
  };
1071
1219
  })(typeof exports !== 'undefined' ? exports : this);