fabric-rails 1.0.12 → 1.0.12.1
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.
- data/CHANGELOG.md +16 -0
- data/README.md +1 -1
- data/lib/fabric/rails/version.rb +2 -2
- data/vendor/assets/javascripts/event.js +1909 -0
- data/vendor/assets/javascripts/fabric.js +64 -16464
- data/vendor/assets/javascripts/fabric/HEADER.js +31 -0
- data/vendor/assets/javascripts/fabric/canvas.class.js +1007 -0
- data/vendor/assets/javascripts/fabric/canvas_animation.mixin.js +113 -0
- data/vendor/assets/javascripts/fabric/canvas_events.mixin.js +482 -0
- data/vendor/assets/javascripts/fabric/canvas_gestures.mixin.js +79 -0
- data/vendor/assets/javascripts/fabric/canvas_serialization.mixin.js +311 -0
- data/vendor/assets/javascripts/fabric/circle.class.js +182 -0
- data/vendor/assets/javascripts/fabric/color.class.js +284 -0
- data/vendor/assets/javascripts/fabric/ellipse.class.js +169 -0
- data/vendor/assets/javascripts/fabric/freedrawing.class.js +256 -0
- data/vendor/assets/javascripts/fabric/gradient.class.js +211 -0
- data/vendor/assets/javascripts/fabric/group.class.js +556 -0
- data/vendor/assets/javascripts/fabric/image.class.js +418 -0
- data/vendor/assets/javascripts/fabric/image_filters.js +809 -0
- data/vendor/assets/javascripts/fabric/intersection.class.js +178 -0
- data/vendor/assets/javascripts/fabric/line.class.js +188 -0
- data/vendor/assets/javascripts/fabric/log.js +26 -0
- data/vendor/assets/javascripts/fabric/node.js +149 -0
- data/vendor/assets/javascripts/fabric/object.class.js +1068 -0
- data/vendor/assets/javascripts/fabric/object_geometry.mixin.js +308 -0
- data/vendor/assets/javascripts/fabric/object_interactivity.mixin.js +496 -0
- data/vendor/assets/javascripts/fabric/object_origin.mixin.js +207 -0
- data/vendor/assets/javascripts/fabric/object_straightening.mixin.js +94 -0
- data/vendor/assets/javascripts/fabric/observable.mixin.js +91 -0
- data/vendor/assets/javascripts/fabric/parser.js +750 -0
- data/vendor/assets/javascripts/fabric/path.class.js +794 -0
- data/vendor/assets/javascripts/fabric/path_group.class.js +240 -0
- data/vendor/assets/javascripts/fabric/pattern.class.js +69 -0
- data/vendor/assets/javascripts/fabric/point.class.js +327 -0
- data/vendor/assets/javascripts/fabric/polygon.class.js +184 -0
- data/vendor/assets/javascripts/fabric/polyline.class.js +157 -0
- data/vendor/assets/javascripts/fabric/rect.class.js +298 -0
- data/vendor/assets/javascripts/fabric/scout.js +45 -0
- data/vendor/assets/javascripts/fabric/shadow.class.js +70 -0
- data/vendor/assets/javascripts/fabric/stateful.js +88 -0
- data/vendor/assets/javascripts/fabric/static_canvas.class.js +1298 -0
- data/vendor/assets/javascripts/fabric/text.class.js +934 -0
- data/vendor/assets/javascripts/fabric/triangle.class.js +108 -0
- data/vendor/assets/javascripts/fabric/util/anim_ease.js +360 -0
- data/vendor/assets/javascripts/fabric/util/dom_event.js +237 -0
- data/vendor/assets/javascripts/fabric/util/dom_misc.js +245 -0
- data/vendor/assets/javascripts/fabric/util/dom_request.js +72 -0
- data/vendor/assets/javascripts/fabric/util/dom_style.js +71 -0
- data/vendor/assets/javascripts/fabric/util/lang_array.js +250 -0
- data/vendor/assets/javascripts/fabric/util/lang_class.js +97 -0
- data/vendor/assets/javascripts/fabric/util/lang_function.js +35 -0
- data/vendor/assets/javascripts/fabric/util/lang_object.js +34 -0
- data/vendor/assets/javascripts/fabric/util/lang_string.js +60 -0
- data/vendor/assets/javascripts/fabric/util/misc.js +406 -0
- data/vendor/assets/javascripts/json2.js +491 -0
- metadata +53 -2
@@ -0,0 +1,45 @@
|
|
1
|
+
/** @ignore */
|
2
|
+
var scout = (function () {
|
3
|
+
var tests = { };
|
4
|
+
return {
|
5
|
+
addTest: function (moduleName, test) {
|
6
|
+
tests[moduleName] = test;
|
7
|
+
},
|
8
|
+
fetch: function () {
|
9
|
+
var modulesToFetch = [ ];
|
10
|
+
for (var moduleName in tests) {
|
11
|
+
if (tests[moduleName]()) {
|
12
|
+
modulesToFetch.push(moduleName);
|
13
|
+
}
|
14
|
+
}
|
15
|
+
return modulesToFetch.join(',');
|
16
|
+
}
|
17
|
+
};
|
18
|
+
})();
|
19
|
+
|
20
|
+
scout.addTest('json2', function () {
|
21
|
+
return typeof JSON === 'undefined';
|
22
|
+
});
|
23
|
+
scout.addTest('indexOf', function () {
|
24
|
+
return typeof Array.prototype.indexOf === 'undefined';
|
25
|
+
});
|
26
|
+
scout.addTest('forEach', function () {
|
27
|
+
return typeof Array.prototype.forEach === 'undefined';
|
28
|
+
});
|
29
|
+
scout.addTest('map', function () {
|
30
|
+
return typeof Array.prototype.map === 'undefined';
|
31
|
+
});
|
32
|
+
scout.addTest('every', function () {
|
33
|
+
return typeof Array.prototype.every === 'undefined';
|
34
|
+
});
|
35
|
+
scout.addTest('some', function () {
|
36
|
+
return typeof Array.prototype.some === 'undefined';
|
37
|
+
});
|
38
|
+
scout.addTest('filter', function () {
|
39
|
+
return typeof Array.prototype.filter === 'undefined';
|
40
|
+
});
|
41
|
+
scout.addTest('reduce', function () {
|
42
|
+
return typeof Array.prototype.reduce === 'undefined';
|
43
|
+
});
|
44
|
+
|
45
|
+
scout.fetch();
|
@@ -0,0 +1,70 @@
|
|
1
|
+
/**
|
2
|
+
* Shadow class
|
3
|
+
* @class Shadow
|
4
|
+
* @memberOf fabric
|
5
|
+
*/
|
6
|
+
fabric.Shadow = fabric.util.createClass(/** @scope fabric.Shadow.prototype */ {
|
7
|
+
|
8
|
+
/**
|
9
|
+
* Shadow color
|
10
|
+
* @property
|
11
|
+
* @type String
|
12
|
+
*/
|
13
|
+
color: 'rgb(0,0,0)',
|
14
|
+
|
15
|
+
/**
|
16
|
+
* Shadow blur
|
17
|
+
* @property
|
18
|
+
* @type Number
|
19
|
+
*/
|
20
|
+
blur: 0,
|
21
|
+
|
22
|
+
/**
|
23
|
+
* Shadow horizontal offset
|
24
|
+
* @property
|
25
|
+
* @type Number
|
26
|
+
*/
|
27
|
+
offsetX: 0,
|
28
|
+
|
29
|
+
/**
|
30
|
+
* Shadow vertical offset
|
31
|
+
* @property
|
32
|
+
* @type Number
|
33
|
+
*/
|
34
|
+
offsetY: 0,
|
35
|
+
|
36
|
+
/**
|
37
|
+
* Constructor
|
38
|
+
* @method initialize
|
39
|
+
* @param [options] Options object with any of color, blur, offsetX, offsetX properties
|
40
|
+
* @return {fabric.Shadow} thisArg
|
41
|
+
*/
|
42
|
+
initialize: function(options) {
|
43
|
+
for (var prop in options) {
|
44
|
+
this[prop] = options[prop];
|
45
|
+
}
|
46
|
+
},
|
47
|
+
|
48
|
+
/**
|
49
|
+
* Returns object representation of a shadow
|
50
|
+
* @method toObject
|
51
|
+
* @return {Object}
|
52
|
+
*/
|
53
|
+
toObject: function() {
|
54
|
+
return {
|
55
|
+
color: this.color,
|
56
|
+
blur: this.blur,
|
57
|
+
offsetX: this.offsetX,
|
58
|
+
offsetY: this.offsetY
|
59
|
+
};
|
60
|
+
},
|
61
|
+
|
62
|
+
/**
|
63
|
+
* Returns SVG representation of a shadow
|
64
|
+
* @method toSVG
|
65
|
+
* @return {String}
|
66
|
+
*/
|
67
|
+
toSVG: function() {
|
68
|
+
|
69
|
+
}
|
70
|
+
});
|
@@ -0,0 +1,88 @@
|
|
1
|
+
fabric.util.object.extend(fabric.Object.prototype, {
|
2
|
+
|
3
|
+
/**
|
4
|
+
* List of properties to consider when checking if state of an object is changed (fabric.Object#hasStateChanged);
|
5
|
+
* as well as for history (undo/redo) purposes
|
6
|
+
* @property
|
7
|
+
* @type Array
|
8
|
+
*/
|
9
|
+
stateProperties: (
|
10
|
+
'top left width height scaleX scaleY flipX flipY ' +
|
11
|
+
'theta angle opacity cornersize fill overlayFill ' +
|
12
|
+
'stroke strokeWidth strokeDashArray fillRule ' +
|
13
|
+
'borderScaleFactor transformMatrix selectable'
|
14
|
+
).split(' '),
|
15
|
+
|
16
|
+
/**
|
17
|
+
* Returns true if state of an object (one if its state properties) was changed
|
18
|
+
* @method hasStateChanged
|
19
|
+
* @return {Boolean} true if instance' state has changed
|
20
|
+
*/
|
21
|
+
hasStateChanged: function() {
|
22
|
+
return this.stateProperties.some(function(prop) {
|
23
|
+
return this[prop] !== this.originalState[prop];
|
24
|
+
}, this);
|
25
|
+
},
|
26
|
+
|
27
|
+
/**
|
28
|
+
* Saves a snapshot of object's state (its state properties)
|
29
|
+
* @method saveState
|
30
|
+
* @return {fabric.Object} thisArg
|
31
|
+
* @chainable
|
32
|
+
*/
|
33
|
+
saveState: function() {
|
34
|
+
this.stateProperties.forEach(function(prop) {
|
35
|
+
this.originalState[prop] = this.get(prop);
|
36
|
+
}, this);
|
37
|
+
return this;
|
38
|
+
},
|
39
|
+
|
40
|
+
/**
|
41
|
+
* Setups state of an object
|
42
|
+
* @method setupState
|
43
|
+
*/
|
44
|
+
setupState: function() {
|
45
|
+
this.originalState = { };
|
46
|
+
this.saveState();
|
47
|
+
}
|
48
|
+
});
|
49
|
+
|
50
|
+
|
51
|
+
// misc:
|
52
|
+
|
53
|
+
// type
|
54
|
+
|
55
|
+
// object rendering:
|
56
|
+
|
57
|
+
// top
|
58
|
+
// left
|
59
|
+
// width
|
60
|
+
// height
|
61
|
+
// scaleX
|
62
|
+
// scaleY
|
63
|
+
// flipX
|
64
|
+
// flipY
|
65
|
+
// theta
|
66
|
+
// opacity
|
67
|
+
// angle
|
68
|
+
// fill
|
69
|
+
// fillRule
|
70
|
+
// overlayFill
|
71
|
+
// stroke
|
72
|
+
// strokeWidth
|
73
|
+
// strokeDashArray
|
74
|
+
// transformMatrix
|
75
|
+
|
76
|
+
// object controls:
|
77
|
+
|
78
|
+
// cornersize
|
79
|
+
// padding
|
80
|
+
// borderColor
|
81
|
+
// cornerColor
|
82
|
+
// borderOpacityWhenMoving
|
83
|
+
// borderScaleFactor
|
84
|
+
// selectable
|
85
|
+
// hasControls
|
86
|
+
// hasBorders
|
87
|
+
// hasRotatingPoint
|
88
|
+
// rotatingPointOffset
|
@@ -0,0 +1,1298 @@
|
|
1
|
+
(function () {
|
2
|
+
|
3
|
+
"use strict";
|
4
|
+
|
5
|
+
if (fabric.StaticCanvas) {
|
6
|
+
fabric.warn('fabric.StaticCanvas is already defined.');
|
7
|
+
return;
|
8
|
+
}
|
9
|
+
|
10
|
+
// aliases for faster resolution
|
11
|
+
var extend = fabric.util.object.extend,
|
12
|
+
getElementOffset = fabric.util.getElementOffset,
|
13
|
+
removeFromArray = fabric.util.removeFromArray,
|
14
|
+
removeListener = fabric.util.removeListener,
|
15
|
+
|
16
|
+
CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element');
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Static canvas class
|
20
|
+
* @class fabric.StaticCanvas
|
21
|
+
* @constructor
|
22
|
+
* @param {HTMLElement | String} el <canvas> element to initialize instance on
|
23
|
+
* @param {Object} [options] Options object
|
24
|
+
*/
|
25
|
+
fabric.StaticCanvas = function (el, options) {
|
26
|
+
options || (options = { });
|
27
|
+
|
28
|
+
this._initStatic(el, options);
|
29
|
+
fabric.StaticCanvas.activeInstance = this;
|
30
|
+
};
|
31
|
+
|
32
|
+
extend(fabric.StaticCanvas.prototype, fabric.Observable);
|
33
|
+
|
34
|
+
extend(fabric.StaticCanvas.prototype, /** @scope fabric.StaticCanvas.prototype */ {
|
35
|
+
|
36
|
+
/**
|
37
|
+
* Background color of canvas instance
|
38
|
+
* @property
|
39
|
+
* @type String
|
40
|
+
*/
|
41
|
+
backgroundColor: '',
|
42
|
+
|
43
|
+
/**
|
44
|
+
* Background image of canvas instance
|
45
|
+
* Should be set via `setBackgroundImage`
|
46
|
+
* @property
|
47
|
+
* @type String
|
48
|
+
*/
|
49
|
+
backgroundImage: '',
|
50
|
+
|
51
|
+
/**
|
52
|
+
* Opacity of the background image of the canvas instance
|
53
|
+
* @property
|
54
|
+
* @type Float
|
55
|
+
*/
|
56
|
+
backgroundImageOpacity: 1.0,
|
57
|
+
|
58
|
+
/**
|
59
|
+
* Indicates whether the background image should be stretched to fit the
|
60
|
+
* dimensions of the canvas instance.
|
61
|
+
* @property
|
62
|
+
* @type Boolean
|
63
|
+
*/
|
64
|
+
backgroundImageStretch: true,
|
65
|
+
|
66
|
+
/**
|
67
|
+
* Overlay image of canvas instance
|
68
|
+
* Should be set via `setOverlayImage`
|
69
|
+
* @property
|
70
|
+
* @type String
|
71
|
+
*/
|
72
|
+
overlayImage: '',
|
73
|
+
|
74
|
+
/**
|
75
|
+
* Left offset of overlay image (if present)
|
76
|
+
* @property
|
77
|
+
* @type Number
|
78
|
+
*/
|
79
|
+
overlayImageLeft: 0,
|
80
|
+
|
81
|
+
/**
|
82
|
+
* Top offset of overlay image (if present)
|
83
|
+
* @property
|
84
|
+
* @type Number
|
85
|
+
*/
|
86
|
+
overlayImageTop: 0,
|
87
|
+
|
88
|
+
/**
|
89
|
+
* Indicates whether toObject/toDatalessObject should include default values
|
90
|
+
* @property
|
91
|
+
* @type Boolean
|
92
|
+
*/
|
93
|
+
includeDefaultValues: true,
|
94
|
+
|
95
|
+
/**
|
96
|
+
* Indicates whether objects' state should be saved
|
97
|
+
* @property
|
98
|
+
* @type Boolean
|
99
|
+
*/
|
100
|
+
stateful: true,
|
101
|
+
|
102
|
+
/**
|
103
|
+
* Indicates whether {@link fabric.Canvas.prototype.add} should also re-render canvas.
|
104
|
+
* Disabling this option could give a great performance boost when adding a lot of objects to canvas at once
|
105
|
+
* (followed by a manual rendering after addition)
|
106
|
+
* @property
|
107
|
+
* @type Boolean
|
108
|
+
*/
|
109
|
+
renderOnAddition: true,
|
110
|
+
|
111
|
+
/**
|
112
|
+
* Function that determines clipping of entire canvas area
|
113
|
+
* Being passed context as first argument. See clipping canvas area in https://github.com/kangax/fabric.js/wiki/FAQ
|
114
|
+
* @property
|
115
|
+
* @type Function
|
116
|
+
*/
|
117
|
+
clipTo: null,
|
118
|
+
|
119
|
+
/**
|
120
|
+
* Indicates whether object controls (borders/controls) are rendered above overlay image
|
121
|
+
* @property
|
122
|
+
* @type Boolean
|
123
|
+
*/
|
124
|
+
controlsAboveOverlay: false,
|
125
|
+
|
126
|
+
/**
|
127
|
+
* Callback; invoked right before object is about to be scaled/rotated
|
128
|
+
* @method onBeforeScaleRotate
|
129
|
+
* @param {fabric.Object} target Object that's about to be scaled/rotated
|
130
|
+
*/
|
131
|
+
onBeforeScaleRotate: function () {
|
132
|
+
/* NOOP */
|
133
|
+
},
|
134
|
+
|
135
|
+
/**
|
136
|
+
* @method _initStatic
|
137
|
+
* @private
|
138
|
+
*/
|
139
|
+
_initStatic: function(el, options) {
|
140
|
+
this._objects = [];
|
141
|
+
|
142
|
+
this._createLowerCanvas(el);
|
143
|
+
this._initOptions(options);
|
144
|
+
|
145
|
+
if (options.overlayImage) {
|
146
|
+
this.setOverlayImage(options.overlayImage, this.renderAll.bind(this));
|
147
|
+
}
|
148
|
+
if (options.backgroundImage) {
|
149
|
+
this.setBackgroundImage(options.backgroundImage, this.renderAll.bind(this));
|
150
|
+
}
|
151
|
+
if (options.backgroundColor) {
|
152
|
+
this.setBackgroundColor(options.backgroundColor, this.renderAll.bind(this));
|
153
|
+
}
|
154
|
+
this.calcOffset();
|
155
|
+
},
|
156
|
+
|
157
|
+
/**
|
158
|
+
* Calculates canvas element offset relative to the document
|
159
|
+
* This method is also attached as "resize" event handler of window
|
160
|
+
* @method calcOffset
|
161
|
+
* @return {fabric.Canvas} instance
|
162
|
+
* @chainable
|
163
|
+
*/
|
164
|
+
calcOffset: function () {
|
165
|
+
this._offset = getElementOffset(this.lowerCanvasEl);
|
166
|
+
return this;
|
167
|
+
},
|
168
|
+
|
169
|
+
/**
|
170
|
+
* Sets overlay image for this canvas
|
171
|
+
* @method setOverlayImage
|
172
|
+
* @param {String} url url of an image to set overlay to
|
173
|
+
* @param {Function} callback callback to invoke when image is loaded and set as an overlay
|
174
|
+
* @param {Object} [options] optional options to set for the overlay image
|
175
|
+
* @return {fabric.Canvas} thisArg
|
176
|
+
* @chainable
|
177
|
+
*/
|
178
|
+
setOverlayImage: function (url, callback, options) { // TODO (kangax): test callback
|
179
|
+
fabric.util.loadImage(url, function(img) {
|
180
|
+
this.overlayImage = img;
|
181
|
+
if (options && ('overlayImageLeft' in options)) {
|
182
|
+
this.overlayImageLeft = options.overlayImageLeft;
|
183
|
+
}
|
184
|
+
if (options && ('overlayImageTop' in options)) {
|
185
|
+
this.overlayImageTop = options.overlayImageTop;
|
186
|
+
}
|
187
|
+
callback && callback();
|
188
|
+
}, this);
|
189
|
+
|
190
|
+
return this;
|
191
|
+
},
|
192
|
+
|
193
|
+
/**
|
194
|
+
* Sets background image for this canvas
|
195
|
+
* @method setBackgroundImage
|
196
|
+
* @param {String} url url of an image to set background to
|
197
|
+
* @param {Function} callback callback to invoke when image is loaded and set as background
|
198
|
+
* @param {Object} [options] optional options to set for the background image
|
199
|
+
* @return {fabric.Canvas} thisArg
|
200
|
+
* @chainable
|
201
|
+
*/
|
202
|
+
setBackgroundImage: function (url, callback, options) {
|
203
|
+
fabric.util.loadImage(url, function(img) {
|
204
|
+
this.backgroundImage = img;
|
205
|
+
if (options && ('backgroundImageOpacity' in options)) {
|
206
|
+
this.backgroundImageOpacity = options.backgroundImageOpacity;
|
207
|
+
}
|
208
|
+
if (options && ('backgroundImageStretch' in options)) {
|
209
|
+
this.backgroundImageStretch = options.backgroundImageStretch;
|
210
|
+
}
|
211
|
+
callback && callback();
|
212
|
+
}, this);
|
213
|
+
|
214
|
+
return this;
|
215
|
+
},
|
216
|
+
|
217
|
+
/**
|
218
|
+
* Sets background color for this canvas
|
219
|
+
* @method setBackgroundColor
|
220
|
+
* @param {String|fabric.Pattern} Color of pattern to set background color to
|
221
|
+
* @param {Function} callback callback to invoke when background color is set
|
222
|
+
* @return {fabric.Canvas} thisArg
|
223
|
+
* @chainable
|
224
|
+
*/
|
225
|
+
setBackgroundColor: function(backgroundColor, callback) {
|
226
|
+
if (backgroundColor.source) {
|
227
|
+
var _this = this;
|
228
|
+
fabric.util.loadImage(backgroundColor.source, function(img) {
|
229
|
+
_this.backgroundColor = new fabric.Pattern({
|
230
|
+
source: img,
|
231
|
+
repeat: backgroundColor.repeat
|
232
|
+
});
|
233
|
+
callback && callback();
|
234
|
+
});
|
235
|
+
}
|
236
|
+
else {
|
237
|
+
this.backgroundColor = backgroundColor;
|
238
|
+
callback && callback();
|
239
|
+
}
|
240
|
+
|
241
|
+
return this;
|
242
|
+
},
|
243
|
+
|
244
|
+
/**
|
245
|
+
* @private
|
246
|
+
* @method _createCanvasElement
|
247
|
+
*/
|
248
|
+
_createCanvasElement: function() {
|
249
|
+
var element = fabric.document.createElement('canvas');
|
250
|
+
if (!element.style) {
|
251
|
+
element.style = { };
|
252
|
+
}
|
253
|
+
if (!element) {
|
254
|
+
throw CANVAS_INIT_ERROR;
|
255
|
+
}
|
256
|
+
this._initCanvasElement(element);
|
257
|
+
return element;
|
258
|
+
},
|
259
|
+
|
260
|
+
/**
|
261
|
+
* @method _initCanvasElement
|
262
|
+
* @param {HTMLElement} element
|
263
|
+
*/
|
264
|
+
_initCanvasElement: function(element) {
|
265
|
+
fabric.util.createCanvasElement(element);
|
266
|
+
|
267
|
+
if (typeof element.getContext === 'undefined') {
|
268
|
+
throw CANVAS_INIT_ERROR;
|
269
|
+
}
|
270
|
+
},
|
271
|
+
|
272
|
+
/**
|
273
|
+
* @method _initOptions
|
274
|
+
* @param {Object} [options]
|
275
|
+
*/
|
276
|
+
_initOptions: function (options) {
|
277
|
+
for (var prop in options) {
|
278
|
+
this[prop] = options[prop];
|
279
|
+
}
|
280
|
+
|
281
|
+
this.width = parseInt(this.lowerCanvasEl.width, 10) || 0;
|
282
|
+
this.height = parseInt(this.lowerCanvasEl.height, 10) || 0;
|
283
|
+
|
284
|
+
if (!this.lowerCanvasEl.style) return;
|
285
|
+
|
286
|
+
this.lowerCanvasEl.style.width = this.width + 'px';
|
287
|
+
this.lowerCanvasEl.style.height = this.height + 'px';
|
288
|
+
},
|
289
|
+
|
290
|
+
/**
|
291
|
+
* Creates a bottom canvas
|
292
|
+
* @method _createLowerCanvas
|
293
|
+
*/
|
294
|
+
_createLowerCanvas: function (canvasEl) {
|
295
|
+
this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement();
|
296
|
+
this._initCanvasElement(this.lowerCanvasEl);
|
297
|
+
|
298
|
+
fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas');
|
299
|
+
|
300
|
+
if (this.interactive) {
|
301
|
+
this._applyCanvasStyle(this.lowerCanvasEl);
|
302
|
+
}
|
303
|
+
|
304
|
+
this.contextContainer = this.lowerCanvasEl.getContext('2d');
|
305
|
+
},
|
306
|
+
|
307
|
+
/**
|
308
|
+
* Returns canvas width (in px)
|
309
|
+
* @method getWidth
|
310
|
+
* @return {Number}
|
311
|
+
*/
|
312
|
+
getWidth: function () {
|
313
|
+
return this.width;
|
314
|
+
},
|
315
|
+
|
316
|
+
/**
|
317
|
+
* Returns canvas height (in px)
|
318
|
+
* @method getHeight
|
319
|
+
* @return {Number}
|
320
|
+
*/
|
321
|
+
getHeight: function () {
|
322
|
+
return this.height;
|
323
|
+
},
|
324
|
+
|
325
|
+
/**
|
326
|
+
* Sets width of this canvas instance
|
327
|
+
* @method setWidth
|
328
|
+
* @param {Number} width value to set width to
|
329
|
+
* @return {fabric.Canvas} instance
|
330
|
+
* @chainable true
|
331
|
+
*/
|
332
|
+
setWidth: function (value) {
|
333
|
+
return this._setDimension('width', value);
|
334
|
+
},
|
335
|
+
|
336
|
+
/**
|
337
|
+
* Sets height of this canvas instance
|
338
|
+
* @method setHeight
|
339
|
+
* @param {Number} height value to set height to
|
340
|
+
* @return {fabric.Canvas} instance
|
341
|
+
* @chainable true
|
342
|
+
*/
|
343
|
+
setHeight: function (value) {
|
344
|
+
return this._setDimension('height', value);
|
345
|
+
},
|
346
|
+
|
347
|
+
/**
|
348
|
+
* Sets dimensions (width, height) of this canvas instance
|
349
|
+
* @method setDimensions
|
350
|
+
* @param {Object} dimensions
|
351
|
+
* @return {fabric.Canvas} thisArg
|
352
|
+
* @chainable
|
353
|
+
*/
|
354
|
+
setDimensions: function(dimensions) {
|
355
|
+
for (var prop in dimensions) {
|
356
|
+
this._setDimension(prop, dimensions[prop]);
|
357
|
+
}
|
358
|
+
return this;
|
359
|
+
},
|
360
|
+
|
361
|
+
/**
|
362
|
+
* Helper for setting width/height
|
363
|
+
* @private
|
364
|
+
* @method _setDimensions
|
365
|
+
* @param {String} prop property (width|height)
|
366
|
+
* @param {Number} value value to set property to
|
367
|
+
* @return {fabric.Canvas} instance
|
368
|
+
* @chainable true
|
369
|
+
*/
|
370
|
+
_setDimension: function (prop, value) {
|
371
|
+
this.lowerCanvasEl[prop] = value;
|
372
|
+
this.lowerCanvasEl.style[prop] = value + 'px';
|
373
|
+
|
374
|
+
if (this.upperCanvasEl) {
|
375
|
+
this.upperCanvasEl[prop] = value;
|
376
|
+
this.upperCanvasEl.style[prop] = value + 'px';
|
377
|
+
}
|
378
|
+
|
379
|
+
if (this.cacheCanvasEl) {
|
380
|
+
this.cacheCanvasEl[prop] = value;
|
381
|
+
}
|
382
|
+
|
383
|
+
if (this.wrapperEl) {
|
384
|
+
this.wrapperEl.style[prop] = value + 'px';
|
385
|
+
}
|
386
|
+
|
387
|
+
this[prop] = value;
|
388
|
+
|
389
|
+
this.calcOffset();
|
390
|
+
this.renderAll();
|
391
|
+
|
392
|
+
return this;
|
393
|
+
},
|
394
|
+
|
395
|
+
/**
|
396
|
+
* Returns <canvas> element corresponding to this instance
|
397
|
+
* @method getElement
|
398
|
+
* @return {HTMLCanvasElement}
|
399
|
+
*/
|
400
|
+
getElement: function () {
|
401
|
+
return this.lowerCanvasEl;
|
402
|
+
},
|
403
|
+
|
404
|
+
/**
|
405
|
+
* Returns currently selected object, if any
|
406
|
+
* @method getActiveObject
|
407
|
+
* @return {fabric.Object}
|
408
|
+
*/
|
409
|
+
getActiveObject: function() {
|
410
|
+
return null;
|
411
|
+
},
|
412
|
+
|
413
|
+
/**
|
414
|
+
* Returns currently selected group of object, if any
|
415
|
+
* @method getActiveGroup
|
416
|
+
* @return {fabric.Group}
|
417
|
+
*/
|
418
|
+
getActiveGroup: function() {
|
419
|
+
return null;
|
420
|
+
},
|
421
|
+
|
422
|
+
/**
|
423
|
+
* Given a context, renders an object on that context
|
424
|
+
* @param ctx {Object} context to render object on
|
425
|
+
* @param object {Object} object to render
|
426
|
+
* @private
|
427
|
+
*/
|
428
|
+
_draw: function (ctx, object) {
|
429
|
+
if (!object) return;
|
430
|
+
|
431
|
+
if (this.controlsAboveOverlay) {
|
432
|
+
var hasBorders = object.hasBorders, hasControls = object.hasControls;
|
433
|
+
object.hasBorders = object.hasControls = false;
|
434
|
+
object.render(ctx);
|
435
|
+
object.hasBorders = hasBorders;
|
436
|
+
object.hasControls = hasControls;
|
437
|
+
}
|
438
|
+
else {
|
439
|
+
object.render(ctx);
|
440
|
+
}
|
441
|
+
},
|
442
|
+
|
443
|
+
/**
|
444
|
+
* Adds objects to canvas, then renders canvas (if `renderOnAddition` is not `false`).
|
445
|
+
* Objects should be instances of (or inherit from) fabric.Object
|
446
|
+
* @method add
|
447
|
+
* @param [...] Zero or more fabric instances
|
448
|
+
* @return {fabric.Canvas} thisArg
|
449
|
+
* @chainable
|
450
|
+
*/
|
451
|
+
add: function () {
|
452
|
+
this._objects.push.apply(this._objects, arguments);
|
453
|
+
for (var i = arguments.length; i--; ) {
|
454
|
+
this._initObject(arguments[i]);
|
455
|
+
}
|
456
|
+
this.renderOnAddition && this.renderAll();
|
457
|
+
return this;
|
458
|
+
},
|
459
|
+
|
460
|
+
/**
|
461
|
+
* @private
|
462
|
+
* @method _initObject
|
463
|
+
*/
|
464
|
+
_initObject: function(obj) {
|
465
|
+
this.stateful && obj.setupState();
|
466
|
+
obj.setCoords();
|
467
|
+
obj.canvas = this;
|
468
|
+
this.fire('object:added', { target: obj });
|
469
|
+
obj.fire('added');
|
470
|
+
},
|
471
|
+
|
472
|
+
/**
|
473
|
+
* Inserts an object to canvas at specified index and renders canvas.
|
474
|
+
* An object should be an instance of (or inherit from) fabric.Object
|
475
|
+
* @method insertAt
|
476
|
+
* @param object {Object} Object to insert
|
477
|
+
* @param index {Number} index to insert object at
|
478
|
+
* @param nonSplicing {Boolean} when `true`, no splicing (shifting) of objects occurs
|
479
|
+
* @return {fabric.Canvas} thisArg
|
480
|
+
* @chainable
|
481
|
+
*/
|
482
|
+
insertAt: function (object, index, nonSplicing) {
|
483
|
+
if (nonSplicing) {
|
484
|
+
this._objects[index] = object;
|
485
|
+
}
|
486
|
+
else {
|
487
|
+
this._objects.splice(index, 0, object);
|
488
|
+
}
|
489
|
+
this._initObject(object);
|
490
|
+
this.renderOnAddition && this.renderAll();
|
491
|
+
return this;
|
492
|
+
},
|
493
|
+
|
494
|
+
/**
|
495
|
+
* Returns an array of objects this instance has
|
496
|
+
* @method getObjects
|
497
|
+
* @return {Array}
|
498
|
+
*/
|
499
|
+
getObjects: function () {
|
500
|
+
return this._objects;
|
501
|
+
},
|
502
|
+
|
503
|
+
/**
|
504
|
+
* Clears specified context of canvas element
|
505
|
+
* @method clearContext
|
506
|
+
* @param context {Object} ctx context to clear
|
507
|
+
* @return {fabric.Canvas} thisArg
|
508
|
+
* @chainable
|
509
|
+
*/
|
510
|
+
clearContext: function(ctx) {
|
511
|
+
ctx.clearRect(0, 0, this.width, this.height);
|
512
|
+
return this;
|
513
|
+
},
|
514
|
+
|
515
|
+
/**
|
516
|
+
* Returns context of canvas where objects are drawn
|
517
|
+
* @method getContext
|
518
|
+
* @return {CanvasRenderingContext2D}
|
519
|
+
*/
|
520
|
+
getContext: function () {
|
521
|
+
return this.contextContainer;
|
522
|
+
},
|
523
|
+
|
524
|
+
/**
|
525
|
+
* Clears all contexts (background, main, top) of an instance
|
526
|
+
* @method clear
|
527
|
+
* @return {fabric.Canvas} thisArg
|
528
|
+
* @chainable
|
529
|
+
*/
|
530
|
+
clear: function () {
|
531
|
+
this._objects.length = 0;
|
532
|
+
if (this.discardActiveGroup) {
|
533
|
+
this.discardActiveGroup();
|
534
|
+
}
|
535
|
+
this.clearContext(this.contextContainer);
|
536
|
+
if (this.contextTop) {
|
537
|
+
this.clearContext(this.contextTop);
|
538
|
+
}
|
539
|
+
this.fire('canvas:cleared');
|
540
|
+
this.renderAll();
|
541
|
+
return this;
|
542
|
+
},
|
543
|
+
|
544
|
+
/**
|
545
|
+
* Renders both the top canvas and the secondary container canvas.
|
546
|
+
* @method renderAll
|
547
|
+
* @param allOnTop {Boolean} optional Whether we want to force all images to be rendered on the top canvas
|
548
|
+
* @return {fabric.Canvas} instance
|
549
|
+
* @chainable
|
550
|
+
*/
|
551
|
+
renderAll: function (allOnTop) {
|
552
|
+
|
553
|
+
var canvasToDrawOn = this[(allOnTop === true && this.interactive) ? 'contextTop' : 'contextContainer'];
|
554
|
+
|
555
|
+
if (this.contextTop && this.selection) {
|
556
|
+
this.clearContext(this.contextTop);
|
557
|
+
}
|
558
|
+
|
559
|
+
if (!allOnTop) {
|
560
|
+
this.clearContext(canvasToDrawOn);
|
561
|
+
}
|
562
|
+
|
563
|
+
this.fire('before:render');
|
564
|
+
|
565
|
+
if (this.clipTo) {
|
566
|
+
this._clipCanvas(canvasToDrawOn);
|
567
|
+
}
|
568
|
+
|
569
|
+
if (this.backgroundColor) {
|
570
|
+
canvasToDrawOn.fillStyle = this.backgroundColor.toLive
|
571
|
+
? this.backgroundColor.toLive(canvasToDrawOn)
|
572
|
+
: this.backgroundColor;
|
573
|
+
|
574
|
+
canvasToDrawOn.fillRect(0, 0, this.width, this.height);
|
575
|
+
}
|
576
|
+
|
577
|
+
if (typeof this.backgroundImage === 'object') {
|
578
|
+
this._drawBackroundImage(canvasToDrawOn);
|
579
|
+
}
|
580
|
+
|
581
|
+
var activeGroup = this.getActiveGroup();
|
582
|
+
for (var i = 0, length = this._objects.length; i < length; ++i) {
|
583
|
+
if (!activeGroup ||
|
584
|
+
(activeGroup && this._objects[i] && !activeGroup.contains(this._objects[i]))) {
|
585
|
+
this._draw(canvasToDrawOn, this._objects[i]);
|
586
|
+
}
|
587
|
+
}
|
588
|
+
|
589
|
+
// delegate rendering to group selection (if one exists)
|
590
|
+
if (activeGroup) {
|
591
|
+
//Store objects in group preserving order, then replace
|
592
|
+
var sortedObjects = [];
|
593
|
+
this.forEachObject(function (object) {
|
594
|
+
if (activeGroup.contains(object)) {
|
595
|
+
sortedObjects.push(object);
|
596
|
+
}
|
597
|
+
});
|
598
|
+
activeGroup._set('objects', sortedObjects);
|
599
|
+
this._draw(canvasToDrawOn, activeGroup);
|
600
|
+
}
|
601
|
+
|
602
|
+
if (this.clipTo) {
|
603
|
+
canvasToDrawOn.restore();
|
604
|
+
}
|
605
|
+
|
606
|
+
if (this.overlayImage) {
|
607
|
+
canvasToDrawOn.drawImage(this.overlayImage, this.overlayImageLeft, this.overlayImageTop);
|
608
|
+
}
|
609
|
+
|
610
|
+
if (this.controlsAboveOverlay) {
|
611
|
+
this.drawControls(canvasToDrawOn);
|
612
|
+
}
|
613
|
+
|
614
|
+
this.fire('after:render');
|
615
|
+
|
616
|
+
return this;
|
617
|
+
},
|
618
|
+
|
619
|
+
/**
|
620
|
+
* @private
|
621
|
+
* @method _clipCanvas
|
622
|
+
*/
|
623
|
+
_clipCanvas: function(canvasToDrawOn) {
|
624
|
+
canvasToDrawOn.save();
|
625
|
+
canvasToDrawOn.beginPath();
|
626
|
+
this.clipTo(canvasToDrawOn);
|
627
|
+
canvasToDrawOn.clip();
|
628
|
+
},
|
629
|
+
|
630
|
+
/**
|
631
|
+
* @private
|
632
|
+
* @method _drawBackroundImage
|
633
|
+
*/
|
634
|
+
_drawBackroundImage: function(canvasToDrawOn) {
|
635
|
+
canvasToDrawOn.save();
|
636
|
+
canvasToDrawOn.globalAlpha = this.backgroundImageOpacity;
|
637
|
+
|
638
|
+
if (this.backgroundImageStretch) {
|
639
|
+
canvasToDrawOn.drawImage(this.backgroundImage, 0, 0, this.width, this.height);
|
640
|
+
}
|
641
|
+
else {
|
642
|
+
canvasToDrawOn.drawImage(this.backgroundImage, 0, 0);
|
643
|
+
}
|
644
|
+
canvasToDrawOn.restore();
|
645
|
+
},
|
646
|
+
|
647
|
+
/**
|
648
|
+
* Method to render only the top canvas.
|
649
|
+
* Also used to render the group selection box.
|
650
|
+
* @method renderTop
|
651
|
+
* @return {fabric.Canvas} thisArg
|
652
|
+
* @chainable
|
653
|
+
*/
|
654
|
+
renderTop: function () {
|
655
|
+
var ctx = this.contextTop || this.contextContainer;
|
656
|
+
this.clearContext(ctx);
|
657
|
+
|
658
|
+
// we render the top context - last object
|
659
|
+
if (this.selection && this._groupSelector) {
|
660
|
+
this._drawSelection();
|
661
|
+
}
|
662
|
+
|
663
|
+
// delegate rendering to group selection if one exists
|
664
|
+
// used for drawing selection borders/controls
|
665
|
+
var activeGroup = this.getActiveGroup();
|
666
|
+
if (activeGroup) {
|
667
|
+
activeGroup.render(ctx);
|
668
|
+
}
|
669
|
+
|
670
|
+
if (this.overlayImage) {
|
671
|
+
ctx.drawImage(this.overlayImage, this.overlayImageLeft, this.overlayImageTop);
|
672
|
+
}
|
673
|
+
|
674
|
+
this.fire('after:render');
|
675
|
+
|
676
|
+
return this;
|
677
|
+
},
|
678
|
+
|
679
|
+
/**
|
680
|
+
* Draws objects' controls (borders/controls)
|
681
|
+
* @method drawControls
|
682
|
+
* @param {Object} ctx context to render controls on
|
683
|
+
*/
|
684
|
+
drawControls: function(ctx) {
|
685
|
+
var activeGroup = this.getActiveGroup();
|
686
|
+
if (activeGroup) {
|
687
|
+
ctx.save();
|
688
|
+
fabric.Group.prototype.transform.call(activeGroup, ctx);
|
689
|
+
activeGroup.drawBorders(ctx).drawControls(ctx);
|
690
|
+
ctx.restore();
|
691
|
+
}
|
692
|
+
else {
|
693
|
+
for (var i = 0, len = this._objects.length; i < len; ++i) {
|
694
|
+
if (!this._objects[i] || !this._objects[i].active) continue;
|
695
|
+
|
696
|
+
ctx.save();
|
697
|
+
fabric.Object.prototype.transform.call(this._objects[i], ctx);
|
698
|
+
this._objects[i].drawBorders(ctx).drawControls(ctx);
|
699
|
+
ctx.restore();
|
700
|
+
|
701
|
+
this.lastRenderedObjectWithControlsAboveOverlay = this._objects[i];
|
702
|
+
}
|
703
|
+
}
|
704
|
+
},
|
705
|
+
|
706
|
+
/**
|
707
|
+
* Exports canvas element to a dataurl image.
|
708
|
+
* @method toDataURL
|
709
|
+
* @param {String} format the format of the output image. Either "jpeg" or "png".
|
710
|
+
* @param {Number} quality quality level (0..1)
|
711
|
+
* @return {String}
|
712
|
+
*/
|
713
|
+
toDataURL: function (format, quality) {
|
714
|
+
var canvasEl = this.upperCanvasEl || this.lowerCanvasEl;
|
715
|
+
|
716
|
+
this.renderAll(true);
|
717
|
+
var data = (fabric.StaticCanvas.supports('toDataURLWithQuality'))
|
718
|
+
? canvasEl.toDataURL('image/' + format, quality)
|
719
|
+
: canvasEl.toDataURL('image/' + format);
|
720
|
+
|
721
|
+
this.contextTop && this.clearContext(this.contextTop);
|
722
|
+
this.renderAll();
|
723
|
+
return data;
|
724
|
+
},
|
725
|
+
|
726
|
+
/**
|
727
|
+
* Exports canvas element to a dataurl image (allowing to change image size via multiplier).
|
728
|
+
* @method toDataURLWithMultiplier
|
729
|
+
* @param {String} format (png|jpeg)
|
730
|
+
* @param {Number} multiplier
|
731
|
+
* @param {Number} quality (0..1)
|
732
|
+
* @return {String}
|
733
|
+
*/
|
734
|
+
toDataURLWithMultiplier: function (format, multiplier, quality) {
|
735
|
+
|
736
|
+
var origWidth = this.getWidth(),
|
737
|
+
origHeight = this.getHeight(),
|
738
|
+
scaledWidth = origWidth * multiplier,
|
739
|
+
scaledHeight = origHeight * multiplier,
|
740
|
+
activeObject = this.getActiveObject(),
|
741
|
+
activeGroup = this.getActiveGroup(),
|
742
|
+
|
743
|
+
ctx = this.contextTop || this.contextContainer;
|
744
|
+
|
745
|
+
this.setWidth(scaledWidth).setHeight(scaledHeight);
|
746
|
+
ctx.scale(multiplier, multiplier);
|
747
|
+
|
748
|
+
if (activeGroup) {
|
749
|
+
// not removing group due to complications with restoring it with correct state afterwords
|
750
|
+
this._tempRemoveBordersControlsFromGroup(activeGroup);
|
751
|
+
}
|
752
|
+
else if (activeObject && this.deactivateAll) {
|
753
|
+
this.deactivateAll();
|
754
|
+
}
|
755
|
+
|
756
|
+
// restoring width, height for `renderAll` to draw
|
757
|
+
// background properly (while context is scaled)
|
758
|
+
this.width = origWidth;
|
759
|
+
this.height = origHeight;
|
760
|
+
|
761
|
+
this.renderAll(true);
|
762
|
+
|
763
|
+
var dataURL = this.toDataURL(format, quality);
|
764
|
+
|
765
|
+
ctx.scale(1 / multiplier, 1 / multiplier);
|
766
|
+
this.setWidth(origWidth).setHeight(origHeight);
|
767
|
+
|
768
|
+
if (activeGroup) {
|
769
|
+
this._restoreBordersControlsOnGroup(activeGroup);
|
770
|
+
}
|
771
|
+
else if (activeObject && this.setActiveObject) {
|
772
|
+
this.setActiveObject(activeObject);
|
773
|
+
}
|
774
|
+
|
775
|
+
this.contextTop && this.clearContext(this.contextTop);
|
776
|
+
this.renderAll();
|
777
|
+
|
778
|
+
return dataURL;
|
779
|
+
},
|
780
|
+
|
781
|
+
/**
|
782
|
+
* @private
|
783
|
+
* @method _tempRemoveBordersControlsFromGroup
|
784
|
+
*/
|
785
|
+
_tempRemoveBordersControlsFromGroup: function(group) {
|
786
|
+
group.origHasControls = group.hasControls;
|
787
|
+
group.origBorderColor = group.borderColor;
|
788
|
+
|
789
|
+
group.hasControls = true;
|
790
|
+
group.borderColor = 'rgba(0,0,0,0)';
|
791
|
+
|
792
|
+
group.forEachObject(function(o) {
|
793
|
+
o.origBorderColor = o.borderColor;
|
794
|
+
o.borderColor = 'rgba(0,0,0,0)';
|
795
|
+
});
|
796
|
+
},
|
797
|
+
|
798
|
+
/**
|
799
|
+
* @private
|
800
|
+
* @method _restoreBordersControlsOnGroup
|
801
|
+
*/
|
802
|
+
_restoreBordersControlsOnGroup: function(group) {
|
803
|
+
group.hideControls = group.origHideControls;
|
804
|
+
group.borderColor = group.origBorderColor;
|
805
|
+
|
806
|
+
group.forEachObject(function(o) {
|
807
|
+
o.borderColor = o.origBorderColor;
|
808
|
+
delete o.origBorderColor;
|
809
|
+
});
|
810
|
+
},
|
811
|
+
|
812
|
+
/**
|
813
|
+
* Returns coordinates of a center of canvas.
|
814
|
+
* Returned value is an object with top and left properties
|
815
|
+
* @method getCenter
|
816
|
+
* @return {Object} object with "top" and "left" number values
|
817
|
+
*/
|
818
|
+
getCenter: function () {
|
819
|
+
return {
|
820
|
+
top: this.getHeight() / 2,
|
821
|
+
left: this.getWidth() / 2
|
822
|
+
};
|
823
|
+
},
|
824
|
+
|
825
|
+
/**
|
826
|
+
* Centers object horizontally.
|
827
|
+
* @method centerObjectH
|
828
|
+
* @param {fabric.Object} object Object to center
|
829
|
+
* @return {fabric.Canvas} thisArg
|
830
|
+
*/
|
831
|
+
centerObjectH: function (object) {
|
832
|
+
object.set('left', this.getCenter().left);
|
833
|
+
this.renderAll();
|
834
|
+
return this;
|
835
|
+
},
|
836
|
+
|
837
|
+
/**
|
838
|
+
* Centers object vertically.
|
839
|
+
* @method centerObjectH
|
840
|
+
* @param {fabric.Object} object Object to center
|
841
|
+
* @return {fabric.Canvas} thisArg
|
842
|
+
* @chainable
|
843
|
+
*/
|
844
|
+
centerObjectV: function (object) {
|
845
|
+
object.set('top', this.getCenter().top);
|
846
|
+
this.renderAll();
|
847
|
+
return this;
|
848
|
+
},
|
849
|
+
|
850
|
+
/**
|
851
|
+
* Centers object vertically and horizontally.
|
852
|
+
* @method centerObject
|
853
|
+
* @param {fabric.Object} object Object to center
|
854
|
+
* @return {fabric.Canvas} thisArg
|
855
|
+
* @chainable
|
856
|
+
*/
|
857
|
+
centerObject: function (object) {
|
858
|
+
return this.centerObjectH(object).centerObjectV(object);
|
859
|
+
},
|
860
|
+
|
861
|
+
/**
|
862
|
+
* Returs dataless JSON representation of canvas
|
863
|
+
* @method toDatalessJSON
|
864
|
+
* @param {Array} propertiesToInclude
|
865
|
+
* @return {String} json string
|
866
|
+
*/
|
867
|
+
toDatalessJSON: function (propertiesToInclude) {
|
868
|
+
return this.toDatalessObject(propertiesToInclude);
|
869
|
+
},
|
870
|
+
|
871
|
+
/**
|
872
|
+
* Returns object representation of canvas
|
873
|
+
* @method toObject
|
874
|
+
* @param {Array} propertiesToInclude
|
875
|
+
* @return {Object} object representation of an instance
|
876
|
+
*/
|
877
|
+
toObject: function (propertiesToInclude) {
|
878
|
+
return this._toObjectMethod('toObject', propertiesToInclude);
|
879
|
+
},
|
880
|
+
|
881
|
+
/**
|
882
|
+
* Returns dataless object representation of canvas
|
883
|
+
* @method toDatalessObject
|
884
|
+
* @param {Array} propertiesToInclude
|
885
|
+
* @return {Object} object representation of an instance
|
886
|
+
*/
|
887
|
+
toDatalessObject: function (propertiesToInclude) {
|
888
|
+
return this._toObjectMethod('toDatalessObject', propertiesToInclude);
|
889
|
+
},
|
890
|
+
|
891
|
+
/**
|
892
|
+
* @private
|
893
|
+
* @method _toObjectMethod
|
894
|
+
*/
|
895
|
+
_toObjectMethod: function (methodName, propertiesToInclude) {
|
896
|
+
var data = {
|
897
|
+
objects: this._objects.map(function (instance) {
|
898
|
+
// TODO (kangax): figure out how to clean this up
|
899
|
+
var originalValue;
|
900
|
+
if (!this.includeDefaultValues) {
|
901
|
+
originalValue = instance.includeDefaultValues;
|
902
|
+
instance.includeDefaultValues = false;
|
903
|
+
}
|
904
|
+
var object = instance[methodName](propertiesToInclude);
|
905
|
+
if (!this.includeDefaultValues) {
|
906
|
+
instance.includeDefaultValues = originalValue;
|
907
|
+
}
|
908
|
+
return object;
|
909
|
+
}, this),
|
910
|
+
background: (this.backgroundColor && this.backgroundColor.toObject)
|
911
|
+
? this.backgroundColor.toObject()
|
912
|
+
: this.backgroundColor
|
913
|
+
};
|
914
|
+
if (this.backgroundImage) {
|
915
|
+
data.backgroundImage = this.backgroundImage.src;
|
916
|
+
data.backgroundImageOpacity = this.backgroundImageOpacity;
|
917
|
+
data.backgroundImageStretch = this.backgroundImageStretch;
|
918
|
+
}
|
919
|
+
if (this.overlayImage) {
|
920
|
+
data.overlayImage = this.overlayImage.src;
|
921
|
+
data.overlayImageLeft = this.overlayImageLeft;
|
922
|
+
data.overlayImageTop = this.overlayImageTop;
|
923
|
+
}
|
924
|
+
fabric.util.populateWithProperties(this, data, propertiesToInclude);
|
925
|
+
return data;
|
926
|
+
},
|
927
|
+
|
928
|
+
/**
|
929
|
+
* Returns SVG representation of canvas
|
930
|
+
* @function
|
931
|
+
* @method toSVG
|
932
|
+
* @param {Object} [options] Options for SVG output ("suppressPreamble: true"
|
933
|
+
* will start the svg output directly at "<svg...")
|
934
|
+
* @return {String}
|
935
|
+
*/
|
936
|
+
toSVG: function(options) {
|
937
|
+
options || (options = { });
|
938
|
+
var markup = [];
|
939
|
+
|
940
|
+
if (!options.suppressPreamble) {
|
941
|
+
markup.push(
|
942
|
+
'<?xml version="1.0" standalone="no" ?>',
|
943
|
+
'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ',
|
944
|
+
'"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'
|
945
|
+
);
|
946
|
+
}
|
947
|
+
markup.push(
|
948
|
+
'<svg ',
|
949
|
+
'xmlns="http://www.w3.org/2000/svg" ',
|
950
|
+
'xmlns:xlink="http://www.w3.org/1999/xlink" ',
|
951
|
+
'version="1.1" ',
|
952
|
+
'width="', this.width, '" ',
|
953
|
+
'height="', this.height, '" ',
|
954
|
+
(this.backgroundColor && !this.backgroundColor.source) ? 'style="background-color: ' + this.backgroundColor +'" ' : null,
|
955
|
+
'xml:space="preserve">',
|
956
|
+
'<desc>Created with Fabric.js ', fabric.version, '</desc>',
|
957
|
+
'<defs>', fabric.createSVGFontFacesMarkup(this.getObjects()), fabric.createSVGRefElementsMarkup(this), '</defs>'
|
958
|
+
);
|
959
|
+
|
960
|
+
if (this.backgroundColor && this.backgroundColor.source) {
|
961
|
+
markup.push(
|
962
|
+
'<rect x="0" y="0" ',
|
963
|
+
'width="', (this.backgroundColor.repeat === 'repeat-y' || this.backgroundColor.repeat === 'no-repeat' ? this.backgroundColor.source.width : this.width),
|
964
|
+
'" height="', (this.backgroundColor.repeat === 'repeat-x' || this.backgroundColor.repeat === 'no-repeat' ? this.backgroundColor.source.height : this.height),
|
965
|
+
'" fill="url(#backgroundColorPattern)"',
|
966
|
+
'></rect>'
|
967
|
+
);
|
968
|
+
}
|
969
|
+
|
970
|
+
if (this.backgroundImage) {
|
971
|
+
markup.push(
|
972
|
+
'<image x="0" y="0" ',
|
973
|
+
'width="', (this.backgroundImageStretch ? this.width : this.backgroundImage.width),
|
974
|
+
'" height="', (this.backgroundImageStretch ? this.height : this.backgroundImage.height),
|
975
|
+
'" preserveAspectRatio="', (this.backgroundImageStretch ? 'none' : 'defer'),
|
976
|
+
'" xlink:href="', this.backgroundImage.src,
|
977
|
+
'" style="opacity:', this.backgroundImageOpacity,
|
978
|
+
'"></image>'
|
979
|
+
);
|
980
|
+
}
|
981
|
+
|
982
|
+
if (this.overlayImage) {
|
983
|
+
markup.push(
|
984
|
+
'<image x="', this.overlayImageLeft,
|
985
|
+
'" y="', this.overlayImageTop,
|
986
|
+
'" width="', this.overlayImage.width,
|
987
|
+
'" height="', this.overlayImage.height,
|
988
|
+
'" xlink:href="', this.overlayImage.src,
|
989
|
+
'"></image>'
|
990
|
+
);
|
991
|
+
}
|
992
|
+
|
993
|
+
for (var i = 0, objects = this.getObjects(), len = objects.length; i < len; i++) {
|
994
|
+
markup.push(objects[i].toSVG());
|
995
|
+
}
|
996
|
+
markup.push('</svg>');
|
997
|
+
|
998
|
+
return markup.join('');
|
999
|
+
},
|
1000
|
+
|
1001
|
+
/**
|
1002
|
+
* Returns true if canvas contains no objects
|
1003
|
+
* @method isEmpty
|
1004
|
+
* @return {Boolean} true if canvas is empty
|
1005
|
+
*/
|
1006
|
+
isEmpty: function () {
|
1007
|
+
return this._objects.length === 0;
|
1008
|
+
},
|
1009
|
+
|
1010
|
+
/**
|
1011
|
+
* Removes an object from canvas and returns it
|
1012
|
+
* @method remove
|
1013
|
+
* @param object {Object} Object to remove
|
1014
|
+
* @return {Object} removed object
|
1015
|
+
*/
|
1016
|
+
remove: function (object) {
|
1017
|
+
// removing active object should fire "selection:cleared" events
|
1018
|
+
if (this.getActiveObject() === object) {
|
1019
|
+
this.fire('before:selection:cleared', { target: object });
|
1020
|
+
this.discardActiveObject();
|
1021
|
+
this.fire('selection:cleared');
|
1022
|
+
}
|
1023
|
+
|
1024
|
+
var objects = this._objects;
|
1025
|
+
var index = objects.indexOf(object);
|
1026
|
+
|
1027
|
+
// removing any object should fire "objct:removed" events
|
1028
|
+
if (index !== -1) {
|
1029
|
+
objects.splice(index,1);
|
1030
|
+
this.fire('object:removed', { target: object });
|
1031
|
+
}
|
1032
|
+
|
1033
|
+
this.renderAll();
|
1034
|
+
return object;
|
1035
|
+
},
|
1036
|
+
|
1037
|
+
/**
|
1038
|
+
* Moves an object to the bottom of the stack of drawn objects
|
1039
|
+
* @method sendToBack
|
1040
|
+
* @param object {fabric.Object} Object to send to back
|
1041
|
+
* @return {fabric.Canvas} thisArg
|
1042
|
+
* @chainable
|
1043
|
+
*/
|
1044
|
+
sendToBack: function (object) {
|
1045
|
+
removeFromArray(this._objects, object);
|
1046
|
+
this._objects.unshift(object);
|
1047
|
+
return this.renderAll();
|
1048
|
+
},
|
1049
|
+
|
1050
|
+
/**
|
1051
|
+
* Moves an object to the top of the stack of drawn objects
|
1052
|
+
* @method bringToFront
|
1053
|
+
* @param object {fabric.Object} Object to send
|
1054
|
+
* @return {fabric.Canvas} thisArg
|
1055
|
+
* @chainable
|
1056
|
+
*/
|
1057
|
+
bringToFront: function (object) {
|
1058
|
+
removeFromArray(this._objects, object);
|
1059
|
+
this._objects.push(object);
|
1060
|
+
return this.renderAll();
|
1061
|
+
},
|
1062
|
+
|
1063
|
+
/**
|
1064
|
+
* Moves an object one level down in stack of drawn objects
|
1065
|
+
* @method sendBackwards
|
1066
|
+
* @param object {fabric.Object} Object to send
|
1067
|
+
* @return {fabric.Canvas} thisArg
|
1068
|
+
* @chainable
|
1069
|
+
*/
|
1070
|
+
sendBackwards: function (object) {
|
1071
|
+
var idx = this._objects.indexOf(object),
|
1072
|
+
nextIntersectingIdx = idx;
|
1073
|
+
|
1074
|
+
// if object is not on the bottom of stack
|
1075
|
+
if (idx !== 0) {
|
1076
|
+
|
1077
|
+
// traverse down the stack looking for the nearest intersecting object
|
1078
|
+
for (var i=idx-1; i>=0; --i) {
|
1079
|
+
|
1080
|
+
var isIntersecting = object.intersectsWithObject(this._objects[i]) ||
|
1081
|
+
object.isContainedWithinObject(this._objects[i]) ||
|
1082
|
+
this._objects[i].isContainedWithinObject(object);
|
1083
|
+
|
1084
|
+
if (isIntersecting) {
|
1085
|
+
nextIntersectingIdx = i;
|
1086
|
+
break;
|
1087
|
+
}
|
1088
|
+
}
|
1089
|
+
removeFromArray(this._objects, object);
|
1090
|
+
this._objects.splice(nextIntersectingIdx, 0, object);
|
1091
|
+
}
|
1092
|
+
return this.renderAll();
|
1093
|
+
},
|
1094
|
+
|
1095
|
+
/**
|
1096
|
+
* Moves an object one level up in stack of drawn objects
|
1097
|
+
* @method bringForward
|
1098
|
+
* @param object {fabric.Object} Object to send
|
1099
|
+
* @return {fabric.Canvas} thisArg
|
1100
|
+
* @chainable
|
1101
|
+
*/
|
1102
|
+
bringForward: function (object) {
|
1103
|
+
var objects = this.getObjects(),
|
1104
|
+
idx = objects.indexOf(object),
|
1105
|
+
nextIntersectingIdx = idx;
|
1106
|
+
|
1107
|
+
|
1108
|
+
// if object is not on top of stack (last item in an array)
|
1109
|
+
if (idx !== objects.length-1) {
|
1110
|
+
|
1111
|
+
// traverse up the stack looking for the nearest intersecting object
|
1112
|
+
for (var i = idx + 1, l = this._objects.length; i < l; ++i) {
|
1113
|
+
|
1114
|
+
var isIntersecting = object.intersectsWithObject(objects[i]) ||
|
1115
|
+
object.isContainedWithinObject(this._objects[i]) ||
|
1116
|
+
this._objects[i].isContainedWithinObject(object);
|
1117
|
+
|
1118
|
+
if (isIntersecting) {
|
1119
|
+
nextIntersectingIdx = i;
|
1120
|
+
break;
|
1121
|
+
}
|
1122
|
+
}
|
1123
|
+
removeFromArray(objects, object);
|
1124
|
+
objects.splice(nextIntersectingIdx, 0, object);
|
1125
|
+
}
|
1126
|
+
this.renderAll();
|
1127
|
+
},
|
1128
|
+
|
1129
|
+
/**
|
1130
|
+
* Returns object at specified index
|
1131
|
+
* @method item
|
1132
|
+
* @param {Number} index
|
1133
|
+
* @return {fabric.Object}
|
1134
|
+
*/
|
1135
|
+
item: function (index) {
|
1136
|
+
return this.getObjects()[index];
|
1137
|
+
},
|
1138
|
+
|
1139
|
+
/**
|
1140
|
+
* Returns number representation of an instance complexity
|
1141
|
+
* @method complexity
|
1142
|
+
* @return {Number} complexity
|
1143
|
+
*/
|
1144
|
+
complexity: function () {
|
1145
|
+
return this.getObjects().reduce(function (memo, current) {
|
1146
|
+
memo += current.complexity ? current.complexity() : 0;
|
1147
|
+
return memo;
|
1148
|
+
}, 0);
|
1149
|
+
},
|
1150
|
+
|
1151
|
+
/**
|
1152
|
+
* Iterates over all objects, invoking callback for each one of them
|
1153
|
+
* @method forEachObject
|
1154
|
+
* @return {fabric.Canvas} thisArg
|
1155
|
+
*/
|
1156
|
+
forEachObject: function(callback, context) {
|
1157
|
+
var objects = this.getObjects(),
|
1158
|
+
i = objects.length;
|
1159
|
+
while (i--) {
|
1160
|
+
callback.call(context, objects[i], i, objects);
|
1161
|
+
}
|
1162
|
+
return this;
|
1163
|
+
},
|
1164
|
+
|
1165
|
+
/**
|
1166
|
+
* Clears a canvas element and removes all event handlers.
|
1167
|
+
* @method dispose
|
1168
|
+
* @return {fabric.Canvas} thisArg
|
1169
|
+
* @chainable
|
1170
|
+
*/
|
1171
|
+
dispose: function () {
|
1172
|
+
this.clear();
|
1173
|
+
if (this.interactive) {
|
1174
|
+
removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
|
1175
|
+
removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
|
1176
|
+
removeListener(fabric.window, 'resize', this._onResize);
|
1177
|
+
}
|
1178
|
+
return this;
|
1179
|
+
},
|
1180
|
+
|
1181
|
+
/**
|
1182
|
+
* @private
|
1183
|
+
* @method _resizeImageToFit
|
1184
|
+
* @param {HTMLImageElement} imgEl
|
1185
|
+
*/
|
1186
|
+
_resizeImageToFit: function (imgEl) {
|
1187
|
+
|
1188
|
+
var imageWidth = imgEl.width || imgEl.offsetWidth,
|
1189
|
+
widthScaleFactor = this.getWidth() / imageWidth;
|
1190
|
+
|
1191
|
+
// scale image down so that it has original dimensions when printed in large resolution
|
1192
|
+
if (imageWidth) {
|
1193
|
+
imgEl.width = imageWidth * widthScaleFactor;
|
1194
|
+
}
|
1195
|
+
}
|
1196
|
+
});
|
1197
|
+
|
1198
|
+
/**
|
1199
|
+
* Returns a string representation of an instance
|
1200
|
+
* @method toString
|
1201
|
+
* @return {String} string representation of an instance
|
1202
|
+
*/
|
1203
|
+
fabric.StaticCanvas.prototype.toString = function () { // Assign explicitly since `extend` doesn't take care of DontEnum bug yet
|
1204
|
+
return '#<fabric.Canvas (' + this.complexity() + '): '+
|
1205
|
+
'{ objects: ' + this.getObjects().length + ' }>';
|
1206
|
+
};
|
1207
|
+
|
1208
|
+
extend(fabric.StaticCanvas, /** @scope fabric.StaticCanvas */ {
|
1209
|
+
|
1210
|
+
/**
|
1211
|
+
* @static
|
1212
|
+
* @property EMPTY_JSON
|
1213
|
+
* @type String
|
1214
|
+
*/
|
1215
|
+
EMPTY_JSON: '{"objects": [], "background": "white"}',
|
1216
|
+
|
1217
|
+
/**
|
1218
|
+
* Takes <canvas> element and transforms its data in such way that it becomes grayscale
|
1219
|
+
* @static
|
1220
|
+
* @method toGrayscale
|
1221
|
+
* @param {HTMLCanvasElement} canvasEl
|
1222
|
+
*/
|
1223
|
+
toGrayscale: function (canvasEl) {
|
1224
|
+
var context = canvasEl.getContext('2d'),
|
1225
|
+
imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
|
1226
|
+
data = imageData.data,
|
1227
|
+
iLen = imageData.width,
|
1228
|
+
jLen = imageData.height,
|
1229
|
+
index, average, i, j;
|
1230
|
+
|
1231
|
+
for (i = 0; i < iLen; i++) {
|
1232
|
+
for (j = 0; j < jLen; j++) {
|
1233
|
+
|
1234
|
+
index = (i * 4) * jLen + (j * 4);
|
1235
|
+
average = (data[index] + data[index + 1] + data[index + 2]) / 3;
|
1236
|
+
|
1237
|
+
data[index] = average;
|
1238
|
+
data[index + 1] = average;
|
1239
|
+
data[index + 2] = average;
|
1240
|
+
}
|
1241
|
+
}
|
1242
|
+
|
1243
|
+
context.putImageData(imageData, 0, 0);
|
1244
|
+
},
|
1245
|
+
|
1246
|
+
/**
|
1247
|
+
* Provides a way to check support of some of the canvas methods
|
1248
|
+
* (either those of HTMLCanvasElement itself, or rendering context)
|
1249
|
+
*
|
1250
|
+
* @method supports
|
1251
|
+
* @param methodName {String} Method to check support for;
|
1252
|
+
* Could be one of "getImageData", "toDataURL" or "toDataURLWithQuality"
|
1253
|
+
* @return {Boolean | null} `true` if method is supported (or at least exists),
|
1254
|
+
* `null` if canvas element or context can not be initialized
|
1255
|
+
*/
|
1256
|
+
supports: function (methodName) {
|
1257
|
+
var el = fabric.util.createCanvasElement();
|
1258
|
+
|
1259
|
+
if (!el || !el.getContext) {
|
1260
|
+
return null;
|
1261
|
+
}
|
1262
|
+
|
1263
|
+
var ctx = el.getContext('2d');
|
1264
|
+
if (!ctx) {
|
1265
|
+
return null;
|
1266
|
+
}
|
1267
|
+
|
1268
|
+
switch (methodName) {
|
1269
|
+
|
1270
|
+
case 'getImageData':
|
1271
|
+
return typeof ctx.getImageData !== 'undefined';
|
1272
|
+
|
1273
|
+
case 'toDataURL':
|
1274
|
+
return typeof el.toDataURL !== 'undefined';
|
1275
|
+
|
1276
|
+
case 'toDataURLWithQuality':
|
1277
|
+
try {
|
1278
|
+
el.toDataURL('image/jpeg', 0);
|
1279
|
+
return true;
|
1280
|
+
}
|
1281
|
+
catch (e) { }
|
1282
|
+
return false;
|
1283
|
+
|
1284
|
+
default:
|
1285
|
+
return null;
|
1286
|
+
}
|
1287
|
+
}
|
1288
|
+
});
|
1289
|
+
|
1290
|
+
/**
|
1291
|
+
* Returs JSON representation of canvas
|
1292
|
+
* @function
|
1293
|
+
* @method toJSON
|
1294
|
+
* @return {String} json string
|
1295
|
+
*/
|
1296
|
+
fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject;
|
1297
|
+
|
1298
|
+
})();
|