fabric 4.6.0-browser → 5.1.0-browser

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
  */
@@ -1772,7 +1776,7 @@
1772
1776
  this.contextContainer = null;
1773
1777
  // restore canvas style
1774
1778
  this.lowerCanvasEl.classList.remove('lower-canvas');
1775
- this.lowerCanvasEl.style = this._originalCanvasStyle;
1779
+ fabric.util.setStyle(this.lowerCanvasEl, this._originalCanvasStyle);
1776
1780
  delete this._originalCanvasStyle;
1777
1781
  // restore canvas size to original size in case retina scaling was applied
1778
1782
  this.lowerCanvasEl.setAttribute('width', this.width);
@@ -1,4 +1,129 @@
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
+ * Animation of a value or list of values.
9
+ * When using lists, think of something like this:
10
+ * fabric.util.animate({
11
+ * startValue: [1, 2, 3],
12
+ * endValue: [2, 4, 6],
13
+ * onChange: function([a, b, c]) {
14
+ * canvas.zoomToPoint({x: b, y: c}, a)
15
+ * canvas.renderAll()
16
+ * }
17
+ * });
18
+ * @example
19
+ * @property {Function} [onChange] Callback; invoked on every value change
20
+ * @property {Function} [onComplete] Callback; invoked when value change is completed
21
+ * @example
22
+ * // Note: startValue, endValue, and byValue must match the type
23
+ * var animationOptions = { startValue: 0, endValue: 1, byValue: 0.25 }
24
+ * var animationOptions = { startValue: [0, 1], endValue: [1, 2], byValue: [0.25, 0.25] }
25
+ * @property {number | number[]} [startValue=0] Starting value
26
+ * @property {number | number[]} [endValue=100] Ending value
27
+ * @property {number | number[]} [byValue=100] Value to modify the property by
28
+ * @property {Function} [easing] Easing function
29
+ * @property {Number} [duration=500] Duration of change (in ms)
30
+ * @property {Function} [abort] Additional function with logic. If returns true, animation aborts.
31
+ *
32
+ * @typedef {() => void} CancelFunction
33
+ *
34
+ * @typedef {Object} AnimationCurrentState
35
+ * @property {number | number[]} currentValue value in range [`startValue`, `endValue`]
36
+ * @property {number} completionRate value in range [0, 1]
37
+ * @property {number} durationRate value in range [0, 1]
38
+ *
39
+ * @typedef {(AnimationOptions & AnimationCurrentState & { cancel: CancelFunction }} AnimationContext
40
+ */
41
+
42
+ /**
43
+ * Array holding all running animations
44
+ * @memberof fabric
45
+ * @type {AnimationContext[]}
46
+ */
47
+ var RUNNING_ANIMATIONS = [];
48
+ fabric.util.object.extend(RUNNING_ANIMATIONS, {
49
+
50
+ /**
51
+ * cancel all running animations at the next requestAnimFrame
52
+ * @returns {AnimationContext[]}
53
+ */
54
+ cancelAll: function () {
55
+ var animations = this.splice(0);
56
+ animations.forEach(function (animation) {
57
+ animation.cancel();
58
+ });
59
+ return animations;
60
+ },
61
+
62
+ /**
63
+ * cancel all running animations attached to canvas at the next requestAnimFrame
64
+ * @param {fabric.Canvas} canvas
65
+ * @returns {AnimationContext[]}
66
+ */
67
+ cancelByCanvas: function (canvas) {
68
+ if (!canvas) {
69
+ return [];
70
+ }
71
+ var cancelled = this.filter(function (animation) {
72
+ return typeof animation.target === 'object' && animation.target.canvas === canvas;
73
+ });
74
+ cancelled.forEach(function (animation) {
75
+ animation.cancel();
76
+ });
77
+ return cancelled;
78
+ },
79
+
80
+ /**
81
+ * cancel all running animations for target at the next requestAnimFrame
82
+ * @param {*} target
83
+ * @returns {AnimationContext[]}
84
+ */
85
+ cancelByTarget: function (target) {
86
+ var cancelled = this.findAnimationsByTarget(target);
87
+ cancelled.forEach(function (animation) {
88
+ animation.cancel();
89
+ });
90
+ return cancelled;
91
+ },
92
+
93
+ /**
94
+ *
95
+ * @param {CancelFunction} cancelFunc the function returned by animate
96
+ * @returns {number}
97
+ */
98
+ findAnimationIndex: function (cancelFunc) {
99
+ return this.indexOf(this.findAnimation(cancelFunc));
100
+ },
101
+
102
+ /**
103
+ *
104
+ * @param {CancelFunction} cancelFunc the function returned by animate
105
+ * @returns {AnimationContext | undefined} animation's options object
106
+ */
107
+ findAnimation: function (cancelFunc) {
108
+ return this.find(function (animation) {
109
+ return animation.cancel === cancelFunc;
110
+ });
111
+ },
112
+
113
+ /**
114
+ *
115
+ * @param {*} target the object that is assigned to the target property of the animation context
116
+ * @returns {AnimationContext[]} array of animation options object associated with target
117
+ */
118
+ findAnimationsByTarget: function (target) {
119
+ if (!target) {
120
+ return [];
121
+ }
122
+ return this.filter(function (animation) {
123
+ return animation.target === target;
124
+ });
125
+ }
126
+ });
2
127
 
3
128
  function noop() {
4
129
  return false;
@@ -11,22 +136,34 @@
11
136
  /**
12
137
  * Changes value from one to another within certain period of time, invoking callbacks as value is being changed.
13
138
  * @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
139
+ * @param {AnimationOptions} [options] Animation options
140
+ * @example
141
+ * // Note: startValue, endValue, and byValue must match the type
142
+ * fabric.util.animate({ startValue: 0, endValue: 1, byValue: 0.25 })
143
+ * fabric.util.animate({ startValue: [0, 1], endValue: [1, 2], byValue: [0.25, 0.25] })
144
+ * @returns {CancelFunction} cancel function
24
145
  */
25
146
  function animate(options) {
26
- var cancel = false;
27
- requestAnimFrame(function(timestamp) {
28
- options || (options = { });
147
+ options || (options = {});
148
+ var cancel = false,
149
+ context,
150
+ removeFromRegistry = function () {
151
+ var index = fabric.runningAnimations.indexOf(context);
152
+ return index > -1 && fabric.runningAnimations.splice(index, 1)[0];
153
+ };
29
154
 
155
+ context = extend(clone(options), {
156
+ cancel: function () {
157
+ cancel = true;
158
+ return removeFromRegistry();
159
+ },
160
+ currentValue: 'startValue' in options ? options.startValue : 0,
161
+ completionRate: 0,
162
+ durationRate: 0
163
+ });
164
+ fabric.runningAnimations.push(context);
165
+
166
+ requestAnimFrame(function(timestamp) {
30
167
  var start = timestamp || +new Date(),
31
168
  duration = options.duration || 500,
32
169
  finish = start + duration, time,
@@ -34,32 +171,44 @@
34
171
  abort = options.abort || noop,
35
172
  onComplete = options.onComplete || noop,
36
173
  easing = options.easing || defaultEasing,
174
+ isMany = 'startValue' in options ? options.startValue.length > 0 : false,
37
175
  startValue = 'startValue' in options ? options.startValue : 0,
38
176
  endValue = 'endValue' in options ? options.endValue : 100,
39
- byValue = options.byValue || endValue - startValue;
177
+ byValue = options.byValue || (isMany ? startValue.map(function(value, i) {
178
+ return endValue[i] - startValue[i];
179
+ }) : endValue - startValue);
40
180
 
41
181
  options.onStart && options.onStart();
42
182
 
43
183
  (function tick(ticktime) {
44
- // TODO: move abort call after calculation
45
- // and pass (current,valuePerc, timePerc) as arguments
46
184
  time = ticktime || +new Date();
47
185
  var currentTime = time > finish ? duration : (time - start),
48
186
  timePerc = currentTime / duration,
49
- current = easing(currentTime, startValue, byValue, duration),
50
- valuePerc = Math.abs((current - startValue) / byValue);
187
+ current = isMany ? startValue.map(function(_value, i) {
188
+ return easing(currentTime, startValue[i], byValue[i], duration);
189
+ }) : easing(currentTime, startValue, byValue, duration),
190
+ valuePerc = isMany ? Math.abs((current[0] - startValue[0]) / byValue[0])
191
+ : Math.abs((current - startValue) / byValue);
192
+ // update context
193
+ context.currentValue = isMany ? current.slice() : current;
194
+ context.completionRate = valuePerc;
195
+ context.durationRate = timePerc;
51
196
  if (cancel) {
52
197
  return;
53
198
  }
54
199
  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);
200
+ removeFromRegistry();
58
201
  return;
59
202
  }
60
203
  if (time > finish) {
61
- onChange(endValue, 1, 1);
204
+ // update context
205
+ context.currentValue = isMany ? endValue.slice() : endValue;
206
+ context.completionRate = 1;
207
+ context.durationRate = 1;
208
+ // execute callbacks
209
+ onChange(isMany ? endValue.slice() : endValue, 1, 1);
62
210
  onComplete(endValue, 1, 1);
211
+ removeFromRegistry();
63
212
  return;
64
213
  }
65
214
  else {
@@ -68,9 +217,8 @@
68
217
  }
69
218
  })(start);
70
219
  });
71
- return function() {
72
- cancel = true;
73
- };
220
+
221
+ return context.cancel;
74
222
  }
75
223
 
76
224
  var _requestAnimFrame = fabric.window.requestAnimationFrame ||
@@ -102,4 +250,5 @@
102
250
  fabric.util.animate = animate;
103
251
  fabric.util.requestAnimFrame = requestAnimFrame;
104
252
  fabric.util.cancelAnimFrame = cancelAnimFrame;
253
+ fabric.runningAnimations = RUNNING_ANIMATIONS;
105
254
  })();