fabric 4.6.0 → 5.0.0-browser
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/HEADER.js +1 -1
- package/README.md +1 -6
- package/SECURITY.md +5 -0
- package/dist/fabric.js +652 -393
- package/dist/fabric.min.js +1 -1
- package/lib/event.js +7 -3
- package/package.json +87 -88
- package/src/brushes/base_brush.class.js +2 -2
- package/src/brushes/pattern_brush.class.js +7 -5
- package/src/brushes/pencil_brush.class.js +33 -4
- package/src/canvas.class.js +9 -17
- package/src/mixins/animation.mixin.js +16 -21
- package/src/mixins/canvas_events.mixin.js +27 -56
- package/src/mixins/eraser_brush.mixin.js +354 -420
- package/src/mixins/itext_behavior.mixin.js +7 -1
- package/src/mixins/object_geometry.mixin.js +3 -34
- package/src/mixins/object_straightening.mixin.js +4 -9
- package/src/parser.js +13 -9
- package/src/shapes/circle.class.js +20 -17
- package/src/shapes/group.class.js +13 -25
- package/src/shapes/image.class.js +3 -3
- package/src/shapes/object.class.js +45 -19
- package/src/shapes/path.class.js +13 -9
- package/src/shapes/polygon.class.js +9 -1
- package/src/shapes/polyline.class.js +33 -9
- package/src/shapes/text.class.js +38 -21
- package/src/static_canvas.class.js +15 -7
- package/src/util/animate.js +146 -22
- package/src/util/misc.js +193 -45
- package/src/util/path.js +2 -56
package/src/shapes/text.class.js
CHANGED
|
@@ -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 = '
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
674
|
-
* @param {Array} vpt
|
|
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
|
|
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);
|
package/src/util/animate.js
CHANGED
|
@@ -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 {
|
|
15
|
-
* @
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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.
|
|
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);
|