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.
Files changed (56) hide show
  1. data/CHANGELOG.md +16 -0
  2. data/README.md +1 -1
  3. data/lib/fabric/rails/version.rb +2 -2
  4. data/vendor/assets/javascripts/event.js +1909 -0
  5. data/vendor/assets/javascripts/fabric.js +64 -16464
  6. data/vendor/assets/javascripts/fabric/HEADER.js +31 -0
  7. data/vendor/assets/javascripts/fabric/canvas.class.js +1007 -0
  8. data/vendor/assets/javascripts/fabric/canvas_animation.mixin.js +113 -0
  9. data/vendor/assets/javascripts/fabric/canvas_events.mixin.js +482 -0
  10. data/vendor/assets/javascripts/fabric/canvas_gestures.mixin.js +79 -0
  11. data/vendor/assets/javascripts/fabric/canvas_serialization.mixin.js +311 -0
  12. data/vendor/assets/javascripts/fabric/circle.class.js +182 -0
  13. data/vendor/assets/javascripts/fabric/color.class.js +284 -0
  14. data/vendor/assets/javascripts/fabric/ellipse.class.js +169 -0
  15. data/vendor/assets/javascripts/fabric/freedrawing.class.js +256 -0
  16. data/vendor/assets/javascripts/fabric/gradient.class.js +211 -0
  17. data/vendor/assets/javascripts/fabric/group.class.js +556 -0
  18. data/vendor/assets/javascripts/fabric/image.class.js +418 -0
  19. data/vendor/assets/javascripts/fabric/image_filters.js +809 -0
  20. data/vendor/assets/javascripts/fabric/intersection.class.js +178 -0
  21. data/vendor/assets/javascripts/fabric/line.class.js +188 -0
  22. data/vendor/assets/javascripts/fabric/log.js +26 -0
  23. data/vendor/assets/javascripts/fabric/node.js +149 -0
  24. data/vendor/assets/javascripts/fabric/object.class.js +1068 -0
  25. data/vendor/assets/javascripts/fabric/object_geometry.mixin.js +308 -0
  26. data/vendor/assets/javascripts/fabric/object_interactivity.mixin.js +496 -0
  27. data/vendor/assets/javascripts/fabric/object_origin.mixin.js +207 -0
  28. data/vendor/assets/javascripts/fabric/object_straightening.mixin.js +94 -0
  29. data/vendor/assets/javascripts/fabric/observable.mixin.js +91 -0
  30. data/vendor/assets/javascripts/fabric/parser.js +750 -0
  31. data/vendor/assets/javascripts/fabric/path.class.js +794 -0
  32. data/vendor/assets/javascripts/fabric/path_group.class.js +240 -0
  33. data/vendor/assets/javascripts/fabric/pattern.class.js +69 -0
  34. data/vendor/assets/javascripts/fabric/point.class.js +327 -0
  35. data/vendor/assets/javascripts/fabric/polygon.class.js +184 -0
  36. data/vendor/assets/javascripts/fabric/polyline.class.js +157 -0
  37. data/vendor/assets/javascripts/fabric/rect.class.js +298 -0
  38. data/vendor/assets/javascripts/fabric/scout.js +45 -0
  39. data/vendor/assets/javascripts/fabric/shadow.class.js +70 -0
  40. data/vendor/assets/javascripts/fabric/stateful.js +88 -0
  41. data/vendor/assets/javascripts/fabric/static_canvas.class.js +1298 -0
  42. data/vendor/assets/javascripts/fabric/text.class.js +934 -0
  43. data/vendor/assets/javascripts/fabric/triangle.class.js +108 -0
  44. data/vendor/assets/javascripts/fabric/util/anim_ease.js +360 -0
  45. data/vendor/assets/javascripts/fabric/util/dom_event.js +237 -0
  46. data/vendor/assets/javascripts/fabric/util/dom_misc.js +245 -0
  47. data/vendor/assets/javascripts/fabric/util/dom_request.js +72 -0
  48. data/vendor/assets/javascripts/fabric/util/dom_style.js +71 -0
  49. data/vendor/assets/javascripts/fabric/util/lang_array.js +250 -0
  50. data/vendor/assets/javascripts/fabric/util/lang_class.js +97 -0
  51. data/vendor/assets/javascripts/fabric/util/lang_function.js +35 -0
  52. data/vendor/assets/javascripts/fabric/util/lang_object.js +34 -0
  53. data/vendor/assets/javascripts/fabric/util/lang_string.js +60 -0
  54. data/vendor/assets/javascripts/fabric/util/misc.js +406 -0
  55. data/vendor/assets/javascripts/json2.js +491 -0
  56. 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 &lt;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
+ })();