Subversion-Projekte lars-tiefland.ci

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
776 lars 1
/* build: `node build.js modules=ALL exclude=json,gestures minifier=uglifyjs` */
2
/*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */
3
 
4
var fabric = fabric || { version: "1.5.0" };
5
if (typeof exports !== 'undefined') {
6
  exports.fabric = fabric;
7
}
8
 
9
if (typeof document !== 'undefined' && typeof window !== 'undefined') {
10
  fabric.document = document;
11
  fabric.window = window;
12
  // ensure globality even if entire library were function wrapped (as in Meteor.js packaging system)
13
  window.fabric = fabric;
14
}
15
else {
16
  // assume we're running under node.js when document/window are not present
17
  fabric.document = require("jsdom")
18
    .jsdom("<!DOCTYPE html><html><head></head><body></body></html>");
19
 
20
  if (fabric.document.createWindow) {
21
    fabric.window = fabric.document.createWindow();
22
  } else {
23
    fabric.window = fabric.document.parentWindow;
24
  }
25
}
26
 
27
/**
28
 * True when in environment that supports touch events
29
 * @type boolean
30
 */
31
fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement;
32
 
33
/**
34
 * True when in environment that's probably Node.js
35
 * @type boolean
36
 */
37
fabric.isLikelyNode = typeof Buffer !== 'undefined' &&
38
                      typeof window === 'undefined';
39
 
40
/* _FROM_SVG_START_ */
41
/**
42
 * Attributes parsed from all SVG elements
43
 * @type array
44
 */
45
fabric.SHARED_ATTRIBUTES = [
46
  "display",
47
  "transform",
48
  "fill", "fill-opacity", "fill-rule",
49
  "opacity",
50
  "stroke", "stroke-dasharray", "stroke-linecap",
51
  "stroke-linejoin", "stroke-miterlimit",
52
  "stroke-opacity", "stroke-width"
53
];
54
/* _FROM_SVG_END_ */
55
 
56
/**
57
 * Pixel per Inch as a default value set to 96. Can be changed for more realistic conversion.
58
 */
59
fabric.DPI = 96;
60
fabric.reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)';
61
 
62
 
63
(function() {
64
 
65
  /**
66
   * @private
67
   * @param {String} eventName
68
   * @param {Function} handler
69
   */
70
  function _removeEventListener(eventName, handler) {
71
    if (!this.__eventListeners[eventName]) {
72
      return;
73
    }
74
 
75
    if (handler) {
76
      fabric.util.removeFromArray(this.__eventListeners[eventName], handler);
77
    }
78
    else {
79
      this.__eventListeners[eventName].length = 0;
80
    }
81
  }
82
 
83
  /**
84
   * Observes specified event
85
   * @deprecated `observe` deprecated since 0.8.34 (use `on` instead)
86
   * @memberOf fabric.Observable
87
   * @alias on
88
   * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler})
89
   * @param {Function} handler Function that receives a notification when an event of the specified type occurs
90
   * @return {Self} thisArg
91
   * @chainable
92
   */
93
  function observe(eventName, handler) {
94
    if (!this.__eventListeners) {
95
      this.__eventListeners = { };
96
    }
97
    // one object with key/value pairs was passed
98
    if (arguments.length === 1) {
99
      for (var prop in eventName) {
100
        this.on(prop, eventName[prop]);
101
      }
102
    }
103
    else {
104
      if (!this.__eventListeners[eventName]) {
105
        this.__eventListeners[eventName] = [ ];
106
      }
107
      this.__eventListeners[eventName].push(handler);
108
    }
109
    return this;
110
  }
111
 
112
  /**
113
   * Stops event observing for a particular event handler. Calling this method
114
   * without arguments removes all handlers for all events
115
   * @deprecated `stopObserving` deprecated since 0.8.34 (use `off` instead)
116
   * @memberOf fabric.Observable
117
   * @alias off
118
   * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler})
119
   * @param {Function} handler Function to be deleted from EventListeners
120
   * @return {Self} thisArg
121
   * @chainable
122
   */
123
  function stopObserving(eventName, handler) {
124
    if (!this.__eventListeners) {
125
      return;
126
    }
127
 
128
    // remove all key/value pairs (event name -> event handler)
129
    if (arguments.length === 0) {
130
      this.__eventListeners = { };
131
    }
132
    // one object with key/value pairs was passed
133
    else if (arguments.length === 1 && typeof arguments[0] === 'object') {
134
      for (var prop in eventName) {
135
        _removeEventListener.call(this, prop, eventName[prop]);
136
      }
137
    }
138
    else {
139
      _removeEventListener.call(this, eventName, handler);
140
    }
141
    return this;
142
  }
143
 
144
  /**
145
   * Fires event with an optional options object
146
   * @deprecated `fire` deprecated since 1.0.7 (use `trigger` instead)
147
   * @memberOf fabric.Observable
148
   * @alias trigger
149
   * @param {String} eventName Event name to fire
150
   * @param {Object} [options] Options object
151
   * @return {Self} thisArg
152
   * @chainable
153
   */
154
  function fire(eventName, options) {
155
    if (!this.__eventListeners) {
156
      return;
157
    }
158
 
159
    var listenersForEvent = this.__eventListeners[eventName];
160
    if (!listenersForEvent) {
161
      return;
162
    }
163
 
164
    for (var i = 0, len = listenersForEvent.length; i < len; i++) {
165
      // avoiding try/catch for perf. reasons
166
      listenersForEvent[i].call(this, options || { });
167
    }
168
    return this;
169
  }
170
 
171
  /**
172
   * @namespace fabric.Observable
173
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#events}
174
   * @see {@link http://fabricjs.com/events/|Events demo}
175
   */
176
  fabric.Observable = {
177
    observe: observe,
178
    stopObserving: stopObserving,
179
    fire: fire,
180
 
181
    on: observe,
182
    off: stopObserving,
183
    trigger: fire
184
  };
185
})();
186
 
187
 
188
/**
189
 * @namespace fabric.Collection
190
 */
191
fabric.Collection = {
192
 
193
  /**
194
   * Adds objects to collection, then renders canvas (if `renderOnAddRemove` is not `false`)
195
   * Objects should be instances of (or inherit from) fabric.Object
196
   * @param {...fabric.Object} object Zero or more fabric instances
197
   * @return {Self} thisArg
198
   */
199
  add: function () {
200
    this._objects.push.apply(this._objects, arguments);
201
    for (var i = 0, length = arguments.length; i < length; i++) {
202
      this._onObjectAdded(arguments[i]);
203
    }
204
    this.renderOnAddRemove && this.renderAll();
205
    return this;
206
  },
207
 
208
  /**
209
   * Inserts an object into collection at specified index, then renders canvas (if `renderOnAddRemove` is not `false`)
210
   * An object should be an instance of (or inherit from) fabric.Object
211
   * @param {Object} object Object to insert
212
   * @param {Number} index Index to insert object at
213
   * @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs
214
   * @return {Self} thisArg
215
   * @chainable
216
   */
217
  insertAt: function (object, index, nonSplicing) {
218
    var objects = this.getObjects();
219
    if (nonSplicing) {
220
      objects[index] = object;
221
    }
222
    else {
223
      objects.splice(index, 0, object);
224
    }
225
    this._onObjectAdded(object);
226
    this.renderOnAddRemove && this.renderAll();
227
    return this;
228
  },
229
 
230
  /**
231
   * Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`)
232
   * @param {...fabric.Object} object Zero or more fabric instances
233
   * @return {Self} thisArg
234
   * @chainable
235
   */
236
  remove: function() {
237
    var objects = this.getObjects(),
238
        index;
239
 
240
    for (var i = 0, length = arguments.length; i < length; i++) {
241
      index = objects.indexOf(arguments[i]);
242
 
243
      // only call onObjectRemoved if an object was actually removed
244
      if (index !== -1) {
245
        objects.splice(index, 1);
246
        this._onObjectRemoved(arguments[i]);
247
      }
248
    }
249
 
250
    this.renderOnAddRemove && this.renderAll();
251
    return this;
252
  },
253
 
254
  /**
255
   * Executes given function for each object in this group
256
   * @param {Function} callback
257
   *                   Callback invoked with current object as first argument,
258
   *                   index - as second and an array of all objects - as third.
259
   *                   Iteration happens in reverse order (for performance reasons).
260
   *                   Callback is invoked in a context of Global Object (e.g. `window`)
261
   *                   when no `context` argument is given
262
   *
263
   * @param {Object} context Context (aka thisObject)
264
   * @return {Self} thisArg
265
   */
266
  forEachObject: function(callback, context) {
267
    var objects = this.getObjects(),
268
        i = objects.length;
269
    while (i--) {
270
      callback.call(context, objects[i], i, objects);
271
    }
272
    return this;
273
  },
274
 
275
  /**
276
   * Returns an array of children objects of this instance
277
   * Type parameter introduced in 1.3.10
278
   * @param {String} [type] When specified, only objects of this type are returned
279
   * @return {Array}
280
   */
281
  getObjects: function(type) {
282
    if (typeof type === 'undefined') {
283
      return this._objects;
284
    }
285
    return this._objects.filter(function(o) {
286
      return o.type === type;
287
    });
288
  },
289
 
290
  /**
291
   * Returns object at specified index
292
   * @param {Number} index
293
   * @return {Self} thisArg
294
   */
295
  item: function (index) {
296
    return this.getObjects()[index];
297
  },
298
 
299
  /**
300
   * Returns true if collection contains no objects
301
   * @return {Boolean} true if collection is empty
302
   */
303
  isEmpty: function () {
304
    return this.getObjects().length === 0;
305
  },
306
 
307
  /**
308
   * Returns a size of a collection (i.e: length of an array containing its objects)
309
   * @return {Number} Collection size
310
   */
311
  size: function() {
312
    return this.getObjects().length;
313
  },
314
 
315
  /**
316
   * Returns true if collection contains an object
317
   * @param {Object} object Object to check against
318
   * @return {Boolean} `true` if collection contains an object
319
   */
320
  contains: function(object) {
321
    return this.getObjects().indexOf(object) > -1;
322
  },
323
 
324
  /**
325
   * Returns number representation of a collection complexity
326
   * @return {Number} complexity
327
   */
328
  complexity: function () {
329
    return this.getObjects().reduce(function (memo, current) {
330
      memo += current.complexity ? current.complexity() : 0;
331
      return memo;
332
    }, 0);
333
  }
334
};
335
 
336
 
337
(function(global) {
338
 
339
  var sqrt = Math.sqrt,
340
      atan2 = Math.atan2,
341
      PiBy180 = Math.PI / 180;
342
 
343
  /**
344
   * @namespace fabric.util
345
   */
346
  fabric.util = {
347
 
348
    /**
349
     * Removes value from an array.
350
     * Presence of value (and its position in an array) is determined via `Array.prototype.indexOf`
351
     * @static
352
     * @memberOf fabric.util
353
     * @param {Array} array
354
     * @param {Any} value
355
     * @return {Array} original array
356
     */
357
    removeFromArray: function(array, value) {
358
      var idx = array.indexOf(value);
359
      if (idx !== -1) {
360
        array.splice(idx, 1);
361
      }
362
      return array;
363
    },
364
 
365
    /**
366
     * Returns random number between 2 specified ones.
367
     * @static
368
     * @memberOf fabric.util
369
     * @param {Number} min lower limit
370
     * @param {Number} max upper limit
371
     * @return {Number} random value (between min and max)
372
     */
373
    getRandomInt: function(min, max) {
374
      return Math.floor(Math.random() * (max - min + 1)) + min;
375
    },
376
 
377
    /**
378
     * Transforms degrees to radians.
379
     * @static
380
     * @memberOf fabric.util
381
     * @param {Number} degrees value in degrees
382
     * @return {Number} value in radians
383
     */
384
    degreesToRadians: function(degrees) {
385
      return degrees * PiBy180;
386
    },
387
 
388
    /**
389
     * Transforms radians to degrees.
390
     * @static
391
     * @memberOf fabric.util
392
     * @param {Number} radians value in radians
393
     * @return {Number} value in degrees
394
     */
395
    radiansToDegrees: function(radians) {
396
      return radians / PiBy180;
397
    },
398
 
399
    /**
400
     * Rotates `point` around `origin` with `radians`
401
     * @static
402
     * @memberOf fabric.util
403
     * @param {fabric.Point} point The point to rotate
404
     * @param {fabric.Point} origin The origin of the rotation
405
     * @param {Number} radians The radians of the angle for the rotation
406
     * @return {fabric.Point} The new rotated point
407
     */
408
    rotatePoint: function(point, origin, radians) {
409
      var sin = Math.sin(radians),
410
          cos = Math.cos(radians);
411
 
412
      point.subtractEquals(origin);
413
 
414
      var rx = point.x * cos - point.y * sin,
415
          ry = point.x * sin + point.y * cos;
416
 
417
      return new fabric.Point(rx, ry).addEquals(origin);
418
    },
419
 
420
    /**
421
     * Apply transform t to point p
422
     * @static
423
     * @memberOf fabric.util
424
     * @param  {fabric.Point} p The point to transform
425
     * @param  {Array} t The transform
426
     * @param  {Boolean} [ignoreOffset] Indicates that the offset should not be applied
427
     * @return {fabric.Point} The transformed point
428
     */
429
    transformPoint: function(p, t, ignoreOffset) {
430
      if (ignoreOffset) {
431
        return new fabric.Point(
432
          t[0] * p.x + t[2] * p.y,
433
          t[1] * p.x + t[3] * p.y
434
        );
435
      }
436
      return new fabric.Point(
437
        t[0] * p.x + t[2] * p.y + t[4],
438
        t[1] * p.x + t[3] * p.y + t[5]
439
      );
440
    },
441
 
442
    /**
443
     * Invert transformation t
444
     * @static
445
     * @memberOf fabric.util
446
     * @param {Array} t The transform
447
     * @return {Array} The inverted transform
448
     */
449
    invertTransform: function(t) {
450
      var r = t.slice(),
451
          a = 1 / (t[0] * t[3] - t[1] * t[2]);
452
      r = [a * t[3], -a * t[1], -a * t[2], a * t[0], 0, 0];
453
      var o = fabric.util.transformPoint({ x: t[4], y: t[5] }, r);
454
      r[4] = -o.x;
455
      r[5] = -o.y;
456
      return r;
457
    },
458
 
459
    /**
460
     * A wrapper around Number#toFixed, which contrary to native method returns number, not string.
461
     * @static
462
     * @memberOf fabric.util
463
     * @param {Number|String} number number to operate on
464
     * @param {Number} fractionDigits number of fraction digits to "leave"
465
     * @return {Number}
466
     */
467
    toFixed: function(number, fractionDigits) {
468
      return parseFloat(Number(number).toFixed(fractionDigits));
469
    },
470
 
471
    /**
472
     * Converts from attribute value to pixel value if applicable.
473
     * Returns converted pixels or original value not converted.
474
     * @param {Number|String} value number to operate on
475
     * @return {Number|String}
476
     */
477
    parseUnit: function(value, fontSize) {
478
      var unit = /\D{0,2}$/.exec(value),
479
          number = parseFloat(value);
480
      if (!fontSize) {
481
        fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE;
482
      }
483
      switch (unit[0]) {
484
        case 'mm':
485
          return number * fabric.DPI / 25.4;
486
 
487
        case 'cm':
488
          return number * fabric.DPI / 2.54;
489
 
490
        case 'in':
491
          return number * fabric.DPI;
492
 
493
        case 'pt':
494
          return number * fabric.DPI / 72; // or * 4 / 3
495
 
496
        case 'pc':
497
          return number * fabric.DPI / 72 * 12; // or * 16
498
 
499
        case 'em':
500
          return number * fontSize;
501
 
502
        default:
503
          return number;
504
      }
505
    },
506
 
507
    /**
508
     * Function which always returns `false`.
509
     * @static
510
     * @memberOf fabric.util
511
     * @return {Boolean}
512
     */
513
    falseFunction: function() {
514
      return false;
515
    },
516
 
517
    /**
518
      * Returns klass "Class" object of given namespace
519
      * @memberOf fabric.util
520
      * @param {String} type Type of object (eg. 'circle')
521
      * @param {String} namespace Namespace to get klass "Class" object from
522
      * @return {Object} klass "Class"
523
      */
524
    getKlass: function(type, namespace) {
525
      // capitalize first letter only
526
      type = fabric.util.string.camelize(type.charAt(0).toUpperCase() + type.slice(1));
527
      return fabric.util.resolveNamespace(namespace)[type];
528
    },
529
 
530
    /**
531
     * Returns object of given namespace
532
     * @memberOf fabric.util
533
     * @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric'
534
     * @return {Object} Object for given namespace (default fabric)
535
     */
536
    resolveNamespace: function(namespace) {
537
      if (!namespace) {
538
        return fabric;
539
      }
540
 
541
      var parts = namespace.split('.'),
542
          len = parts.length,
543
          obj = global || fabric.window;
544
 
545
      for (var i = 0; i < len; ++i) {
546
        obj = obj[parts[i]];
547
      }
548
 
549
      return obj;
550
    },
551
 
552
    /**
553
     * Loads image element from given url and passes it to a callback
554
     * @memberOf fabric.util
555
     * @param {String} url URL representing an image
556
     * @param {Function} callback Callback; invoked with loaded image
557
     * @param {Any} [context] Context to invoke callback in
558
     * @param {Object} [crossOrigin] crossOrigin value to set image element to
559
     */
560
    loadImage: function(url, callback, context, crossOrigin) {
561
      if (!url) {
562
        callback && callback.call(context, url);
563
        return;
564
      }
565
 
566
      var img = fabric.util.createImage();
567
 
568
      /** @ignore */
569
      img.onload = function () {
570
        callback && callback.call(context, img);
571
        img = img.onload = img.onerror = null;
572
      };
573
 
574
      /** @ignore */
575
      img.onerror = function() {
576
        fabric.log('Error loading ' + img.src);
577
        callback && callback.call(context, null, true);
578
        img = img.onload = img.onerror = null;
579
      };
580
 
581
      // data-urls appear to be buggy with crossOrigin
582
      // https://github.com/kangax/fabric.js/commit/d0abb90f1cd5c5ef9d2a94d3fb21a22330da3e0a#commitcomment-4513767
583
      // see https://code.google.com/p/chromium/issues/detail?id=315152
584
      //     https://bugzilla.mozilla.org/show_bug.cgi?id=935069
585
      if (url.indexOf('data') !== 0 && typeof crossOrigin !== 'undefined') {
586
        img.crossOrigin = crossOrigin;
587
      }
588
 
589
      img.src = url;
590
    },
591
 
592
    /**
593
     * Creates corresponding fabric instances from their object representations
594
     * @static
595
     * @memberOf fabric.util
596
     * @param {Array} objects Objects to enliven
597
     * @param {Function} callback Callback to invoke when all objects are created
598
     * @param {String} namespace Namespace to get klass "Class" object from
599
     * @param {Function} reviver Method for further parsing of object elements,
600
     * called after each fabric object created.
601
     */
602
    enlivenObjects: function(objects, callback, namespace, reviver) {
603
      objects = objects || [ ];
604
 
605
      function onLoaded() {
606
        if (++numLoadedObjects === numTotalObjects) {
607
          callback && callback(enlivenedObjects);
608
        }
609
      }
610
 
611
      var enlivenedObjects = [ ],
612
          numLoadedObjects = 0,
613
          numTotalObjects = objects.length;
614
 
615
      if (!numTotalObjects) {
616
        callback && callback(enlivenedObjects);
617
        return;
618
      }
619
 
620
      objects.forEach(function (o, index) {
621
        // if sparse array
622
        if (!o || !o.type) {
623
          onLoaded();
624
          return;
625
        }
626
        var klass = fabric.util.getKlass(o.type, namespace);
627
        if (klass.async) {
628
          klass.fromObject(o, function (obj, error) {
629
            if (!error) {
630
              enlivenedObjects[index] = obj;
631
              reviver && reviver(o, enlivenedObjects[index]);
632
            }
633
            onLoaded();
634
          });
635
        }
636
        else {
637
          enlivenedObjects[index] = klass.fromObject(o);
638
          reviver && reviver(o, enlivenedObjects[index]);
639
          onLoaded();
640
        }
641
      });
642
    },
643
 
644
    /**
645
     * Groups SVG elements (usually those retrieved from SVG document)
646
     * @static
647
     * @memberOf fabric.util
648
     * @param {Array} elements SVG elements to group
649
     * @param {Object} [options] Options object
650
     * @return {fabric.Object|fabric.PathGroup}
651
     */
652
    groupSVGElements: function(elements, options, path) {
653
      var object;
654
 
655
      object = new fabric.PathGroup(elements, options);
656
 
657
      if (typeof path !== 'undefined') {
658
        object.setSourcePath(path);
659
      }
660
      return object;
661
    },
662
 
663
    /**
664
     * Populates an object with properties of another object
665
     * @static
666
     * @memberOf fabric.util
667
     * @param {Object} source Source object
668
     * @param {Object} destination Destination object
669
     * @return {Array} properties Propertie names to include
670
     */
671
    populateWithProperties: function(source, destination, properties) {
672
      if (properties && Object.prototype.toString.call(properties) === '[object Array]') {
673
        for (var i = 0, len = properties.length; i < len; i++) {
674
          if (properties[i] in source) {
675
            destination[properties[i]] = source[properties[i]];
676
          }
677
        }
678
      }
679
    },
680
 
681
    /**
682
     * Draws a dashed line between two points
683
     *
684
     * This method is used to draw dashed line around selection area.
685
     * See <a href="http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas">dotted stroke in canvas</a>
686
     *
687
     * @param {CanvasRenderingContext2D} ctx context
688
     * @param {Number} x  start x coordinate
689
     * @param {Number} y start y coordinate
690
     * @param {Number} x2 end x coordinate
691
     * @param {Number} y2 end y coordinate
692
     * @param {Array} da dash array pattern
693
     */
694
    drawDashedLine: function(ctx, x, y, x2, y2, da) {
695
      var dx = x2 - x,
696
          dy = y2 - y,
697
          len = sqrt(dx * dx + dy * dy),
698
          rot = atan2(dy, dx),
699
          dc = da.length,
700
          di = 0,
701
          draw = true;
702
 
703
      ctx.save();
704
      ctx.translate(x, y);
705
      ctx.moveTo(0, 0);
706
      ctx.rotate(rot);
707
 
708
      x = 0;
709
      while (len > x) {
710
        x += da[di++ % dc];
711
        if (x > len) {
712
          x = len;
713
        }
714
        ctx[draw ? 'lineTo' : 'moveTo'](x, 0);
715
        draw = !draw;
716
      }
717
 
718
      ctx.restore();
719
    },
720
 
721
    /**
722
     * Creates canvas element and initializes it via excanvas if necessary
723
     * @static
724
     * @memberOf fabric.util
725
     * @param {CanvasElement} [canvasEl] optional canvas element to initialize;
726
     * when not given, element is created implicitly
727
     * @return {CanvasElement} initialized canvas element
728
     */
729
    createCanvasElement: function(canvasEl) {
730
      canvasEl || (canvasEl = fabric.document.createElement('canvas'));
731
      //jscs:disable requireCamelCaseOrUpperCaseIdentifiers
732
      if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') {
733
        G_vmlCanvasManager.initElement(canvasEl);
734
      }
735
      //jscs:enable requireCamelCaseOrUpperCaseIdentifiers
736
      return canvasEl;
737
    },
738
 
739
    /**
740
     * Creates image element (works on client and node)
741
     * @static
742
     * @memberOf fabric.util
743
     * @return {HTMLImageElement} HTML image element
744
     */
745
    createImage: function() {
746
      return fabric.isLikelyNode
747
        ? new (require('canvas').Image)()
748
        : fabric.document.createElement('img');
749
    },
750
 
751
    /**
752
     * Creates accessors (getXXX, setXXX) for a "class", based on "stateProperties" array
753
     * @static
754
     * @memberOf fabric.util
755
     * @param {Object} klass "Class" to create accessors for
756
     */
757
    createAccessors: function(klass) {
758
      var proto = klass.prototype;
759
 
760
      for (var i = proto.stateProperties.length; i--; ) {
761
 
762
        var propName = proto.stateProperties[i],
763
            capitalizedPropName = propName.charAt(0).toUpperCase() + propName.slice(1),
764
            setterName = 'set' + capitalizedPropName,
765
            getterName = 'get' + capitalizedPropName;
766
 
767
        // using `new Function` for better introspection
768
        if (!proto[getterName]) {
769
          proto[getterName] = (function(property) {
770
            return new Function('return this.get("' + property + '")');
771
          })(propName);
772
        }
773
        if (!proto[setterName]) {
774
          proto[setterName] = (function(property) {
775
            return new Function('value', 'return this.set("' + property + '", value)');
776
          })(propName);
777
        }
778
      }
779
    },
780
 
781
    /**
782
     * @static
783
     * @memberOf fabric.util
784
     * @param {fabric.Object} receiver Object implementing `clipTo` method
785
     * @param {CanvasRenderingContext2D} ctx Context to clip
786
     */
787
    clipContext: function(receiver, ctx) {
788
      ctx.save();
789
      ctx.beginPath();
790
      receiver.clipTo(ctx);
791
      ctx.clip();
792
    },
793
 
794
    /**
795
     * Multiply matrix A by matrix B to nest transformations
796
     * @static
797
     * @memberOf fabric.util
798
     * @param  {Array} a First transformMatrix
799
     * @param  {Array} b Second transformMatrix
800
     * @return {Array} The product of the two transform matrices
801
     */
802
    multiplyTransformMatrices: function(a, b) {
803
      // Matrix multiply a * b
804
      return [
805
        a[0] * b[0] + a[2] * b[1],
806
        a[1] * b[0] + a[3] * b[1],
807
        a[0] * b[2] + a[2] * b[3],
808
        a[1] * b[2] + a[3] * b[3],
809
        a[0] * b[4] + a[2] * b[5] + a[4],
810
        a[1] * b[4] + a[3] * b[5] + a[5]
811
      ];
812
    },
813
 
814
    /**
815
     * Returns string representation of function body
816
     * @param {Function} fn Function to get body of
817
     * @return {String} Function body
818
     */
819
    getFunctionBody: function(fn) {
820
      return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1];
821
    },
822
 
823
    /**
824
     * Returns true if context has transparent pixel
825
     * at specified location (taking tolerance into account)
826
     * @param {CanvasRenderingContext2D} ctx context
827
     * @param {Number} x x coordinate
828
     * @param {Number} y y coordinate
829
     * @param {Number} tolerance Tolerance
830
     */
831
    isTransparent: function(ctx, x, y, tolerance) {
832
 
833
      // If tolerance is > 0 adjust start coords to take into account.
834
      // If moves off Canvas fix to 0
835
      if (tolerance > 0) {
836
        if (x > tolerance) {
837
          x -= tolerance;
838
        }
839
        else {
840
          x = 0;
841
        }
842
        if (y > tolerance) {
843
          y -= tolerance;
844
        }
845
        else {
846
          y = 0;
847
        }
848
      }
849
 
850
      var _isTransparent = true,
851
          imageData = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1);
852
 
853
      // Split image data - for tolerance > 1, pixelDataSize = 4;
854
      for (var i = 3, l = imageData.data.length; i < l; i += 4) {
855
        var temp = imageData.data[i];
856
        _isTransparent = temp <= 0;
857
        if (_isTransparent === false) {
858
          break; // Stop if colour found
859
        }
860
      }
861
 
862
      imageData = null;
863
 
864
      return _isTransparent;
865
    }
866
  };
867
 
868
})(typeof exports !== 'undefined' ? exports : this);
869
 
870
 
871
(function() {
872
 
873
  var arcToSegmentsCache = { },
874
      segmentToBezierCache = { },
875
      boundsOfCurveCache = { },
876
      _join = Array.prototype.join;
877
 
878
  /* Adapted from http://dxr.mozilla.org/mozilla-central/source/content/svg/content/src/nsSVGPathDataParser.cpp
879
   * by Andrea Bogazzi code is under MPL. if you don't have a copy of the license you can take it here
880
   * http://mozilla.org/MPL/2.0/
881
   */
882
  function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) {
883
    var argsString = _join.call(arguments);
884
    if (arcToSegmentsCache[argsString]) {
885
      return arcToSegmentsCache[argsString];
886
    }
887
 
888
    var PI = Math.PI, th = rotateX * PI / 180,
889
        sinTh = Math.sin(th),
890
        cosTh = Math.cos(th),
891
        fromX = 0, fromY = 0;
892
 
893
    rx = Math.abs(rx);
894
    ry = Math.abs(ry);
895
 
896
    var px = -cosTh * toX * 0.5 - sinTh * toY * 0.5,
897
        py = -cosTh * toY * 0.5 + sinTh * toX * 0.5,
898
        rx2 = rx * rx, ry2 = ry * ry, py2 = py * py, px2 = px * px,
899
        pl = rx2 * ry2 - rx2 * py2 - ry2 * px2,
900
        root = 0;
901
 
902
    if (pl < 0) {
903
      var s = Math.sqrt(1 - pl/(rx2 * ry2));
904
      rx *= s;
905
      ry *= s;
906
    }
907
    else {
908
      root = (large === sweep ? -1.0 : 1.0) *
909
              Math.sqrt( pl /(rx2 * py2 + ry2 * px2));
910
    }
911
 
912
    var cx = root * rx * py / ry,
913
        cy = -root * ry * px / rx,
914
        cx1 = cosTh * cx - sinTh * cy + toX * 0.5,
915
        cy1 = sinTh * cx + cosTh * cy + toY * 0.5,
916
        mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry),
917
        dtheta = calcVectorAngle((px - cx) / rx, (py - cy) / ry, (-px - cx) / rx, (-py - cy) / ry);
918
 
919
    if (sweep === 0 && dtheta > 0) {
920
      dtheta -= 2 * PI;
921
    }
922
    else if (sweep === 1 && dtheta < 0) {
923
      dtheta += 2 * PI;
924
    }
925
 
926
    // Convert into cubic bezier segments <= 90deg
927
    var segments = Math.ceil(Math.abs(dtheta / PI * 2)),
928
        result = [], mDelta = dtheta / segments,
929
        mT = 8 / 3 * Math.sin(mDelta / 4) * Math.sin(mDelta / 4) / Math.sin(mDelta / 2),
930
        th3 = mTheta + mDelta;
931
 
932
    for (var i = 0; i < segments; i++) {
933
      result[i] = segmentToBezier(mTheta, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY);
934
      fromX = result[i][4];
935
      fromY = result[i][5];
936
      mTheta = th3;
937
      th3 += mDelta;
938
    }
939
    arcToSegmentsCache[argsString] = result;
940
    return result;
941
  }
942
 
943
  function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) {
944
    var argsString2 = _join.call(arguments);
945
    if (segmentToBezierCache[argsString2]) {
946
      return segmentToBezierCache[argsString2];
947
    }
948
 
949
    var costh2 = Math.cos(th2),
950
        sinth2 = Math.sin(th2),
951
        costh3 = Math.cos(th3),
952
        sinth3 = Math.sin(th3),
953
        toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1,
954
        toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1,
955
        cp1X = fromX + mT * ( - cosTh * rx * sinth2 - sinTh * ry * costh2),
956
        cp1Y = fromY + mT * ( - sinTh * rx * sinth2 + cosTh * ry * costh2),
957
        cp2X = toX + mT * ( cosTh * rx * sinth3 + sinTh * ry * costh3),
958
        cp2Y = toY + mT * ( sinTh * rx * sinth3 - cosTh * ry * costh3);
959
 
960
    segmentToBezierCache[argsString2] = [
961
      cp1X, cp1Y,
962
      cp2X, cp2Y,
963
      toX, toY
964
    ];
965
    return segmentToBezierCache[argsString2];
966
  }
967
 
968
  /*
969
  * Private
970
  */
971
  function calcVectorAngle(ux, uy, vx, vy) {
972
    var ta = Math.atan2(uy, ux),
973
        tb = Math.atan2(vy, vx);
974
    if (tb >= ta) {
975
      return tb - ta;
976
    }
977
    else {
978
      return 2 * Math.PI - (ta - tb);
979
    }
980
  }
981
 
982
  /**
983
   * Draws arc
984
   * @param {CanvasRenderingContext2D} ctx
985
   * @param {Number} fx
986
   * @param {Number} fy
987
   * @param {Array} coords
988
   */
989
  fabric.util.drawArc = function(ctx, fx, fy, coords) {
990
    var rx = coords[0],
991
        ry = coords[1],
992
        rot = coords[2],
993
        large = coords[3],
994
        sweep = coords[4],
995
        tx = coords[5],
996
        ty = coords[6],
997
        segs = [[ ], [ ], [ ], [ ]],
998
        segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot);
999
 
1000
    for (var i = 0, len = segsNorm.length; i < len; i++) {
1001
      segs[i][0] = segsNorm[i][0] + fx;
1002
      segs[i][1] = segsNorm[i][1] + fy;
1003
      segs[i][2] = segsNorm[i][2] + fx;
1004
      segs[i][3] = segsNorm[i][3] + fy;
1005
      segs[i][4] = segsNorm[i][4] + fx;
1006
      segs[i][5] = segsNorm[i][5] + fy;
1007
      ctx.bezierCurveTo.apply(ctx, segs[i]);
1008
    }
1009
  };
1010
 
1011
  /**
1012
   * Calculate bounding box of a elliptic-arc
1013
   * @param {Number} fx start point of arc
1014
   * @param {Number} fy
1015
   * @param {Number} rx horizontal radius
1016
   * @param {Number} ry vertical radius
1017
   * @param {Number} rot angle of horizontal axe
1018
   * @param {Number} large 1 or 0, whatever the arc is the big or the small on the 2 points
1019
   * @param {Number} sweep 1 or 0, 1 clockwise or counterclockwise direction
1020
   * @param {Number} tx end point of arc
1021
   * @param {Number} ty
1022
   */
1023
  fabric.util.getBoundsOfArc = function(fx, fy, rx, ry, rot, large, sweep, tx, ty) {
1024
 
1025
    var fromX = 0, fromY = 0, bound = [ ], bounds = [ ],
1026
    segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot),
1027
    boundCopy = [[ ], [ ]];
1028
 
1029
    for (var i = 0, len = segs.length; i < len; i++) {
1030
      bound = getBoundsOfCurve(fromX, fromY, segs[i][0], segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5]);
1031
      boundCopy[0].x = bound[0].x + fx;
1032
      boundCopy[0].y = bound[0].y + fy;
1033
      boundCopy[1].x = bound[1].x + fx;
1034
      boundCopy[1].y = bound[1].y + fy;
1035
      bounds.push(boundCopy[0]);
1036
      bounds.push(boundCopy[1]);
1037
      fromX = segs[i][4];
1038
      fromY = segs[i][5];
1039
    }
1040
    return bounds;
1041
  };
1042
 
1043
  /**
1044
   * Calculate bounding box of a beziercurve
1045
   * @param {Number} x0 starting point
1046
   * @param {Number} y0
1047
   * @param {Number} x1 first control point
1048
   * @param {Number} y1
1049
   * @param {Number} x2 secondo control point
1050
   * @param {Number} y2
1051
   * @param {Number} x3 end of beizer
1052
   * @param {Number} y3
1053
   */
1054
  // taken from http://jsbin.com/ivomiq/56/edit  no credits available for that.
1055
  function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) {
1056
    var argsString = _join.call(arguments);
1057
    if (boundsOfCurveCache[argsString]) {
1058
      return boundsOfCurveCache[argsString];
1059
    }
1060
 
1061
    var sqrt = Math.sqrt,
1062
        min = Math.min, max = Math.max,
1063
        abs = Math.abs, tvalues = [ ],
1064
        bounds = [[ ], [ ]],
1065
        a, b, c, t, t1, t2, b2ac, sqrtb2ac;
1066
 
1067
    b = 6 * x0 - 12 * x1 + 6 * x2;
1068
    a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
1069
    c = 3 * x1 - 3 * x0;
1070
 
1071
    for (var i = 0; i < 2; ++i) {
1072
      if (i > 0) {
1073
        b = 6 * y0 - 12 * y1 + 6 * y2;
1074
        a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
1075
        c = 3 * y1 - 3 * y0;
1076
      }
1077
 
1078
      if (abs(a) < 1e-12) {
1079
        if (abs(b) < 1e-12) {
1080
          continue;
1081
        }
1082
        t = -c / b;
1083
        if (0 < t && t < 1) {
1084
          tvalues.push(t);
1085
        }
1086
        continue;
1087
      }
1088
      b2ac = b * b - 4 * c * a;
1089
      if (b2ac < 0) {
1090
        continue;
1091
      }
1092
      sqrtb2ac = sqrt(b2ac);
1093
      t1 = (-b + sqrtb2ac) / (2 * a);
1094
      if (0 < t1 && t1 < 1) {
1095
        tvalues.push(t1);
1096
      }
1097
      t2 = (-b - sqrtb2ac) / (2 * a);
1098
      if (0 < t2 && t2 < 1) {
1099
        tvalues.push(t2);
1100
      }
1101
    }
1102
 
1103
    var x, y, j = tvalues.length, jlen = j, mt;
1104
    while (j--) {
1105
      t = tvalues[j];
1106
      mt = 1 - t;
1107
      x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
1108
      bounds[0][j] = x;
1109
 
1110
      y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
1111
      bounds[1][j] = y;
1112
    }
1113
 
1114
    bounds[0][jlen] = x0;
1115
    bounds[1][jlen] = y0;
1116
    bounds[0][jlen + 1] = x3;
1117
    bounds[1][jlen + 1] = y3;
1118
    var result = [
1119
      {
1120
        x: min.apply(null, bounds[0]),
1121
        y: min.apply(null, bounds[1])
1122
      },
1123
      {
1124
        x: max.apply(null, bounds[0]),
1125
        y: max.apply(null, bounds[1])
1126
      }
1127
    ];
1128
    boundsOfCurveCache[argsString] = result;
1129
    return result;
1130
  }
1131
 
1132
  fabric.util.getBoundsOfCurve = getBoundsOfCurve;
1133
 
1134
})();
1135
 
1136
 
1137
(function() {
1138
 
1139
  var slice = Array.prototype.slice;
1140
 
1141
  /* _ES5_COMPAT_START_ */
1142
 
1143
  if (!Array.prototype.indexOf) {
1144
    /**
1145
     * Finds index of an element in an array
1146
     * @param {Any} searchElement
1147
     * @param {Number} [fromIndex]
1148
     * @return {Number}
1149
     */
1150
    Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
1151
      if (this === void 0 || this === null) {
1152
        throw new TypeError();
1153
      }
1154
      var t = Object(this), len = t.length >>> 0;
1155
      if (len === 0) {
1156
        return -1;
1157
      }
1158
      var n = 0;
1159
      if (arguments.length > 0) {
1160
        n = Number(arguments[1]);
1161
        if (n !== n) { // shortcut for verifying if it's NaN
1162
          n = 0;
1163
        }
1164
        else if (n !== 0 && n !== Number.POSITIVE_INFINITY && n !== Number.NEGATIVE_INFINITY) {
1165
          n = (n > 0 || -1) * Math.floor(Math.abs(n));
1166
        }
1167
      }
1168
      if (n >= len) {
1169
        return -1;
1170
      }
1171
      var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
1172
      for (; k < len; k++) {
1173
        if (k in t && t[k] === searchElement) {
1174
          return k;
1175
        }
1176
      }
1177
      return -1;
1178
    };
1179
  }
1180
 
1181
  if (!Array.prototype.forEach) {
1182
    /**
1183
     * Iterates an array, invoking callback for each element
1184
     * @param {Function} fn Callback to invoke for each element
1185
     * @param {Object} [context] Context to invoke callback in
1186
     * @return {Array}
1187
     */
1188
    Array.prototype.forEach = function(fn, context) {
1189
      for (var i = 0, len = this.length >>> 0; i < len; i++) {
1190
        if (i in this) {
1191
          fn.call(context, this[i], i, this);
1192
        }
1193
      }
1194
    };
1195
  }
1196
 
1197
  if (!Array.prototype.map) {
1198
    /**
1199
     * Returns a result of iterating over an array, invoking callback for each element
1200
     * @param {Function} fn Callback to invoke for each element
1201
     * @param {Object} [context] Context to invoke callback in
1202
     * @return {Array}
1203
     */
1204
    Array.prototype.map = function(fn, context) {
1205
      var result = [ ];
1206
      for (var i = 0, len = this.length >>> 0; i < len; i++) {
1207
        if (i in this) {
1208
          result[i] = fn.call(context, this[i], i, this);
1209
        }
1210
      }
1211
      return result;
1212
    };
1213
  }
1214
 
1215
  if (!Array.prototype.every) {
1216
    /**
1217
     * Returns true if a callback returns truthy value for all elements in an array
1218
     * @param {Function} fn Callback to invoke for each element
1219
     * @param {Object} [context] Context to invoke callback in
1220
     * @return {Boolean}
1221
     */
1222
    Array.prototype.every = function(fn, context) {
1223
      for (var i = 0, len = this.length >>> 0; i < len; i++) {
1224
        if (i in this && !fn.call(context, this[i], i, this)) {
1225
          return false;
1226
        }
1227
      }
1228
      return true;
1229
    };
1230
  }
1231
 
1232
  if (!Array.prototype.some) {
1233
    /**
1234
     * Returns true if a callback returns truthy value for at least one element in an array
1235
     * @param {Function} fn Callback to invoke for each element
1236
     * @param {Object} [context] Context to invoke callback in
1237
     * @return {Boolean}
1238
     */
1239
    Array.prototype.some = function(fn, context) {
1240
      for (var i = 0, len = this.length >>> 0; i < len; i++) {
1241
        if (i in this && fn.call(context, this[i], i, this)) {
1242
          return true;
1243
        }
1244
      }
1245
      return false;
1246
    };
1247
  }
1248
 
1249
  if (!Array.prototype.filter) {
1250
    /**
1251
     * Returns the result of iterating over elements in an array
1252
     * @param {Function} fn Callback to invoke for each element
1253
     * @param {Object} [context] Context to invoke callback in
1254
     * @return {Array}
1255
     */
1256
    Array.prototype.filter = function(fn, context) {
1257
      var result = [ ], val;
1258
      for (var i = 0, len = this.length >>> 0; i < len; i++) {
1259
        if (i in this) {
1260
          val = this[i]; // in case fn mutates this
1261
          if (fn.call(context, val, i, this)) {
1262
            result.push(val);
1263
          }
1264
        }
1265
      }
1266
      return result;
1267
    };
1268
  }
1269
 
1270
  if (!Array.prototype.reduce) {
1271
    /**
1272
     * Returns "folded" (reduced) result of iterating over elements in an array
1273
     * @param {Function} fn Callback to invoke for each element
1274
     * @param {Object} [initial] Object to use as the first argument to the first call of the callback
1275
     * @return {Any}
1276
     */
1277
    Array.prototype.reduce = function(fn /*, initial*/) {
1278
      var len = this.length >>> 0,
1279
          i = 0,
1280
          rv;
1281
 
1282
      if (arguments.length > 1) {
1283
        rv = arguments[1];
1284
      }
1285
      else {
1286
        do {
1287
          if (i in this) {
1288
            rv = this[i++];
1289
            break;
1290
          }
1291
          // if array contains no values, no initial value to return
1292
          if (++i >= len) {
1293
            throw new TypeError();
1294
          }
1295
        }
1296
        while (true);
1297
      }
1298
      for (; i < len; i++) {
1299
        if (i in this) {
1300
          rv = fn.call(null, rv, this[i], i, this);
1301
        }
1302
      }
1303
      return rv;
1304
    };
1305
  }
1306
 
1307
  /* _ES5_COMPAT_END_ */
1308
 
1309
  /**
1310
   * Invokes method on all items in a given array
1311
   * @memberOf fabric.util.array
1312
   * @param {Array} array Array to iterate over
1313
   * @param {String} method Name of a method to invoke
1314
   * @return {Array}
1315
   */
1316
  function invoke(array, method) {
1317
    var args = slice.call(arguments, 2), result = [ ];
1318
    for (var i = 0, len = array.length; i < len; i++) {
1319
      result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]);
1320
    }
1321
    return result;
1322
  }
1323
 
1324
  /**
1325
   * Finds maximum value in array (not necessarily "first" one)
1326
   * @memberOf fabric.util.array
1327
   * @param {Array} array Array to iterate over
1328
   * @param {String} byProperty
1329
   * @return {Any}
1330
   */
1331
  function max(array, byProperty) {
1332
    return find(array, byProperty, function(value1, value2) {
1333
      return value1 >= value2;
1334
    });
1335
  }
1336
 
1337
  /**
1338
   * Finds minimum value in array (not necessarily "first" one)
1339
   * @memberOf fabric.util.array
1340
   * @param {Array} array Array to iterate over
1341
   * @param {String} byProperty
1342
   * @return {Any}
1343
   */
1344
  function min(array, byProperty) {
1345
    return find(array, byProperty, function(value1, value2) {
1346
      return value1 < value2;
1347
    });
1348
  }
1349
 
1350
  /**
1351
   * @private
1352
   */
1353
  function find(array, byProperty, condition) {
1354
    if (!array || array.length === 0) {
1355
      return;
1356
    }
1357
 
1358
    var i = array.length - 1,
1359
        result = byProperty ? array[i][byProperty] : array[i];
1360
    if (byProperty) {
1361
      while (i--) {
1362
        if (condition(array[i][byProperty], result)) {
1363
          result = array[i][byProperty];
1364
        }
1365
      }
1366
    }
1367
    else {
1368
      while (i--) {
1369
        if (condition(array[i], result)) {
1370
          result = array[i];
1371
        }
1372
      }
1373
    }
1374
    return result;
1375
  }
1376
 
1377
  /**
1378
   * @namespace fabric.util.array
1379
   */
1380
  fabric.util.array = {
1381
    invoke: invoke,
1382
    min: min,
1383
    max: max
1384
  };
1385
 
1386
})();
1387
 
1388
 
1389
(function() {
1390
 
1391
  /**
1392
   * Copies all enumerable properties of one object to another
1393
   * @memberOf fabric.util.object
1394
   * @param {Object} destination Where to copy to
1395
   * @param {Object} source Where to copy from
1396
   * @return {Object}
1397
   */
1398
  function extend(destination, source) {
1399
    // JScript DontEnum bug is not taken care of
1400
    for (var property in source) {
1401
      destination[property] = source[property];
1402
    }
1403
    return destination;
1404
  }
1405
 
1406
  /**
1407
   * Creates an empty object and copies all enumerable properties of another object to it
1408
   * @memberOf fabric.util.object
1409
   * @param {Object} object Object to clone
1410
   * @return {Object}
1411
   */
1412
  function clone(object) {
1413
    return extend({ }, object);
1414
  }
1415
 
1416
  /** @namespace fabric.util.object */
1417
  fabric.util.object = {
1418
    extend: extend,
1419
    clone: clone
1420
  };
1421
 
1422
})();
1423
 
1424
 
1425
(function() {
1426
 
1427
  /* _ES5_COMPAT_START_ */
1428
  if (!String.prototype.trim) {
1429
    /**
1430
     * Trims a string (removing whitespace from the beginning and the end)
1431
     * @function external:String#trim
1432
     * @see <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/Trim">String#trim on MDN</a>
1433
     */
1434
    String.prototype.trim = function () {
1435
      // this trim is not fully ES3 or ES5 compliant, but it should cover most cases for now
1436
      return this.replace(/^[\s\xA0]+/, '').replace(/[\s\xA0]+$/, '');
1437
    };
1438
  }
1439
  /* _ES5_COMPAT_END_ */
1440
 
1441
  /**
1442
   * Camelizes a string
1443
   * @memberOf fabric.util.string
1444
   * @param {String} string String to camelize
1445
   * @return {String} Camelized version of a string
1446
   */
1447
  function camelize(string) {
1448
    return string.replace(/-+(.)?/g, function(match, character) {
1449
      return character ? character.toUpperCase() : '';
1450
    });
1451
  }
1452
 
1453
  /**
1454
   * Capitalizes a string
1455
   * @memberOf fabric.util.string
1456
   * @param {String} string String to capitalize
1457
   * @param {Boolean} [firstLetterOnly] If true only first letter is capitalized
1458
   * and other letters stay untouched, if false first letter is capitalized
1459
   * and other letters are converted to lowercase.
1460
   * @return {String} Capitalized version of a string
1461
   */
1462
  function capitalize(string, firstLetterOnly) {
1463
    return string.charAt(0).toUpperCase() +
1464
      (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase());
1465
  }
1466
 
1467
  /**
1468
   * Escapes XML in a string
1469
   * @memberOf fabric.util.string
1470
   * @param {String} string String to escape
1471
   * @return {String} Escaped version of a string
1472
   */
1473
  function escapeXml(string) {
1474
    return string.replace(/&/g, '&amp;')
1475
       .replace(/"/g, '&quot;')
1476
       .replace(/'/g, '&apos;')
1477
       .replace(/</g, '&lt;')
1478
       .replace(/>/g, '&gt;');
1479
  }
1480
 
1481
  /**
1482
   * String utilities
1483
   * @namespace fabric.util.string
1484
   */
1485
  fabric.util.string = {
1486
    camelize: camelize,
1487
    capitalize: capitalize,
1488
    escapeXml: escapeXml
1489
  };
1490
}());
1491
 
1492
 
1493
/* _ES5_COMPAT_START_ */
1494
(function() {
1495
 
1496
  var slice = Array.prototype.slice,
1497
      apply = Function.prototype.apply,
1498
      Dummy = function() { };
1499
 
1500
  if (!Function.prototype.bind) {
1501
    /**
1502
     * Cross-browser approximation of ES5 Function.prototype.bind (not fully spec conforming)
1503
     * @see <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind">Function#bind on MDN</a>
1504
     * @param {Object} thisArg Object to bind function to
1505
     * @param {Any[]} [...] Values to pass to a bound function
1506
     * @return {Function}
1507
     */
1508
    Function.prototype.bind = function(thisArg) {
1509
      var _this = this, args = slice.call(arguments, 1), bound;
1510
      if (args.length) {
1511
        bound = function() {
1512
          return apply.call(_this, this instanceof Dummy ? this : thisArg, args.concat(slice.call(arguments)));
1513
        };
1514
      }
1515
      else {
1516
        /** @ignore */
1517
        bound = function() {
1518
          return apply.call(_this, this instanceof Dummy ? this : thisArg, arguments);
1519
        };
1520
      }
1521
      Dummy.prototype = this.prototype;
1522
      bound.prototype = new Dummy();
1523
 
1524
      return bound;
1525
    };
1526
  }
1527
 
1528
})();
1529
/* _ES5_COMPAT_END_ */
1530
 
1531
 
1532
(function() {
1533
 
1534
  var slice = Array.prototype.slice, emptyFunction = function() { },
1535
 
1536
      IS_DONTENUM_BUGGY = (function() {
1537
        for (var p in { toString: 1 }) {
1538
          if (p === 'toString') {
1539
            return false;
1540
          }
1541
        }
1542
        return true;
1543
      })(),
1544
 
1545
      /** @ignore */
1546
      addMethods = function(klass, source, parent) {
1547
        for (var property in source) {
1548
 
1549
          if (property in klass.prototype &&
1550
              typeof klass.prototype[property] === 'function' &&
1551
              (source[property] + '').indexOf('callSuper') > -1) {
1552
 
1553
            klass.prototype[property] = (function(property) {
1554
              return function() {
1555
 
1556
                var superclass = this.constructor.superclass;
1557
                this.constructor.superclass = parent;
1558
                var returnValue = source[property].apply(this, arguments);
1559
                this.constructor.superclass = superclass;
1560
 
1561
                if (property !== 'initialize') {
1562
                  return returnValue;
1563
                }
1564
              };
1565
            })(property);
1566
          }
1567
          else {
1568
            klass.prototype[property] = source[property];
1569
          }
1570
 
1571
          if (IS_DONTENUM_BUGGY) {
1572
            if (source.toString !== Object.prototype.toString) {
1573
              klass.prototype.toString = source.toString;
1574
            }
1575
            if (source.valueOf !== Object.prototype.valueOf) {
1576
              klass.prototype.valueOf = source.valueOf;
1577
            }
1578
          }
1579
        }
1580
      };
1581
 
1582
  function Subclass() { }
1583
 
1584
  function callSuper(methodName) {
1585
    var fn = this.constructor.superclass.prototype[methodName];
1586
    return (arguments.length > 1)
1587
      ? fn.apply(this, slice.call(arguments, 1))
1588
      : fn.call(this);
1589
  }
1590
 
1591
  /**
1592
   * Helper for creation of "classes".
1593
   * @memberOf fabric.util
1594
   * @param {Function} [parent] optional "Class" to inherit from
1595
   * @param {Object} [properties] Properties shared by all instances of this class
1596
   *                  (be careful modifying objects defined here as this would affect all instances)
1597
   */
1598
  function createClass() {
1599
    var parent = null,
1600
        properties = slice.call(arguments, 0);
1601
 
1602
    if (typeof properties[0] === 'function') {
1603
      parent = properties.shift();
1604
    }
1605
    function klass() {
1606
      this.initialize.apply(this, arguments);
1607
    }
1608
 
1609
    klass.superclass = parent;
1610
    klass.subclasses = [ ];
1611
 
1612
    if (parent) {
1613
      Subclass.prototype = parent.prototype;
1614
      klass.prototype = new Subclass();
1615
      parent.subclasses.push(klass);
1616
    }
1617
    for (var i = 0, length = properties.length; i < length; i++) {
1618
      addMethods(klass, properties[i], parent);
1619
    }
1620
    if (!klass.prototype.initialize) {
1621
      klass.prototype.initialize = emptyFunction;
1622
    }
1623
    klass.prototype.constructor = klass;
1624
    klass.prototype.callSuper = callSuper;
1625
    return klass;
1626
  }
1627
 
1628
  fabric.util.createClass = createClass;
1629
})();
1630
 
1631
 
1632
(function () {
1633
 
1634
  var unknown = 'unknown';
1635
 
1636
  /* EVENT HANDLING */
1637
 
1638
  function areHostMethods(object) {
1639
    var methodNames = Array.prototype.slice.call(arguments, 1),
1640
        t, i, len = methodNames.length;
1641
    for (i = 0; i < len; i++) {
1642
      t = typeof object[methodNames[i]];
1643
      if (!(/^(?:function|object|unknown)$/).test(t)) {
1644
        return false;
1645
      }
1646
    }
1647
    return true;
1648
  }
1649
 
1650
  /** @ignore */
1651
  var getElement,
1652
      setElement,
1653
      getUniqueId = (function () {
1654
        var uid = 0;
1655
        return function (element) {
1656
          return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++);
1657
        };
1658
      })();
1659
 
1660
  (function () {
1661
    var elements = { };
1662
    /** @ignore */
1663
    getElement = function (uid) {
1664
      return elements[uid];
1665
    };
1666
    /** @ignore */
1667
    setElement = function (uid, element) {
1668
      elements[uid] = element;
1669
    };
1670
  })();
1671
 
1672
  function createListener(uid, handler) {
1673
    return {
1674
      handler: handler,
1675
      wrappedHandler: createWrappedHandler(uid, handler)
1676
    };
1677
  }
1678
 
1679
  function createWrappedHandler(uid, handler) {
1680
    return function (e) {
1681
      handler.call(getElement(uid), e || fabric.window.event);
1682
    };
1683
  }
1684
 
1685
  function createDispatcher(uid, eventName) {
1686
    return function (e) {
1687
      if (handlers[uid] && handlers[uid][eventName]) {
1688
        var handlersForEvent = handlers[uid][eventName];
1689
        for (var i = 0, len = handlersForEvent.length; i < len; i++) {
1690
          handlersForEvent[i].call(this, e || fabric.window.event);
1691
        }
1692
      }
1693
    };
1694
  }
1695
 
1696
  var shouldUseAddListenerRemoveListener = (
1697
        areHostMethods(fabric.document.documentElement, 'addEventListener', 'removeEventListener') &&
1698
        areHostMethods(fabric.window, 'addEventListener', 'removeEventListener')),
1699
 
1700
      shouldUseAttachEventDetachEvent = (
1701
        areHostMethods(fabric.document.documentElement, 'attachEvent', 'detachEvent') &&
1702
        areHostMethods(fabric.window, 'attachEvent', 'detachEvent')),
1703
 
1704
      // IE branch
1705
      listeners = { },
1706
 
1707
      // DOM L0 branch
1708
      handlers = { },
1709
 
1710
      addListener, removeListener;
1711
 
1712
  if (shouldUseAddListenerRemoveListener) {
1713
    /** @ignore */
1714
    addListener = function (element, eventName, handler) {
1715
      element.addEventListener(eventName, handler, false);
1716
    };
1717
    /** @ignore */
1718
    removeListener = function (element, eventName, handler) {
1719
      element.removeEventListener(eventName, handler, false);
1720
    };
1721
  }
1722
 
1723
  else if (shouldUseAttachEventDetachEvent) {
1724
    /** @ignore */
1725
    addListener = function (element, eventName, handler) {
1726
      var uid = getUniqueId(element);
1727
      setElement(uid, element);
1728
      if (!listeners[uid]) {
1729
        listeners[uid] = { };
1730
      }
1731
      if (!listeners[uid][eventName]) {
1732
        listeners[uid][eventName] = [ ];
1733
 
1734
      }
1735
      var listener = createListener(uid, handler);
1736
      listeners[uid][eventName].push(listener);
1737
      element.attachEvent('on' + eventName, listener.wrappedHandler);
1738
    };
1739
    /** @ignore */
1740
    removeListener = function (element, eventName, handler) {
1741
      var uid = getUniqueId(element), listener;
1742
      if (listeners[uid] && listeners[uid][eventName]) {
1743
        for (var i = 0, len = listeners[uid][eventName].length; i < len; i++) {
1744
          listener = listeners[uid][eventName][i];
1745
          if (listener && listener.handler === handler) {
1746
            element.detachEvent('on' + eventName, listener.wrappedHandler);
1747
            listeners[uid][eventName][i] = null;
1748
          }
1749
        }
1750
      }
1751
    };
1752
  }
1753
  else {
1754
    /** @ignore */
1755
    addListener = function (element, eventName, handler) {
1756
      var uid = getUniqueId(element);
1757
      if (!handlers[uid]) {
1758
        handlers[uid] = { };
1759
      }
1760
      if (!handlers[uid][eventName]) {
1761
        handlers[uid][eventName] = [ ];
1762
        var existingHandler = element['on' + eventName];
1763
        if (existingHandler) {
1764
          handlers[uid][eventName].push(existingHandler);
1765
        }
1766
        element['on' + eventName] = createDispatcher(uid, eventName);
1767
      }
1768
      handlers[uid][eventName].push(handler);
1769
    };
1770
    /** @ignore */
1771
    removeListener = function (element, eventName, handler) {
1772
      var uid = getUniqueId(element);
1773
      if (handlers[uid] && handlers[uid][eventName]) {
1774
        var handlersForEvent = handlers[uid][eventName];
1775
        for (var i = 0, len = handlersForEvent.length; i < len; i++) {
1776
          if (handlersForEvent[i] === handler) {
1777
            handlersForEvent.splice(i, 1);
1778
          }
1779
        }
1780
      }
1781
    };
1782
  }
1783
 
1784
  /**
1785
   * Adds an event listener to an element
1786
   * @function
1787
   * @memberOf fabric.util
1788
   * @param {HTMLElement} element
1789
   * @param {String} eventName
1790
   * @param {Function} handler
1791
   */
1792
  fabric.util.addListener = addListener;
1793
 
1794
  /**
1795
   * Removes an event listener from an element
1796
   * @function
1797
   * @memberOf fabric.util
1798
   * @param {HTMLElement} element
1799
   * @param {String} eventName
1800
   * @param {Function} handler
1801
   */
1802
  fabric.util.removeListener = removeListener;
1803
 
1804
  /**
1805
   * Cross-browser wrapper for getting event's coordinates
1806
   * @memberOf fabric.util
1807
   * @param {Event} event Event object
1808
   * @param {HTMLCanvasElement} upperCanvasEl &lt;canvas> element on which object selection is drawn
1809
   */
1810
  function getPointer(event, upperCanvasEl) {
1811
    event || (event = fabric.window.event);
1812
 
1813
    var element = event.target ||
1814
                  (typeof event.srcElement !== unknown ? event.srcElement : null),
1815
 
1816
        scroll = fabric.util.getScrollLeftTop(element, upperCanvasEl);
1817
 
1818
    return {
1819
      x: pointerX(event) + scroll.left,
1820
      y: pointerY(event) + scroll.top
1821
    };
1822
  }
1823
 
1824
  var pointerX = function(event) {
1825
    // looks like in IE (<9) clientX at certain point (apparently when mouseup fires on VML element)
1826
    // is represented as COM object, with all the consequences, like "unknown" type and error on [[Get]]
1827
    // need to investigate later
1828
    return (typeof event.clientX !== unknown ? event.clientX : 0);
1829
  },
1830
 
1831
  pointerY = function(event) {
1832
    return (typeof event.clientY !== unknown ? event.clientY : 0);
1833
  };
1834
 
1835
  function _getPointer(event, pageProp, clientProp) {
1836
    var touchProp = event.type === 'touchend' ? 'changedTouches' : 'touches';
1837
 
1838
    return (event[touchProp] && event[touchProp][0]
1839
      ? (event[touchProp][0][pageProp] - (event[touchProp][0][pageProp] - event[touchProp][0][clientProp]))
1840
        || event[clientProp]
1841
      : event[clientProp]);
1842
  }
1843
 
1844
  if (fabric.isTouchSupported) {
1845
    pointerX = function(event) {
1846
      return _getPointer(event, 'pageX', 'clientX');
1847
    };
1848
    pointerY = function(event) {
1849
      return _getPointer(event, 'pageY', 'clientY');
1850
    };
1851
  }
1852
 
1853
  fabric.util.getPointer = getPointer;
1854
 
1855
  fabric.util.object.extend(fabric.util, fabric.Observable);
1856
 
1857
})();
1858
 
1859
 
1860
(function () {
1861
 
1862
  /**
1863
   * Cross-browser wrapper for setting element's style
1864
   * @memberOf fabric.util
1865
   * @param {HTMLElement} element
1866
   * @param {Object} styles
1867
   * @return {HTMLElement} Element that was passed as a first argument
1868
   */
1869
  function setStyle(element, styles) {
1870
    var elementStyle = element.style;
1871
    if (!elementStyle) {
1872
      return element;
1873
    }
1874
    if (typeof styles === 'string') {
1875
      element.style.cssText += ';' + styles;
1876
      return styles.indexOf('opacity') > -1
1877
        ? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1])
1878
        : element;
1879
    }
1880
    for (var property in styles) {
1881
      if (property === 'opacity') {
1882
        setOpacity(element, styles[property]);
1883
      }
1884
      else {
1885
        var normalizedProperty = (property === 'float' || property === 'cssFloat')
1886
          ? (typeof elementStyle.styleFloat === 'undefined' ? 'cssFloat' : 'styleFloat')
1887
          : property;
1888
        elementStyle[normalizedProperty] = styles[property];
1889
      }
1890
    }
1891
    return element;
1892
  }
1893
 
1894
  var parseEl = fabric.document.createElement('div'),
1895
      supportsOpacity = typeof parseEl.style.opacity === 'string',
1896
      supportsFilters = typeof parseEl.style.filter === 'string',
1897
      reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,
1898
 
1899
      /** @ignore */
1900
      setOpacity = function (element) { return element; };
1901
 
1902
  if (supportsOpacity) {
1903
    /** @ignore */
1904
    setOpacity = function(element, value) {
1905
      element.style.opacity = value;
1906
      return element;
1907
    };
1908
  }
1909
  else if (supportsFilters) {
1910
    /** @ignore */
1911
    setOpacity = function(element, value) {
1912
      var es = element.style;
1913
      if (element.currentStyle && !element.currentStyle.hasLayout) {
1914
        es.zoom = 1;
1915
      }
1916
      if (reOpacity.test(es.filter)) {
1917
        value = value >= 0.9999 ? '' : ('alpha(opacity=' + (value * 100) + ')');
1918
        es.filter = es.filter.replace(reOpacity, value);
1919
      }
1920
      else {
1921
        es.filter += ' alpha(opacity=' + (value * 100) + ')';
1922
      }
1923
      return element;
1924
    };
1925
  }
1926
 
1927
  fabric.util.setStyle = setStyle;
1928
 
1929
})();
1930
 
1931
 
1932
(function() {
1933
 
1934
  var _slice = Array.prototype.slice;
1935
 
1936
  /**
1937
   * Takes id and returns an element with that id (if one exists in a document)
1938
   * @memberOf fabric.util
1939
   * @param {String|HTMLElement} id
1940
   * @return {HTMLElement|null}
1941
   */
1942
  function getById(id) {
1943
    return typeof id === 'string' ? fabric.document.getElementById(id) : id;
1944
  }
1945
 
1946
  var sliceCanConvertNodelists,
1947
      /**
1948
       * Converts an array-like object (e.g. arguments or NodeList) to an array
1949
       * @memberOf fabric.util
1950
       * @param {Object} arrayLike
1951
       * @return {Array}
1952
       */
1953
      toArray = function(arrayLike) {
1954
        return _slice.call(arrayLike, 0);
1955
      };
1956
 
1957
  try {
1958
    sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array;
1959
  }
1960
  catch (err) { }
1961
 
1962
  if (!sliceCanConvertNodelists) {
1963
    toArray = function(arrayLike) {
1964
      var arr = new Array(arrayLike.length), i = arrayLike.length;
1965
      while (i--) {
1966
        arr[i] = arrayLike[i];
1967
      }
1968
      return arr;
1969
    };
1970
  }
1971
 
1972
  /**
1973
   * Creates specified element with specified attributes
1974
   * @memberOf fabric.util
1975
   * @param {String} tagName Type of an element to create
1976
   * @param {Object} [attributes] Attributes to set on an element
1977
   * @return {HTMLElement} Newly created element
1978
   */
1979
  function makeElement(tagName, attributes) {
1980
    var el = fabric.document.createElement(tagName);
1981
    for (var prop in attributes) {
1982
      if (prop === 'class') {
1983
        el.className = attributes[prop];
1984
      }
1985
      else if (prop === 'for') {
1986
        el.htmlFor = attributes[prop];
1987
      }
1988
      else {
1989
        el.setAttribute(prop, attributes[prop]);
1990
      }
1991
    }
1992
    return el;
1993
  }
1994
 
1995
  /**
1996
   * Adds class to an element
1997
   * @memberOf fabric.util
1998
   * @param {HTMLElement} element Element to add class to
1999
   * @param {String} className Class to add to an element
2000
   */
2001
  function addClass(element, className) {
2002
    if (element && (' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) {
2003
      element.className += (element.className ? ' ' : '') + className;
2004
    }
2005
  }
2006
 
2007
  /**
2008
   * Wraps element with another element
2009
   * @memberOf fabric.util
2010
   * @param {HTMLElement} element Element to wrap
2011
   * @param {HTMLElement|String} wrapper Element to wrap with
2012
   * @param {Object} [attributes] Attributes to set on a wrapper
2013
   * @return {HTMLElement} wrapper
2014
   */
2015
  function wrapElement(element, wrapper, attributes) {
2016
    if (typeof wrapper === 'string') {
2017
      wrapper = makeElement(wrapper, attributes);
2018
    }
2019
    if (element.parentNode) {
2020
      element.parentNode.replaceChild(wrapper, element);
2021
    }
2022
    wrapper.appendChild(element);
2023
    return wrapper;
2024
  }
2025
 
2026
  /**
2027
   * Returns element scroll offsets
2028
   * @memberOf fabric.util
2029
   * @param {HTMLElement} element Element to operate on
2030
   * @param {HTMLElement} upperCanvasEl Upper canvas element
2031
   * @return {Object} Object with left/top values
2032
   */
2033
  function getScrollLeftTop(element, upperCanvasEl) {
2034
 
2035
    var firstFixedAncestor,
2036
        origElement,
2037
        left = 0,
2038
        top = 0,
2039
        docElement = fabric.document.documentElement,
2040
        body = fabric.document.body || {
2041
          scrollLeft: 0, scrollTop: 0
2042
        };
2043
 
2044
    origElement = element;
2045
 
2046
    while (element && element.parentNode && !firstFixedAncestor) {
2047
 
2048
      element = element.parentNode;
2049
 
2050
      if (element.nodeType === 1 &&
2051
          fabric.util.getElementStyle(element, 'position') === 'fixed') {
2052
        firstFixedAncestor = element;
2053
      }
2054
 
2055
      if (element.nodeType === 1 &&
2056
          origElement !== upperCanvasEl &&
2057
          fabric.util.getElementStyle(element, 'position') === 'absolute') {
2058
        left = 0;
2059
        top = 0;
2060
      }
2061
      else if (element === fabric.document) {
2062
        left = body.scrollLeft || docElement.scrollLeft || 0;
2063
        top = body.scrollTop ||  docElement.scrollTop || 0;
2064
      }
2065
      else {
2066
        left += element.scrollLeft || 0;
2067
        top += element.scrollTop || 0;
2068
      }
2069
    }
2070
 
2071
    return { left: left, top: top };
2072
  }
2073
 
2074
  /**
2075
   * Returns offset for a given element
2076
   * @function
2077
   * @memberOf fabric.util
2078
   * @param {HTMLElement} element Element to get offset for
2079
   * @return {Object} Object with "left" and "top" properties
2080
   */
2081
  function getElementOffset(element) {
2082
    var docElem,
2083
        doc = element && element.ownerDocument,
2084
        box = { left: 0, top: 0 },
2085
        offset = { left: 0, top: 0 },
2086
        scrollLeftTop,
2087
        offsetAttributes = {
2088
          borderLeftWidth: 'left',
2089
          borderTopWidth:  'top',
2090
          paddingLeft:     'left',
2091
          paddingTop:      'top'
2092
        };
2093
 
2094
    if (!doc) {
2095
      return { left: 0, top: 0 };
2096
    }
2097
 
2098
    for (var attr in offsetAttributes) {
2099
      offset[offsetAttributes[attr]] += parseInt(getElementStyle(element, attr), 10) || 0;
2100
    }
2101
 
2102
    docElem = doc.documentElement;
2103
    if ( typeof element.getBoundingClientRect !== 'undefined' ) {
2104
      box = element.getBoundingClientRect();
2105
    }
2106
 
2107
    scrollLeftTop = fabric.util.getScrollLeftTop(element, null);
2108
 
2109
    return {
2110
      left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left,
2111
      top: box.top + scrollLeftTop.top - (docElem.clientTop || 0)  + offset.top
2112
    };
2113
  }
2114
 
2115
  /**
2116
  * Returns style attribute value of a given element
2117
  * @memberOf fabric.util
2118
  * @param {HTMLElement} element Element to get style attribute for
2119
  * @param {String} attr Style attribute to get for element
2120
  * @return {String} Style attribute value of the given element.
2121
  */
2122
  var getElementStyle;
2123
  if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) {
2124
    getElementStyle = function(element, attr) {
2125
      var style = fabric.document.defaultView.getComputedStyle(element, null);
2126
      return style ? style[attr] : undefined;
2127
    };
2128
  }
2129
  else {
2130
    getElementStyle = function(element, attr) {
2131
      var value = element.style[attr];
2132
      if (!value && element.currentStyle) {
2133
        value = element.currentStyle[attr];
2134
      }
2135
      return value;
2136
    };
2137
  }
2138
 
2139
  (function () {
2140
    var style = fabric.document.documentElement.style,
2141
        selectProp = 'userSelect' in style
2142
          ? 'userSelect'
2143
          : 'MozUserSelect' in style
2144
            ? 'MozUserSelect'
2145
            : 'WebkitUserSelect' in style
2146
              ? 'WebkitUserSelect'
2147
              : 'KhtmlUserSelect' in style
2148
                ? 'KhtmlUserSelect'
2149
                : '';
2150
 
2151
    /**
2152
     * Makes element unselectable
2153
     * @memberOf fabric.util
2154
     * @param {HTMLElement} element Element to make unselectable
2155
     * @return {HTMLElement} Element that was passed in
2156
     */
2157
    function makeElementUnselectable(element) {
2158
      if (typeof element.onselectstart !== 'undefined') {
2159
        element.onselectstart = fabric.util.falseFunction;
2160
      }
2161
      if (selectProp) {
2162
        element.style[selectProp] = 'none';
2163
      }
2164
      else if (typeof element.unselectable === 'string') {
2165
        element.unselectable = 'on';
2166
      }
2167
      return element;
2168
    }
2169
 
2170
    /**
2171
     * Makes element selectable
2172
     * @memberOf fabric.util
2173
     * @param {HTMLElement} element Element to make selectable
2174
     * @return {HTMLElement} Element that was passed in
2175
     */
2176
    function makeElementSelectable(element) {
2177
      if (typeof element.onselectstart !== 'undefined') {
2178
        element.onselectstart = null;
2179
      }
2180
      if (selectProp) {
2181
        element.style[selectProp] = '';
2182
      }
2183
      else if (typeof element.unselectable === 'string') {
2184
        element.unselectable = '';
2185
      }
2186
      return element;
2187
    }
2188
 
2189
    fabric.util.makeElementUnselectable = makeElementUnselectable;
2190
    fabric.util.makeElementSelectable = makeElementSelectable;
2191
  })();
2192
 
2193
  (function() {
2194
 
2195
    /**
2196
     * Inserts a script element with a given url into a document; invokes callback, when that script is finished loading
2197
     * @memberOf fabric.util
2198
     * @param {String} url URL of a script to load
2199
     * @param {Function} callback Callback to execute when script is finished loading
2200
     */
2201
    function getScript(url, callback) {
2202
      var headEl = fabric.document.getElementsByTagName('head')[0],
2203
          scriptEl = fabric.document.createElement('script'),
2204
          loading = true;
2205
 
2206
      /** @ignore */
2207
      scriptEl.onload = /** @ignore */ scriptEl.onreadystatechange = function(e) {
2208
        if (loading) {
2209
          if (typeof this.readyState === 'string' &&
2210
              this.readyState !== 'loaded' &&
2211
              this.readyState !== 'complete') {
2212
            return;
2213
          }
2214
          loading = false;
2215
          callback(e || fabric.window.event);
2216
          scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null;
2217
        }
2218
      };
2219
      scriptEl.src = url;
2220
      headEl.appendChild(scriptEl);
2221
      // causes issue in Opera
2222
      // headEl.removeChild(scriptEl);
2223
    }
2224
 
2225
    fabric.util.getScript = getScript;
2226
  })();
2227
 
2228
  fabric.util.getById = getById;
2229
  fabric.util.toArray = toArray;
2230
  fabric.util.makeElement = makeElement;
2231
  fabric.util.addClass = addClass;
2232
  fabric.util.wrapElement = wrapElement;
2233
  fabric.util.getScrollLeftTop = getScrollLeftTop;
2234
  fabric.util.getElementOffset = getElementOffset;
2235
  fabric.util.getElementStyle = getElementStyle;
2236
 
2237
})();
2238
 
2239
 
2240
(function() {
2241
 
2242
  function addParamToUrl(url, param) {
2243
    return url + (/\?/.test(url) ? '&' : '?') + param;
2244
  }
2245
 
2246
  var makeXHR = (function() {
2247
    var factories = [
2248
      function() { return new ActiveXObject('Microsoft.XMLHTTP'); },
2249
      function() { return new ActiveXObject('Msxml2.XMLHTTP'); },
2250
      function() { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); },
2251
      function() { return new XMLHttpRequest(); }
2252
    ];
2253
    for (var i = factories.length; i--; ) {
2254
      try {
2255
        var req = factories[i]();
2256
        if (req) {
2257
          return factories[i];
2258
        }
2259
      }
2260
      catch (err) { }
2261
    }
2262
  })();
2263
 
2264
  function emptyFn() { }
2265
 
2266
  /**
2267
   * Cross-browser abstraction for sending XMLHttpRequest
2268
   * @memberOf fabric.util
2269
   * @param {String} url URL to send XMLHttpRequest to
2270
   * @param {Object} [options] Options object
2271
   * @param {String} [options.method="GET"]
2272
   * @param {Function} options.onComplete Callback to invoke when request is completed
2273
   * @return {XMLHttpRequest} request
2274
   */
2275
  function request(url, options) {
2276
 
2277
    options || (options = { });
2278
 
2279
    var method = options.method ? options.method.toUpperCase() : 'GET',
2280
        onComplete = options.onComplete || function() { },
2281
        xhr = makeXHR(),
2282
        body;
2283
 
2284
    /** @ignore */
2285
    xhr.onreadystatechange = function() {
2286
      if (xhr.readyState === 4) {
2287
        onComplete(xhr);
2288
        xhr.onreadystatechange = emptyFn;
2289
      }
2290
    };
2291
 
2292
    if (method === 'GET') {
2293
      body = null;
2294
      if (typeof options.parameters === 'string') {
2295
        url = addParamToUrl(url, options.parameters);
2296
      }
2297
    }
2298
 
2299
    xhr.open(method, url, true);
2300
 
2301
    if (method === 'POST' || method === 'PUT') {
2302
      xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
2303
    }
2304
 
2305
    xhr.send(body);
2306
    return xhr;
2307
  }
2308
 
2309
  fabric.util.request = request;
2310
})();
2311
 
2312
 
2313
/**
2314
 * Wrapper around `console.log` (when available)
2315
 * @param {Any} [values] Values to log
2316
 */
2317
fabric.log = function() { };
2318
 
2319
/**
2320
 * Wrapper around `console.warn` (when available)
2321
 * @param {Any} [values] Values to log as a warning
2322
 */
2323
fabric.warn = function() { };
2324
 
2325
if (typeof console !== 'undefined') {
2326
 
2327
  ['log', 'warn'].forEach(function(methodName) {
2328
 
2329
    if (typeof console[methodName] !== 'undefined' &&
2330
        typeof console[methodName].apply === 'function') {
2331
 
2332
      fabric[methodName] = function() {
2333
        return console[methodName].apply(console, arguments);
2334
      };
2335
    }
2336
  });
2337
}
2338
 
2339
 
2340
(function() {
2341
 
2342
  /**
2343
   * Changes value from one to another within certain period of time, invoking callbacks as value is being changed.
2344
   * @memberOf fabric.util
2345
   * @param {Object} [options] Animation options
2346
   * @param {Function} [options.onChange] Callback; invoked on every value change
2347
   * @param {Function} [options.onComplete] Callback; invoked when value change is completed
2348
   * @param {Number} [options.startValue=0] Starting value
2349
   * @param {Number} [options.endValue=100] Ending value
2350
   * @param {Number} [options.byValue=100] Value to modify the property by
2351
   * @param {Function} [options.easing] Easing function
2352
   * @param {Number} [options.duration=500] Duration of change (in ms)
2353
   */
2354
  function animate(options) {
2355
 
2356
    requestAnimFrame(function(timestamp) {
2357
      options || (options = { });
2358
 
2359
      var start = timestamp || +new Date(),
2360
          duration = options.duration || 500,
2361
          finish = start + duration, time,
2362
          onChange = options.onChange || function() { },
2363
          abort = options.abort || function() { return false; },
2364
          easing = options.easing || function(t, b, c, d) {return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;},
2365
          startValue = 'startValue' in options ? options.startValue : 0,
2366
          endValue = 'endValue' in options ? options.endValue : 100,
2367
          byValue = options.byValue || endValue - startValue;
2368
 
2369
      options.onStart && options.onStart();
2370
 
2371
      (function tick(ticktime) {
2372
        time = ticktime || +new Date();
2373
        var currentTime = time > finish ? duration : (time - start);
2374
        if (abort()) {
2375
          options.onComplete && options.onComplete();
2376
          return;
2377
        }
2378
        onChange(easing(currentTime, startValue, byValue, duration));
2379
        if (time > finish) {
2380
          options.onComplete && options.onComplete();
2381
          return;
2382
        }
2383
        requestAnimFrame(tick);
2384
      })(start);
2385
    });
2386
 
2387
  }
2388
 
2389
  var _requestAnimFrame = fabric.window.requestAnimationFrame       ||
2390
                          fabric.window.webkitRequestAnimationFrame ||
2391
                          fabric.window.mozRequestAnimationFrame    ||
2392
                          fabric.window.oRequestAnimationFrame      ||
2393
                          fabric.window.msRequestAnimationFrame     ||
2394
                          function(callback) {
2395
                            fabric.window.setTimeout(callback, 1000 / 60);
2396
                          };
2397
  /**
2398
    * requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/
2399
    * In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method
2400
    * @memberOf fabric.util
2401
    * @param {Function} callback Callback to invoke
2402
    * @param {DOMElement} element optional Element to associate with animation
2403
    */
2404
  function requestAnimFrame() {
2405
    return _requestAnimFrame.apply(fabric.window, arguments);
2406
  }
2407
 
2408
  fabric.util.animate = animate;
2409
  fabric.util.requestAnimFrame = requestAnimFrame;
2410
 
2411
})();
2412
 
2413
 
2414
(function() {
2415
 
2416
  function normalize(a, c, p, s) {
2417
    if (a < Math.abs(c)) {
2418
      a = c;
2419
      s = p / 4;
2420
    }
2421
    else {
2422
      s = p / (2 * Math.PI) * Math.asin(c / a);
2423
    }
2424
    return { a: a, c: c, p: p, s: s };
2425
  }
2426
 
2427
  function elastic(opts, t, d) {
2428
    return opts.a *
2429
      Math.pow(2, 10 * (t -= 1)) *
2430
      Math.sin( (t * d - opts.s) * (2 * Math.PI) / opts.p );
2431
  }
2432
 
2433
  /**
2434
   * Cubic easing out
2435
   * @memberOf fabric.util.ease
2436
   */
2437
  function easeOutCubic(t, b, c, d) {
2438
    return c * ((t = t / d - 1) * t * t + 1) + b;
2439
  }
2440
 
2441
  /**
2442
   * Cubic easing in and out
2443
   * @memberOf fabric.util.ease
2444
   */
2445
  function easeInOutCubic(t, b, c, d) {
2446
    t /= d/2;
2447
    if (t < 1) {
2448
      return c / 2 * t * t * t + b;
2449
    }
2450
    return c / 2 * ((t -= 2) * t * t + 2) + b;
2451
  }
2452
 
2453
  /**
2454
   * Quartic easing in
2455
   * @memberOf fabric.util.ease
2456
   */
2457
  function easeInQuart(t, b, c, d) {
2458
    return c * (t /= d) * t * t * t + b;
2459
  }
2460
 
2461
  /**
2462
   * Quartic easing out
2463
   * @memberOf fabric.util.ease
2464
   */
2465
  function easeOutQuart(t, b, c, d) {
2466
    return -c * ((t = t / d - 1) * t * t * t - 1) + b;
2467
  }
2468
 
2469
  /**
2470
   * Quartic easing in and out
2471
   * @memberOf fabric.util.ease
2472
   */
2473
  function easeInOutQuart(t, b, c, d) {
2474
    t /= d / 2;
2475
    if (t < 1) {
2476
      return c / 2 * t * t * t * t + b;
2477
    }
2478
    return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
2479
  }
2480
 
2481
  /**
2482
   * Quintic easing in
2483
   * @memberOf fabric.util.ease
2484
   */
2485
  function easeInQuint(t, b, c, d) {
2486
    return c * (t /= d) * t * t * t * t + b;
2487
  }
2488
 
2489
  /**
2490
   * Quintic easing out
2491
   * @memberOf fabric.util.ease
2492
   */
2493
  function easeOutQuint(t, b, c, d) {
2494
    return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
2495
  }
2496
 
2497
  /**
2498
   * Quintic easing in and out
2499
   * @memberOf fabric.util.ease
2500
   */
2501
  function easeInOutQuint(t, b, c, d) {
2502
    t /= d / 2;
2503
    if (t < 1) {
2504
      return c / 2 * t * t * t * t * t + b;
2505
    }
2506
    return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
2507
  }
2508
 
2509
  /**
2510
   * Sinusoidal easing in
2511
   * @memberOf fabric.util.ease
2512
   */
2513
  function easeInSine(t, b, c, d) {
2514
    return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
2515
  }
2516
 
2517
  /**
2518
   * Sinusoidal easing out
2519
   * @memberOf fabric.util.ease
2520
   */
2521
  function easeOutSine(t, b, c, d) {
2522
    return c * Math.sin(t / d * (Math.PI / 2)) + b;
2523
  }
2524
 
2525
  /**
2526
   * Sinusoidal easing in and out
2527
   * @memberOf fabric.util.ease
2528
   */
2529
  function easeInOutSine(t, b, c, d) {
2530
    return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
2531
  }
2532
 
2533
  /**
2534
   * Exponential easing in
2535
   * @memberOf fabric.util.ease
2536
   */
2537
  function easeInExpo(t, b, c, d) {
2538
    return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b;
2539
  }
2540
 
2541
  /**
2542
   * Exponential easing out
2543
   * @memberOf fabric.util.ease
2544
   */
2545
  function easeOutExpo(t, b, c, d) {
2546
    return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
2547
  }
2548
 
2549
  /**
2550
   * Exponential easing in and out
2551
   * @memberOf fabric.util.ease
2552
   */
2553
  function easeInOutExpo(t, b, c, d) {
2554
    if (t === 0) {
2555
      return b;
2556
    }
2557
    if (t === d) {
2558
      return b + c;
2559
    }
2560
    t /= d / 2;
2561
    if (t < 1) {
2562
      return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
2563
    }
2564
    return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;
2565
  }
2566
 
2567
  /**
2568
   * Circular easing in
2569
   * @memberOf fabric.util.ease
2570
   */
2571
  function easeInCirc(t, b, c, d) {
2572
    return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
2573
  }
2574
 
2575
  /**
2576
   * Circular easing out
2577
   * @memberOf fabric.util.ease
2578
   */
2579
  function easeOutCirc(t, b, c, d) {
2580
    return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
2581
  }
2582
 
2583
  /**
2584
   * Circular easing in and out
2585
   * @memberOf fabric.util.ease
2586
   */
2587
  function easeInOutCirc(t, b, c, d) {
2588
    t /= d / 2;
2589
    if (t < 1) {
2590
      return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;
2591
    }
2592
    return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
2593
  }
2594
 
2595
  /**
2596
   * Elastic easing in
2597
   * @memberOf fabric.util.ease
2598
   */
2599
  function easeInElastic(t, b, c, d) {
2600
    var s = 1.70158, p = 0, a = c;
2601
    if (t === 0) {
2602
      return b;
2603
    }
2604
    t /= d;
2605
    if (t === 1) {
2606
      return b + c;
2607
    }
2608
    if (!p) {
2609
      p = d * 0.3;
2610
    }
2611
    var opts = normalize(a, c, p, s);
2612
    return -elastic(opts, t, d) + b;
2613
  }
2614
 
2615
  /**
2616
   * Elastic easing out
2617
   * @memberOf fabric.util.ease
2618
   */
2619
  function easeOutElastic(t, b, c, d) {
2620
    var s = 1.70158, p = 0, a = c;
2621
    if (t === 0) {
2622
      return b;
2623
    }
2624
    t /= d;
2625
    if (t === 1) {
2626
      return b + c;
2627
    }
2628
    if (!p) {
2629
      p = d * 0.3;
2630
    }
2631
    var opts = normalize(a, c, p, s);
2632
    return opts.a * Math.pow(2, -10 * t) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) + opts.c + b;
2633
  }
2634
 
2635
  /**
2636
   * Elastic easing in and out
2637
   * @memberOf fabric.util.ease
2638
   */
2639
  function easeInOutElastic(t, b, c, d) {
2640
    var s = 1.70158, p = 0, a = c;
2641
    if (t === 0) {
2642
      return b;
2643
    }
2644
    t /= d / 2;
2645
    if (t === 2) {
2646
      return b + c;
2647
    }
2648
    if (!p) {
2649
      p = d * (0.3 * 1.5);
2650
    }
2651
    var opts = normalize(a, c, p, s);
2652
    if (t < 1) {
2653
      return -0.5 * elastic(opts, t, d) + b;
2654
    }
2655
    return opts.a * Math.pow(2, -10 * (t -= 1)) *
2656
      Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) * 0.5 + opts.c + b;
2657
  }
2658
 
2659
  /**
2660
   * Backwards easing in
2661
   * @memberOf fabric.util.ease
2662
   */
2663
  function easeInBack(t, b, c, d, s) {
2664
    if (s === undefined) {
2665
      s = 1.70158;
2666
    }
2667
    return c * (t /= d) * t * ((s + 1) * t - s) + b;
2668
  }
2669
 
2670
  /**
2671
   * Backwards easing out
2672
   * @memberOf fabric.util.ease
2673
   */
2674
  function easeOutBack(t, b, c, d, s) {
2675
    if (s === undefined) {
2676
      s = 1.70158;
2677
    }
2678
    return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
2679
  }
2680
 
2681
  /**
2682
   * Backwards easing in and out
2683
   * @memberOf fabric.util.ease
2684
   */
2685
  function easeInOutBack(t, b, c, d, s) {
2686
    if (s === undefined) {
2687
      s = 1.70158;
2688
    }
2689
    t /= d / 2;
2690
    if (t < 1) {
2691
      return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
2692
    }
2693
    return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
2694
  }
2695
 
2696
  /**
2697
   * Bouncing easing in
2698
   * @memberOf fabric.util.ease
2699
   */
2700
  function easeInBounce(t, b, c, d) {
2701
    return c - easeOutBounce (d - t, 0, c, d) + b;
2702
  }
2703
 
2704
  /**
2705
   * Bouncing easing out
2706
   * @memberOf fabric.util.ease
2707
   */
2708
  function easeOutBounce(t, b, c, d) {
2709
    if ((t /= d) < (1 / 2.75)) {
2710
      return c * (7.5625 * t * t) + b;
2711
    }
2712
    else if (t < (2/2.75)) {
2713
      return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b;
2714
    }
2715
    else if (t < (2.5/2.75)) {
2716
      return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b;
2717
    }
2718
    else {
2719
      return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b;
2720
    }
2721
  }
2722
 
2723
  /**
2724
   * Bouncing easing in and out
2725
   * @memberOf fabric.util.ease
2726
   */
2727
  function easeInOutBounce(t, b, c, d) {
2728
    if (t < d / 2) {
2729
      return easeInBounce (t * 2, 0, c, d) * 0.5 + b;
2730
    }
2731
    return easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;
2732
  }
2733
 
2734
  /**
2735
   * Easing functions
2736
   * See <a href="http://gizma.com/easing/">Easing Equations by Robert Penner</a>
2737
   * @namespace fabric.util.ease
2738
   */
2739
  fabric.util.ease = {
2740
 
2741
    /**
2742
     * Quadratic easing in
2743
     * @memberOf fabric.util.ease
2744
     */
2745
    easeInQuad: function(t, b, c, d) {
2746
      return c * (t /= d) * t + b;
2747
    },
2748
 
2749
    /**
2750
     * Quadratic easing out
2751
     * @memberOf fabric.util.ease
2752
     */
2753
    easeOutQuad: function(t, b, c, d) {
2754
      return -c * (t /= d) * (t - 2) + b;
2755
    },
2756
 
2757
    /**
2758
     * Quadratic easing in and out
2759
     * @memberOf fabric.util.ease
2760
     */
2761
    easeInOutQuad: function(t, b, c, d) {
2762
      t /= (d / 2);
2763
      if (t < 1) {
2764
        return c / 2 * t * t + b;
2765
      }
2766
      return -c / 2 * ((--t) * (t - 2) - 1) + b;
2767
    },
2768
 
2769
    /**
2770
     * Cubic easing in
2771
     * @memberOf fabric.util.ease
2772
     */
2773
    easeInCubic: function(t, b, c, d) {
2774
      return c * (t /= d) * t * t + b;
2775
    },
2776
 
2777
    easeOutCubic: easeOutCubic,
2778
    easeInOutCubic: easeInOutCubic,
2779
    easeInQuart: easeInQuart,
2780
    easeOutQuart: easeOutQuart,
2781
    easeInOutQuart: easeInOutQuart,
2782
    easeInQuint: easeInQuint,
2783
    easeOutQuint: easeOutQuint,
2784
    easeInOutQuint: easeInOutQuint,
2785
    easeInSine: easeInSine,
2786
    easeOutSine: easeOutSine,
2787
    easeInOutSine: easeInOutSine,
2788
    easeInExpo: easeInExpo,
2789
    easeOutExpo: easeOutExpo,
2790
    easeInOutExpo: easeInOutExpo,
2791
    easeInCirc: easeInCirc,
2792
    easeOutCirc: easeOutCirc,
2793
    easeInOutCirc: easeInOutCirc,
2794
    easeInElastic: easeInElastic,
2795
    easeOutElastic: easeOutElastic,
2796
    easeInOutElastic: easeInOutElastic,
2797
    easeInBack: easeInBack,
2798
    easeOutBack: easeOutBack,
2799
    easeInOutBack: easeInOutBack,
2800
    easeInBounce: easeInBounce,
2801
    easeOutBounce: easeOutBounce,
2802
    easeInOutBounce: easeInOutBounce
2803
  };
2804
 
2805
}());
2806
 
2807
 
2808
(function(global) {
2809
 
2810
  'use strict';
2811
 
2812
  /**
2813
   * @name fabric
2814
   * @namespace
2815
   */
2816
 
2817
  var fabric = global.fabric || (global.fabric = { }),
2818
      extend = fabric.util.object.extend,
2819
      capitalize = fabric.util.string.capitalize,
2820
      clone = fabric.util.object.clone,
2821
      toFixed = fabric.util.toFixed,
2822
      parseUnit = fabric.util.parseUnit,
2823
      multiplyTransformMatrices = fabric.util.multiplyTransformMatrices,
2824
 
2825
      attributesMap = {
2826
        cx:                   'left',
2827
        x:                    'left',
2828
        r:                    'radius',
2829
        cy:                   'top',
2830
        y:                    'top',
2831
        display:              'visible',
2832
        visibility:           'visible',
2833
        transform:            'transformMatrix',
2834
        'fill-opacity':       'fillOpacity',
2835
        'fill-rule':          'fillRule',
2836
        'font-family':        'fontFamily',
2837
        'font-size':          'fontSize',
2838
        'font-style':         'fontStyle',
2839
        'font-weight':        'fontWeight',
2840
        'stroke-dasharray':   'strokeDashArray',
2841
        'stroke-linecap':     'strokeLineCap',
2842
        'stroke-linejoin':    'strokeLineJoin',
2843
        'stroke-miterlimit':  'strokeMiterLimit',
2844
        'stroke-opacity':     'strokeOpacity',
2845
        'stroke-width':       'strokeWidth',
2846
        'text-decoration':    'textDecoration',
2847
        'text-anchor':        'originX'
2848
      },
2849
 
2850
      colorAttributes = {
2851
        stroke: 'strokeOpacity',
2852
        fill:   'fillOpacity'
2853
      };
2854
 
2855
  fabric.cssRules = { };
2856
  fabric.gradientDefs = { };
2857
 
2858
  function normalizeAttr(attr) {
2859
    // transform attribute names
2860
    if (attr in attributesMap) {
2861
      return attributesMap[attr];
2862
    }
2863
    return attr;
2864
  }
2865
 
2866
  function normalizeValue(attr, value, parentAttributes, fontSize) {
2867
    var isArray = Object.prototype.toString.call(value) === '[object Array]',
2868
        parsed;
2869
 
2870
    if ((attr === 'fill' || attr === 'stroke') && value === 'none') {
2871
      value = '';
2872
    }
2873
    else if (attr === 'strokeDashArray') {
2874
      value = value.replace(/,/g, ' ').split(/\s+/).map(function(n) {
2875
        return parseFloat(n);
2876
      });
2877
    }
2878
    else if (attr === 'transformMatrix') {
2879
      if (parentAttributes && parentAttributes.transformMatrix) {
2880
        value = multiplyTransformMatrices(
2881
          parentAttributes.transformMatrix, fabric.parseTransformAttribute(value));
2882
      }
2883
      else {
2884
        value = fabric.parseTransformAttribute(value);
2885
      }
2886
    }
2887
    else if (attr === 'visible') {
2888
      value = (value === 'none' || value === 'hidden') ? false : true;
2889
      // display=none on parent element always takes precedence over child element
2890
      if (parentAttributes && parentAttributes.visible === false) {
2891
        value = false;
2892
      }
2893
    }
2894
    else if (attr === 'originX' /* text-anchor */) {
2895
      value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center';
2896
    }
2897
    else {
2898
      parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize);
2899
    }
2900
 
2901
    return (!isArray && isNaN(parsed) ? value : parsed);
2902
  }
2903
 
2904
  /**
2905
   * @private
2906
   * @param {Object} attributes Array of attributes to parse
2907
   */
2908
  function _setStrokeFillOpacity(attributes) {
2909
    for (var attr in colorAttributes) {
2910
 
2911
      if (!attributes[attr] || typeof attributes[colorAttributes[attr]] === 'undefined') {
2912
        continue;
2913
      }
2914
 
2915
      if (attributes[attr].indexOf('url(') === 0) {
2916
        continue;
2917
      }
2918
 
2919
      var color = new fabric.Color(attributes[attr]);
2920
      attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba();
2921
    }
2922
    return attributes;
2923
  }
2924
 
2925
  /**
2926
   * Parses "transform" attribute, returning an array of values
2927
   * @static
2928
   * @function
2929
   * @memberOf fabric
2930
   * @param {String} attributeValue String containing attribute value
2931
   * @return {Array} Array of 6 elements representing transformation matrix
2932
   */
2933
  fabric.parseTransformAttribute = (function() {
2934
    function rotateMatrix(matrix, args) {
2935
      var angle = args[0];
2936
 
2937
      matrix[0] = Math.cos(angle);
2938
      matrix[1] = Math.sin(angle);
2939
      matrix[2] = -Math.sin(angle);
2940
      matrix[3] = Math.cos(angle);
2941
    }
2942
 
2943
    function scaleMatrix(matrix, args) {
2944
      var multiplierX = args[0],
2945
          multiplierY = (args.length === 2) ? args[1] : args[0];
2946
 
2947
      matrix[0] = multiplierX;
2948
      matrix[3] = multiplierY;
2949
    }
2950
 
2951
    function skewXMatrix(matrix, args) {
2952
      matrix[2] = Math.tan(fabric.util.degreesToRadians(args[0]));
2953
    }
2954
 
2955
    function skewYMatrix(matrix, args) {
2956
      matrix[1] = Math.tan(fabric.util.degreesToRadians(args[0]));
2957
    }
2958
 
2959
    function translateMatrix(matrix, args) {
2960
      matrix[4] = args[0];
2961
      if (args.length === 2) {
2962
        matrix[5] = args[1];
2963
      }
2964
    }
2965
 
2966
    // identity matrix
2967
    var iMatrix = [
2968
          1, // a
2969
          0, // b
2970
          0, // c
2971
          1, // d
2972
          0, // e
2973
 
2974
        ],
2975
 
2976
        // == begin transform regexp
2977
        number = fabric.reNum,
2978
 
2979
        commaWsp = '(?:\\s+,?\\s*|,\\s*)',
2980
 
2981
        skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))',
2982
 
2983
        skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))',
2984
 
2985
        rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' +
2986
                    commaWsp + '(' + number + ')' +
2987
                    commaWsp + '(' + number + '))?\\s*\\))',
2988
 
2989
        scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' +
2990
                    commaWsp + '(' + number + '))?\\s*\\))',
2991
 
2992
        translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' +
2993
                    commaWsp + '(' + number + '))?\\s*\\))',
2994
 
2995
        matrix = '(?:(matrix)\\s*\\(\\s*' +
2996
                  '(' + number + ')' + commaWsp +
2997
                  '(' + number + ')' + commaWsp +
2998
                  '(' + number + ')' + commaWsp +
2999
                  '(' + number + ')' + commaWsp +
3000
                  '(' + number + ')' + commaWsp +
3001
                  '(' + number + ')' +
3002
                  '\\s*\\))',
3003
 
3004
        transform = '(?:' +
3005
                    matrix + '|' +
3006
                    translate + '|' +
3007
                    scale + '|' +
3008
                    rotate + '|' +
3009
                    skewX + '|' +
3010
                    skewY +
3011
                    ')',
3012
 
3013
        transforms = '(?:' + transform + '(?:' + commaWsp + transform + ')*' + ')',
3014
 
3015
        transformList = '^\\s*(?:' + transforms + '?)\\s*$',
3016
 
3017
        // http://www.w3.org/TR/SVG/coords.html#TransformAttribute
3018
        reTransformList = new RegExp(transformList),
3019
        // == end transform regexp
3020
 
3021
        reTransform = new RegExp(transform, 'g');
3022
 
3023
    return function(attributeValue) {
3024
 
3025
      // start with identity matrix
3026
      var matrix = iMatrix.concat(),
3027
          matrices = [ ];
3028
 
3029
      // return if no argument was given or
3030
      // an argument does not match transform attribute regexp
3031
      if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) {
3032
        return matrix;
3033
      }
3034
 
3035
      attributeValue.replace(reTransform, function(match) {
3036
 
3037
        var m = new RegExp(transform).exec(match).filter(function (match) {
3038
              return (match !== '' && match != null);
3039
            }),
3040
            operation = m[1],
3041
            args = m.slice(2).map(parseFloat);
3042
 
3043
        switch (operation) {
3044
          case 'translate':
3045
            translateMatrix(matrix, args);
3046
            break;
3047
          case 'rotate':
3048
            args[0] = fabric.util.degreesToRadians(args[0]);
3049
            rotateMatrix(matrix, args);
3050
            break;
3051
          case 'scale':
3052
            scaleMatrix(matrix, args);
3053
            break;
3054
          case 'skewX':
3055
            skewXMatrix(matrix, args);
3056
            break;
3057
          case 'skewY':
3058
            skewYMatrix(matrix, args);
3059
            break;
3060
          case 'matrix':
3061
            matrix = args;
3062
            break;
3063
        }
3064
 
3065
        // snapshot current matrix into matrices array
3066
        matrices.push(matrix.concat());
3067
        // reset
3068
        matrix = iMatrix.concat();
3069
      });
3070
 
3071
      var combinedMatrix = matrices[0];
3072
      while (matrices.length > 1) {
3073
        matrices.shift();
3074
        combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]);
3075
      }
3076
      return combinedMatrix;
3077
    };
3078
  })();
3079
 
3080
  /**
3081
   * @private
3082
   */
3083
  function parseStyleString(style, oStyle) {
3084
    var attr, value;
3085
    style.replace(/;$/, '').split(';').forEach(function (chunk) {
3086
      var pair = chunk.split(':');
3087
 
3088
      attr = normalizeAttr(pair[0].trim().toLowerCase());
3089
      value = normalizeValue(attr, pair[1].trim());
3090
 
3091
      oStyle[attr] = value;
3092
    });
3093
  }
3094
 
3095
  /**
3096
   * @private
3097
   */
3098
  function parseStyleObject(style, oStyle) {
3099
    var attr, value;
3100
    for (var prop in style) {
3101
      if (typeof style[prop] === 'undefined') {
3102
        continue;
3103
      }
3104
 
3105
      attr = normalizeAttr(prop.toLowerCase());
3106
      value = normalizeValue(attr, style[prop]);
3107
 
3108
      oStyle[attr] = value;
3109
    }
3110
  }
3111
 
3112
  /**
3113
   * @private
3114
   */
3115
  function getGlobalStylesForElement(element, svgUid) {
3116
    var styles = { };
3117
    for (var rule in fabric.cssRules[svgUid]) {
3118
      if (elementMatchesRule(element, rule.split(' '))) {
3119
        for (var property in fabric.cssRules[svgUid][rule]) {
3120
          styles[property] = fabric.cssRules[svgUid][rule][property];
3121
        }
3122
      }
3123
    }
3124
    return styles;
3125
  }
3126
 
3127
  /**
3128
   * @private
3129
   */
3130
  function elementMatchesRule(element, selectors) {
3131
    var firstMatching, parentMatching = true;
3132
    //start from rightmost selector.
3133
    firstMatching = selectorMatches(element, selectors.pop());
3134
    if (firstMatching && selectors.length) {
3135
      parentMatching = doesSomeParentMatch(element, selectors);
3136
    }
3137
    return firstMatching && parentMatching && (selectors.length === 0);
3138
  }
3139
 
3140
  function doesSomeParentMatch(element, selectors) {
3141
    var selector, parentMatching = true;
3142
    while (element.parentNode && element.parentNode.nodeType === 1 && selectors.length) {
3143
      if (parentMatching) {
3144
        selector = selectors.pop();
3145
      }
3146
      element = element.parentNode;
3147
      parentMatching = selectorMatches(element, selector);
3148
    }
3149
    return selectors.length === 0;
3150
  }
3151
  /**
3152
   * @private
3153
   */
3154
  function selectorMatches(element, selector) {
3155
    var nodeName = element.nodeName,
3156
        classNames = element.getAttribute('class'),
3157
        id = element.getAttribute('id'), matcher;
3158
    // i check if a selector matches slicing away part from it.
3159
    // if i get empty string i should match
3160
    matcher = new RegExp('^' + nodeName, 'i');
3161
    selector = selector.replace(matcher, '');
3162
    if (id && selector.length) {
3163
      matcher = new RegExp('#' + id + '(?![a-zA-Z\\-]+)', 'i');
3164
      selector = selector.replace(matcher, '');
3165
    }
3166
    if (classNames && selector.length) {
3167
      classNames = classNames.split(' ');
3168
      for (var i = classNames.length; i--;) {
3169
        matcher = new RegExp('\\.' + classNames[i] + '(?![a-zA-Z\\-]+)', 'i');
3170
        selector = selector.replace(matcher, '');
3171
      }
3172
    }
3173
    return selector.length === 0;
3174
  }
3175
 
3176
  /**
3177
   * @private
3178
   */
3179
  function parseUseDirectives(doc) {
3180
    var nodelist = doc.getElementsByTagName('use');
3181
    while (nodelist.length) {
3182
      var el = nodelist[0],
3183
          xlink = el.getAttribute('xlink:href').substr(1),
3184
          x = el.getAttribute('x') || 0,
3185
          y = el.getAttribute('y') || 0,
3186
          el2 = doc.getElementById(xlink).cloneNode(true),
3187
          currentTrans = (el2.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')',
3188
          parentNode;
3189
 
3190
      for (var j = 0, attrs = el.attributes, l = attrs.length; j < l; j++) {
3191
        var attr = attrs.item(j);
3192
        if (attr.nodeName === 'x' || attr.nodeName === 'y' || attr.nodeName === 'xlink:href') {
3193
          continue;
3194
        }
3195
 
3196
        if (attr.nodeName === 'transform') {
3197
          currentTrans = attr.nodeValue + ' ' + currentTrans;
3198
        }
3199
        else {
3200
          el2.setAttribute(attr.nodeName, attr.nodeValue);
3201
        }
3202
      }
3203
 
3204
      el2.setAttribute('transform', currentTrans);
3205
      el2.setAttribute('instantiated_by_use', '1');
3206
      el2.removeAttribute('id');
3207
      parentNode = el.parentNode;
3208
      parentNode.replaceChild(el2, el);
3209
    }
3210
  }
3211
 
3212
  // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
3213
  // matches, e.g.: +14.56e-12, etc.
3214
  var reViewBoxAttrValue = new RegExp(
3215
    '^' +
3216
    '\\s*(' + fabric.reNum + '+)\\s*,?' +
3217
    '\\s*(' + fabric.reNum + '+)\\s*,?' +
3218
    '\\s*(' + fabric.reNum + '+)\\s*,?' +
3219
    '\\s*(' + fabric.reNum + '+)\\s*' +
3220
    '$'
3221
  );
3222
 
3223
  /**
3224
   * Add a <g> element that envelop all child elements and makes the viewbox transformMatrix descend on all elements
3225
   */
3226
  function addVBTransform(element, widthAttr, heightAttr) {
3227
 
3228
    var viewBoxAttr = element.getAttribute('viewBox'),
3229
        scaleX = 1,
3230
        scaleY = 1,
3231
        minX = 0,
3232
        minY = 0,
3233
        viewBoxWidth, viewBoxHeight, matrix, el;
3234
 
3235
    if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) {
3236
      minX = -parseFloat(viewBoxAttr[1]),
3237
      minY = -parseFloat(viewBoxAttr[2]),
3238
      viewBoxWidth = parseFloat(viewBoxAttr[3]),
3239
      viewBoxHeight = parseFloat(viewBoxAttr[4]);
3240
    }
3241
    else {
3242
      return;
3243
    }
3244
    if (widthAttr && widthAttr !== viewBoxWidth) {
3245
      scaleX = widthAttr / viewBoxWidth;
3246
    }
3247
    if (heightAttr && heightAttr !== viewBoxHeight) {
3248
      scaleY = heightAttr / viewBoxHeight;
3249
    }
3250
 
3251
    // default is to preserve aspect ratio
3252
    // preserveAspectRatio attribute to be implemented
3253
    scaleY = scaleX = (scaleX > scaleY ? scaleY : scaleX);
3254
 
3255
    if (!(scaleX !== 1 || scaleY !== 1 || minX !== 0 || minY !== 0)) {
3256
      return;
3257
    }
3258
    matrix = ' matrix(' + scaleX +
3259
                  ' 0' +
3260
                  ' 0 ' +
3261
                  scaleY + ' ' +
3262
                  (minX * scaleX) + ' ' +
3263
                  (minY * scaleY) + ') ';
3264
 
3265
    if (element.tagName === 'svg') {
3266
      el = element.ownerDocument.createElement('g');
3267
      while (element.firstChild != null) {
3268
        el.appendChild(element.firstChild);
3269
      }
3270
      element.appendChild(el);
3271
    }
3272
    else {
3273
      el = element;
3274
      matrix = el.getAttribute('transform') + matrix;
3275
    }
3276
 
3277
    el.setAttribute('transform', matrix);
3278
  }
3279
 
3280
  /**
3281
   * Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback
3282
   * @static
3283
   * @function
3284
   * @memberOf fabric
3285
   * @param {SVGDocument} doc SVG document to parse
3286
   * @param {Function} callback Callback to call when parsing is finished; It's being passed an array of elements (parsed from a document).
3287
   * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
3288
   */
3289
  fabric.parseSVGDocument = (function() {
3290
 
3291
    var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/,
3292
        reViewBoxTagNames = /^(symbol|image|marker|pattern|view)$/;
3293
 
3294
    function hasAncestorWithNodeName(element, nodeName) {
3295
      while (element && (element = element.parentNode)) {
3296
        if (nodeName.test(element.nodeName) && !element.getAttribute('instantiated_by_use')) {
3297
          return true;
3298
        }
3299
      }
3300
      return false;
3301
    }
3302
 
3303
    return function(doc, callback, reviver) {
3304
      if (!doc) {
3305
        return;
3306
      }
3307
 
3308
      parseUseDirectives(doc);
3309
 
3310
      var startTime = new Date(),
3311
          svgUid =  fabric.Object.__uid++,
3312
          widthAttr, heightAttr, toBeParsed = false;
3313
 
3314
      if (doc.getAttribute('width') && doc.getAttribute('width') !== '100%') {
3315
        widthAttr = parseUnit(doc.getAttribute('width'));
3316
      }
3317
      if (doc.getAttribute('height') && doc.getAttribute('height') !== '100%') {
3318
        heightAttr = parseUnit(doc.getAttribute('height'));
3319
      }
3320
 
3321
      if (!widthAttr || !heightAttr) {
3322
        var viewBoxAttr = doc.getAttribute('viewBox');
3323
        if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) {
3324
          widthAttr = parseFloat(viewBoxAttr[3]),
3325
          heightAttr = parseFloat(viewBoxAttr[4]);
3326
        }
3327
        else {
3328
          toBeParsed = true;
3329
        }
3330
      }
3331
 
3332
      addVBTransform(doc, widthAttr, heightAttr);
3333
 
3334
      var descendants = fabric.util.toArray(doc.getElementsByTagName('*'));
3335
 
3336
      if (descendants.length === 0 && fabric.isLikelyNode) {
3337
        // we're likely in node, where "o3-xml" library fails to gEBTN("*")
3338
        // https://github.com/ajaxorg/node-o3-xml/issues/21
3339
        descendants = doc.selectNodes('//*[name(.)!="svg"]');
3340
        var arr = [ ];
3341
        for (var i = 0, len = descendants.length; i < len; i++) {
3342
          arr[i] = descendants[i];
3343
        }
3344
        descendants = arr;
3345
      }
3346
 
3347
      var elements = descendants.filter(function(el) {
3348
        reViewBoxTagNames.test(el.tagName) && addVBTransform(el, 0, 0);
3349
        return reAllowedSVGTagNames.test(el.tagName) &&
3350
              !hasAncestorWithNodeName(el, /^(?:pattern|defs|symbol)$/); // http://www.w3.org/TR/SVG/struct.html#DefsElement
3351
      });
3352
 
3353
      if (!elements || (elements && !elements.length)) {
3354
        callback && callback([], {});
3355
        return;
3356
      }
3357
 
3358
      var options = {
3359
        width: widthAttr,
3360
        height: heightAttr,
3361
        svgUid: svgUid,
3362
        toBeParsed: toBeParsed
3363
      };
3364
 
3365
      fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc);
3366
      fabric.cssRules[svgUid] = fabric.getCSSRules(doc);
3367
      // Precedence of rules:   style > class > attribute
3368
      fabric.parseElements(elements, function(instances) {
3369
        fabric.documentParsingTime = new Date() - startTime;
3370
        if (callback) {
3371
          callback(instances, options);
3372
        }
3373
      }, clone(options), reviver);
3374
    };
3375
  })();
3376
 
3377
  /**
3378
   * Used for caching SVG documents (loaded via `fabric.Canvas#loadSVGFromURL`)
3379
   * @namespace
3380
   */
3381
  var svgCache = {
3382
 
3383
    /**
3384
    * @param {String} name
3385
    * @param {Function} callback
3386
    */
3387
    has: function (name, callback) {
3388
      callback(false);
3389
    },
3390
 
3391
    get: function () {
3392
      /* NOOP */
3393
    },
3394
 
3395
    set: function () {
3396
      /* NOOP */
3397
    }
3398
  };
3399
 
3400
  /**
3401
   * @private
3402
   */
3403
  function _enlivenCachedObject(cachedObject) {
3404
 
3405
    var objects = cachedObject.objects,
3406
        options = cachedObject.options;
3407
 
3408
    objects = objects.map(function (o) {
3409
      return fabric[capitalize(o.type)].fromObject(o);
3410
    });
3411
 
3412
    return ({ objects: objects, options: options });
3413
  }
3414
 
3415
  /**
3416
   * @private
3417
   */
3418
  function _createSVGPattern(markup, canvas, property) {
3419
    if (canvas[property] && canvas[property].toSVG) {
3420
      markup.push(
3421
        '<pattern x="0" y="0" id="', property, 'Pattern" ',
3422
          'width="', canvas[property].source.width,
3423
          '" height="', canvas[property].source.height,
3424
          '" patternUnits="userSpaceOnUse">',
3425
        '<image x="0" y="0" ',
3426
        'width="', canvas[property].source.width,
3427
        '" height="', canvas[property].source.height,
3428
        '" xlink:href="', canvas[property].source.src,
3429
        '"></image></pattern>'
3430
      );
3431
    }
3432
  }
3433
 
3434
  var reFontDeclaration = new RegExp(
3435
    '(normal|italic)?\\s*(normal|small-caps)?\\s*' +
3436
    '(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\\s*(' +
3437
      fabric.reNum +
3438
    '(?:px|cm|mm|em|pt|pc|in)*)(?:\\/(normal|' + fabric.reNum + '))?\\s+(.*)');
3439
 
3440
  extend(fabric, {
3441
    /**
3442
     * Parses a short font declaration, building adding its properties to a style object
3443
     * @static
3444
     * @function
3445
     * @memberOf fabric
3446
     * @param {String} value font declaration
3447
     * @param {Object} oStyle definition
3448
     */
3449
    parseFontDeclaration: function(value, oStyle) {
3450
      var match = value.match(reFontDeclaration);
3451
 
3452
      if (!match) {
3453
        return;
3454
      }
3455
      var fontStyle = match[1],
3456
          // font variant is not used
3457
          // fontVariant = match[2],
3458
          fontWeight = match[3],
3459
          fontSize = match[4],
3460
          lineHeight = match[5],
3461
          fontFamily = match[6];
3462
 
3463
      if (fontStyle) {
3464
        oStyle.fontStyle = fontStyle;
3465
      }
3466
      if (fontWeight) {
3467
        oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight);
3468
      }
3469
      if (fontSize) {
3470
        oStyle.fontSize = parseUnit(fontSize);
3471
      }
3472
      if (fontFamily) {
3473
        oStyle.fontFamily = fontFamily;
3474
      }
3475
      if (lineHeight) {
3476
        oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight;
3477
      }
3478
    },
3479
 
3480
    /**
3481
     * Parses an SVG document, returning all of the gradient declarations found in it
3482
     * @static
3483
     * @function
3484
     * @memberOf fabric
3485
     * @param {SVGDocument} doc SVG document to parse
3486
     * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element
3487
     */
3488
    getGradientDefs: function(doc) {
3489
      var linearGradientEls = doc.getElementsByTagName('linearGradient'),
3490
          radialGradientEls = doc.getElementsByTagName('radialGradient'),
3491
          el, i, j = 0, id, xlink, elList = [ ],
3492
          gradientDefs = { }, idsToXlinkMap = { };
3493
 
3494
      elList.length = linearGradientEls.length + radialGradientEls.length;
3495
      i = linearGradientEls.length;
3496
      while (i--) {
3497
        elList[j++] = linearGradientEls[i];
3498
      }
3499
      i = radialGradientEls.length;
3500
      while (i--) {
3501
        elList[j++] = radialGradientEls[i];
3502
      }
3503
 
3504
      while (j--) {
3505
        el = elList[j];
3506
        xlink = el.getAttribute('xlink:href');
3507
        id = el.getAttribute('id');
3508
        if (xlink) {
3509
          idsToXlinkMap[id] = xlink.substr(1);
3510
        }
3511
        gradientDefs[id] = el;
3512
      }
3513
 
3514
      for (id in idsToXlinkMap) {
3515
        var el2 = gradientDefs[idsToXlinkMap[id]].cloneNode(true);
3516
        el = gradientDefs[id];
3517
        while (el2.firstChild) {
3518
          el.appendChild(el2.firstChild);
3519
        }
3520
      }
3521
      return gradientDefs;
3522
    },
3523
 
3524
    /**
3525
     * Returns an object of attributes' name/value, given element and an array of attribute names;
3526
     * Parses parent "g" nodes recursively upwards.
3527
     * @static
3528
     * @memberOf fabric
3529
     * @param {DOMElement} element Element to parse
3530
     * @param {Array} attributes Array of attributes to parse
3531
     * @return {Object} object containing parsed attributes' names/values
3532
     */
3533
    parseAttributes: function(element, attributes, svgUid) {
3534
 
3535
      if (!element) {
3536
        return;
3537
      }
3538
 
3539
      var value,
3540
          parentAttributes = { },
3541
          fontSize;
3542
 
3543
      if (typeof svgUid === 'undefined') {
3544
        svgUid = element.getAttribute('svgUid');
3545
      }
3546
      // if there's a parent container (`g` or `a` or `symbol` node), parse its attributes recursively upwards
3547
      if (element.parentNode && /^symbol|[g|a]$/i.test(element.parentNode.nodeName)) {
3548
        parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid);
3549
      }
3550
      fontSize = (parentAttributes && parentAttributes.fontSize ) ||
3551
                 element.getAttribute('font-size') || fabric.Text.DEFAULT_SVG_FONT_SIZE;
3552
 
3553
      var ownAttributes = attributes.reduce(function(memo, attr) {
3554
        value = element.getAttribute(attr);
3555
        if (value) {
3556
          attr = normalizeAttr(attr);
3557
          value = normalizeValue(attr, value, parentAttributes, fontSize);
3558
 
3559
          memo[attr] = value;
3560
        }
3561
        return memo;
3562
      }, { });
3563
 
3564
      // add values parsed from style, which take precedence over attributes
3565
      // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes)
3566
      ownAttributes = extend(ownAttributes,
3567
        extend(getGlobalStylesForElement(element, svgUid), fabric.parseStyleAttribute(element)));
3568
      if (ownAttributes.font) {
3569
        fabric.parseFontDeclaration(ownAttributes.font, ownAttributes);
3570
      }
3571
      return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes));
3572
    },
3573
 
3574
    /**
3575
     * Transforms an array of svg elements to corresponding fabric.* instances
3576
     * @static
3577
     * @memberOf fabric
3578
     * @param {Array} elements Array of elements to parse
3579
     * @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements)
3580
     * @param {Object} [options] Options object
3581
     * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
3582
     */
3583
    parseElements: function(elements, callback, options, reviver) {
3584
      new fabric.ElementsParser(elements, callback, options, reviver).parse();
3585
    },
3586
 
3587
    /**
3588
     * Parses "style" attribute, retuning an object with values
3589
     * @static
3590
     * @memberOf fabric
3591
     * @param {SVGElement} element Element to parse
3592
     * @return {Object} Objects with values parsed from style attribute of an element
3593
     */
3594
    parseStyleAttribute: function(element) {
3595
      var oStyle = { },
3596
          style = element.getAttribute('style');
3597
 
3598
      if (!style) {
3599
        return oStyle;
3600
      }
3601
 
3602
      if (typeof style === 'string') {
3603
        parseStyleString(style, oStyle);
3604
      }
3605
      else {
3606
        parseStyleObject(style, oStyle);
3607
      }
3608
 
3609
      return oStyle;
3610
    },
3611
 
3612
    /**
3613
     * Parses "points" attribute, returning an array of values
3614
     * @static
3615
     * @memberOf fabric
3616
     * @param {String} points points attribute string
3617
     * @return {Array} array of points
3618
     */
3619
    parsePointsAttribute: function(points) {
3620
 
3621
      // points attribute is required and must not be empty
3622
      if (!points) {
3623
        return null;
3624
      }
3625
 
3626
      // replace commas with whitespace and remove bookending whitespace
3627
      points = points.replace(/,/g, ' ').trim();
3628
 
3629
      points = points.split(/\s+/);
3630
      var parsedPoints = [ ], i, len;
3631
 
3632
      i = 0;
3633
      len = points.length;
3634
      for (; i < len; i+=2) {
3635
        parsedPoints.push({
3636
          x: parseFloat(points[i]),
3637
          y: parseFloat(points[i + 1])
3638
        });
3639
      }
3640
 
3641
      // odd number of points is an error
3642
      // if (parsedPoints.length % 2 !== 0) {
3643
      //   return null;
3644
      // }
3645
 
3646
      return parsedPoints;
3647
    },
3648
 
3649
    /**
3650
     * Returns CSS rules for a given SVG document
3651
     * @static
3652
     * @function
3653
     * @memberOf fabric
3654
     * @param {SVGDocument} doc SVG document to parse
3655
     * @return {Object} CSS rules of this document
3656
     */
3657
    getCSSRules: function(doc) {
3658
      var styles = doc.getElementsByTagName('style'),
3659
          allRules = { }, rules;
3660
 
3661
      // very crude parsing of style contents
3662
      for (var i = 0, len = styles.length; i < len; i++) {
3663
        var styleContents = styles[i].textContent;
3664
 
3665
        // remove comments
3666
        styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, '');
3667
        if (styleContents.trim() === '') {
3668
          continue;
3669
        }
3670
        rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);
3671
        rules = rules.map(function(rule) { return rule.trim(); });
3672
 
3673
        rules.forEach(function(rule) {
3674
 
3675
          var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/),
3676
          ruleObj = { }, declaration = match[2].trim(),
3677
          propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/);
3678
 
3679
          for (var i = 0, len = propertyValuePairs.length; i < len; i++) {
3680
            var pair = propertyValuePairs[i].split(/\s*:\s*/),
3681
                property = normalizeAttr(pair[0]),
3682
                value = normalizeValue(property, pair[1], pair[0]);
3683
            ruleObj[property] = value;
3684
          }
3685
          rule = match[1];
3686
          rule.split(',').forEach(function(_rule) {
3687
            _rule = _rule.replace(/^svg/i, '').trim();
3688
            if (_rule === '') {
3689
              return;
3690
            }
3691
            allRules[_rule] = fabric.util.object.clone(ruleObj);
3692
          });
3693
        });
3694
      }
3695
      return allRules;
3696
    },
3697
 
3698
    /**
3699
     * Takes url corresponding to an SVG document, and parses it into a set of fabric objects. Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy)
3700
     * @memberof fabric
3701
     * @param {String} url
3702
     * @param {Function} callback
3703
     * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
3704
     */
3705
    loadSVGFromURL: function(url, callback, reviver) {
3706
 
3707
      url = url.replace(/^\n\s*/, '').trim();
3708
      svgCache.has(url, function (hasUrl) {
3709
        if (hasUrl) {
3710
          svgCache.get(url, function (value) {
3711
            var enlivedRecord = _enlivenCachedObject(value);
3712
            callback(enlivedRecord.objects, enlivedRecord.options);
3713
          });
3714
        }
3715
        else {
3716
          new fabric.util.request(url, {
3717
            method: 'get',
3718
            onComplete: onComplete
3719
          });
3720
        }
3721
      });
3722
 
3723
      function onComplete(r) {
3724
 
3725
        var xml = r.responseXML;
3726
        if (xml && !xml.documentElement && fabric.window.ActiveXObject && r.responseText) {
3727
          xml = new ActiveXObject('Microsoft.XMLDOM');
3728
          xml.async = 'false';
3729
          //IE chokes on DOCTYPE
3730
          xml.loadXML(r.responseText.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, ''));
3731
        }
3732
        if (!xml || !xml.documentElement) {
3733
          return;
3734
        }
3735
 
3736
        fabric.parseSVGDocument(xml.documentElement, function (results, options) {
3737
          svgCache.set(url, {
3738
            objects: fabric.util.array.invoke(results, 'toObject'),
3739
            options: options
3740
          });
3741
          callback(results, options);
3742
        }, reviver);
3743
      }
3744
    },
3745
 
3746
    /**
3747
     * Takes string corresponding to an SVG document, and parses it into a set of fabric objects
3748
     * @memberof fabric
3749
     * @param {String} string
3750
     * @param {Function} callback
3751
     * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
3752
     */
3753
    loadSVGFromString: function(string, callback, reviver) {
3754
      string = string.trim();
3755
      var doc;
3756
      if (typeof DOMParser !== 'undefined') {
3757
        var parser = new DOMParser();
3758
        if (parser && parser.parseFromString) {
3759
          doc = parser.parseFromString(string, 'text/xml');
3760
        }
3761
      }
3762
      else if (fabric.window.ActiveXObject) {
3763
        doc = new ActiveXObject('Microsoft.XMLDOM');
3764
        doc.async = 'false';
3765
        // IE chokes on DOCTYPE
3766
        doc.loadXML(string.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, ''));
3767
      }
3768
 
3769
      fabric.parseSVGDocument(doc.documentElement, function (results, options) {
3770
        callback(results, options);
3771
      }, reviver);
3772
    },
3773
 
3774
    /**
3775
     * Creates markup containing SVG font faces
3776
     * @param {Array} objects Array of fabric objects
3777
     * @return {String}
3778
     */
3779
    createSVGFontFacesMarkup: function(objects) {
3780
      var markup = '';
3781
 
3782
      for (var i = 0, len = objects.length; i < len; i++) {
3783
        if (objects[i].type !== 'text' || !objects[i].path) {
3784
          continue;
3785
        }
3786
 
3787
        markup += [
3788
          //jscs:disable validateIndentation
3789
          '@font-face {',
3790
            'font-family: ', objects[i].fontFamily, '; ',
3791
            'src: url(\'', objects[i].path, '\')',
3792
          '}'
3793
          //jscs:enable validateIndentation
3794
        ].join('');
3795
      }
3796
 
3797
      if (markup) {
3798
        markup = [
3799
          //jscs:disable validateIndentation
3800
          '<style type="text/css">',
3801
            '<![CDATA[',
3802
              markup,
3803
            ']]>',
3804
          '</style>'
3805
          //jscs:enable validateIndentation
3806
        ].join('');
3807
      }
3808
 
3809
      return markup;
3810
    },
3811
 
3812
    /**
3813
     * Creates markup containing SVG referenced elements like patterns, gradients etc.
3814
     * @param {fabric.Canvas} canvas instance of fabric.Canvas
3815
     * @return {String}
3816
     */
3817
    createSVGRefElementsMarkup: function(canvas) {
3818
      var markup = [ ];
3819
 
3820
      _createSVGPattern(markup, canvas, 'backgroundColor');
3821
      _createSVGPattern(markup, canvas, 'overlayColor');
3822
 
3823
      return markup.join('');
3824
    }
3825
  });
3826
 
3827
})(typeof exports !== 'undefined' ? exports : this);
3828
 
3829
 
3830
fabric.ElementsParser = function(elements, callback, options, reviver) {
3831
  this.elements = elements;
3832
  this.callback = callback;
3833
  this.options = options;
3834
  this.reviver = reviver;
3835
  this.svgUid = (options && options.svgUid) || 0;
3836
};
3837
 
3838
fabric.ElementsParser.prototype.parse = function() {
3839
  this.instances = new Array(this.elements.length);
3840
  this.numElements = this.elements.length;
3841
 
3842
  this.createObjects();
3843
};
3844
 
3845
fabric.ElementsParser.prototype.createObjects = function() {
3846
  for (var i = 0, len = this.elements.length; i < len; i++) {
3847
    this.elements[i].setAttribute('svgUid', this.svgUid);
3848
    (function(_this, i) {
3849
      setTimeout(function() {
3850
        _this.createObject(_this.elements[i], i);
3851
      }, 0);
3852
    })(this, i);
3853
  }
3854
};
3855
 
3856
fabric.ElementsParser.prototype.createObject = function(el, index) {
3857
  var klass = fabric[fabric.util.string.capitalize(el.tagName)];
3858
  if (klass && klass.fromElement) {
3859
    try {
3860
      this._createObject(klass, el, index);
3861
    }
3862
    catch (err) {
3863
      fabric.log(err);
3864
    }
3865
  }
3866
  else {
3867
    this.checkIfDone();
3868
  }
3869
};
3870
 
3871
fabric.ElementsParser.prototype._createObject = function(klass, el, index) {
3872
  if (klass.async) {
3873
    klass.fromElement(el, this.createCallback(index, el), this.options);
3874
  }
3875
  else {
3876
    var obj = klass.fromElement(el, this.options);
3877
    this.resolveGradient(obj, 'fill');
3878
    this.resolveGradient(obj, 'stroke');
3879
    this.reviver && this.reviver(el, obj);
3880
    this.instances[index] = obj;
3881
    this.checkIfDone();
3882
  }
3883
};
3884
 
3885
fabric.ElementsParser.prototype.createCallback = function(index, el) {
3886
  var _this = this;
3887
  return function(obj) {
3888
    _this.resolveGradient(obj, 'fill');
3889
    _this.resolveGradient(obj, 'stroke');
3890
    _this.reviver && _this.reviver(el, obj);
3891
    _this.instances[index] = obj;
3892
    _this.checkIfDone();
3893
  };
3894
};
3895
 
3896
fabric.ElementsParser.prototype.resolveGradient = function(obj, property) {
3897
 
3898
  var instanceFillValue = obj.get(property);
3899
  if (!(/^url\(/).test(instanceFillValue)) {
3900
    return;
3901
  }
3902
  var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1);
3903
  if (fabric.gradientDefs[this.svgUid][gradientId]) {
3904
    obj.set(property,
3905
      fabric.Gradient.fromElement(fabric.gradientDefs[this.svgUid][gradientId], obj));
3906
  }
3907
};
3908
 
3909
fabric.ElementsParser.prototype.checkIfDone = function() {
3910
  if (--this.numElements === 0) {
3911
    this.instances = this.instances.filter(function(el) {
3912
      return el != null;
3913
    });
3914
    this.callback(this.instances);
3915
  }
3916
};
3917
 
3918
 
3919
(function(global) {
3920
 
3921
  'use strict';
3922
 
3923
  /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */
3924
 
3925
  var fabric = global.fabric || (global.fabric = { });
3926
 
3927
  if (fabric.Point) {
3928
    fabric.warn('fabric.Point is already defined');
3929
    return;
3930
  }
3931
 
3932
  fabric.Point = Point;
3933
 
3934
  /**
3935
   * Point class
3936
   * @class fabric.Point
3937
   * @memberOf fabric
3938
   * @constructor
3939
   * @param {Number} x
3940
   * @param {Number} y
3941
   * @return {fabric.Point} thisArg
3942
   */
3943
  function Point(x, y) {
3944
    this.x = x;
3945
    this.y = y;
3946
  }
3947
 
3948
  Point.prototype = /** @lends fabric.Point.prototype */ {
3949
 
3950
    constructor: Point,
3951
 
3952
    /**
3953
     * Adds another point to this one and returns another one
3954
     * @param {fabric.Point} that
3955
     * @return {fabric.Point} new Point instance with added values
3956
     */
3957
    add: function (that) {
3958
      return new Point(this.x + that.x, this.y + that.y);
3959
    },
3960
 
3961
    /**
3962
     * Adds another point to this one
3963
     * @param {fabric.Point} that
3964
     * @return {fabric.Point} thisArg
3965
     */
3966
    addEquals: function (that) {
3967
      this.x += that.x;
3968
      this.y += that.y;
3969
      return this;
3970
    },
3971
 
3972
    /**
3973
     * Adds value to this point and returns a new one
3974
     * @param {Number} scalar
3975
     * @return {fabric.Point} new Point with added value
3976
     */
3977
    scalarAdd: function (scalar) {
3978
      return new Point(this.x + scalar, this.y + scalar);
3979
    },
3980
 
3981
    /**
3982
     * Adds value to this point
3983
     * @param {Number} scalar
3984
     * @return {fabric.Point} thisArg
3985
     */
3986
    scalarAddEquals: function (scalar) {
3987
      this.x += scalar;
3988
      this.y += scalar;
3989
      return this;
3990
    },
3991
 
3992
    /**
3993
     * Subtracts another point from this point and returns a new one
3994
     * @param {fabric.Point} that
3995
     * @return {fabric.Point} new Point object with subtracted values
3996
     */
3997
    subtract: function (that) {
3998
      return new Point(this.x - that.x, this.y - that.y);
3999
    },
4000
 
4001
    /**
4002
     * Subtracts another point from this point
4003
     * @param {fabric.Point} that
4004
     * @return {fabric.Point} thisArg
4005
     */
4006
    subtractEquals: function (that) {
4007
      this.x -= that.x;
4008
      this.y -= that.y;
4009
      return this;
4010
    },
4011
 
4012
    /**
4013
     * Subtracts value from this point and returns a new one
4014
     * @param {Number} scalar
4015
     * @return {fabric.Point}
4016
     */
4017
    scalarSubtract: function (scalar) {
4018
      return new Point(this.x - scalar, this.y - scalar);
4019
    },
4020
 
4021
    /**
4022
     * Subtracts value from this point
4023
     * @param {Number} scalar
4024
     * @return {fabric.Point} thisArg
4025
     */
4026
    scalarSubtractEquals: function (scalar) {
4027
      this.x -= scalar;
4028
      this.y -= scalar;
4029
      return this;
4030
    },
4031
 
4032
    /**
4033
     * Miltiplies this point by a value and returns a new one
4034
     * @param {Number} scalar
4035
     * @return {fabric.Point}
4036
     */
4037
    multiply: function (scalar) {
4038
      return new Point(this.x * scalar, this.y * scalar);
4039
    },
4040
 
4041
    /**
4042
     * Miltiplies this point by a value
4043
     * @param {Number} scalar
4044
     * @return {fabric.Point} thisArg
4045
     */
4046
    multiplyEquals: function (scalar) {
4047
      this.x *= scalar;
4048
      this.y *= scalar;
4049
      return this;
4050
    },
4051
 
4052
    /**
4053
     * Divides this point by a value and returns a new one
4054
     * @param {Number} scalar
4055
     * @return {fabric.Point}
4056
     */
4057
    divide: function (scalar) {
4058
      return new Point(this.x / scalar, this.y / scalar);
4059
    },
4060
 
4061
    /**
4062
     * Divides this point by a value
4063
     * @param {Number} scalar
4064
     * @return {fabric.Point} thisArg
4065
     */
4066
    divideEquals: function (scalar) {
4067
      this.x /= scalar;
4068
      this.y /= scalar;
4069
      return this;
4070
    },
4071
 
4072
    /**
4073
     * Returns true if this point is equal to another one
4074
     * @param {fabric.Point} that
4075
     * @return {Boolean}
4076
     */
4077
    eq: function (that) {
4078
      return (this.x === that.x && this.y === that.y);
4079
    },
4080
 
4081
    /**
4082
     * Returns true if this point is less than another one
4083
     * @param {fabric.Point} that
4084
     * @return {Boolean}
4085
     */
4086
    lt: function (that) {
4087
      return (this.x < that.x && this.y < that.y);
4088
    },
4089
 
4090
    /**
4091
     * Returns true if this point is less than or equal to another one
4092
     * @param {fabric.Point} that
4093
     * @return {Boolean}
4094
     */
4095
    lte: function (that) {
4096
      return (this.x <= that.x && this.y <= that.y);
4097
    },
4098
 
4099
    /**
4100
 
4101
     * Returns true if this point is greater another one
4102
     * @param {fabric.Point} that
4103
     * @return {Boolean}
4104
     */
4105
    gt: function (that) {
4106
      return (this.x > that.x && this.y > that.y);
4107
    },
4108
 
4109
    /**
4110
     * Returns true if this point is greater than or equal to another one
4111
     * @param {fabric.Point} that
4112
     * @return {Boolean}
4113
     */
4114
    gte: function (that) {
4115
      return (this.x >= that.x && this.y >= that.y);
4116
    },
4117
 
4118
    /**
4119
     * Returns new point which is the result of linear interpolation with this one and another one
4120
     * @param {fabric.Point} that
4121
     * @param {Number} t
4122
     * @return {fabric.Point}
4123
     */
4124
    lerp: function (that, t) {
4125
      return new Point(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t);
4126
    },
4127
 
4128
    /**
4129
     * Returns distance from this point and another one
4130
     * @param {fabric.Point} that
4131
     * @return {Number}
4132
     */
4133
    distanceFrom: function (that) {
4134
      var dx = this.x - that.x,
4135
          dy = this.y - that.y;
4136
      return Math.sqrt(dx * dx + dy * dy);
4137
    },
4138
 
4139
    /**
4140
     * Returns the point between this point and another one
4141
     * @param {fabric.Point} that
4142
     * @return {fabric.Point}
4143
     */
4144
    midPointFrom: function (that) {
4145
      return new Point(this.x + (that.x - this.x)/2, this.y + (that.y - this.y)/2);
4146
    },
4147
 
4148
    /**
4149
     * Returns a new point which is the min of this and another one
4150
     * @param {fabric.Point} that
4151
     * @return {fabric.Point}
4152
     */
4153
    min: function (that) {
4154
      return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y));
4155
    },
4156
 
4157
    /**
4158
     * Returns a new point which is the max of this and another one
4159
     * @param {fabric.Point} that
4160
     * @return {fabric.Point}
4161
     */
4162
    max: function (that) {
4163
      return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y));
4164
    },
4165
 
4166
    /**
4167
     * Returns string representation of this point
4168
     * @return {String}
4169
     */
4170
    toString: function () {
4171
      return this.x + ',' + this.y;
4172
    },
4173
 
4174
    /**
4175
     * Sets x/y of this point
4176
     * @param {Number} x
4177
     * @param {Number} y
4178
     */
4179
    setXY: function (x, y) {
4180
      this.x = x;
4181
      this.y = y;
4182
    },
4183
 
4184
    /**
4185
     * Sets x/y of this point from another point
4186
     * @param {fabric.Point} that
4187
     */
4188
    setFromPoint: function (that) {
4189
      this.x = that.x;
4190
      this.y = that.y;
4191
    },
4192
 
4193
    /**
4194
     * Swaps x/y of this point and another point
4195
     * @param {fabric.Point} that
4196
     */
4197
    swap: function (that) {
4198
      var x = this.x,
4199
          y = this.y;
4200
      this.x = that.x;
4201
      this.y = that.y;
4202
      that.x = x;
4203
      that.y = y;
4204
    }
4205
  };
4206
 
4207
})(typeof exports !== 'undefined' ? exports : this);
4208
 
4209
 
4210
(function(global) {
4211
 
4212
  'use strict';
4213
 
4214
  /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */
4215
  var fabric = global.fabric || (global.fabric = { });
4216
 
4217
  if (fabric.Intersection) {
4218
    fabric.warn('fabric.Intersection is already defined');
4219
    return;
4220
  }
4221
 
4222
  /**
4223
   * Intersection class
4224
   * @class fabric.Intersection
4225
   * @memberOf fabric
4226
   * @constructor
4227
   */
4228
  function Intersection(status) {
4229
    this.status = status;
4230
    this.points = [];
4231
  }
4232
 
4233
  fabric.Intersection = Intersection;
4234
 
4235
  fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ {
4236
 
4237
    /**
4238
     * Appends a point to intersection
4239
     * @param {fabric.Point} point
4240
     */
4241
    appendPoint: function (point) {
4242
      this.points.push(point);
4243
    },
4244
 
4245
    /**
4246
     * Appends points to intersection
4247
     * @param {Array} points
4248
     */
4249
    appendPoints: function (points) {
4250
      this.points = this.points.concat(points);
4251
    }
4252
  };
4253
 
4254
  /**
4255
   * Checks if one line intersects another
4256
   * @static
4257
   * @param {fabric.Point} a1
4258
   * @param {fabric.Point} a2
4259
   * @param {fabric.Point} b1
4260
   * @param {fabric.Point} b2
4261
   * @return {fabric.Intersection}
4262
   */
4263
  fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) {
4264
    var result,
4265
        uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x),
4266
        ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x),
4267
        uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);
4268
    if (uB !== 0) {
4269
      var ua = uaT / uB,
4270
          ub = ubT / uB;
4271
      if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
4272
        result = new Intersection('Intersection');
4273
        result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y)));
4274
      }
4275
      else {
4276
        result = new Intersection();
4277
      }
4278
    }
4279
    else {
4280
      if (uaT === 0 || ubT === 0) {
4281
        result = new Intersection('Coincident');
4282
      }
4283
      else {
4284
        result = new Intersection('Parallel');
4285
      }
4286
    }
4287
    return result;
4288
  };
4289
 
4290
  /**
4291
   * Checks if line intersects polygon
4292
   * @static
4293
   * @param {fabric.Point} a1
4294
   * @param {fabric.Point} a2
4295
   * @param {Array} points
4296
   * @return {fabric.Intersection}
4297
   */
4298
  fabric.Intersection.intersectLinePolygon = function(a1, a2, points) {
4299
    var result = new Intersection(),
4300
        length = points.length;
4301
 
4302
    for (var i = 0; i < length; i++) {
4303
      var b1 = points[i],
4304
          b2 = points[(i + 1) % length],
4305
          inter = Intersection.intersectLineLine(a1, a2, b1, b2);
4306
 
4307
      result.appendPoints(inter.points);
4308
    }
4309
    if (result.points.length > 0) {
4310
      result.status = 'Intersection';
4311
    }
4312
    return result;
4313
  };
4314
 
4315
  /**
4316
   * Checks if polygon intersects another polygon
4317
   * @static
4318
   * @param {Array} points1
4319
   * @param {Array} points2
4320
   * @return {fabric.Intersection}
4321
   */
4322
  fabric.Intersection.intersectPolygonPolygon = function (points1, points2) {
4323
    var result = new Intersection(),
4324
        length = points1.length;
4325
 
4326
    for (var i = 0; i < length; i++) {
4327
      var a1 = points1[i],
4328
          a2 = points1[(i + 1) % length],
4329
          inter = Intersection.intersectLinePolygon(a1, a2, points2);
4330
 
4331
      result.appendPoints(inter.points);
4332
    }
4333
    if (result.points.length > 0) {
4334
      result.status = 'Intersection';
4335
    }
4336
    return result;
4337
  };
4338
 
4339
  /**
4340
   * Checks if polygon intersects rectangle
4341
   * @static
4342
   * @param {Array} points
4343
   * @param {Number} r1
4344
   * @param {Number} r2
4345
   * @return {fabric.Intersection}
4346
   */
4347
  fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) {
4348
    var min = r1.min(r2),
4349
        max = r1.max(r2),
4350
        topRight = new fabric.Point(max.x, min.y),
4351
        bottomLeft = new fabric.Point(min.x, max.y),
4352
        inter1 = Intersection.intersectLinePolygon(min, topRight, points),
4353
        inter2 = Intersection.intersectLinePolygon(topRight, max, points),
4354
        inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points),
4355
        inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points),
4356
        result = new Intersection();
4357
 
4358
    result.appendPoints(inter1.points);
4359
    result.appendPoints(inter2.points);
4360
    result.appendPoints(inter3.points);
4361
    result.appendPoints(inter4.points);
4362
 
4363
    if (result.points.length > 0) {
4364
      result.status = 'Intersection';
4365
    }
4366
    return result;
4367
  };
4368
 
4369
})(typeof exports !== 'undefined' ? exports : this);
4370
 
4371
 
4372
(function(global) {
4373
 
4374
  'use strict';
4375
 
4376
  var fabric = global.fabric || (global.fabric = { });
4377
 
4378
  if (fabric.Color) {
4379
    fabric.warn('fabric.Color is already defined.');
4380
    return;
4381
  }
4382
 
4383
  /**
4384
   * Color class
4385
   * The purpose of {@link fabric.Color} is to abstract and encapsulate common color operations;
4386
   * {@link fabric.Color} is a constructor and creates instances of {@link fabric.Color} objects.
4387
   *
4388
   * @class fabric.Color
4389
   * @param {String} color optional in hex or rgb(a) format
4390
   * @return {fabric.Color} thisArg
4391
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors}
4392
   */
4393
  function Color(color) {
4394
    if (!color) {
4395
      this.setSource([0, 0, 0, 1]);
4396
    }
4397
    else {
4398
      this._tryParsingColor(color);
4399
    }
4400
  }
4401
 
4402
  fabric.Color = Color;
4403
 
4404
  fabric.Color.prototype = /** @lends fabric.Color.prototype */ {
4405
 
4406
    /**
4407
     * @private
4408
     * @param {String|Array} color Color value to parse
4409
     */
4410
    _tryParsingColor: function(color) {
4411
      var source;
4412
 
4413
      if (color in Color.colorNameMap) {
4414
        color = Color.colorNameMap[color];
4415
      }
4416
 
4417
      if (color === 'transparent') {
4418
        this.setSource([255, 255, 255, 0]);
4419
        return;
4420
      }
4421
 
4422
      source = Color.sourceFromHex(color);
4423
 
4424
      if (!source) {
4425
        source = Color.sourceFromRgb(color);
4426
      }
4427
      if (!source) {
4428
        source = Color.sourceFromHsl(color);
4429
      }
4430
      if (source) {
4431
        this.setSource(source);
4432
      }
4433
    },
4434
 
4435
    /**
4436
     * Adapted from <a href="https://rawgithub.com/mjijackson/mjijackson.github.com/master/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript.html">https://github.com/mjijackson</a>
4437
     * @private
4438
     * @param {Number} r Red color value
4439
     * @param {Number} g Green color value
4440
     * @param {Number} b Blue color value
4441
     * @return {Array} Hsl color
4442
     */
4443
    _rgbToHsl: function(r, g, b) {
4444
      r /= 255, g /= 255, b /= 255;
4445
 
4446
      var h, s, l,
4447
          max = fabric.util.array.max([r, g, b]),
4448
          min = fabric.util.array.min([r, g, b]);
4449
 
4450
      l = (max + min) / 2;
4451
 
4452
      if (max === min) {
4453
        h = s = 0; // achromatic
4454
      }
4455
      else {
4456
        var d = max - min;
4457
        s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
4458
        switch (max) {
4459
          case r:
4460
            h = (g - b) / d + (g < b ? 6 : 0);
4461
            break;
4462
          case g:
4463
            h = (b - r) / d + 2;
4464
            break;
4465
          case b:
4466
            h = (r - g) / d + 4;
4467
            break;
4468
        }
4469
        h /= 6;
4470
      }
4471
 
4472
      return [
4473
        Math.round(h * 360),
4474
        Math.round(s * 100),
4475
        Math.round(l * 100)
4476
      ];
4477
    },
4478
 
4479
    /**
4480
     * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1])
4481
     * @return {Array}
4482
     */
4483
    getSource: function() {
4484
      return this._source;
4485
    },
4486
 
4487
    /**
4488
     * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1])
4489
     * @param {Array} source
4490
     */
4491
    setSource: function(source) {
4492
      this._source = source;
4493
    },
4494
 
4495
    /**
4496
     * Returns color represenation in RGB format
4497
     * @return {String} ex: rgb(0-255,0-255,0-255)
4498
     */
4499
    toRgb: function() {
4500
      var source = this.getSource();
4501
      return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')';
4502
    },
4503
 
4504
    /**
4505
     * Returns color represenation in RGBA format
4506
     * @return {String} ex: rgba(0-255,0-255,0-255,0-1)
4507
     */
4508
    toRgba: function() {
4509
      var source = this.getSource();
4510
      return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')';
4511
    },
4512
 
4513
    /**
4514
     * Returns color represenation in HSL format
4515
     * @return {String} ex: hsl(0-360,0%-100%,0%-100%)
4516
     */
4517
    toHsl: function() {
4518
      var source = this.getSource(),
4519
          hsl = this._rgbToHsl(source[0], source[1], source[2]);
4520
 
4521
      return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)';
4522
    },
4523
 
4524
    /**
4525
     * Returns color represenation in HSLA format
4526
     * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1)
4527
     */
4528
    toHsla: function() {
4529
      var source = this.getSource(),
4530
          hsl = this._rgbToHsl(source[0], source[1], source[2]);
4531
 
4532
      return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')';
4533
    },
4534
 
4535
    /**
4536
     * Returns color represenation in HEX format
4537
     * @return {String} ex: FF5555
4538
     */
4539
    toHex: function() {
4540
      var source = this.getSource(), r, g, b;
4541
 
4542
      r = source[0].toString(16);
4543
      r = (r.length === 1) ? ('0' + r) : r;
4544
 
4545
      g = source[1].toString(16);
4546
      g = (g.length === 1) ? ('0' + g) : g;
4547
 
4548
      b = source[2].toString(16);
4549
      b = (b.length === 1) ? ('0' + b) : b;
4550
 
4551
      return r.toUpperCase() + g.toUpperCase() + b.toUpperCase();
4552
    },
4553
 
4554
    /**
4555
     * Gets value of alpha channel for this color
4556
     * @return {Number} 0-1
4557
     */
4558
    getAlpha: function() {
4559
      return this.getSource()[3];
4560
    },
4561
 
4562
    /**
4563
     * Sets value of alpha channel for this color
4564
     * @param {Number} alpha Alpha value 0-1
4565
     * @return {fabric.Color} thisArg
4566
     */
4567
    setAlpha: function(alpha) {
4568
      var source = this.getSource();
4569
      source[3] = alpha;
4570
      this.setSource(source);
4571
      return this;
4572
    },
4573
 
4574
    /**
4575
     * Transforms color to its grayscale representation
4576
     * @return {fabric.Color} thisArg
4577
     */
4578
    toGrayscale: function() {
4579
      var source = this.getSource(),
4580
          average = parseInt((source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 10),
4581
          currentAlpha = source[3];
4582
      this.setSource([average, average, average, currentAlpha]);
4583
      return this;
4584
    },
4585
 
4586
    /**
4587
     * Transforms color to its black and white representation
4588
     * @param {Number} threshold
4589
     * @return {fabric.Color} thisArg
4590
     */
4591
    toBlackWhite: function(threshold) {
4592
      var source = this.getSource(),
4593
          average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0),
4594
          currentAlpha = source[3];
4595
 
4596
      threshold = threshold || 127;
4597
 
4598
      average = (Number(average) < Number(threshold)) ? 0 : 255;
4599
      this.setSource([average, average, average, currentAlpha]);
4600
      return this;
4601
    },
4602
 
4603
    /**
4604
     * Overlays color with another color
4605
     * @param {String|fabric.Color} otherColor
4606
     * @return {fabric.Color} thisArg
4607
     */
4608
    overlayWith: function(otherColor) {
4609
      if (!(otherColor instanceof Color)) {
4610
        otherColor = new Color(otherColor);
4611
      }
4612
 
4613
      var result = [],
4614
          alpha = this.getAlpha(),
4615
          otherAlpha = 0.5,
4616
          source = this.getSource(),
4617
          otherSource = otherColor.getSource();
4618
 
4619
      for (var i = 0; i < 3; i++) {
4620
        result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha)));
4621
      }
4622
 
4623
      result[3] = alpha;
4624
      this.setSource(result);
4625
      return this;
4626
    }
4627
  };
4628
 
4629
  /**
4630
   * Regex matching color in RGB or RGBA formats (ex: rgb(0, 0, 0), rgba(255, 100, 10, 0.5), rgba( 255 , 100 , 10 , 0.5 ), rgb(1,1,1), rgba(100%, 60%, 10%, 0.5))
4631
   * @static
4632
   * @field
4633
   * @memberOf fabric.Color
4634
   */
4635
  fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/;
4636
 
4637
  /**
4638
   * Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 ))
4639
   * @static
4640
   * @field
4641
   * @memberOf fabric.Color
4642
   */
4643
  fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/;
4644
 
4645
  /**
4646
   * Regex matching color in HEX format (ex: #FF5555, 010155, aff)
4647
   * @static
4648
   * @field
4649
   * @memberOf fabric.Color
4650
   */
4651
  fabric.Color.reHex = /^#?([0-9a-f]{6}|[0-9a-f]{3})$/i;
4652
 
4653
  /**
4654
   * Map of the 17 basic color names with HEX code
4655
   * @static
4656
   * @field
4657
   * @memberOf fabric.Color
4658
   * @see: http://www.w3.org/TR/CSS2/syndata.html#color-units
4659
   */
4660
  fabric.Color.colorNameMap = {
4661
    aqua:    '#00FFFF',
4662
    black:   '#000000',
4663
    blue:    '#0000FF',
4664
    fuchsia: '#FF00FF',
4665
    gray:    '#808080',
4666
    green:   '#008000',
4667
    lime:    '#00FF00',
4668
    maroon:  '#800000',
4669
    navy:    '#000080',
4670
    olive:   '#808000',
4671
    orange:  '#FFA500',
4672
    purple:  '#800080',
4673
    red:     '#FF0000',
4674
    silver:  '#C0C0C0',
4675
    teal:    '#008080',
4676
    white:   '#FFFFFF',
4677
    yellow:  '#FFFF00'
4678
  };
4679
 
4680
  /**
4681
   * @private
4682
   * @param {Number} p
4683
   * @param {Number} q
4684
   * @param {Number} t
4685
   * @return {Number}
4686
   */
4687
  function hue2rgb(p, q, t) {
4688
    if (t < 0) {
4689
      t += 1;
4690
    }
4691
    if (t > 1) {
4692
      t -= 1;
4693
    }
4694
    if (t < 1/6) {
4695
      return p + (q - p) * 6 * t;
4696
    }
4697
    if (t < 1/2) {
4698
      return q;
4699
    }
4700
    if (t < 2/3) {
4701
      return p + (q - p) * (2/3 - t) * 6;
4702
    }
4703
    return p;
4704
  }
4705
 
4706
  /**
4707
   * Returns new color object, when given a color in RGB format
4708
   * @memberOf fabric.Color
4709
   * @param {String} color Color value ex: rgb(0-255,0-255,0-255)
4710
   * @return {fabric.Color}
4711
   */
4712
  fabric.Color.fromRgb = function(color) {
4713
    return Color.fromSource(Color.sourceFromRgb(color));
4714
  };
4715
 
4716
  /**
4717
   * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format
4718
   * @memberOf fabric.Color
4719
   * @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%)
4720
   * @return {Array} source
4721
   */
4722
  fabric.Color.sourceFromRgb = function(color) {
4723
    var match = color.match(Color.reRGBa);
4724
    if (match) {
4725
      var r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1),
4726
          g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1),
4727
          b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1);
4728
 
4729
      return [
4730
        parseInt(r, 10),
4731
        parseInt(g, 10),
4732
        parseInt(b, 10),
4733
        match[4] ? parseFloat(match[4]) : 1
4734
      ];
4735
    }
4736
  };
4737
 
4738
  /**
4739
   * Returns new color object, when given a color in RGBA format
4740
   * @static
4741
   * @function
4742
   * @memberOf fabric.Color
4743
   * @param {String} color
4744
   * @return {fabric.Color}
4745
   */
4746
  fabric.Color.fromRgba = Color.fromRgb;
4747
 
4748
  /**
4749
   * Returns new color object, when given a color in HSL format
4750
   * @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%)
4751
   * @memberOf fabric.Color
4752
   * @return {fabric.Color}
4753
   */
4754
  fabric.Color.fromHsl = function(color) {
4755
    return Color.fromSource(Color.sourceFromHsl(color));
4756
  };
4757
 
4758
  /**
4759
   * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HSL or HSLA format.
4760
   * Adapted from <a href="https://rawgithub.com/mjijackson/mjijackson.github.com/master/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript.html">https://github.com/mjijackson</a>
4761
   * @memberOf fabric.Color
4762
   * @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1)
4763
   * @return {Array} source
4764
   * @see http://http://www.w3.org/TR/css3-color/#hsl-color
4765
   */
4766
  fabric.Color.sourceFromHsl = function(color) {
4767
    var match = color.match(Color.reHSLa);
4768
    if (!match) {
4769
      return;
4770
    }
4771
 
4772
    var h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360,
4773
        s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1),
4774
        l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1),
4775
        r, g, b;
4776
 
4777
    if (s === 0) {
4778
      r = g = b = l;
4779
    }
4780
    else {
4781
      var q = l <= 0.5 ? l * (s + 1) : l + s - l * s,
4782
          p = l * 2 - q;
4783
 
4784
      r = hue2rgb(p, q, h + 1/3);
4785
      g = hue2rgb(p, q, h);
4786
      b = hue2rgb(p, q, h - 1/3);
4787
    }
4788
 
4789
    return [
4790
      Math.round(r * 255),
4791
      Math.round(g * 255),
4792
      Math.round(b * 255),
4793
      match[4] ? parseFloat(match[4]) : 1
4794
    ];
4795
  };
4796
 
4797
  /**
4798
   * Returns new color object, when given a color in HSLA format
4799
   * @static
4800
   * @function
4801
   * @memberOf fabric.Color
4802
   * @param {String} color
4803
   * @return {fabric.Color}
4804
   */
4805
  fabric.Color.fromHsla = Color.fromHsl;
4806
 
4807
  /**
4808
   * Returns new color object, when given a color in HEX format
4809
   * @static
4810
   * @memberOf fabric.Color
4811
   * @param {String} color Color value ex: FF5555
4812
   * @return {fabric.Color}
4813
   */
4814
  fabric.Color.fromHex = function(color) {
4815
    return Color.fromSource(Color.sourceFromHex(color));
4816
  };
4817
 
4818
  /**
4819
   * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HEX format
4820
   * @static
4821
   * @memberOf fabric.Color
4822
   * @param {String} color ex: FF5555
4823
   * @return {Array} source
4824
   */
4825
  fabric.Color.sourceFromHex = function(color) {
4826
    if (color.match(Color.reHex)) {
4827
      var value = color.slice(color.indexOf('#') + 1),
4828
          isShortNotation = (value.length === 3),
4829
          r = isShortNotation ? (value.charAt(0) + value.charAt(0)) : value.substring(0, 2),
4830
          g = isShortNotation ? (value.charAt(1) + value.charAt(1)) : value.substring(2, 4),
4831
          b = isShortNotation ? (value.charAt(2) + value.charAt(2)) : value.substring(4, 6);
4832
 
4833
      return [
4834
        parseInt(r, 16),
4835
        parseInt(g, 16),
4836
        parseInt(b, 16),
4837
        1
4838
      ];
4839
    }
4840
  };
4841
 
4842
  /**
4843
   * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5])
4844
   * @static
4845
   * @memberOf fabric.Color
4846
   * @param {Array} source
4847
   * @return {fabric.Color}
4848
   */
4849
  fabric.Color.fromSource = function(source) {
4850
    var oColor = new Color();
4851
    oColor.setSource(source);
4852
    return oColor;
4853
  };
4854
 
4855
})(typeof exports !== 'undefined' ? exports : this);
4856
 
4857
 
4858
(function() {
4859
 
4860
  /* _FROM_SVG_START_ */
4861
  function getColorStop(el) {
4862
    var style = el.getAttribute('style'),
4863
        offset = el.getAttribute('offset'),
4864
        color, colorAlpha, opacity;
4865
 
4866
    // convert percents to absolute values
4867
    offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1);
4868
    offset = offset < 0 ? 0 : offset > 1 ? 1 : offset;
4869
    if (style) {
4870
      var keyValuePairs = style.split(/\s*;\s*/);
4871
 
4872
      if (keyValuePairs[keyValuePairs.length - 1] === '') {
4873
        keyValuePairs.pop();
4874
      }
4875
 
4876
      for (var i = keyValuePairs.length; i--; ) {
4877
 
4878
        var split = keyValuePairs[i].split(/\s*:\s*/),
4879
            key = split[0].trim(),
4880
            value = split[1].trim();
4881
 
4882
        if (key === 'stop-color') {
4883
          color = value;
4884
        }
4885
        else if (key === 'stop-opacity') {
4886
          opacity = value;
4887
        }
4888
      }
4889
    }
4890
 
4891
    if (!color) {
4892
      color = el.getAttribute('stop-color') || 'rgb(0,0,0)';
4893
    }
4894
    if (!opacity) {
4895
      opacity = el.getAttribute('stop-opacity');
4896
    }
4897
 
4898
    color = new fabric.Color(color);
4899
    colorAlpha = color.getAlpha();
4900
    opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity);
4901
    opacity *= colorAlpha;
4902
 
4903
    return {
4904
      offset: offset,
4905
      color: color.toRgb(),
4906
      opacity: opacity
4907
    };
4908
  }
4909
 
4910
  function getLinearCoords(el) {
4911
    return {
4912
      x1: el.getAttribute('x1') || 0,
4913
      y1: el.getAttribute('y1') || 0,
4914
      x2: el.getAttribute('x2') || '100%',
4915
      y2: el.getAttribute('y2') || 0
4916
    };
4917
  }
4918
 
4919
  function getRadialCoords(el) {
4920
    return {
4921
      x1: el.getAttribute('fx') || el.getAttribute('cx') || '50%',
4922
      y1: el.getAttribute('fy') || el.getAttribute('cy') || '50%',
4923
      r1: 0,
4924
      x2: el.getAttribute('cx') || '50%',
4925
      y2: el.getAttribute('cy') || '50%',
4926
      r2: el.getAttribute('r') || '50%'
4927
    };
4928
  }
4929
  /* _FROM_SVG_END_ */
4930
 
4931
  /**
4932
   * Gradient class
4933
   * @class fabric.Gradient
4934
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#gradients}
4935
   * @see {@link fabric.Gradient#initialize} for constructor definition
4936
   */
4937
  fabric.Gradient = fabric.util.createClass(/** @lends fabric.Gradient.prototype */ {
4938
 
4939
    /**
4940
     * Horizontal offset for aligning gradients coming from SVG when outside pathgroups
4941
     * @type Number
4942
     * @default 0
4943
     */
4944
    offsetX: 0,
4945
 
4946
    /**
4947
     * Vertical offset for aligning gradients coming from SVG when outside pathgroups
4948
     * @type Number
4949
     * @default 0
4950
     */
4951
    offsetY: 0,
4952
 
4953
    /**
4954
     * Constructor
4955
     * @param {Object} [options] Options object with type, coords, gradientUnits and colorStops
4956
     * @return {fabric.Gradient} thisArg
4957
     */
4958
    initialize: function(options) {
4959
      options || (options = { });
4960
 
4961
      var coords = { };
4962
 
4963
      this.id = fabric.Object.__uid++;
4964
      this.type = options.type || 'linear';
4965
 
4966
      coords = {
4967
        x1: options.coords.x1 || 0,
4968
        y1: options.coords.y1 || 0,
4969
        x2: options.coords.x2 || 0,
4970
        y2: options.coords.y2 || 0
4971
      };
4972
 
4973
      if (this.type === 'radial') {
4974
        coords.r1 = options.coords.r1 || 0;
4975
        coords.r2 = options.coords.r2 || 0;
4976
      }
4977
      this.coords = coords;
4978
      this.colorStops = options.colorStops.slice();
4979
      if (options.gradientTransform) {
4980
        this.gradientTransform = options.gradientTransform;
4981
      }
4982
      this.offsetX = options.offsetX || this.offsetX;
4983
      this.offsetY = options.offsetY || this.offsetY;
4984
    },
4985
 
4986
    /**
4987
     * Adds another colorStop
4988
     * @param {Object} colorStop Object with offset and color
4989
     * @return {fabric.Gradient} thisArg
4990
     */
4991
    addColorStop: function(colorStop) {
4992
      for (var position in colorStop) {
4993
        var color = new fabric.Color(colorStop[position]);
4994
        this.colorStops.push({
4995
          offset: position,
4996
          color: color.toRgb(),
4997
          opacity: color.getAlpha()
4998
        });
4999
      }
5000
      return this;
5001
    },
5002
 
5003
    /**
5004
     * Returns object representation of a gradient
5005
     * @return {Object}
5006
     */
5007
    toObject: function() {
5008
      return {
5009
        type: this.type,
5010
        coords: this.coords,
5011
        colorStops: this.colorStops,
5012
        offsetX: this.offsetX,
5013
        offsetY: this.offsetY
5014
      };
5015
    },
5016
 
5017
    /* _TO_SVG_START_ */
5018
    /**
5019
     * Returns SVG representation of an gradient
5020
     * @param {Object} object Object to create a gradient for
5021
     * @param {Boolean} normalize Whether coords should be normalized
5022
     * @return {String} SVG representation of an gradient (linear/radial)
5023
     */
5024
    toSVG: function(object) {
5025
      var coords = fabric.util.object.clone(this.coords),
5026
          markup, commonAttributes;
5027
 
5028
      // colorStops must be sorted ascending
5029
      this.colorStops.sort(function(a, b) {
5030
        return a.offset - b.offset;
5031
      });
5032
 
5033
      if (!(object.group && object.group.type === 'path-group')) {
5034
        for (var prop in coords) {
5035
          if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
5036
            coords[prop] += this.offsetX - object.width / 2;
5037
          }
5038
          else if (prop === 'y1' || prop === 'y2') {
5039
            coords[prop] += this.offsetY - object.height / 2;
5040
          }
5041
        }
5042
      }
5043
 
5044
      commonAttributes = 'id="SVGID_' + this.id +
5045
                     '" gradientUnits="userSpaceOnUse"';
5046
      if (this.gradientTransform) {
5047
        commonAttributes += ' gradientTransform="matrix(' + this.gradientTransform.join(' ') + ')" ';
5048
      }
5049
      if (this.type === 'linear') {
5050
        markup = [
5051
          //jscs:disable validateIndentation
5052
          '<linearGradient ',
5053
            commonAttributes,
5054
            ' x1="', coords.x1,
5055
            '" y1="', coords.y1,
5056
            '" x2="', coords.x2,
5057
            '" y2="', coords.y2,
5058
          '">\n'
5059
          //jscs:enable validateIndentation
5060
        ];
5061
      }
5062
      else if (this.type === 'radial') {
5063
        markup = [
5064
          //jscs:disable validateIndentation
5065
          '<radialGradient ',
5066
            commonAttributes,
5067
            ' cx="', coords.x2,
5068
            '" cy="', coords.y2,
5069
            '" r="', coords.r2,
5070
            '" fx="', coords.x1,
5071
            '" fy="', coords.y1,
5072
          '">\n'
5073
          //jscs:enable validateIndentation
5074
        ];
5075
      }
5076
 
5077
      for (var i = 0; i < this.colorStops.length; i++) {
5078
        markup.push(
5079
          //jscs:disable validateIndentation
5080
          '<stop ',
5081
            'offset="', (this.colorStops[i].offset * 100) + '%',
5082
            '" style="stop-color:', this.colorStops[i].color,
5083
            (this.colorStops[i].opacity != null ? ';stop-opacity: ' + this.colorStops[i].opacity : ';'),
5084
          '"/>\n'
5085
          //jscs:enable validateIndentation
5086
        );
5087
      }
5088
 
5089
      markup.push((this.type === 'linear' ? '</linearGradient>\n' : '</radialGradient>\n'));
5090
 
5091
      return markup.join('');
5092
    },
5093
    /* _TO_SVG_END_ */
5094
 
5095
    /**
5096
     * Returns an instance of CanvasGradient
5097
     * @param {CanvasRenderingContext2D} ctx Context to render on
5098
     * @return {CanvasGradient}
5099
     */
5100
    toLive: function(ctx, object) {
5101
      var gradient, prop, coords = fabric.util.object.clone(this.coords);
5102
 
5103
      if (!this.type) {
5104
        return;
5105
      }
5106
 
5107
      if (object.group && object.group.type === 'path-group') {
5108
        for (prop in coords) {
5109
          if (prop === 'x1' || prop === 'x2') {
5110
            coords[prop] += -this.offsetX + object.width / 2;
5111
          }
5112
          else if (prop === 'y1' || prop === 'y2') {
5113
            coords[prop] += -this.offsetY + object.height / 2;
5114
          }
5115
        }
5116
      }
5117
 
5118
      if (object.type === 'text' || object.type === 'i-text') {
5119
        for (prop in coords) {
5120
          if (prop === 'x1' || prop === 'x2') {
5121
            coords[prop] -= object.width / 2;
5122
          }
5123
          else if (prop === 'y1' || prop === 'y2') {
5124
            coords[prop] -= object.height / 2;
5125
          }
5126
        }
5127
      }
5128
 
5129
      if (this.type === 'linear') {
5130
        gradient = ctx.createLinearGradient(
5131
          coords.x1, coords.y1, coords.x2, coords.y2);
5132
      }
5133
      else if (this.type === 'radial') {
5134
        gradient = ctx.createRadialGradient(
5135
          coords.x1, coords.y1, coords.r1, coords.x2, coords.y2, coords.r2);
5136
      }
5137
 
5138
      for (var i = 0, len = this.colorStops.length; i < len; i++) {
5139
        var color = this.colorStops[i].color,
5140
            opacity = this.colorStops[i].opacity,
5141
            offset = this.colorStops[i].offset;
5142
 
5143
        if (typeof opacity !== 'undefined') {
5144
          color = new fabric.Color(color).setAlpha(opacity).toRgba();
5145
        }
5146
        gradient.addColorStop(parseFloat(offset), color);
5147
      }
5148
 
5149
      return gradient;
5150
    }
5151
  });
5152
 
5153
  fabric.util.object.extend(fabric.Gradient, {
5154
 
5155
    /* _FROM_SVG_START_ */
5156
    /**
5157
     * Returns {@link fabric.Gradient} instance from an SVG element
5158
     * @static
5159
     * @memberof fabric.Gradient
5160
     * @param {SVGGradientElement} el SVG gradient element
5161
     * @param {fabric.Object} instance
5162
     * @return {fabric.Gradient} Gradient instance
5163
     * @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement
5164
     * @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement
5165
     */
5166
    fromElement: function(el, instance) {
5167
 
5168
      /**
5169
       *  @example:
5170
       *
5171
       *  <linearGradient id="linearGrad1">
5172
       *    <stop offset="0%" stop-color="white"/>
5173
       *    <stop offset="100%" stop-color="black"/>
5174
       *  </linearGradient>
5175
       *
5176
       *  OR
5177
       *
5178
       *  <linearGradient id="linearGrad2">
5179
       *    <stop offset="0" style="stop-color:rgb(255,255,255)"/>
5180
       *    <stop offset="1" style="stop-color:rgb(0,0,0)"/>
5181
       *  </linearGradient>
5182
       *
5183
       *  OR
5184
       *
5185
       *  <radialGradient id="radialGrad1">
5186
       *    <stop offset="0%" stop-color="white" stop-opacity="1" />
5187
       *    <stop offset="50%" stop-color="black" stop-opacity="0.5" />
5188
       *    <stop offset="100%" stop-color="white" stop-opacity="1" />
5189
       *  </radialGradient>
5190
       *
5191
       *  OR
5192
       *
5193
       *  <radialGradient id="radialGrad2">
5194
       *    <stop offset="0" stop-color="rgb(255,255,255)" />
5195
       *    <stop offset="0.5" stop-color="rgb(0,0,0)" />
5196
       *    <stop offset="1" stop-color="rgb(255,255,255)" />
5197
       *  </radialGradient>
5198
       *
5199
       */
5200
 
5201
      var colorStopEls = el.getElementsByTagName('stop'),
5202
          type = (el.nodeName === 'linearGradient' ? 'linear' : 'radial'),
5203
          gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox',
5204
          gradientTransform = el.getAttribute('gradientTransform'),
5205
          colorStops = [],
5206
          coords = { }, ellipseMatrix;
5207
 
5208
      if (type === 'linear') {
5209
        coords = getLinearCoords(el);
5210
      }
5211
      else if (type === 'radial') {
5212
        coords = getRadialCoords(el);
5213
      }
5214
 
5215
      for (var i = colorStopEls.length; i--; ) {
5216
        colorStops.push(getColorStop(colorStopEls[i]));
5217
      }
5218
 
5219
      ellipseMatrix = _convertPercentUnitsToValues(instance, coords, gradientUnits);
5220
 
5221
      var gradient = new fabric.Gradient({
5222
        type: type,
5223
        coords: coords,
5224
        colorStops: colorStops,
5225
        offsetX: -instance.left,
5226
        offsetY: -instance.top
5227
      });
5228
 
5229
      if (gradientTransform || ellipseMatrix !== '') {
5230
        gradient.gradientTransform = fabric.parseTransformAttribute((gradientTransform || '') + ellipseMatrix);
5231
      }
5232
      return gradient;
5233
    },
5234
    /* _FROM_SVG_END_ */
5235
 
5236
    /**
5237
     * Returns {@link fabric.Gradient} instance from its object representation
5238
     * @static
5239
     * @memberof fabric.Gradient
5240
     * @param {Object} obj
5241
     * @param {Object} [options] Options object
5242
     */
5243
    forObject: function(obj, options) {
5244
      options || (options = { });
5245
      _convertPercentUnitsToValues(obj, options.coords, 'userSpaceOnUse');
5246
      return new fabric.Gradient(options);
5247
    }
5248
  });
5249
 
5250
  /**
5251
   * @private
5252
   */
5253
  function _convertPercentUnitsToValues(object, options, gradientUnits) {
5254
    var propValue, addFactor = 0, multFactor = 1, ellipseMatrix = '';
5255
    for (var prop in options) {
5256
      propValue = parseFloat(options[prop], 10);
5257
      if (typeof options[prop] === 'string' && /^\d+%$/.test(options[prop])) {
5258
        multFactor = 0.01;
5259
      }
5260
      else {
5261
        multFactor = 1;
5262
      }
5263
      if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
5264
        multFactor *= gradientUnits === 'objectBoundingBox' ? object.width : 1;
5265
        addFactor = gradientUnits === 'objectBoundingBox' ? object.left || 0 : 0;
5266
      }
5267
      else if (prop === 'y1' || prop === 'y2') {
5268
        multFactor *= gradientUnits === 'objectBoundingBox' ? object.height : 1;
5269
        addFactor = gradientUnits === 'objectBoundingBox' ? object.top || 0 : 0;
5270
      }
5271
      options[prop] = propValue * multFactor + addFactor;
5272
    }
5273
    if (object.type === 'ellipse' &&
5274
        options.r2 !== null &&
5275
        gradientUnits === 'objectBoundingBox' &&
5276
        object.rx !== object.ry) {
5277
 
5278
      var scaleFactor = object.ry/object.rx;
5279
      ellipseMatrix = ' scale(1, ' + scaleFactor + ')';
5280
      if (options.y1) {
5281
        options.y1 /= scaleFactor;
5282
      }
5283
      if (options.y2) {
5284
        options.y2 /= scaleFactor;
5285
      }
5286
    }
5287
    return ellipseMatrix;
5288
  }
5289
})();
5290
 
5291
 
5292
/**
5293
 * Pattern class
5294
 * @class fabric.Pattern
5295
 * @see {@link http://fabricjs.com/patterns/|Pattern demo}
5296
 * @see {@link http://fabricjs.com/dynamic-patterns/|DynamicPattern demo}
5297
 * @see {@link fabric.Pattern#initialize} for constructor definition
5298
 */
5299
fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ {
5300
 
5301
  /**
5302
   * Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat)
5303
   * @type String
5304
   * @default
5305
   */
5306
  repeat: 'repeat',
5307
 
5308
  /**
5309
   * Pattern horizontal offset from object's left/top corner
5310
   * @type Number
5311
   * @default
5312
   */
5313
  offsetX: 0,
5314
 
5315
  /**
5316
   * Pattern vertical offset from object's left/top corner
5317
   * @type Number
5318
   * @default
5319
   */
5320
  offsetY: 0,
5321
 
5322
  /**
5323
   * Constructor
5324
   * @param {Object} [options] Options object
5325
   * @return {fabric.Pattern} thisArg
5326
   */
5327
  initialize: function(options) {
5328
    options || (options = { });
5329
 
5330
    this.id = fabric.Object.__uid++;
5331
 
5332
    if (options.source) {
5333
      if (typeof options.source === 'string') {
5334
        // function string
5335
        if (typeof fabric.util.getFunctionBody(options.source) !== 'undefined') {
5336
          this.source = new Function(fabric.util.getFunctionBody(options.source));
5337
        }
5338
        else {
5339
          // img src string
5340
          var _this = this;
5341
          this.source = fabric.util.createImage();
5342
          fabric.util.loadImage(options.source, function(img) {
5343
            _this.source = img;
5344
          });
5345
        }
5346
      }
5347
      else {
5348
        // img element
5349
        this.source = options.source;
5350
      }
5351
    }
5352
    if (options.repeat) {
5353
      this.repeat = options.repeat;
5354
    }
5355
    if (options.offsetX) {
5356
      this.offsetX = options.offsetX;
5357
    }
5358
    if (options.offsetY) {
5359
      this.offsetY = options.offsetY;
5360
    }
5361
  },
5362
 
5363
  /**
5364
   * Returns object representation of a pattern
5365
   * @return {Object} Object representation of a pattern instance
5366
   */
5367
  toObject: function() {
5368
 
5369
    var source;
5370
 
5371
    // callback
5372
    if (typeof this.source === 'function') {
5373
      source = String(this.source);
5374
    }
5375
    // <img> element
5376
    else if (typeof this.source.src === 'string') {
5377
      source = this.source.src;
5378
    }
5379
 
5380
    return {
5381
      source: source,
5382
      repeat: this.repeat,
5383
      offsetX: this.offsetX,
5384
      offsetY: this.offsetY
5385
    };
5386
  },
5387
 
5388
  /* _TO_SVG_START_ */
5389
  /**
5390
   * Returns SVG representation of a pattern
5391
   * @param {fabric.Object} object
5392
   * @return {String} SVG representation of a pattern
5393
   */
5394
  toSVG: function(object) {
5395
    var patternSource = typeof this.source === 'function' ? this.source() : this.source,
5396
        patternWidth = patternSource.width / object.getWidth(),
5397
        patternHeight = patternSource.height / object.getHeight(),
5398
        patternOffsetX = this.offsetX / object.getWidth(),
5399
        patternOffsetY = this.offsetY / object.getHeight(),
5400
        patternImgSrc = '';
5401
    if (this.repeat === 'repeat-x' || this.repeat === 'no-repeat') {
5402
      patternHeight = 1;
5403
    }
5404
    if (this.repeat === 'repeat-y' || this.repeat === 'no-repeat') {
5405
      patternWidth = 1;
5406
    }
5407
    if (patternSource.src) {
5408
      patternImgSrc = patternSource.src;
5409
    }
5410
    else if (patternSource.toDataURL) {
5411
      patternImgSrc = patternSource.toDataURL();
5412
    }
5413
 
5414
    return '<pattern id="SVGID_' + this.id +
5415
                  '" x="' + patternOffsetX +
5416
                  '" y="' + patternOffsetY +
5417
                  '" width="' + patternWidth +
5418
                  '" height="' + patternHeight + '">\n' +
5419
             '<image x="0" y="0"' +
5420
                    ' width="' + patternSource.width +
5421
                    '" height="' + patternSource.height +
5422
                    '" xlink:href="' + patternImgSrc +
5423
             '"></image>\n' +
5424
           '</pattern>\n';
5425
  },
5426
  /* _TO_SVG_END_ */
5427
 
5428
  /**
5429
   * Returns an instance of CanvasPattern
5430
   * @param {CanvasRenderingContext2D} ctx Context to create pattern
5431
   * @return {CanvasPattern}
5432
   */
5433
  toLive: function(ctx) {
5434
    var source = typeof this.source === 'function'
5435
      ? this.source()
5436
      : this.source;
5437
 
5438
    // if the image failed to load, return, and allow rest to continue loading
5439
    if (!source) {
5440
      return '';
5441
    }
5442
 
5443
    // if an image
5444
    if (typeof source.src !== 'undefined') {
5445
      if (!source.complete) {
5446
        return '';
5447
      }
5448
      if (source.naturalWidth === 0 || source.naturalHeight === 0) {
5449
        return '';
5450
      }
5451
    }
5452
    return ctx.createPattern(source, this.repeat);
5453
  }
5454
});
5455
 
5456
 
5457
(function(global) {
5458
 
5459
  'use strict';
5460
 
5461
  var fabric = global.fabric || (global.fabric = { }),
5462
      toFixed = fabric.util.toFixed;
5463
 
5464
  if (fabric.Shadow) {
5465
    fabric.warn('fabric.Shadow is already defined.');
5466
    return;
5467
  }
5468
 
5469
  /**
5470
   * Shadow class
5471
   * @class fabric.Shadow
5472
   * @see {@link http://fabricjs.com/shadows/|Shadow demo}
5473
   * @see {@link fabric.Shadow#initialize} for constructor definition
5474
   */
5475
  fabric.Shadow = fabric.util.createClass(/** @lends fabric.Shadow.prototype */ {
5476
 
5477
    /**
5478
     * Shadow color
5479
     * @type String
5480
     * @default
5481
     */
5482
    color: 'rgb(0,0,0)',
5483
 
5484
    /**
5485
     * Shadow blur
5486
     * @type Number
5487
     */
5488
    blur: 0,
5489
 
5490
    /**
5491
     * Shadow horizontal offset
5492
     * @type Number
5493
     * @default
5494
     */
5495
    offsetX: 0,
5496
 
5497
    /**
5498
     * Shadow vertical offset
5499
     * @type Number
5500
     * @default
5501
     */
5502
    offsetY: 0,
5503
 
5504
    /**
5505
     * Whether the shadow should affect stroke operations
5506
     * @type Boolean
5507
     * @default
5508
     */
5509
    affectStroke: false,
5510
 
5511
    /**
5512
     * Indicates whether toObject should include default values
5513
     * @type Boolean
5514
     * @default
5515
     */
5516
    includeDefaultValues: true,
5517
 
5518
    /**
5519
     * Constructor
5520
     * @param {Object|String} [options] Options object with any of color, blur, offsetX, offsetX properties or string (e.g. "rgba(0,0,0,0.2) 2px 2px 10px, "2px 2px 10px rgba(0,0,0,0.2)")
5521
     * @return {fabric.Shadow} thisArg
5522
     */
5523
    initialize: function(options) {
5524
 
5525
      if (typeof options === 'string') {
5526
        options = this._parseShadow(options);
5527
      }
5528
 
5529
      for (var prop in options) {
5530
        this[prop] = options[prop];
5531
      }
5532
 
5533
      this.id = fabric.Object.__uid++;
5534
    },
5535
 
5536
    /**
5537
     * @private
5538
     * @param {String} shadow Shadow value to parse
5539
     * @return {Object} Shadow object with color, offsetX, offsetY and blur
5540
     */
5541
    _parseShadow: function(shadow) {
5542
      var shadowStr = shadow.trim(),
5543
          offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [ ],
5544
          color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, '') || 'rgb(0,0,0)';
5545
 
5546
      return {
5547
        color: color.trim(),
5548
        offsetX: parseInt(offsetsAndBlur[1], 10) || 0,
5549
        offsetY: parseInt(offsetsAndBlur[2], 10) || 0,
5550
        blur: parseInt(offsetsAndBlur[3], 10) || 0
5551
      };
5552
    },
5553
 
5554
    /**
5555
     * Returns a string representation of an instance
5556
     * @see http://www.w3.org/TR/css-text-decor-3/#text-shadow
5557
     * @return {String} Returns CSS3 text-shadow declaration
5558
     */
5559
    toString: function() {
5560
      return [this.offsetX, this.offsetY, this.blur, this.color].join('px ');
5561
    },
5562
 
5563
    /* _TO_SVG_START_ */
5564
    /**
5565
     * Returns SVG representation of a shadow
5566
     * @param {fabric.Object} object
5567
     * @return {String} SVG representation of a shadow
5568
     */
5569
    toSVG: function(object) {
5570
      var mode = 'SourceAlpha', fBoxX = 40, fBoxY = 40;
5571
 
5572
      if (object && (object.fill === this.color || object.stroke === this.color)) {
5573
        mode = 'SourceGraphic';
5574
      }
5575
 
5576
      if (object.width && object.height) {
5577
        //http://www.w3.org/TR/SVG/filters.html#FilterEffectsRegion
5578
        // we add some extra space to filter box to contain the blur ( 20 )
5579
        fBoxX = toFixed(Math.abs(this.offsetX / object.getWidth()), 2) * 100 + 20;
5580
        fBoxY = toFixed(Math.abs(this.offsetY / object.getHeight()), 2) * 100 + 20;
5581
      }
5582
 
5583
      return (
5584
        '<filter id="SVGID_' + this.id + '" y="-' + fBoxY + '%" height="' + (100 + 2 * fBoxY) + '%" ' +
5585
          'x="-' + fBoxX + '%" width="' + (100 + 2 * fBoxX) + '%" ' + '>\n' +
5586
          '\t<feGaussianBlur in="' + mode + '" stdDeviation="' +
5587
            toFixed(this.blur ? this.blur / 2 : 0, 3) +
5588
          '" result="blurOut"></feGaussianBlur>\n' +
5589
          '\t<feColorMatrix result="matrixOut" in="blurOut" type="matrix" ' +
5590
          'values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.30 0" ></feColorMatrix >\n' +
5591
          '\t<feOffset dx="' + this.offsetX + '" dy="' + this.offsetY + '"></feOffset>\n' +
5592
          '\t<feMerge>\n' +
5593
            '\t\t<feMergeNode></feMergeNode>\n' +
5594
            '\t\t<feMergeNode in="SourceGraphic"></feMergeNode>\n' +
5595
          '\t</feMerge>\n' +
5596
        '</filter>\n');
5597
    },
5598
    /* _TO_SVG_END_ */
5599
 
5600
    /**
5601
     * Returns object representation of a shadow
5602
     * @return {Object} Object representation of a shadow instance
5603
     */
5604
    toObject: function() {
5605
      if (this.includeDefaultValues) {
5606
        return {
5607
          color: this.color,
5608
          blur: this.blur,
5609
          offsetX: this.offsetX,
5610
          offsetY: this.offsetY
5611
        };
5612
      }
5613
      var obj = { }, proto = fabric.Shadow.prototype;
5614
      if (this.color !== proto.color) {
5615
        obj.color = this.color;
5616
      }
5617
      if (this.blur !== proto.blur) {
5618
        obj.blur = this.blur;
5619
      }
5620
      if (this.offsetX !== proto.offsetX) {
5621
        obj.offsetX = this.offsetX;
5622
      }
5623
      if (this.offsetY !== proto.offsetY) {
5624
        obj.offsetY = this.offsetY;
5625
      }
5626
      return obj;
5627
    }
5628
  });
5629
 
5630
  /**
5631
   * Regex matching shadow offsetX, offsetY and blur (ex: "2px 2px 10px rgba(0,0,0,0.2)", "rgb(0,255,0) 2px 2px")
5632
   * @static
5633
   * @field
5634
   * @memberOf fabric.Shadow
5635
   */
5636
  fabric.Shadow.reOffsetsAndBlur = /(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/;
5637
 
5638
})(typeof exports !== 'undefined' ? exports : this);
5639
 
5640
 
5641
(function () {
5642
 
5643
  'use strict';
5644
 
5645
  if (fabric.StaticCanvas) {
5646
    fabric.warn('fabric.StaticCanvas is already defined.');
5647
    return;
5648
  }
5649
 
5650
  // aliases for faster resolution
5651
  var extend = fabric.util.object.extend,
5652
      getElementOffset = fabric.util.getElementOffset,
5653
      removeFromArray = fabric.util.removeFromArray,
5654
 
5655
      CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element');
5656
 
5657
  /**
5658
   * Static canvas class
5659
   * @class fabric.StaticCanvas
5660
   * @mixes fabric.Collection
5661
   * @mixes fabric.Observable
5662
   * @see {@link http://fabricjs.com/static_canvas/|StaticCanvas demo}
5663
   * @see {@link fabric.StaticCanvas#initialize} for constructor definition
5664
   * @fires before:render
5665
   * @fires after:render
5666
   * @fires canvas:cleared
5667
   * @fires object:added
5668
   * @fires object:removed
5669
   */
5670
  fabric.StaticCanvas = fabric.util.createClass(/** @lends fabric.StaticCanvas.prototype */ {
5671
 
5672
    /**
5673
     * Constructor
5674
     * @param {HTMLElement | String} el &lt;canvas> element to initialize instance on
5675
     * @param {Object} [options] Options object
5676
     * @return {Object} thisArg
5677
     */
5678
    initialize: function(el, options) {
5679
      options || (options = { });
5680
 
5681
      this._initStatic(el, options);
5682
      fabric.StaticCanvas.activeInstance = this;
5683
    },
5684
 
5685
    /**
5686
     * Background color of canvas instance.
5687
     * Should be set via {@link fabric.StaticCanvas#setBackgroundColor}.
5688
     * @type {(String|fabric.Pattern)}
5689
     * @default
5690
     */
5691
    backgroundColor: '',
5692
 
5693
    /**
5694
     * Background image of canvas instance.
5695
     * Should be set via {@link fabric.StaticCanvas#setBackgroundImage}.
5696
     * <b>Backwards incompatibility note:</b> The "backgroundImageOpacity"
5697
     * and "backgroundImageStretch" properties are deprecated since 1.3.9.
5698
     * Use {@link fabric.Image#opacity}, {@link fabric.Image#width} and {@link fabric.Image#height}.
5699
     * @type fabric.Image
5700
     * @default
5701
     */
5702
    backgroundImage: null,
5703
 
5704
    /**
5705
     * Overlay color of canvas instance.
5706
     * Should be set via {@link fabric.StaticCanvas#setOverlayColor}
5707
     * @since 1.3.9
5708
     * @type {(String|fabric.Pattern)}
5709
     * @default
5710
     */
5711
    overlayColor: '',
5712
 
5713
    /**
5714
     * Overlay image of canvas instance.
5715
     * Should be set via {@link fabric.StaticCanvas#setOverlayImage}.
5716
     * <b>Backwards incompatibility note:</b> The "overlayImageLeft"
5717
     * and "overlayImageTop" properties are deprecated since 1.3.9.
5718
     * Use {@link fabric.Image#left} and {@link fabric.Image#top}.
5719
     * @type fabric.Image
5720
     * @default
5721
     */
5722
    overlayImage: null,
5723
 
5724
    /**
5725
     * Indicates whether toObject/toDatalessObject should include default values
5726
     * @type Boolean
5727
     * @default
5728
     */
5729
    includeDefaultValues: true,
5730
 
5731
    /**
5732
     * Indicates whether objects' state should be saved
5733
     * @type Boolean
5734
     * @default
5735
     */
5736
    stateful: true,
5737
 
5738
    /**
5739
     * Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove} should also re-render canvas.
5740
     * Disabling this option could give a great performance boost when adding/removing a lot of objects to/from canvas at once
5741
     * (followed by a manual rendering after addition/deletion)
5742
     * @type Boolean
5743
     * @default
5744
     */
5745
    renderOnAddRemove: true,
5746
 
5747
    /**
5748
     * Function that determines clipping of entire canvas area
5749
     * Being passed context as first argument. See clipping canvas area in {@link https://github.com/kangax/fabric.js/wiki/FAQ}
5750
     * @type Function
5751
     * @default
5752
     */
5753
    clipTo: null,
5754
 
5755
    /**
5756
     * Indicates whether object controls (borders/controls) are rendered above overlay image
5757
     * @type Boolean
5758
     * @default
5759
     */
5760
    controlsAboveOverlay: false,
5761
 
5762
    /**
5763
     * Indicates whether the browser can be scrolled when using a touchscreen and dragging on the canvas
5764
     * @type Boolean
5765
     * @default
5766
     */
5767
    allowTouchScrolling: false,
5768
 
5769
    /**
5770
     * Indicates whether this canvas will use image smoothing, this is on by default in browsers
5771
     * @type Boolean
5772
     * @default
5773
     */
5774
    imageSmoothingEnabled: true,
5775
 
5776
    /**
5777
     * Indicates whether objects should remain in current stack position when selected. When false objects are brought to top and rendered as part of the selection group
5778
     * @type Boolean
5779
     * @default
5780
     */
5781
    preserveObjectStacking: false,
5782
 
5783
    /**
5784
     * The transformation (in the format of Canvas transform) which focuses the viewport
5785
     * @type Array
5786
     * @default
5787
     */
5788
    viewportTransform: [1, 0, 0, 1, 0, 0],
5789
 
5790
    /**
5791
     * Callback; invoked right before object is about to be scaled/rotated
5792
     */
5793
    onBeforeScaleRotate: function () {
5794
      /* NOOP */
5795
    },
5796
 
5797
    /**
5798
     * @private
5799
     * @param {HTMLElement | String} el &lt;canvas> element to initialize instance on
5800
     * @param {Object} [options] Options object
5801
     */
5802
    _initStatic: function(el, options) {
5803
      this._objects = [];
5804
 
5805
      this._createLowerCanvas(el);
5806
      this._initOptions(options);
5807
      this._setImageSmoothing();
5808
 
5809
      if (options.overlayImage) {
5810
        this.setOverlayImage(options.overlayImage, this.renderAll.bind(this));
5811
      }
5812
      if (options.backgroundImage) {
5813
        this.setBackgroundImage(options.backgroundImage, this.renderAll.bind(this));
5814
      }
5815
      if (options.backgroundColor) {
5816
        this.setBackgroundColor(options.backgroundColor, this.renderAll.bind(this));
5817
      }
5818
      if (options.overlayColor) {
5819
        this.setOverlayColor(options.overlayColor, this.renderAll.bind(this));
5820
      }
5821
      this.calcOffset();
5822
    },
5823
 
5824
    /**
5825
     * Calculates canvas element offset relative to the document
5826
     * This method is also attached as "resize" event handler of window
5827
     * @return {fabric.Canvas} instance
5828
     * @chainable
5829
     */
5830
    calcOffset: function () {
5831
      this._offset = getElementOffset(this.lowerCanvasEl);
5832
      return this;
5833
    },
5834
 
5835
    /**
5836
     * Sets {@link fabric.StaticCanvas#overlayImage|overlay image} for this canvas
5837
     * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set overlay to
5838
     * @param {Function} callback callback to invoke when image is loaded and set as an overlay
5839
     * @param {Object} [options] Optional options to set for the {@link fabric.Image|overlay image}.
5840
     * @return {fabric.Canvas} thisArg
5841
     * @chainable
5842
     * @see {@link http://jsfiddle.net/fabricjs/MnzHT/|jsFiddle demo}
5843
     * @example <caption>Normal overlayImage with left/top = 0</caption>
5844
     * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
5845
     *   // Needed to position overlayImage at 0/0
5846
     *   originX: 'left',
5847
     *   originY: 'top'
5848
     * });
5849
     * @example <caption>overlayImage with different properties</caption>
5850
     * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
5851
     *   opacity: 0.5,
5852
     *   angle: 45,
5853
     *   left: 400,
5854
     *   top: 400,
5855
     *   originX: 'left',
5856
     *   originY: 'top'
5857
     * });
5858
     * @example <caption>Stretched overlayImage #1 - width/height correspond to canvas width/height</caption>
5859
     * fabric.Image.fromURL('http://fabricjs.com/assets/jail_cell_bars.png', function(img) {
5860
     *    img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'});
5861
     *    canvas.setOverlayImage(img, canvas.renderAll.bind(canvas));
5862
     * });
5863
     * @example <caption>Stretched overlayImage #2 - width/height correspond to canvas width/height</caption>
5864
     * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
5865
     *   width: canvas.width,
5866
     *   height: canvas.height,
5867
     *   // Needed to position overlayImage at 0/0
5868
     *   originX: 'left',
5869
     *   originY: 'top'
5870
     * });
5871
     * @example <caption>overlayImage loaded from cross-origin</caption>
5872
     * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
5873
     *   opacity: 0.5,
5874
     *   angle: 45,
5875
     *   left: 400,
5876
     *   top: 400,
5877
     *   originX: 'left',
5878
     *   originY: 'top',
5879
     *   crossOrigin: 'anonymous'
5880
     * });
5881
     */
5882
    setOverlayImage: function (image, callback, options) {
5883
      return this.__setBgOverlayImage('overlayImage', image, callback, options);
5884
    },
5885
 
5886
    /**
5887
     * Sets {@link fabric.StaticCanvas#backgroundImage|background image} for this canvas
5888
     * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background to
5889
     * @param {Function} callback Callback to invoke when image is loaded and set as background
5890
     * @param {Object} [options] Optional options to set for the {@link fabric.Image|background image}.
5891
     * @return {fabric.Canvas} thisArg
5892
     * @chainable
5893
     * @see {@link http://jsfiddle.net/fabricjs/YH9yD/|jsFiddle demo}
5894
     * @example <caption>Normal backgroundImage with left/top = 0</caption>
5895
     * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
5896
     *   // Needed to position backgroundImage at 0/0
5897
     *   originX: 'left',
5898
     *   originY: 'top'
5899
     * });
5900
     * @example <caption>backgroundImage with different properties</caption>
5901
     * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
5902
     *   opacity: 0.5,
5903
     *   angle: 45,
5904
     *   left: 400,
5905
     *   top: 400,
5906
     *   originX: 'left',
5907
     *   originY: 'top'
5908
     * });
5909
     * @example <caption>Stretched backgroundImage #1 - width/height correspond to canvas width/height</caption>
5910
     * fabric.Image.fromURL('http://fabricjs.com/assets/honey_im_subtle.png', function(img) {
5911
     *    img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'});
5912
     *    canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas));
5913
     * });
5914
     * @example <caption>Stretched backgroundImage #2 - width/height correspond to canvas width/height</caption>
5915
     * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
5916
     *   width: canvas.width,
5917
     *   height: canvas.height,
5918
     *   // Needed to position backgroundImage at 0/0
5919
     *   originX: 'left',
5920
     *   originY: 'top'
5921
     * });
5922
     * @example <caption>backgroundImage loaded from cross-origin</caption>
5923
     * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
5924
     *   opacity: 0.5,
5925
     *   angle: 45,
5926
     *   left: 400,
5927
     *   top: 400,
5928
     *   originX: 'left',
5929
     *   originY: 'top',
5930
     *   crossOrigin: 'anonymous'
5931
     * });
5932
     */
5933
    setBackgroundImage: function (image, callback, options) {
5934
      return this.__setBgOverlayImage('backgroundImage', image, callback, options);
5935
    },
5936
 
5937
    /**
5938
     * Sets {@link fabric.StaticCanvas#overlayColor|background color} for this canvas
5939
     * @param {(String|fabric.Pattern)} overlayColor Color or pattern to set background color to
5940
     * @param {Function} callback Callback to invoke when background color is set
5941
     * @return {fabric.Canvas} thisArg
5942
     * @chainable
5943
     * @see {@link http://jsfiddle.net/fabricjs/pB55h/|jsFiddle demo}
5944
     * @example <caption>Normal overlayColor - color value</caption>
5945
     * canvas.setOverlayColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas));
5946
     * @example <caption>fabric.Pattern used as overlayColor</caption>
5947
     * canvas.setOverlayColor({
5948
     *   source: 'http://fabricjs.com/assets/escheresque_ste.png'
5949
     * }, canvas.renderAll.bind(canvas));
5950
     * @example <caption>fabric.Pattern used as overlayColor with repeat and offset</caption>
5951
     * canvas.setOverlayColor({
5952
     *   source: 'http://fabricjs.com/assets/escheresque_ste.png',
5953
     *   repeat: 'repeat',
5954
     *   offsetX: 200,
5955
     *   offsetY: 100
5956
     * }, canvas.renderAll.bind(canvas));
5957
     */
5958
    setOverlayColor: function(overlayColor, callback) {
5959
      return this.__setBgOverlayColor('overlayColor', overlayColor, callback);
5960
    },
5961
 
5962
    /**
5963
     * Sets {@link fabric.StaticCanvas#backgroundColor|background color} for this canvas
5964
     * @param {(String|fabric.Pattern)} backgroundColor Color or pattern to set background color to
5965
     * @param {Function} callback Callback to invoke when background color is set
5966
     * @return {fabric.Canvas} thisArg
5967
     * @chainable
5968
     * @see {@link http://jsfiddle.net/fabricjs/hXzvk/|jsFiddle demo}
5969
     * @example <caption>Normal backgroundColor - color value</caption>
5970
     * canvas.setBackgroundColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas));
5971
     * @example <caption>fabric.Pattern used as backgroundColor</caption>
5972
     * canvas.setBackgroundColor({
5973
     *   source: 'http://fabricjs.com/assets/escheresque_ste.png'
5974
     * }, canvas.renderAll.bind(canvas));
5975
     * @example <caption>fabric.Pattern used as backgroundColor with repeat and offset</caption>
5976
     * canvas.setBackgroundColor({
5977
     *   source: 'http://fabricjs.com/assets/escheresque_ste.png',
5978
     *   repeat: 'repeat',
5979
     *   offsetX: 200,
5980
     *   offsetY: 100
5981
     * }, canvas.renderAll.bind(canvas));
5982
     */
5983
    setBackgroundColor: function(backgroundColor, callback) {
5984
      return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback);
5985
    },
5986
 
5987
    /**
5988
     * @private
5989
     * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-imagesmoothingenabled|WhatWG Canvas Standard}
5990
     */
5991
    _setImageSmoothing: function() {
5992
      var ctx = this.getContext();
5993
 
5994
      ctx.imageSmoothingEnabled       = this.imageSmoothingEnabled;
5995
      ctx.webkitImageSmoothingEnabled = this.imageSmoothingEnabled;
5996
      ctx.mozImageSmoothingEnabled    = this.imageSmoothingEnabled;
5997
      ctx.msImageSmoothingEnabled     = this.imageSmoothingEnabled;
5998
      ctx.oImageSmoothingEnabled      = this.imageSmoothingEnabled;
5999
    },
6000
 
6001
    /**
6002
     * @private
6003
     * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage}
6004
     * or {@link fabric.StaticCanvas#overlayImage|overlayImage})
6005
     * @param {(fabric.Image|String|null)} image fabric.Image instance, URL of an image or null to set background or overlay to
6006
     * @param {Function} callback Callback to invoke when image is loaded and set as background or overlay
6007
     * @param {Object} [options] Optional options to set for the {@link fabric.Image|image}.
6008
     */
6009
    __setBgOverlayImage: function(property, image, callback, options) {
6010
      if (typeof image === 'string') {
6011
        fabric.util.loadImage(image, function(img) {
6012
          this[property] = new fabric.Image(img, options);
6013
          callback && callback();
6014
        }, this, options && options.crossOrigin);
6015
      }
6016
      else {
6017
        options && image.setOptions(options);
6018
        this[property] = image;
6019
        callback && callback();
6020
      }
6021
 
6022
      return this;
6023
    },
6024
 
6025
    /**
6026
     * @private
6027
     * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundColor|backgroundColor}
6028
     * or {@link fabric.StaticCanvas#overlayColor|overlayColor})
6029
     * @param {(Object|String|null)} color Object with pattern information, color value or null
6030
     * @param {Function} [callback] Callback is invoked when color is set
6031
     */
6032
    __setBgOverlayColor: function(property, color, callback) {
6033
      if (color && color.source) {
6034
        var _this = this;
6035
        fabric.util.loadImage(color.source, function(img) {
6036
          _this[property] = new fabric.Pattern({
6037
            source: img,
6038
            repeat: color.repeat,
6039
            offsetX: color.offsetX,
6040
            offsetY: color.offsetY
6041
          });
6042
          callback && callback();
6043
        });
6044
      }
6045
      else {
6046
        this[property] = color;
6047
        callback && callback();
6048
      }
6049
 
6050
      return this;
6051
    },
6052
 
6053
    /**
6054
     * @private
6055
     */
6056
    _createCanvasElement: function() {
6057
      var element = fabric.document.createElement('canvas');
6058
      if (!element.style) {
6059
        element.style = { };
6060
      }
6061
      if (!element) {
6062
        throw CANVAS_INIT_ERROR;
6063
      }
6064
      this._initCanvasElement(element);
6065
      return element;
6066
    },
6067
 
6068
    /**
6069
     * @private
6070
     * @param {HTMLElement} element
6071
     */
6072
    _initCanvasElement: function(element) {
6073
      fabric.util.createCanvasElement(element);
6074
 
6075
      if (typeof element.getContext === 'undefined') {
6076
        throw CANVAS_INIT_ERROR;
6077
      }
6078
    },
6079
 
6080
    /**
6081
     * @private
6082
     * @param {Object} [options] Options object
6083
     */
6084
    _initOptions: function (options) {
6085
      for (var prop in options) {
6086
        this[prop] = options[prop];
6087
      }
6088
 
6089
      this.width = this.width || parseInt(this.lowerCanvasEl.width, 10) || 0;
6090
      this.height = this.height || parseInt(this.lowerCanvasEl.height, 10) || 0;
6091
 
6092
      if (!this.lowerCanvasEl.style) {
6093
        return;
6094
      }
6095
 
6096
      this.lowerCanvasEl.width = this.width;
6097
      this.lowerCanvasEl.height = this.height;
6098
 
6099
      this.lowerCanvasEl.style.width = this.width + 'px';
6100
      this.lowerCanvasEl.style.height = this.height + 'px';
6101
 
6102
      this.viewportTransform = this.viewportTransform.slice();
6103
    },
6104
 
6105
    /**
6106
     * Creates a bottom canvas
6107
     * @private
6108
     * @param {HTMLElement} [canvasEl]
6109
     */
6110
    _createLowerCanvas: function (canvasEl) {
6111
      this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement();
6112
      this._initCanvasElement(this.lowerCanvasEl);
6113
 
6114
      fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas');
6115
 
6116
      if (this.interactive) {
6117
        this._applyCanvasStyle(this.lowerCanvasEl);
6118
      }
6119
 
6120
      this.contextContainer = this.lowerCanvasEl.getContext('2d');
6121
    },
6122
 
6123
    /**
6124
     * Returns canvas width (in px)
6125
     * @return {Number}
6126
     */
6127
    getWidth: function () {
6128
      return this.width;
6129
    },
6130
 
6131
    /**
6132
     * Returns canvas height (in px)
6133
     * @return {Number}
6134
     */
6135
    getHeight: function () {
6136
      return this.height;
6137
    },
6138
 
6139
    /**
6140
     * Sets width of this canvas instance
6141
     * @param {Number|String} value                         Value to set width to
6142
     * @param {Object}        [options]                     Options object
6143
     * @param {Boolean}       [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions
6144
     * @param {Boolean}       [options.cssOnly=false]       Set the given dimensions only as css dimensions
6145
     * @return {fabric.Canvas} instance
6146
     * @chainable true
6147
     */
6148
    setWidth: function (value, options) {
6149
      return this.setDimensions({ width: value }, options);
6150
    },
6151
 
6152
    /**
6153
     * Sets height of this canvas instance
6154
     * @param {Number|String} value                         Value to set height to
6155
     * @param {Object}        [options]                     Options object
6156
     * @param {Boolean}       [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions
6157
     * @param {Boolean}       [options.cssOnly=false]       Set the given dimensions only as css dimensions
6158
     * @return {fabric.Canvas} instance
6159
     * @chainable true
6160
     */
6161
    setHeight: function (value, options) {
6162
      return this.setDimensions({ height: value }, options);
6163
    },
6164
 
6165
    /**
6166
     * Sets dimensions (width, height) of this canvas instance. when options.cssOnly flag active you should also supply the unit of measure (px/%/em)
6167
     * @param {Object}        dimensions                    Object with width/height properties
6168
     * @param {Number|String} [dimensions.width]            Width of canvas element
6169
     * @param {Number|String} [dimensions.height]           Height of canvas element
6170
     * @param {Object}        [options]                     Options object
6171
     * @param {Boolean}       [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions
6172
     * @param {Boolean}       [options.cssOnly=false]       Set the given dimensions only as css dimensions
6173
     * @return {fabric.Canvas} thisArg
6174
     * @chainable
6175
     */
6176
    setDimensions: function (dimensions, options) {
6177
      var cssValue;
6178
 
6179
      options = options || {};
6180
 
6181
      for (var prop in dimensions) {
6182
        cssValue = dimensions[prop];
6183
 
6184
        if (!options.cssOnly) {
6185
          this._setBackstoreDimension(prop, dimensions[prop]);
6186
          cssValue += 'px';
6187
        }
6188
 
6189
        if (!options.backstoreOnly) {
6190
          this._setCssDimension(prop, cssValue);
6191
        }
6192
      }
6193
 
6194
      if (!options.cssOnly) {
6195
        this.renderAll();
6196
      }
6197
 
6198
      this.calcOffset();
6199
 
6200
      return this;
6201
    },
6202
 
6203
    /**
6204
     * Helper for setting width/height
6205
     * @private
6206
     * @param {String} prop property (width|height)
6207
     * @param {Number} value value to set property to
6208
     * @return {fabric.Canvas} instance
6209
     * @chainable true
6210
     */
6211
    _setBackstoreDimension: function (prop, value) {
6212
      this.lowerCanvasEl[prop] = value;
6213
 
6214
      if (this.upperCanvasEl) {
6215
        this.upperCanvasEl[prop] = value;
6216
      }
6217
 
6218
      if (this.cacheCanvasEl) {
6219
        this.cacheCanvasEl[prop] = value;
6220
      }
6221
 
6222
      this[prop] = value;
6223
 
6224
      return this;
6225
    },
6226
 
6227
    /**
6228
     * Helper for setting css width/height
6229
     * @private
6230
     * @param {String} prop property (width|height)
6231
     * @param {String} value value to set property to
6232
     * @return {fabric.Canvas} instance
6233
     * @chainable true
6234
     */
6235
    _setCssDimension: function (prop, value) {
6236
      this.lowerCanvasEl.style[prop] = value;
6237
 
6238
      if (this.upperCanvasEl) {
6239
        this.upperCanvasEl.style[prop] = value;
6240
      }
6241
 
6242
      if (this.wrapperEl) {
6243
        this.wrapperEl.style[prop] = value;
6244
      }
6245
 
6246
      return this;
6247
    },
6248
 
6249
    /**
6250
     * Returns canvas zoom level
6251
     * @return {Number}
6252
     */
6253
    getZoom: function () {
6254
      return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]);
6255
    },
6256
 
6257
    /**
6258
     * Sets viewport transform of this canvas instance
6259
     * @param {Array} vpt the transform in the form of context.transform
6260
     * @return {fabric.Canvas} instance
6261
     * @chainable true
6262
     */
6263
    setViewportTransform: function (vpt) {
6264
      var activeGroup = this.getActiveGroup();
6265
      this.viewportTransform = vpt;
6266
      this.renderAll();
6267
      for (var i = 0, len = this._objects.length; i < len; i++) {
6268
        this._objects[i].setCoords();
6269
      }
6270
      if (activeGroup) {
6271
        activeGroup.setCoords();
6272
      }
6273
      return this;
6274
    },
6275
 
6276
    /**
6277
     * Sets zoom level of this canvas instance, zoom centered around point
6278
     * @param {fabric.Point} point to zoom with respect to
6279
     * @param {Number} value to set zoom to, less than 1 zooms out
6280
     * @return {fabric.Canvas} instance
6281
     * @chainable true
6282
     */
6283
    zoomToPoint: function (point, value) {
6284
      // TODO: just change the scale, preserve other transformations
6285
      var before = point;
6286
      point = fabric.util.transformPoint(point, fabric.util.invertTransform(this.viewportTransform));
6287
      this.viewportTransform[0] = value;
6288
      this.viewportTransform[3] = value;
6289
      var after = fabric.util.transformPoint(point, this.viewportTransform);
6290
      this.viewportTransform[4] += before.x - after.x;
6291
      this.viewportTransform[5] += before.y - after.y;
6292
      this.renderAll();
6293
      for (var i = 0, len = this._objects.length; i < len; i++) {
6294
        this._objects[i].setCoords();
6295
      }
6296
      return this;
6297
    },
6298
 
6299
    /**
6300
     * Sets zoom level of this canvas instance
6301
     * @param {Number} value to set zoom to, less than 1 zooms out
6302
     * @return {fabric.Canvas} instance
6303
     * @chainable true
6304
     */
6305
    setZoom: function (value) {
6306
      this.zoomToPoint(new fabric.Point(0, 0), value);
6307
      return this;
6308
    },
6309
 
6310
    /**
6311
     * Pan viewport so as to place point at top left corner of canvas
6312
     * @param {fabric.Point} point to move to
6313
     * @return {fabric.Canvas} instance
6314
     * @chainable true
6315
     */
6316
    absolutePan: function (point) {
6317
      this.viewportTransform[4] = -point.x;
6318
      this.viewportTransform[5] = -point.y;
6319
      this.renderAll();
6320
      for (var i = 0, len = this._objects.length; i < len; i++) {
6321
        this._objects[i].setCoords();
6322
      }
6323
      return this;
6324
    },
6325
 
6326
    /**
6327
     * Pans viewpoint relatively
6328
     * @param {fabric.Point} point (position vector) to move by
6329
     * @return {fabric.Canvas} instance
6330
     * @chainable true
6331
     */
6332
    relativePan: function (point) {
6333
      return this.absolutePan(new fabric.Point(
6334
        -point.x - this.viewportTransform[4],
6335
        -point.y - this.viewportTransform[5]
6336
      ));
6337
    },
6338
 
6339
    /**
6340
     * Returns &lt;canvas> element corresponding to this instance
6341
     * @return {HTMLCanvasElement}
6342
     */
6343
    getElement: function () {
6344
      return this.lowerCanvasEl;
6345
    },
6346
 
6347
    /**
6348
     * Returns currently selected object, if any
6349
     * @return {fabric.Object}
6350
     */
6351
    getActiveObject: function() {
6352
      return null;
6353
    },
6354
 
6355
    /**
6356
     * Returns currently selected group of object, if any
6357
     * @return {fabric.Group}
6358
     */
6359
    getActiveGroup: function() {
6360
      return null;
6361
    },
6362
 
6363
    /**
6364
     * Given a context, renders an object on that context
6365
     * @param {CanvasRenderingContext2D} ctx Context to render object on
6366
     * @param {fabric.Object} object Object to render
6367
     * @private
6368
     */
6369
    _draw: function (ctx, object) {
6370
      if (!object) {
6371
        return;
6372
      }
6373
 
6374
      ctx.save();
6375
      var v = this.viewportTransform;
6376
      ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
6377
      if (this._shouldRenderObject(object)) {
6378
        object.render(ctx);
6379
      }
6380
      ctx.restore();
6381
      if (!this.controlsAboveOverlay) {
6382
        object._renderControls(ctx);
6383
      }
6384
    },
6385
 
6386
    _shouldRenderObject: function(object) {
6387
      if (!object) {
6388
        return false;
6389
      }
6390
      return (object !== this.getActiveGroup() || !this.preserveObjectStacking);
6391
    },
6392
 
6393
    /**
6394
     * @private
6395
     * @param {fabric.Object} obj Object that was added
6396
     */
6397
    _onObjectAdded: function(obj) {
6398
      this.stateful && obj.setupState();
6399
      obj.canvas = this;
6400
      obj.setCoords();
6401
      this.fire('object:added', { target: obj });
6402
      obj.fire('added');
6403
    },
6404
 
6405
    /**
6406
     * @private
6407
     * @param {fabric.Object} obj Object that was removed
6408
     */
6409
    _onObjectRemoved: function(obj) {
6410
      // removing active object should fire "selection:cleared" events
6411
      if (this.getActiveObject() === obj) {
6412
        this.fire('before:selection:cleared', { target: obj });
6413
        this._discardActiveObject();
6414
        this.fire('selection:cleared');
6415
      }
6416
 
6417
      this.fire('object:removed', { target: obj });
6418
      obj.fire('removed');
6419
    },
6420
 
6421
    /**
6422
     * Clears specified context of canvas element
6423
     * @param {CanvasRenderingContext2D} ctx Context to clear
6424
     * @return {fabric.Canvas} thisArg
6425
     * @chainable
6426
     */
6427
    clearContext: function(ctx) {
6428
      ctx.clearRect(0, 0, this.width, this.height);
6429
      return this;
6430
    },
6431
 
6432
    /**
6433
     * Returns context of canvas where objects are drawn
6434
     * @return {CanvasRenderingContext2D}
6435
     */
6436
    getContext: function () {
6437
      return this.contextContainer;
6438
    },
6439
 
6440
    /**
6441
     * Clears all contexts (background, main, top) of an instance
6442
     * @return {fabric.Canvas} thisArg
6443
     * @chainable
6444
     */
6445
    clear: function () {
6446
      this._objects.length = 0;
6447
      if (this.discardActiveGroup) {
6448
        this.discardActiveGroup();
6449
      }
6450
      if (this.discardActiveObject) {
6451
        this.discardActiveObject();
6452
      }
6453
      this.clearContext(this.contextContainer);
6454
      if (this.contextTop) {
6455
        this.clearContext(this.contextTop);
6456
      }
6457
      this.fire('canvas:cleared');
6458
      this.renderAll();
6459
      return this;
6460
    },
6461
 
6462
    /**
6463
     * Renders both the top canvas and the secondary container canvas.
6464
     * @param {Boolean} [allOnTop] Whether we want to force all images to be rendered on the top canvas
6465
     * @return {fabric.Canvas} instance
6466
     * @chainable
6467
     */
6468
    renderAll: function (allOnTop) {
6469
      var canvasToDrawOn = this[(allOnTop === true && this.interactive) ? 'contextTop' : 'contextContainer'],
6470
          activeGroup = this.getActiveGroup();
6471
 
6472
      if (this.contextTop && this.selection && !this._groupSelector) {
6473
        this.clearContext(this.contextTop);
6474
      }
6475
 
6476
      if (!allOnTop) {
6477
        this.clearContext(canvasToDrawOn);
6478
      }
6479
 
6480
      this.fire('before:render');
6481
 
6482
      if (this.clipTo) {
6483
        fabric.util.clipContext(this, canvasToDrawOn);
6484
      }
6485
 
6486
      this._renderBackground(canvasToDrawOn);
6487
      this._renderObjects(canvasToDrawOn, activeGroup);
6488
      this._renderActiveGroup(canvasToDrawOn, activeGroup);
6489
 
6490
      if (this.clipTo) {
6491
        canvasToDrawOn.restore();
6492
      }
6493
 
6494
      this._renderOverlay(canvasToDrawOn);
6495
 
6496
      if (this.controlsAboveOverlay && this.interactive) {
6497
        this.drawControls(canvasToDrawOn);
6498
      }
6499
 
6500
      this.fire('after:render');
6501
 
6502
      return this;
6503
    },
6504
 
6505
    /**
6506
     * @private
6507
     * @param {CanvasRenderingContext2D} ctx Context to render on
6508
     * @param {fabric.Group} activeGroup
6509
     */
6510
    _renderObjects: function(ctx, activeGroup) {
6511
      var i, length;
6512
 
6513
      // fast path
6514
      if (!activeGroup || this.preserveObjectStacking) {
6515
        for (i = 0, length = this._objects.length; i < length; ++i) {
6516
          this._draw(ctx, this._objects[i]);
6517
        }
6518
      }
6519
      else {
6520
        for (i = 0, length = this._objects.length; i < length; ++i) {
6521
          if (this._objects[i] && !activeGroup.contains(this._objects[i])) {
6522
            this._draw(ctx, this._objects[i]);
6523
          }
6524
        }
6525
      }
6526
    },
6527
 
6528
    /**
6529
     * @private
6530
     * @param {CanvasRenderingContext2D} ctx Context to render on
6531
     * @param {fabric.Group} activeGroup
6532
     */
6533
    _renderActiveGroup: function(ctx, activeGroup) {
6534
 
6535
      // delegate rendering to group selection (if one exists)
6536
      if (activeGroup) {
6537
 
6538
        //Store objects in group preserving order, then replace
6539
        var sortedObjects = [];
6540
        this.forEachObject(function (object) {
6541
          if (activeGroup.contains(object)) {
6542
            sortedObjects.push(object);
6543
          }
6544
        });
6545
        activeGroup._set('objects', sortedObjects);
6546
        this._draw(ctx, activeGroup);
6547
      }
6548
    },
6549
 
6550
    /**
6551
     * @private
6552
     * @param {CanvasRenderingContext2D} ctx Context to render on
6553
     */
6554
    _renderBackground: function(ctx) {
6555
      if (this.backgroundColor) {
6556
        ctx.fillStyle = this.backgroundColor.toLive
6557
          ? this.backgroundColor.toLive(ctx)
6558
          : this.backgroundColor;
6559
 
6560
        ctx.fillRect(
6561
          this.backgroundColor.offsetX || 0,
6562
          this.backgroundColor.offsetY || 0,
6563
          this.width,
6564
          this.height);
6565
      }
6566
      if (this.backgroundImage) {
6567
        this._draw(ctx, this.backgroundImage);
6568
      }
6569
    },
6570
 
6571
    /**
6572
     * @private
6573
     * @param {CanvasRenderingContext2D} ctx Context to render on
6574
     */
6575
    _renderOverlay: function(ctx) {
6576
      if (this.overlayColor) {
6577
        ctx.fillStyle = this.overlayColor.toLive
6578
          ? this.overlayColor.toLive(ctx)
6579
          : this.overlayColor;
6580
 
6581
        ctx.fillRect(
6582
          this.overlayColor.offsetX || 0,
6583
          this.overlayColor.offsetY || 0,
6584
          this.width,
6585
          this.height);
6586
      }
6587
      if (this.overlayImage) {
6588
        this._draw(ctx, this.overlayImage);
6589
      }
6590
    },
6591
 
6592
    /**
6593
     * Method to render only the top canvas.
6594
     * Also used to render the group selection box.
6595
     * @return {fabric.Canvas} thisArg
6596
     * @chainable
6597
     */
6598
    renderTop: function () {
6599
      var ctx = this.contextTop || this.contextContainer;
6600
      this.clearContext(ctx);
6601
 
6602
      // we render the top context - last object
6603
      if (this.selection && this._groupSelector) {
6604
        this._drawSelection();
6605
      }
6606
 
6607
      // delegate rendering to group selection if one exists
6608
      // used for drawing selection borders/controls
6609
      var activeGroup = this.getActiveGroup();
6610
      if (activeGroup) {
6611
        activeGroup.render(ctx);
6612
      }
6613
 
6614
      this._renderOverlay(ctx);
6615
 
6616
      this.fire('after:render');
6617
 
6618
      return this;
6619
    },
6620
 
6621
    /**
6622
     * Returns coordinates of a center of canvas.
6623
     * Returned value is an object with top and left properties
6624
     * @return {Object} object with "top" and "left" number values
6625
     */
6626
    getCenter: function () {
6627
      return {
6628
        top: this.getHeight() / 2,
6629
        left: this.getWidth() / 2
6630
      };
6631
    },
6632
 
6633
    /**
6634
     * Centers object horizontally.
6635
     * You might need to call `setCoords` on an object after centering, to update controls area.
6636
     * @param {fabric.Object} object Object to center horizontally
6637
     * @return {fabric.Canvas} thisArg
6638
     */
6639
    centerObjectH: function (object) {
6640
      this._centerObject(object, new fabric.Point(this.getCenter().left, object.getCenterPoint().y));
6641
      this.renderAll();
6642
      return this;
6643
    },
6644
 
6645
    /**
6646
     * Centers object vertically.
6647
     * You might need to call `setCoords` on an object after centering, to update controls area.
6648
     * @param {fabric.Object} object Object to center vertically
6649
     * @return {fabric.Canvas} thisArg
6650
     * @chainable
6651
     */
6652
    centerObjectV: function (object) {
6653
      this._centerObject(object, new fabric.Point(object.getCenterPoint().x, this.getCenter().top));
6654
      this.renderAll();
6655
      return this;
6656
    },
6657
 
6658
    /**
6659
     * Centers object vertically and horizontally.
6660
     * You might need to call `setCoords` on an object after centering, to update controls area.
6661
     * @param {fabric.Object} object Object to center vertically and horizontally
6662
     * @return {fabric.Canvas} thisArg
6663
     * @chainable
6664
     */
6665
    centerObject: function(object) {
6666
      var center = this.getCenter();
6667
 
6668
      this._centerObject(object, new fabric.Point(center.left, center.top));
6669
      this.renderAll();
6670
      return this;
6671
    },
6672
 
6673
    /**
6674
     * @private
6675
     * @param {fabric.Object} object Object to center
6676
     * @param {fabric.Point} center Center point
6677
     * @return {fabric.Canvas} thisArg
6678
     * @chainable
6679
     */
6680
    _centerObject: function(object, center) {
6681
      object.setPositionByOrigin(center, 'center', 'center');
6682
      return this;
6683
    },
6684
 
6685
    /**
6686
     * Returs dataless JSON representation of canvas
6687
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
6688
     * @return {String} json string
6689
     */
6690
    toDatalessJSON: function (propertiesToInclude) {
6691
      return this.toDatalessObject(propertiesToInclude);
6692
    },
6693
 
6694
    /**
6695
     * Returns object representation of canvas
6696
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
6697
     * @return {Object} object representation of an instance
6698
     */
6699
    toObject: function (propertiesToInclude) {
6700
      return this._toObjectMethod('toObject', propertiesToInclude);
6701
    },
6702
 
6703
    /**
6704
     * Returns dataless object representation of canvas
6705
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
6706
     * @return {Object} object representation of an instance
6707
     */
6708
    toDatalessObject: function (propertiesToInclude) {
6709
      return this._toObjectMethod('toDatalessObject', propertiesToInclude);
6710
    },
6711
 
6712
    /**
6713
     * @private
6714
     */
6715
    _toObjectMethod: function (methodName, propertiesToInclude) {
6716
 
6717
      var data = {
6718
        objects: this._toObjects(methodName, propertiesToInclude)
6719
      };
6720
 
6721
      extend(data, this.__serializeBgOverlay());
6722
 
6723
      fabric.util.populateWithProperties(this, data, propertiesToInclude);
6724
 
6725
      return data;
6726
    },
6727
 
6728
    /**
6729
     * @private
6730
     */
6731
    _toObjects: function(methodName, propertiesToInclude) {
6732
      return this.getObjects().map(function(instance) {
6733
        return this._toObject(instance, methodName, propertiesToInclude);
6734
      }, this);
6735
    },
6736
 
6737
    /**
6738
     * @private
6739
     */
6740
    _toObject: function(instance, methodName, propertiesToInclude) {
6741
      var originalValue;
6742
 
6743
      if (!this.includeDefaultValues) {
6744
        originalValue = instance.includeDefaultValues;
6745
        instance.includeDefaultValues = false;
6746
      }
6747
 
6748
      //If the object is part of the current selection group, it should
6749
      //be transformed appropriately
6750
      //i.e. it should be serialised as it would appear if the selection group
6751
      //were to be destroyed.
6752
      var originalProperties = this._realizeGroupTransformOnObject(instance),
6753
          object = instance[methodName](propertiesToInclude);
6754
      if (!this.includeDefaultValues) {
6755
        instance.includeDefaultValues = originalValue;
6756
      }
6757
 
6758
      //Undo the damage we did by changing all of its properties
6759
      this._unwindGroupTransformOnObject(instance, originalProperties);
6760
 
6761
      return object;
6762
    },
6763
 
6764
    /**
6765
     * Realises an object's group transformation on it
6766
     * @private
6767
     * @param {fabric.Object} [instance] the object to transform (gets mutated)
6768
     * @returns the original values of instance which were changed
6769
     */
6770
    _realizeGroupTransformOnObject: function(instance) {
6771
      var layoutProps = ['angle', 'flipX', 'flipY', 'height', 'left', 'scaleX', 'scaleY', 'top', 'width'];
6772
      if (instance.group && instance.group === this.getActiveGroup()) {
6773
        //Copy all the positionally relevant properties across now
6774
        var originalValues = {};
6775
        layoutProps.forEach(function(prop) {
6776
          originalValues[prop] = instance[prop];
6777
        });
6778
        this.getActiveGroup().realizeTransform(instance);
6779
        return originalValues;
6780
      }
6781
      else {
6782
        return null;
6783
      }
6784
    },
6785
 
6786
    /*
6787
     * Restores the changed properties of instance
6788
     * @private
6789
     * @param {fabric.Object} [instance] the object to un-transform (gets mutated)
6790
     * @param {Object} [originalValues] the original values of instance, as returned by _realizeGroupTransformOnObject
6791
     */
6792
    _unwindGroupTransformOnObject: function(instance, originalValues) {
6793
      if (originalValues) {
6794
        instance.set(originalValues);
6795
      }
6796
    },
6797
 
6798
    /**
6799
     * @private
6800
     */
6801
    __serializeBgOverlay: function() {
6802
      var data = {
6803
        background: (this.backgroundColor && this.backgroundColor.toObject)
6804
          ? this.backgroundColor.toObject()
6805
          : this.backgroundColor
6806
      };
6807
 
6808
      if (this.overlayColor) {
6809
        data.overlay = this.overlayColor.toObject
6810
          ? this.overlayColor.toObject()
6811
          : this.overlayColor;
6812
      }
6813
      if (this.backgroundImage) {
6814
        data.backgroundImage = this.backgroundImage.toObject();
6815
      }
6816
      if (this.overlayImage) {
6817
        data.overlayImage = this.overlayImage.toObject();
6818
      }
6819
 
6820
      return data;
6821
    },
6822
 
6823
    /* _TO_SVG_START_ */
6824
    /**
6825
     * When true, getSvgTransform() will apply the StaticCanvas.viewportTransform to the SVG transformation. When true,
6826
     * a zoomed canvas will then produce zoomed SVG output.
6827
     * @type Boolean
6828
     * @default
6829
     */
6830
    svgViewportTransformation: true,
6831
 
6832
    /**
6833
     * Returns SVG representation of canvas
6834
     * @function
6835
     * @param {Object} [options] Options object for SVG output
6836
     * @param {Boolean} [options.suppressPreamble=false] If true xml tag is not included
6837
     * @param {Object} [options.viewBox] SVG viewbox object
6838
     * @param {Number} [options.viewBox.x] x-cooridnate of viewbox
6839
     * @param {Number} [options.viewBox.y] y-coordinate of viewbox
6840
     * @param {Number} [options.viewBox.width] Width of viewbox
6841
     * @param {Number} [options.viewBox.height] Height of viewbox
6842
     * @param {String} [options.encoding=UTF-8] Encoding of SVG output
6843
     * @param {Function} [reviver] Method for further parsing of svg elements, called after each fabric object converted into svg representation.
6844
     * @return {String} SVG string
6845
     * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization}
6846
     * @see {@link http://jsfiddle.net/fabricjs/jQ3ZZ/|jsFiddle demo}
6847
     * @example <caption>Normal SVG output</caption>
6848
     * var svg = canvas.toSVG();
6849
     * @example <caption>SVG output without preamble (without &lt;?xml ../>)</caption>
6850
     * var svg = canvas.toSVG({suppressPreamble: true});
6851
     * @example <caption>SVG output with viewBox attribute</caption>
6852
     * var svg = canvas.toSVG({
6853
     *   viewBox: {
6854
     *     x: 100,
6855
     *     y: 100,
6856
     *     width: 200,
6857
     *     height: 300
6858
     *   }
6859
     * });
6860
     * @example <caption>SVG output with different encoding (default: UTF-8)</caption>
6861
     * var svg = canvas.toSVG({encoding: 'ISO-8859-1'});
6862
     * @example <caption>Modify SVG output with reviver function</caption>
6863
     * var svg = canvas.toSVG(null, function(svg) {
6864
     *   return svg.replace('stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; ', '');
6865
     * });
6866
     */
6867
    toSVG: function(options, reviver) {
6868
      options || (options = { });
6869
 
6870
      var markup = [];
6871
 
6872
      this._setSVGPreamble(markup, options);
6873
      this._setSVGHeader(markup, options);
6874
 
6875
      this._setSVGBgOverlayColor(markup, 'backgroundColor');
6876
      this._setSVGBgOverlayImage(markup, 'backgroundImage');
6877
 
6878
      this._setSVGObjects(markup, reviver);
6879
 
6880
      this._setSVGBgOverlayColor(markup, 'overlayColor');
6881
      this._setSVGBgOverlayImage(markup, 'overlayImage');
6882
 
6883
      markup.push('</svg>');
6884
 
6885
      return markup.join('');
6886
    },
6887
 
6888
    /**
6889
     * @private
6890
     */
6891
    _setSVGPreamble: function(markup, options) {
6892
      if (!options.suppressPreamble) {
6893
        markup.push(
6894
          '<?xml version="1.0" encoding="', (options.encoding || 'UTF-8'), '" standalone="no" ?>',
6895
            '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ',
6896
              '"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n'
6897
        );
6898
      }
6899
    },
6900
 
6901
    /**
6902
     * @private
6903
     */
6904
    _setSVGHeader: function(markup, options) {
6905
      var width, height, vpt;
6906
 
6907
      if (options.viewBox) {
6908
        width = options.viewBox.width;
6909
        height = options.viewBox.height;
6910
      }
6911
      else {
6912
        width = this.width;
6913
        height = this.height;
6914
        if (!this.svgViewportTransformation) {
6915
          vpt = this.viewportTransform;
6916
          width /= vpt[0];
6917
          height /= vpt[3];
6918
        }
6919
      }
6920
 
6921
      markup.push(
6922
        '<svg ',
6923
          'xmlns="http://www.w3.org/2000/svg" ',
6924
          'xmlns:xlink="http://www.w3.org/1999/xlink" ',
6925
          'version="1.1" ',
6926
          'width="', width, '" ',
6927
          'height="', height, '" ',
6928
          (this.backgroundColor && !this.backgroundColor.toLive
6929
            ? 'style="background-color: ' + this.backgroundColor + '" '
6930
            : null),
6931
          (options.viewBox
6932
              ? 'viewBox="' +
6933
                options.viewBox.x + ' ' +
6934
                options.viewBox.y + ' ' +
6935
                options.viewBox.width + ' ' +
6936
                options.viewBox.height + '" '
6937
              : null),
6938
          'xml:space="preserve">',
6939
        '<desc>Created with Fabric.js ', fabric.version, '</desc>',
6940
        '<defs>',
6941
          fabric.createSVGFontFacesMarkup(this.getObjects()),
6942
          fabric.createSVGRefElementsMarkup(this),
6943
        '</defs>'
6944
      );
6945
    },
6946
 
6947
    /**
6948
     * @private
6949
     */
6950
    _setSVGObjects: function(markup, reviver) {
6951
      for (var i = 0, objects = this.getObjects(), len = objects.length; i < len; i++) {
6952
        var instance = objects[i],
6953
            //If the object is in a selection group, simulate what would happen to that
6954
            //object when the group is deselected
6955
            originalProperties = this._realizeGroupTransformOnObject(instance);
6956
        markup.push(instance.toSVG(reviver));
6957
        this._unwindGroupTransformOnObject(instance, originalProperties);
6958
      }
6959
    },
6960
 
6961
    /**
6962
     * @private
6963
     */
6964
    _setSVGBgOverlayImage: function(markup, property) {
6965
      if (this[property] && this[property].toSVG) {
6966
        markup.push(this[property].toSVG());
6967
      }
6968
    },
6969
 
6970
    /**
6971
     * @private
6972
     */
6973
    _setSVGBgOverlayColor: function(markup, property) {
6974
      if (this[property] && this[property].source) {
6975
        markup.push(
6976
          '<rect x="', this[property].offsetX, '" y="', this[property].offsetY, '" ',
6977
            'width="',
6978
              (this[property].repeat === 'repeat-y' || this[property].repeat === 'no-repeat'
6979
                ? this[property].source.width
6980
                : this.width),
6981
            '" height="',
6982
              (this[property].repeat === 'repeat-x' || this[property].repeat === 'no-repeat'
6983
                ? this[property].source.height
6984
                : this.height),
6985
            '" fill="url(#' + property + 'Pattern)"',
6986
          '></rect>'
6987
        );
6988
      }
6989
      else if (this[property] && property === 'overlayColor') {
6990
        markup.push(
6991
          '<rect x="0" y="0" ',
6992
            'width="', this.width,
6993
            '" height="', this.height,
6994
            '" fill="', this[property], '"',
6995
          '></rect>'
6996
        );
6997
      }
6998
    },
6999
    /* _TO_SVG_END_ */
7000
 
7001
    /**
7002
     * Moves an object to the bottom of the stack of drawn objects
7003
     * @param {fabric.Object} object Object to send to back
7004
     * @return {fabric.Canvas} thisArg
7005
     * @chainable
7006
     */
7007
    sendToBack: function (object) {
7008
      removeFromArray(this._objects, object);
7009
      this._objects.unshift(object);
7010
      return this.renderAll && this.renderAll();
7011
    },
7012
 
7013
    /**
7014
     * Moves an object to the top of the stack of drawn objects
7015
     * @param {fabric.Object} object Object to send
7016
     * @return {fabric.Canvas} thisArg
7017
     * @chainable
7018
     */
7019
    bringToFront: function (object) {
7020
      removeFromArray(this._objects, object);
7021
      this._objects.push(object);
7022
      return this.renderAll && this.renderAll();
7023
    },
7024
 
7025
    /**
7026
     * Moves an object down in stack of drawn objects
7027
     * @param {fabric.Object} object Object to send
7028
     * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object
7029
     * @return {fabric.Canvas} thisArg
7030
     * @chainable
7031
     */
7032
    sendBackwards: function (object, intersecting) {
7033
      var idx = this._objects.indexOf(object);
7034
 
7035
      // if object is not on the bottom of stack
7036
      if (idx !== 0) {
7037
        var newIdx = this._findNewLowerIndex(object, idx, intersecting);
7038
 
7039
        removeFromArray(this._objects, object);
7040
        this._objects.splice(newIdx, 0, object);
7041
        this.renderAll && this.renderAll();
7042
      }
7043
      return this;
7044
    },
7045
 
7046
    /**
7047
     * @private
7048
     */
7049
    _findNewLowerIndex: function(object, idx, intersecting) {
7050
      var newIdx;
7051
 
7052
      if (intersecting) {
7053
        newIdx = idx;
7054
 
7055
        // traverse down the stack looking for the nearest intersecting object
7056
        for (var i = idx - 1; i >= 0; --i) {
7057
 
7058
          var isIntersecting = object.intersectsWithObject(this._objects[i]) ||
7059
                               object.isContainedWithinObject(this._objects[i]) ||
7060
                               this._objects[i].isContainedWithinObject(object);
7061
 
7062
          if (isIntersecting) {
7063
            newIdx = i;
7064
            break;
7065
          }
7066
        }
7067
      }
7068
      else {
7069
        newIdx = idx - 1;
7070
      }
7071
 
7072
      return newIdx;
7073
    },
7074
 
7075
    /**
7076
     * Moves an object up in stack of drawn objects
7077
     * @param {fabric.Object} object Object to send
7078
     * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object
7079
     * @return {fabric.Canvas} thisArg
7080
     * @chainable
7081
     */
7082
    bringForward: function (object, intersecting) {
7083
      var idx = this._objects.indexOf(object);
7084
 
7085
      // if object is not on top of stack (last item in an array)
7086
      if (idx !== this._objects.length - 1) {
7087
        var newIdx = this._findNewUpperIndex(object, idx, intersecting);
7088
 
7089
        removeFromArray(this._objects, object);
7090
        this._objects.splice(newIdx, 0, object);
7091
        this.renderAll && this.renderAll();
7092
      }
7093
      return this;
7094
    },
7095
 
7096
    /**
7097
     * @private
7098
     */
7099
    _findNewUpperIndex: function(object, idx, intersecting) {
7100
      var newIdx;
7101
 
7102
      if (intersecting) {
7103
        newIdx = idx;
7104
 
7105
        // traverse up the stack looking for the nearest intersecting object
7106
        for (var i = idx + 1; i < this._objects.length; ++i) {
7107
 
7108
          var isIntersecting = object.intersectsWithObject(this._objects[i]) ||
7109
                               object.isContainedWithinObject(this._objects[i]) ||
7110
                               this._objects[i].isContainedWithinObject(object);
7111
 
7112
          if (isIntersecting) {
7113
            newIdx = i;
7114
            break;
7115
          }
7116
        }
7117
      }
7118
      else {
7119
        newIdx = idx + 1;
7120
      }
7121
 
7122
      return newIdx;
7123
    },
7124
 
7125
    /**
7126
     * Moves an object to specified level in stack of drawn objects
7127
     * @param {fabric.Object} object Object to send
7128
     * @param {Number} index Position to move to
7129
     * @return {fabric.Canvas} thisArg
7130
     * @chainable
7131
     */
7132
    moveTo: function (object, index) {
7133
      removeFromArray(this._objects, object);
7134
      this._objects.splice(index, 0, object);
7135
      return this.renderAll && this.renderAll();
7136
    },
7137
 
7138
    /**
7139
     * Clears a canvas element and removes all event listeners
7140
     * @return {fabric.Canvas} thisArg
7141
     * @chainable
7142
     */
7143
    dispose: function () {
7144
      this.clear();
7145
      this.interactive && this.removeListeners();
7146
      return this;
7147
    },
7148
 
7149
    /**
7150
     * Returns a string representation of an instance
7151
     * @return {String} string representation of an instance
7152
     */
7153
    toString: function () {
7154
      return '#<fabric.Canvas (' + this.complexity() + '): ' +
7155
               '{ objects: ' + this.getObjects().length + ' }>';
7156
    }
7157
  });
7158
 
7159
  extend(fabric.StaticCanvas.prototype, fabric.Observable);
7160
  extend(fabric.StaticCanvas.prototype, fabric.Collection);
7161
  extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter);
7162
 
7163
  extend(fabric.StaticCanvas, /** @lends fabric.StaticCanvas */ {
7164
 
7165
    /**
7166
     * @static
7167
     * @type String
7168
     * @default
7169
     */
7170
    EMPTY_JSON: '{"objects": [], "background": "white"}',
7171
 
7172
    /**
7173
     * Provides a way to check support of some of the canvas methods
7174
     * (either those of HTMLCanvasElement itself, or rendering context)
7175
     *
7176
     * @param {String} methodName Method to check support for;
7177
     *                            Could be one of "getImageData", "toDataURL", "toDataURLWithQuality" or "setLineDash"
7178
     * @return {Boolean | null} `true` if method is supported (or at least exists),
7179
     *                          `null` if canvas element or context can not be initialized
7180
     */
7181
    supports: function (methodName) {
7182
      var el = fabric.util.createCanvasElement();
7183
 
7184
      if (!el || !el.getContext) {
7185
        return null;
7186
      }
7187
 
7188
      var ctx = el.getContext('2d');
7189
      if (!ctx) {
7190
        return null;
7191
      }
7192
 
7193
      switch (methodName) {
7194
 
7195
        case 'getImageData':
7196
          return typeof ctx.getImageData !== 'undefined';
7197
 
7198
        case 'setLineDash':
7199
          return typeof ctx.setLineDash !== 'undefined';
7200
 
7201
        case 'toDataURL':
7202
          return typeof el.toDataURL !== 'undefined';
7203
 
7204
        case 'toDataURLWithQuality':
7205
          try {
7206
            el.toDataURL('image/jpeg', 0);
7207
            return true;
7208
          }
7209
          catch (e) { }
7210
          return false;
7211
 
7212
        default:
7213
          return null;
7214
      }
7215
    }
7216
  });
7217
 
7218
  /**
7219
   * Returns JSON representation of canvas
7220
   * @function
7221
   * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
7222
   * @return {String} JSON string
7223
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization}
7224
   * @see {@link http://jsfiddle.net/fabricjs/pec86/|jsFiddle demo}
7225
   * @example <caption>JSON without additional properties</caption>
7226
   * var json = canvas.toJSON();
7227
   * @example <caption>JSON with additional properties included</caption>
7228
   * var json = canvas.toJSON(['lockMovementX', 'lockMovementY', 'lockRotation', 'lockScalingX', 'lockScalingY', 'lockUniScaling']);
7229
   * @example <caption>JSON without default values</caption>
7230
   * canvas.includeDefaultValues = false;
7231
   * var json = canvas.toJSON();
7232
   */
7233
  fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject;
7234
 
7235
})();
7236
 
7237
 
7238
/**
7239
 * BaseBrush class
7240
 * @class fabric.BaseBrush
7241
 * @see {@link http://fabricjs.com/freedrawing/|Freedrawing demo}
7242
 */
7243
fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ {
7244
 
7245
  /**
7246
   * Color of a brush
7247
   * @type String
7248
   * @default
7249
   */
7250
  color:            'rgb(0, 0, 0)',
7251
 
7252
  /**
7253
   * Width of a brush
7254
   * @type Number
7255
   * @default
7256
   */
7257
  width:            1,
7258
 
7259
  /**
7260
   * Shadow object representing shadow of this shape.
7261
   * <b>Backwards incompatibility note:</b> This property replaces "shadowColor" (String), "shadowOffsetX" (Number),
7262
   * "shadowOffsetY" (Number) and "shadowBlur" (Number) since v1.2.12
7263
   * @type fabric.Shadow
7264
   * @default
7265
   */
7266
  shadow:          null,
7267
 
7268
  /**
7269
   * Line endings style of a brush (one of "butt", "round", "square")
7270
   * @type String
7271
   * @default
7272
   */
7273
  strokeLineCap:    'round',
7274
 
7275
  /**
7276
   * Corner style of a brush (one of "bevil", "round", "miter")
7277
   * @type String
7278
   * @default
7279
   */
7280
  strokeLineJoin:   'round',
7281
 
7282
  /**
7283
   * Stroke Dash Array.
7284
   * @type Array
7285
   * @default
7286
   */
7287
  strokeDashArray:   null,
7288
 
7289
  /**
7290
   * Sets shadow of an object
7291
   * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)")
7292
   * @return {fabric.Object} thisArg
7293
   * @chainable
7294
   */
7295
  setShadow: function(options) {
7296
    this.shadow = new fabric.Shadow(options);
7297
    return this;
7298
  },
7299
 
7300
  /**
7301
   * Sets brush styles
7302
   * @private
7303
   */
7304
  _setBrushStyles: function() {
7305
    var ctx = this.canvas.contextTop;
7306
 
7307
    ctx.strokeStyle = this.color;
7308
    ctx.lineWidth = this.width;
7309
    ctx.lineCap = this.strokeLineCap;
7310
    ctx.lineJoin = this.strokeLineJoin;
7311
    if (this.strokeDashArray && fabric.StaticCanvas.supports('setLineDash')) {
7312
      ctx.setLineDash(this.strokeDashArray);
7313
    }
7314
  },
7315
 
7316
  /**
7317
   * Sets brush shadow styles
7318
   * @private
7319
   */
7320
  _setShadow: function() {
7321
    if (!this.shadow) {
7322
      return;
7323
    }
7324
 
7325
    var ctx = this.canvas.contextTop;
7326
 
7327
    ctx.shadowColor = this.shadow.color;
7328
    ctx.shadowBlur = this.shadow.blur;
7329
    ctx.shadowOffsetX = this.shadow.offsetX;
7330
    ctx.shadowOffsetY = this.shadow.offsetY;
7331
  },
7332
 
7333
  /**
7334
   * Removes brush shadow styles
7335
   * @private
7336
   */
7337
  _resetShadow: function() {
7338
    var ctx = this.canvas.contextTop;
7339
 
7340
    ctx.shadowColor = '';
7341
    ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0;
7342
  }
7343
});
7344
 
7345
 
7346
(function() {
7347
 
7348
  /**
7349
   * PencilBrush class
7350
   * @class fabric.PencilBrush
7351
   * @extends fabric.BaseBrush
7352
   */
7353
  fabric.PencilBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.PencilBrush.prototype */ {
7354
 
7355
    /**
7356
     * Constructor
7357
     * @param {fabric.Canvas} canvas
7358
     * @return {fabric.PencilBrush} Instance of a pencil brush
7359
     */
7360
    initialize: function(canvas) {
7361
      this.canvas = canvas;
7362
      this._points = [ ];
7363
    },
7364
 
7365
    /**
7366
     * Inovoked on mouse down
7367
     * @param {Object} pointer
7368
     */
7369
    onMouseDown: function(pointer) {
7370
      this._prepareForDrawing(pointer);
7371
      // capture coordinates immediately
7372
      // this allows to draw dots (when movement never occurs)
7373
      this._captureDrawingPath(pointer);
7374
      this._render();
7375
    },
7376
 
7377
    /**
7378
     * Inovoked on mouse move
7379
     * @param {Object} pointer
7380
     */
7381
    onMouseMove: function(pointer) {
7382
      this._captureDrawingPath(pointer);
7383
      // redraw curve
7384
      // clear top canvas
7385
      this.canvas.clearContext(this.canvas.contextTop);
7386
      this._render();
7387
    },
7388
 
7389
    /**
7390
     * Invoked on mouse up
7391
     */
7392
    onMouseUp: function() {
7393
      this._finalizeAndAddPath();
7394
    },
7395
 
7396
    /**
7397
     * @private
7398
     * @param {Object} pointer Actual mouse position related to the canvas.
7399
     */
7400
    _prepareForDrawing: function(pointer) {
7401
 
7402
      var p = new fabric.Point(pointer.x, pointer.y);
7403
 
7404
      this._reset();
7405
      this._addPoint(p);
7406
 
7407
      this.canvas.contextTop.moveTo(p.x, p.y);
7408
    },
7409
 
7410
    /**
7411
     * @private
7412
     * @param {fabric.Point} point Point to be added to points array
7413
     */
7414
    _addPoint: function(point) {
7415
      this._points.push(point);
7416
    },
7417
 
7418
    /**
7419
     * Clear points array and set contextTop canvas style.
7420
     * @private
7421
     */
7422
    _reset: function() {
7423
      this._points.length = 0;
7424
 
7425
      this._setBrushStyles();
7426
      this._setShadow();
7427
    },
7428
 
7429
    /**
7430
     * @private
7431
     * @param {Object} pointer Actual mouse position related to the canvas.
7432
     */
7433
    _captureDrawingPath: function(pointer) {
7434
      var pointerPoint = new fabric.Point(pointer.x, pointer.y);
7435
      this._addPoint(pointerPoint);
7436
    },
7437
 
7438
    /**
7439
     * Draw a smooth path on the topCanvas using quadraticCurveTo
7440
     * @private
7441
     */
7442
    _render: function() {
7443
      var ctx  = this.canvas.contextTop,
7444
          v = this.canvas.viewportTransform,
7445
          p1 = this._points[0],
7446
          p2 = this._points[1];
7447
 
7448
      ctx.save();
7449
      ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
7450
      ctx.beginPath();
7451
 
7452
      //if we only have 2 points in the path and they are the same
7453
      //it means that the user only clicked the canvas without moving the mouse
7454
      //then we should be drawing a dot. A path isn't drawn between two identical dots
7455
      //that's why we set them apart a bit
7456
      if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) {
7457
        p1.x -= 0.5;
7458
        p2.x += 0.5;
7459
      }
7460
      ctx.moveTo(p1.x, p1.y);
7461
 
7462
      for (var i = 1, len = this._points.length; i < len; i++) {
7463
        // we pick the point between pi + 1 & pi + 2 as the
7464
        // end point and p1 as our control point.
7465
        var midPoint = p1.midPointFrom(p2);
7466
        ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y);
7467
 
7468
        p1 = this._points[i];
7469
        p2 = this._points[i + 1];
7470
      }
7471
      // Draw last line as a straight line while
7472
      // we wait for the next point to be able to calculate
7473
      // the bezier control point
7474
      ctx.lineTo(p1.x, p1.y);
7475
      ctx.stroke();
7476
      ctx.restore();
7477
    },
7478
 
7479
    /**
7480
     * Converts points to SVG path
7481
     * @param {Array} points Array of points
7482
     * @param {Number} minX
7483
     * @param {Number} minY
7484
     * @return {String} SVG path
7485
     */
7486
    convertPointsToSVGPath: function(points) {
7487
      var path = [],
7488
          p1 = new fabric.Point(points[0].x, points[0].y),
7489
          p2 = new fabric.Point(points[1].x, points[1].y);
7490
 
7491
      path.push('M ', points[0].x, ' ', points[0].y, ' ');
7492
      for (var i = 1, len = points.length; i < len; i++) {
7493
        var midPoint = p1.midPointFrom(p2);
7494
        // p1 is our bezier control point
7495
        // midpoint is our endpoint
7496
        // start point is p(i-1) value.
7497
        path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' ');
7498
        p1 = new fabric.Point(points[i].x, points[i].y);
7499
        if ((i + 1) < points.length) {
7500
          p2 = new fabric.Point(points[i + 1].x, points[i + 1].y);
7501
        }
7502
      }
7503
      path.push('L ', p1.x, ' ', p1.y, ' ');
7504
      return path;
7505
    },
7506
 
7507
    /**
7508
     * Creates fabric.Path object to add on canvas
7509
     * @param {String} pathData Path data
7510
     * @return {fabric.Path} Path to add on canvas
7511
     */
7512
    createPath: function(pathData) {
7513
      var path = new fabric.Path(pathData, {
7514
                   fill: null,
7515
                   stroke: this.color,
7516
                   strokeWidth: this.width,
7517
                   strokeLineCap: this.strokeLineCap,
7518
                   strokeLineJoin: this.strokeLineJoin,
7519
                   strokeDashArray: this.strokeDashArray,
7520
                   originX: 'center',
7521
                   originY: 'center'
7522
                 });
7523
 
7524
      if (this.shadow) {
7525
        this.shadow.affectStroke = true;
7526
        path.setShadow(this.shadow);
7527
      }
7528
 
7529
      return path;
7530
    },
7531
 
7532
    /**
7533
     * On mouseup after drawing the path on contextTop canvas
7534
     * we use the points captured to create an new fabric path object
7535
     * and add it to the fabric canvas.
7536
     */
7537
    _finalizeAndAddPath: function() {
7538
      var ctx = this.canvas.contextTop;
7539
      ctx.closePath();
7540
 
7541
      var pathData = this.convertPointsToSVGPath(this._points).join('');
7542
      if (pathData === 'M 0 0 Q 0 0 0 0 L 0 0') {
7543
        // do not create 0 width/height paths, as they are
7544
        // rendered inconsistently across browsers
7545
        // Firefox 4, for example, renders a dot,
7546
        // whereas Chrome 10 renders nothing
7547
        this.canvas.renderAll();
7548
        return;
7549
      }
7550
 
7551
      var path = this.createPath(pathData);
7552
 
7553
      this.canvas.add(path);
7554
      path.setCoords();
7555
 
7556
      this.canvas.clearContext(this.canvas.contextTop);
7557
      this._resetShadow();
7558
      this.canvas.renderAll();
7559
 
7560
      // fire event 'path' created
7561
      this.canvas.fire('path:created', { path: path });
7562
    }
7563
  });
7564
})();
7565
 
7566
 
7567
/**
7568
 * CircleBrush class
7569
 * @class fabric.CircleBrush
7570
 */
7571
fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.CircleBrush.prototype */ {
7572
 
7573
  /**
7574
   * Width of a brush
7575
   * @type Number
7576
   * @default
7577
   */
7578
  width: 10,
7579
 
7580
  /**
7581
   * Constructor
7582
   * @param {fabric.Canvas} canvas
7583
   * @return {fabric.CircleBrush} Instance of a circle brush
7584
   */
7585
  initialize: function(canvas) {
7586
    this.canvas = canvas;
7587
    this.points = [ ];
7588
  },
7589
  /**
7590
  * Invoked inside on mouse down and mouse move
7591
  * @param {Object} pointer
7592
  */
7593
  drawDot: function(pointer) {
7594
    var point = this.addPoint(pointer),
7595
        ctx = this.canvas.contextTop,
7596
        v = this.canvas.viewportTransform;
7597
    ctx.save();
7598
    ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
7599
 
7600
    ctx.fillStyle = point.fill;
7601
    ctx.beginPath();
7602
    ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false);
7603
    ctx.closePath();
7604
    ctx.fill();
7605
 
7606
    ctx.restore();
7607
  },
7608
 
7609
  /**
7610
   * Invoked on mouse down
7611
   */
7612
  onMouseDown: function(pointer) {
7613
    this.points.length = 0;
7614
    this.canvas.clearContext(this.canvas.contextTop);
7615
    this._setShadow();
7616
    this.drawDot(pointer);
7617
  },
7618
 
7619
  /**
7620
   * Invoked on mouse move
7621
   * @param {Object} pointer
7622
   */
7623
  onMouseMove: function(pointer) {
7624
    this.drawDot(pointer);
7625
  },
7626
 
7627
  /**
7628
   * Invoked on mouse up
7629
   */
7630
  onMouseUp: function() {
7631
    var originalRenderOnAddRemove = this.canvas.renderOnAddRemove;
7632
    this.canvas.renderOnAddRemove = false;
7633
 
7634
    var circles = [ ];
7635
 
7636
    for (var i = 0, len = this.points.length; i < len; i++) {
7637
      var point = this.points[i],
7638
          circle = new fabric.Circle({
7639
            radius: point.radius,
7640
            left: point.x,
7641
            top: point.y,
7642
            originX: 'center',
7643
            originY: 'center',
7644
            fill: point.fill
7645
          });
7646
 
7647
      this.shadow && circle.setShadow(this.shadow);
7648
 
7649
      circles.push(circle);
7650
    }
7651
    var group = new fabric.Group(circles, { originX: 'center', originY: 'center' });
7652
    group.canvas = this.canvas;
7653
 
7654
    this.canvas.add(group);
7655
    this.canvas.fire('path:created', { path: group });
7656
 
7657
    this.canvas.clearContext(this.canvas.contextTop);
7658
    this._resetShadow();
7659
    this.canvas.renderOnAddRemove = originalRenderOnAddRemove;
7660
    this.canvas.renderAll();
7661
  },
7662
 
7663
  /**
7664
   * @param {Object} pointer
7665
   * @return {fabric.Point} Just added pointer point
7666
   */
7667
  addPoint: function(pointer) {
7668
    var pointerPoint = new fabric.Point(pointer.x, pointer.y),
7669
 
7670
        circleRadius = fabric.util.getRandomInt(
7671
                        Math.max(0, this.width - 20), this.width + 20) / 2,
7672
 
7673
        circleColor = new fabric.Color(this.color)
7674
                        .setAlpha(fabric.util.getRandomInt(0, 100) / 100)
7675
                        .toRgba();
7676
 
7677
    pointerPoint.radius = circleRadius;
7678
    pointerPoint.fill = circleColor;
7679
 
7680
    this.points.push(pointerPoint);
7681
 
7682
    return pointerPoint;
7683
  }
7684
});
7685
 
7686
 
7687
/**
7688
 * SprayBrush class
7689
 * @class fabric.SprayBrush
7690
 */
7691
fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric.SprayBrush.prototype */ {
7692
 
7693
  /**
7694
   * Width of a spray
7695
   * @type Number
7696
   * @default
7697
   */
7698
  width:              10,
7699
 
7700
  /**
7701
   * Density of a spray (number of dots per chunk)
7702
   * @type Number
7703
   * @default
7704
   */
7705
  density:            20,
7706
 
7707
  /**
7708
   * Width of spray dots
7709
   * @type Number
7710
   * @default
7711
   */
7712
  dotWidth:           1,
7713
 
7714
  /**
7715
   * Width variance of spray dots
7716
   * @type Number
7717
   * @default
7718
   */
7719
  dotWidthVariance:   1,
7720
 
7721
  /**
7722
   * Whether opacity of a dot should be random
7723
   * @type Boolean
7724
   * @default
7725
   */
7726
  randomOpacity:        false,
7727
 
7728
  /**
7729
   * Whether overlapping dots (rectangles) should be removed (for performance reasons)
7730
   * @type Boolean
7731
   * @default
7732
   */
7733
  optimizeOverlapping:  true,
7734
 
7735
  /**
7736
   * Constructor
7737
   * @param {fabric.Canvas} canvas
7738
   * @return {fabric.SprayBrush} Instance of a spray brush
7739
   */
7740
  initialize: function(canvas) {
7741
    this.canvas = canvas;
7742
    this.sprayChunks = [ ];
7743
  },
7744
 
7745
  /**
7746
   * Invoked on mouse down
7747
   * @param {Object} pointer
7748
   */
7749
  onMouseDown: function(pointer) {
7750
    this.sprayChunks.length = 0;
7751
    this.canvas.clearContext(this.canvas.contextTop);
7752
    this._setShadow();
7753
 
7754
    this.addSprayChunk(pointer);
7755
    this.render();
7756
  },
7757
 
7758
  /**
7759
   * Invoked on mouse move
7760
   * @param {Object} pointer
7761
   */
7762
  onMouseMove: function(pointer) {
7763
    this.addSprayChunk(pointer);
7764
    this.render();
7765
  },
7766
 
7767
  /**
7768
   * Invoked on mouse up
7769
   */
7770
  onMouseUp: function() {
7771
    var originalRenderOnAddRemove = this.canvas.renderOnAddRemove;
7772
    this.canvas.renderOnAddRemove = false;
7773
 
7774
    var rects = [ ];
7775
 
7776
    for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) {
7777
      var sprayChunk = this.sprayChunks[i];
7778
 
7779
      for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) {
7780
 
7781
        var rect = new fabric.Rect({
7782
          width: sprayChunk[j].width,
7783
          height: sprayChunk[j].width,
7784
          left: sprayChunk[j].x + 1,
7785
          top: sprayChunk[j].y + 1,
7786
          originX: 'center',
7787
          originY: 'center',
7788
          fill: this.color
7789
        });
7790
 
7791
        this.shadow && rect.setShadow(this.shadow);
7792
        rects.push(rect);
7793
      }
7794
    }
7795
 
7796
    if (this.optimizeOverlapping) {
7797
      rects = this._getOptimizedRects(rects);
7798
    }
7799
 
7800
    var group = new fabric.Group(rects, { originX: 'center', originY: 'center' });
7801
    group.canvas = this.canvas;
7802
 
7803
    this.canvas.add(group);
7804
    this.canvas.fire('path:created', { path: group });
7805
 
7806
    this.canvas.clearContext(this.canvas.contextTop);
7807
    this._resetShadow();
7808
    this.canvas.renderOnAddRemove = originalRenderOnAddRemove;
7809
    this.canvas.renderAll();
7810
  },
7811
 
7812
  /**
7813
   * @private
7814
   * @param {Array} rects
7815
   */
7816
  _getOptimizedRects: function(rects) {
7817
 
7818
    // avoid creating duplicate rects at the same coordinates
7819
    var uniqueRects = { }, key;
7820
 
7821
    for (var i = 0, len = rects.length; i < len; i++) {
7822
      key = rects[i].left + '' + rects[i].top;
7823
      if (!uniqueRects[key]) {
7824
        uniqueRects[key] = rects[i];
7825
      }
7826
    }
7827
    var uniqueRectsArray = [ ];
7828
    for (key in uniqueRects) {
7829
      uniqueRectsArray.push(uniqueRects[key]);
7830
    }
7831
 
7832
    return uniqueRectsArray;
7833
  },
7834
 
7835
  /**
7836
   * Renders brush
7837
   */
7838
  render: function() {
7839
    var ctx = this.canvas.contextTop;
7840
    ctx.fillStyle = this.color;
7841
 
7842
    var v = this.canvas.viewportTransform;
7843
    ctx.save();
7844
    ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
7845
 
7846
    for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) {
7847
      var point = this.sprayChunkPoints[i];
7848
      if (typeof point.opacity !== 'undefined') {
7849
        ctx.globalAlpha = point.opacity;
7850
      }
7851
      ctx.fillRect(point.x, point.y, point.width, point.width);
7852
    }
7853
    ctx.restore();
7854
  },
7855
 
7856
  /**
7857
   * @param {Object} pointer
7858
   */
7859
  addSprayChunk: function(pointer) {
7860
    this.sprayChunkPoints = [ ];
7861
 
7862
    var x, y, width, radius = this.width / 2;
7863
 
7864
    for (var i = 0; i < this.density; i++) {
7865
 
7866
      x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius);
7867
      y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius);
7868
 
7869
      if (this.dotWidthVariance) {
7870
        width = fabric.util.getRandomInt(
7871
          // bottom clamp width to 1
7872
          Math.max(1, this.dotWidth - this.dotWidthVariance),
7873
          this.dotWidth + this.dotWidthVariance);
7874
      }
7875
      else {
7876
        width = this.dotWidth;
7877
      }
7878
 
7879
      var point = new fabric.Point(x, y);
7880
      point.width = width;
7881
 
7882
      if (this.randomOpacity) {
7883
        point.opacity = fabric.util.getRandomInt(0, 100) / 100;
7884
      }
7885
 
7886
      this.sprayChunkPoints.push(point);
7887
    }
7888
 
7889
    this.sprayChunks.push(this.sprayChunkPoints);
7890
  }
7891
});
7892
 
7893
 
7894
/**
7895
 * PatternBrush class
7896
 * @class fabric.PatternBrush
7897
 * @extends fabric.BaseBrush
7898
 */
7899
fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fabric.PatternBrush.prototype */ {
7900
 
7901
  getPatternSrc: function() {
7902
 
7903
    var dotWidth = 20,
7904
        dotDistance = 5,
7905
        patternCanvas = fabric.document.createElement('canvas'),
7906
        patternCtx = patternCanvas.getContext('2d');
7907
 
7908
    patternCanvas.width = patternCanvas.height = dotWidth + dotDistance;
7909
 
7910
    patternCtx.fillStyle = this.color;
7911
    patternCtx.beginPath();
7912
    patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false);
7913
    patternCtx.closePath();
7914
    patternCtx.fill();
7915
 
7916
    return patternCanvas;
7917
  },
7918
 
7919
  getPatternSrcFunction: function() {
7920
    return String(this.getPatternSrc).replace('this.color', '"' + this.color + '"');
7921
  },
7922
 
7923
  /**
7924
   * Creates "pattern" instance property
7925
   */
7926
  getPattern: function() {
7927
    return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), 'repeat');
7928
  },
7929
 
7930
  /**
7931
   * Sets brush styles
7932
   */
7933
  _setBrushStyles: function() {
7934
    this.callSuper('_setBrushStyles');
7935
    this.canvas.contextTop.strokeStyle = this.getPattern();
7936
  },
7937
 
7938
  /**
7939
   * Creates path
7940
   */
7941
  createPath: function(pathData) {
7942
    var path = this.callSuper('createPath', pathData);
7943
    path.stroke = new fabric.Pattern({
7944
      source: this.source || this.getPatternSrcFunction()
7945
    });
7946
    return path;
7947
  }
7948
});
7949
 
7950
 
7951
(function() {
7952
 
7953
  var getPointer = fabric.util.getPointer,
7954
      degreesToRadians = fabric.util.degreesToRadians,
7955
      radiansToDegrees = fabric.util.radiansToDegrees,
7956
      atan2 = Math.atan2,
7957
      abs = Math.abs,
7958
 
7959
      STROKE_OFFSET = 0.5;
7960
 
7961
  /**
7962
   * Canvas class
7963
   * @class fabric.Canvas
7964
   * @extends fabric.StaticCanvas
7965
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#canvas}
7966
   * @see {@link fabric.Canvas#initialize} for constructor definition
7967
   *
7968
   * @fires object:modified
7969
   * @fires object:rotating
7970
   * @fires object:scaling
7971
   * @fires object:moving
7972
   * @fires object:selected
7973
   *
7974
   * @fires before:selection:cleared
7975
   * @fires selection:cleared
7976
   * @fires selection:created
7977
   *
7978
   * @fires path:created
7979
   * @fires mouse:down
7980
   * @fires mouse:move
7981
   * @fires mouse:up
7982
   * @fires mouse:over
7983
   * @fires mouse:out
7984
   *
7985
   */
7986
  fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, /** @lends fabric.Canvas.prototype */ {
7987
 
7988
    /**
7989
     * Constructor
7990
     * @param {HTMLElement | String} el &lt;canvas> element to initialize instance on
7991
     * @param {Object} [options] Options object
7992
     * @return {Object} thisArg
7993
     */
7994
    initialize: function(el, options) {
7995
      options || (options = { });
7996
 
7997
      this._initStatic(el, options);
7998
      this._initInteractive();
7999
      this._createCacheCanvas();
8000
 
8001
      fabric.Canvas.activeInstance = this;
8002
    },
8003
 
8004
    /**
8005
     * When true, objects can be transformed by one side (unproportionally)
8006
     * @type Boolean
8007
     * @default
8008
     */
8009
    uniScaleTransform:      false,
8010
 
8011
    /**
8012
     * When true, objects use center point as the origin of scale transformation.
8013
     * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean).
8014
     * @since 1.3.4
8015
     * @type Boolean
8016
     * @default
8017
     */
8018
    centeredScaling:        false,
8019
 
8020
    /**
8021
     * When true, objects use center point as the origin of rotate transformation.
8022
     * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean).
8023
     * @since 1.3.4
8024
     * @type Boolean
8025
     * @default
8026
     */
8027
    centeredRotation:       false,
8028
 
8029
    /**
8030
     * Indicates that canvas is interactive. This property should not be changed.
8031
     * @type Boolean
8032
     * @default
8033
     */
8034
    interactive:            true,
8035
 
8036
    /**
8037
     * Indicates whether group selection should be enabled
8038
     * @type Boolean
8039
     * @default
8040
     */
8041
    selection:              true,
8042
 
8043
    /**
8044
     * Color of selection
8045
     * @type String
8046
     * @default
8047
     */
8048
    selectionColor:         'rgba(100, 100, 255, 0.3)', // blue
8049
 
8050
    /**
8051
     * Default dash array pattern
8052
     * If not empty the selection border is dashed
8053
     * @type Array
8054
     */
8055
    selectionDashArray:     [ ],
8056
 
8057
    /**
8058
     * Color of the border of selection (usually slightly darker than color of selection itself)
8059
     * @type String
8060
     * @default
8061
     */
8062
    selectionBorderColor:   'rgba(255, 255, 255, 0.3)',
8063
 
8064
    /**
8065
     * Width of a line used in object/group selection
8066
     * @type Number
8067
     * @default
8068
     */
8069
    selectionLineWidth:     1,
8070
 
8071
    /**
8072
     * Default cursor value used when hovering over an object on canvas
8073
     * @type String
8074
     * @default
8075
     */
8076
    hoverCursor:            'move',
8077
 
8078
    /**
8079
     * Default cursor value used when moving an object on canvas
8080
     * @type String
8081
     * @default
8082
     */
8083
    moveCursor:             'move',
8084
 
8085
    /**
8086
     * Default cursor value used for the entire canvas
8087
     * @type String
8088
     * @default
8089
     */
8090
    defaultCursor:          'default',
8091
 
8092
    /**
8093
     * Cursor value used during free drawing
8094
     * @type String
8095
     * @default
8096
     */
8097
    freeDrawingCursor:      'crosshair',
8098
 
8099
    /**
8100
     * Cursor value used for rotation point
8101
     * @type String
8102
     * @default
8103
     */
8104
    rotationCursor:         'crosshair',
8105
 
8106
    /**
8107
     * Default element class that's given to wrapper (div) element of canvas
8108
     * @type String
8109
     * @default
8110
     */
8111
    containerClass:         'canvas-container',
8112
 
8113
    /**
8114
     * When true, object detection happens on per-pixel basis rather than on per-bounding-box
8115
     * @type Boolean
8116
     * @default
8117
     */
8118
    perPixelTargetFind:     false,
8119
 
8120
    /**
8121
     * Number of pixels around target pixel to tolerate (consider active) during object detection
8122
     * @type Number
8123
     * @default
8124
     */
8125
    targetFindTolerance:    0,
8126
 
8127
    /**
8128
     * When true, target detection is skipped when hovering over canvas. This can be used to improve performance.
8129
     * @type Boolean
8130
     * @default
8131
     */
8132
    skipTargetFind:         false,
8133
 
8134
    /**
8135
     * @private
8136
     */
8137
    _initInteractive: function() {
8138
      this._currentTransform = null;
8139
      this._groupSelector = null;
8140
      this._initWrapperElement();
8141
      this._createUpperCanvas();
8142
      this._initEventListeners();
8143
 
8144
      this.freeDrawingBrush = fabric.PencilBrush && new fabric.PencilBrush(this);
8145
 
8146
      this.calcOffset();
8147
    },
8148
 
8149
    /**
8150
     * Resets the current transform to its original values and chooses the type of resizing based on the event
8151
     * @private
8152
     * @param {Event} e Event object fired on mousemove
8153
     */
8154
    _resetCurrentTransform: function(e) {
8155
      var t = this._currentTransform;
8156
 
8157
      t.target.set({
8158
        scaleX: t.original.scaleX,
8159
        scaleY: t.original.scaleY,
8160
        left: t.original.left,
8161
        top: t.original.top
8162
      });
8163
 
8164
      if (this._shouldCenterTransform(e, t.target)) {
8165
        if (t.action === 'rotate') {
8166
          this._setOriginToCenter(t.target);
8167
        }
8168
        else {
8169
          if (t.originX !== 'center') {
8170
            if (t.originX === 'right') {
8171
              t.mouseXSign = -1;
8172
            }
8173
            else {
8174
              t.mouseXSign = 1;
8175
            }
8176
          }
8177
          if (t.originY !== 'center') {
8178
            if (t.originY === 'bottom') {
8179
              t.mouseYSign = -1;
8180
            }
8181
            else {
8182
              t.mouseYSign = 1;
8183
            }
8184
          }
8185
 
8186
          t.originX = 'center';
8187
          t.originY = 'center';
8188
        }
8189
      }
8190
      else {
8191
        t.originX = t.original.originX;
8192
        t.originY = t.original.originY;
8193
      }
8194
    },
8195
 
8196
    /**
8197
     * Checks if point is contained within an area of given object
8198
     * @param {Event} e Event object
8199
     * @param {fabric.Object} target Object to test against
8200
     * @return {Boolean} true if point is contained within an area of given object
8201
     */
8202
    containsPoint: function (e, target) {
8203
      var pointer = this.getPointer(e, true),
8204
          xy = this._normalizePointer(target, pointer);
8205
 
8206
      // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html
8207
      // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html
8208
      return (target.containsPoint(xy) || target._findTargetCorner(pointer));
8209
    },
8210
 
8211
    /**
8212
     * @private
8213
     */
8214
    _normalizePointer: function (object, pointer) {
8215
      var activeGroup = this.getActiveGroup(),
8216
          x = pointer.x,
8217
          y = pointer.y,
8218
          isObjectInGroup = (
8219
            activeGroup &&
8220
            object.type !== 'group' &&
8221
            activeGroup.contains(object)),
8222
          lt;
8223
 
8224
      if (isObjectInGroup) {
8225
        lt = new fabric.Point(activeGroup.left, activeGroup.top);
8226
        lt = fabric.util.transformPoint(lt, this.viewportTransform, true);
8227
        x -= lt.x;
8228
        y -= lt.y;
8229
      }
8230
      return { x: x, y: y };
8231
    },
8232
 
8233
    /**
8234
     * Returns true if object is transparent at a certain location
8235
     * @param {fabric.Object} target Object to check
8236
     * @param {Number} x Left coordinate
8237
     * @param {Number} y Top coordinate
8238
     * @return {Boolean}
8239
     */
8240
    isTargetTransparent: function (target, x, y) {
8241
      var hasBorders = target.hasBorders,
8242
          transparentCorners = target.transparentCorners;
8243
 
8244
      target.hasBorders = target.transparentCorners = false;
8245
 
8246
      this._draw(this.contextCache, target);
8247
 
8248
      target.hasBorders = hasBorders;
8249
      target.transparentCorners = transparentCorners;
8250
 
8251
      var isTransparent = fabric.util.isTransparent(
8252
        this.contextCache, x, y, this.targetFindTolerance);
8253
 
8254
      this.clearContext(this.contextCache);
8255
 
8256
      return isTransparent;
8257
    },
8258
 
8259
    /**
8260
     * @private
8261
     * @param {Event} e Event object
8262
     * @param {fabric.Object} target
8263
     */
8264
    _shouldClearSelection: function (e, target) {
8265
      var activeGroup = this.getActiveGroup(),
8266
          activeObject = this.getActiveObject();
8267
 
8268
      return (
8269
        !target
8270
        ||
8271
        (target &&
8272
          activeGroup &&
8273
          !activeGroup.contains(target) &&
8274
          activeGroup !== target &&
8275
          !e.shiftKey)
8276
        ||
8277
        (target && !target.evented)
8278
        ||
8279
        (target &&
8280
          !target.selectable &&
8281
          activeObject &&
8282
          activeObject !== target)
8283
      );
8284
    },
8285
 
8286
    /**
8287
     * @private
8288
     * @param {Event} e Event object
8289
     * @param {fabric.Object} target
8290
     */
8291
    _shouldCenterTransform: function (e, target) {
8292
      if (!target) {
8293
        return;
8294
      }
8295
 
8296
      var t = this._currentTransform,
8297
          centerTransform;
8298
 
8299
      if (t.action === 'scale' || t.action === 'scaleX' || t.action === 'scaleY') {
8300
        centerTransform = this.centeredScaling || target.centeredScaling;
8301
      }
8302
      else if (t.action === 'rotate') {
8303
        centerTransform = this.centeredRotation || target.centeredRotation;
8304
      }
8305
 
8306
      return centerTransform ? !e.altKey : e.altKey;
8307
    },
8308
 
8309
    /**
8310
     * @private
8311
     */
8312
    _getOriginFromCorner: function(target, corner) {
8313
      var origin = {
8314
        x: target.originX,
8315
        y: target.originY
8316
      };
8317
 
8318
      if (corner === 'ml' || corner === 'tl' || corner === 'bl') {
8319
        origin.x = 'right';
8320
      }
8321
      else if (corner === 'mr' || corner === 'tr' || corner === 'br') {
8322
        origin.x = 'left';
8323
      }
8324
 
8325
      if (corner === 'tl' || corner === 'mt' || corner === 'tr') {
8326
        origin.y = 'bottom';
8327
      }
8328
      else if (corner === 'bl' || corner === 'mb' || corner === 'br') {
8329
        origin.y = 'top';
8330
      }
8331
 
8332
      return origin;
8333
    },
8334
 
8335
    /**
8336
     * @private
8337
     */
8338
    _getActionFromCorner: function(target, corner) {
8339
      var action = 'drag';
8340
      if (corner) {
8341
        action = (corner === 'ml' || corner === 'mr')
8342
          ? 'scaleX'
8343
          : (corner === 'mt' || corner === 'mb')
8344
            ? 'scaleY'
8345
            : corner === 'mtr'
8346
              ? 'rotate'
8347
              : 'scale';
8348
      }
8349
      return action;
8350
    },
8351
 
8352
    /**
8353
     * @private
8354
     * @param {Event} e Event object
8355
     * @param {fabric.Object} target
8356
     */
8357
    _setupCurrentTransform: function (e, target) {
8358
      if (!target) {
8359
        return;
8360
      }
8361
 
8362
      var pointer = this.getPointer(e),
8363
          corner = target._findTargetCorner(this.getPointer(e, true)),
8364
          action = this._getActionFromCorner(target, corner),
8365
          origin = this._getOriginFromCorner(target, corner);
8366
 
8367
      this._currentTransform = {
8368
        target: target,
8369
        action: action,
8370
        scaleX: target.scaleX,
8371
        scaleY: target.scaleY,
8372
        offsetX: pointer.x - target.left,
8373
        offsetY: pointer.y - target.top,
8374
        originX: origin.x,
8375
        originY: origin.y,
8376
        ex: pointer.x,
8377
        ey: pointer.y,
8378
        left: target.left,
8379
        top: target.top,
8380
        theta: degreesToRadians(target.angle),
8381
        width: target.width * target.scaleX,
8382
        mouseXSign: 1,
8383
        mouseYSign: 1
8384
      };
8385
 
8386
      this._currentTransform.original = {
8387
        left: target.left,
8388
        top: target.top,
8389
        scaleX: target.scaleX,
8390
        scaleY: target.scaleY,
8391
        originX: origin.x,
8392
        originY: origin.y
8393
      };
8394
 
8395
      this._resetCurrentTransform(e);
8396
    },
8397
 
8398
    /**
8399
     * Translates object by "setting" its left/top
8400
     * @private
8401
     * @param {Number} x pointer's x coordinate
8402
     * @param {Number} y pointer's y coordinate
8403
     */
8404
    _translateObject: function (x, y) {
8405
      var target = this._currentTransform.target;
8406
 
8407
      if (!target.get('lockMovementX')) {
8408
        target.set('left', x - this._currentTransform.offsetX);
8409
      }
8410
      if (!target.get('lockMovementY')) {
8411
        target.set('top', y - this._currentTransform.offsetY);
8412
      }
8413
    },
8414
 
8415
    /**
8416
     * Scales object by invoking its scaleX/scaleY methods
8417
     * @private
8418
     * @param {Number} x pointer's x coordinate
8419
     * @param {Number} y pointer's y coordinate
8420
     * @param {String} by Either 'x' or 'y' - specifies dimension constraint by which to scale an object.
8421
     *                    When not provided, an object is scaled by both dimensions equally
8422
     */
8423
    _scaleObject: function (x, y, by) {
8424
      var t = this._currentTransform,
8425
          target = t.target,
8426
          lockScalingX = target.get('lockScalingX'),
8427
          lockScalingY = target.get('lockScalingY'),
8428
          lockScalingFlip = target.get('lockScalingFlip');
8429
 
8430
      if (lockScalingX && lockScalingY) {
8431
        return;
8432
      }
8433
 
8434
      // Get the constraint point
8435
      var constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY),
8436
          localMouse = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY);
8437
 
8438
      this._setLocalMouse(localMouse, t);
8439
 
8440
      // Actually scale the object
8441
      this._setObjectScale(localMouse, t, lockScalingX, lockScalingY, by, lockScalingFlip);
8442
 
8443
      // Make sure the constraints apply
8444
      target.setPositionByOrigin(constraintPosition, t.originX, t.originY);
8445
    },
8446
 
8447
    /**
8448
     * @private
8449
     */
8450
    _setObjectScale: function(localMouse, transform, lockScalingX, lockScalingY, by, lockScalingFlip) {
8451
      var target = transform.target, forbidScalingX = false, forbidScalingY = false,
8452
          strokeWidth = target.stroke ? target.strokeWidth : 0;
8453
 
8454
      transform.newScaleX = localMouse.x / (target.width + strokeWidth / 2);
8455
      transform.newScaleY = localMouse.y / (target.height + strokeWidth / 2);
8456
 
8457
      if (lockScalingFlip && transform.newScaleX <= 0 && transform.newScaleX < target.scaleX) {
8458
        forbidScalingX = true;
8459
      }
8460
 
8461
      if (lockScalingFlip && transform.newScaleY <= 0 && transform.newScaleY < target.scaleY) {
8462
        forbidScalingY = true;
8463
      }
8464
 
8465
      if (by === 'equally' && !lockScalingX && !lockScalingY) {
8466
        forbidScalingX || forbidScalingY || this._scaleObjectEqually(localMouse, target, transform);
8467
      }
8468
      else if (!by) {
8469
        forbidScalingX || lockScalingX || target.set('scaleX', transform.newScaleX);
8470
        forbidScalingY || lockScalingY || target.set('scaleY', transform.newScaleY);
8471
      }
8472
      else if (by === 'x' && !target.get('lockUniScaling')) {
8473
        forbidScalingX || lockScalingX || target.set('scaleX', transform.newScaleX);
8474
      }
8475
      else if (by === 'y' && !target.get('lockUniScaling')) {
8476
        forbidScalingY || lockScalingY || target.set('scaleY', transform.newScaleY);
8477
      }
8478
 
8479
      forbidScalingX || forbidScalingY || this._flipObject(transform, by);
8480
 
8481
    },
8482
 
8483
    /**
8484
     * @private
8485
     */
8486
    _scaleObjectEqually: function(localMouse, target, transform) {
8487
 
8488
      var dist = localMouse.y + localMouse.x,
8489
          strokeWidth = target.stroke ? target.strokeWidth : 0,
8490
          lastDist = (target.height + (strokeWidth / 2)) * transform.original.scaleY +
8491
                     (target.width + (strokeWidth / 2)) * transform.original.scaleX;
8492
 
8493
      // We use transform.scaleX/Y instead of target.scaleX/Y
8494
      // because the object may have a min scale and we'll loose the proportions
8495
      transform.newScaleX = transform.original.scaleX * dist / lastDist;
8496
      transform.newScaleY = transform.original.scaleY * dist / lastDist;
8497
 
8498
      target.set('scaleX', transform.newScaleX);
8499
      target.set('scaleY', transform.newScaleY);
8500
    },
8501
 
8502
    /**
8503
     * @private
8504
     */
8505
    _flipObject: function(transform, by) {
8506
      if (transform.newScaleX < 0 && by !== 'y') {
8507
        if (transform.originX === 'left') {
8508
          transform.originX = 'right';
8509
        }
8510
        else if (transform.originX === 'right') {
8511
          transform.originX = 'left';
8512
        }
8513
      }
8514
 
8515
      if (transform.newScaleY < 0 && by !== 'x') {
8516
        if (transform.originY === 'top') {
8517
          transform.originY = 'bottom';
8518
        }
8519
        else if (transform.originY === 'bottom') {
8520
          transform.originY = 'top';
8521
        }
8522
      }
8523
    },
8524
 
8525
    /**
8526
     * @private
8527
     */
8528
    _setLocalMouse: function(localMouse, t) {
8529
      var target = t.target;
8530
 
8531
      if (t.originX === 'right') {
8532
        localMouse.x *= -1;
8533
      }
8534
      else if (t.originX === 'center') {
8535
        localMouse.x *= t.mouseXSign * 2;
8536
 
8537
        if (localMouse.x < 0) {
8538
          t.mouseXSign = -t.mouseXSign;
8539
        }
8540
      }
8541
 
8542
      if (t.originY === 'bottom') {
8543
        localMouse.y *= -1;
8544
      }
8545
      else if (t.originY === 'center') {
8546
        localMouse.y *= t.mouseYSign * 2;
8547
 
8548
        if (localMouse.y < 0) {
8549
          t.mouseYSign = -t.mouseYSign;
8550
        }
8551
      }
8552
 
8553
      // adjust the mouse coordinates when dealing with padding
8554
      if (abs(localMouse.x) > target.padding) {
8555
        if (localMouse.x < 0) {
8556
          localMouse.x += target.padding;
8557
        }
8558
        else {
8559
          localMouse.x -= target.padding;
8560
        }
8561
      }
8562
      else { // mouse is within the padding, set to 0
8563
        localMouse.x = 0;
8564
      }
8565
 
8566
      if (abs(localMouse.y) > target.padding) {
8567
        if (localMouse.y < 0) {
8568
          localMouse.y += target.padding;
8569
        }
8570
        else {
8571
          localMouse.y -= target.padding;
8572
        }
8573
      }
8574
      else {
8575
        localMouse.y = 0;
8576
      }
8577
    },
8578
 
8579
    /**
8580
     * Rotates object by invoking its rotate method
8581
     * @private
8582
     * @param {Number} x pointer's x coordinate
8583
     * @param {Number} y pointer's y coordinate
8584
     */
8585
    _rotateObject: function (x, y) {
8586
 
8587
      var t = this._currentTransform;
8588
 
8589
      if (t.target.get('lockRotation')) {
8590
        return;
8591
      }
8592
 
8593
      var lastAngle = atan2(t.ey - t.top, t.ex - t.left),
8594
          curAngle = atan2(y - t.top, x - t.left),
8595
          angle = radiansToDegrees(curAngle - lastAngle + t.theta);
8596
 
8597
      // normalize angle to positive value
8598
      if (angle < 0) {
8599
        angle = 360 + angle;
8600
      }
8601
 
8602
      t.target.angle = angle % 360;
8603
    },
8604
 
8605
    /**
8606
     * Set the cursor type of the canvas element
8607
     * @param {String} value Cursor type of the canvas element.
8608
     * @see http://www.w3.org/TR/css3-ui/#cursor
8609
     */
8610
    setCursor: function (value) {
8611
      this.upperCanvasEl.style.cursor = value;
8612
    },
8613
 
8614
    /**
8615
     * @private
8616
     */
8617
    _resetObjectTransform: function (target) {
8618
      target.scaleX = 1;
8619
      target.scaleY = 1;
8620
      target.setAngle(0);
8621
    },
8622
 
8623
    /**
8624
     * @private
8625
     */
8626
    _drawSelection: function () {
8627
      var ctx = this.contextTop,
8628
          groupSelector = this._groupSelector,
8629
          left = groupSelector.left,
8630
          top = groupSelector.top,
8631
          aleft = abs(left),
8632
          atop = abs(top);
8633
 
8634
      ctx.fillStyle = this.selectionColor;
8635
 
8636
      ctx.fillRect(
8637
        groupSelector.ex - ((left > 0) ? 0 : -left),
8638
        groupSelector.ey - ((top > 0) ? 0 : -top),
8639
        aleft,
8640
        atop
8641
      );
8642
 
8643
      ctx.lineWidth = this.selectionLineWidth;
8644
      ctx.strokeStyle = this.selectionBorderColor;
8645
 
8646
      // selection border
8647
      if (this.selectionDashArray.length > 1) {
8648
 
8649
        var px = groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0: aleft),
8650
            py = groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0: atop);
8651
 
8652
        ctx.beginPath();
8653
 
8654
        fabric.util.drawDashedLine(ctx, px, py, px + aleft, py, this.selectionDashArray);
8655
        fabric.util.drawDashedLine(ctx, px, py + atop - 1, px + aleft, py + atop - 1, this.selectionDashArray);
8656
        fabric.util.drawDashedLine(ctx, px, py, px, py + atop, this.selectionDashArray);
8657
        fabric.util.drawDashedLine(ctx, px + aleft - 1, py, px + aleft - 1, py + atop, this.selectionDashArray);
8658
 
8659
        ctx.closePath();
8660
        ctx.stroke();
8661
      }
8662
      else {
8663
        ctx.strokeRect(
8664
          groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft),
8665
          groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop),
8666
          aleft,
8667
          atop
8668
        );
8669
      }
8670
    },
8671
 
8672
    /**
8673
     * @private
8674
     */
8675
    _isLastRenderedObject: function(e) {
8676
      return (
8677
        this.controlsAboveOverlay &&
8678
        this.lastRenderedObjectWithControlsAboveOverlay &&
8679
        this.lastRenderedObjectWithControlsAboveOverlay.visible &&
8680
        this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay) &&
8681
        this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(this.getPointer(e, true)));
8682
    },
8683
 
8684
    /**
8685
     * Method that determines what object we are clicking on
8686
     * @param {Event} e mouse event
8687
     * @param {Boolean} skipGroup when true, group is skipped and only objects are traversed through
8688
     */
8689
    findTarget: function (e, skipGroup) {
8690
      if (this.skipTargetFind) {
8691
        return;
8692
      }
8693
 
8694
      if (this._isLastRenderedObject(e)) {
8695
        return this.lastRenderedObjectWithControlsAboveOverlay;
8696
      }
8697
 
8698
      // first check current group (if one exists)
8699
      var activeGroup = this.getActiveGroup();
8700
      if (activeGroup && !skipGroup && this.containsPoint(e, activeGroup)) {
8701
        return activeGroup;
8702
      }
8703
 
8704
      var target = this._searchPossibleTargets(e);
8705
      this._fireOverOutEvents(target);
8706
 
8707
      return target;
8708
    },
8709
 
8710
    /**
8711
     * @private
8712
     */
8713
    _fireOverOutEvents: function(target) {
8714
      if (target) {
8715
        if (this._hoveredTarget !== target) {
8716
          this.fire('mouse:over', { target: target });
8717
          target.fire('mouseover');
8718
          if (this._hoveredTarget) {
8719
            this.fire('mouse:out', { target: this._hoveredTarget });
8720
            this._hoveredTarget.fire('mouseout');
8721
          }
8722
          this._hoveredTarget = target;
8723
        }
8724
      }
8725
      else if (this._hoveredTarget) {
8726
        this.fire('mouse:out', { target: this._hoveredTarget });
8727
        this._hoveredTarget.fire('mouseout');
8728
        this._hoveredTarget = null;
8729
      }
8730
    },
8731
 
8732
    /**
8733
    * @private
8734
    */
8735
    _checkTarget: function(e, obj, pointer) {
8736
      if (obj &&
8737
          obj.visible &&
8738
          obj.evented &&
8739
          this.containsPoint(e, obj)){
8740
        if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) {
8741
          var isTransparent = this.isTargetTransparent(obj, pointer.x, pointer.y);
8742
          if (!isTransparent) {
8743
            return true;
8744
          }
8745
        }
8746
        else {
8747
          return true;
8748
        }
8749
      }
8750
    },
8751
 
8752
    /**
8753
     * @private
8754
     */
8755
    _searchPossibleTargets: function(e) {
8756
 
8757
      // Cache all targets where their bounding box contains point.
8758
      var target,
8759
          pointer = this.getPointer(e, true),
8760
          i = this._objects.length;
8761
      // Do not check for currently grouped objects, since we check the parent group itself.
8762
      while (i--) {
8763
        if (!this._objects[i].group && this._checkTarget(e, this._objects[i], pointer)){
8764
          this.relatedTarget = this._objects[i];
8765
          target = this._objects[i];
8766
          break;
8767
        }
8768
      }
8769
 
8770
      return target;
8771
    },
8772
 
8773
    /**
8774
     * Returns pointer coordinates relative to canvas.
8775
     * @param {Event} e
8776
     * @return {Object} object with "x" and "y" number values
8777
     */
8778
    getPointer: function (e, ignoreZoom, upperCanvasEl) {
8779
      if (!upperCanvasEl) {
8780
        upperCanvasEl = this.upperCanvasEl;
8781
      }
8782
      var pointer = getPointer(e, upperCanvasEl),
8783
          bounds = upperCanvasEl.getBoundingClientRect(),
8784
          boundsWidth = bounds.width || 0,
8785
          boundsHeight = bounds.height || 0,
8786
          cssScale;
8787
 
8788
      if (!boundsWidth || !boundsHeight ) {
8789
        if ('top' in bounds && 'bottom' in bounds) {
8790
          boundsHeight = Math.abs( bounds.top - bounds.bottom );
8791
        }
8792
        if ('right' in bounds && 'left' in bounds) {
8793
          boundsWidth = Math.abs( bounds.right - bounds.left );
8794
        }
8795
      }
8796
 
8797
      this.calcOffset();
8798
 
8799
      pointer.x = pointer.x - this._offset.left;
8800
      pointer.y = pointer.y - this._offset.top;
8801
      if (!ignoreZoom) {
8802
        pointer = fabric.util.transformPoint(
8803
          pointer,
8804
          fabric.util.invertTransform(this.viewportTransform)
8805
        );
8806
      }
8807
 
8808
      if (boundsWidth === 0 || boundsHeight === 0) {
8809
        // If bounds are not available (i.e. not visible), do not apply scale.
8810
        cssScale = { width: 1, height: 1 };
8811
      }
8812
      else {
8813
        cssScale = {
8814
          width: upperCanvasEl.width / boundsWidth,
8815
          height: upperCanvasEl.height / boundsHeight
8816
        };
8817
      }
8818
 
8819
      return {
8820
        x: pointer.x * cssScale.width,
8821
        y: pointer.y * cssScale.height
8822
      };
8823
    },
8824
 
8825
    /**
8826
     * @private
8827
     * @throws {CANVAS_INIT_ERROR} If canvas can not be initialized
8828
     */
8829
    _createUpperCanvas: function () {
8830
      var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, '');
8831
 
8832
      this.upperCanvasEl = this._createCanvasElement();
8833
      fabric.util.addClass(this.upperCanvasEl, 'upper-canvas ' + lowerCanvasClass);
8834
 
8835
      this.wrapperEl.appendChild(this.upperCanvasEl);
8836
 
8837
      this._copyCanvasStyle(this.lowerCanvasEl, this.upperCanvasEl);
8838
      this._applyCanvasStyle(this.upperCanvasEl);
8839
      this.contextTop = this.upperCanvasEl.getContext('2d');
8840
    },
8841
 
8842
    /**
8843
     * @private
8844
     */
8845
    _createCacheCanvas: function () {
8846
      this.cacheCanvasEl = this._createCanvasElement();
8847
      this.cacheCanvasEl.setAttribute('width', this.width);
8848
      this.cacheCanvasEl.setAttribute('height', this.height);
8849
      this.contextCache = this.cacheCanvasEl.getContext('2d');
8850
    },
8851
 
8852
    /**
8853
     * @private
8854
     */
8855
    _initWrapperElement: function () {
8856
      this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, 'div', {
8857
        'class': this.containerClass
8858
      });
8859
      fabric.util.setStyle(this.wrapperEl, {
8860
        width: this.getWidth() + 'px',
8861
        height: this.getHeight() + 'px',
8862
        position: 'relative'
8863
      });
8864
      fabric.util.makeElementUnselectable(this.wrapperEl);
8865
    },
8866
 
8867
    /**
8868
     * @private
8869
     * @param {HTMLElement} element canvas element to apply styles on
8870
     */
8871
    _applyCanvasStyle: function (element) {
8872
      var width = this.getWidth() || element.width,
8873
          height = this.getHeight() || element.height;
8874
 
8875
      fabric.util.setStyle(element, {
8876
        position: 'absolute',
8877
        width: width + 'px',
8878
        height: height + 'px',
8879
        left: 0,
8880
        top: 0
8881
      });
8882
      element.width = width;
8883
      element.height = height;
8884
      fabric.util.makeElementUnselectable(element);
8885
    },
8886
 
8887
    /**
8888
     * Copys the the entire inline style from one element (fromEl) to another (toEl)
8889
     * @private
8890
     * @param {Element} fromEl Element style is copied from
8891
     * @param {Element} toEl Element copied style is applied to
8892
     */
8893
    _copyCanvasStyle: function (fromEl, toEl) {
8894
      toEl.style.cssText = fromEl.style.cssText;
8895
    },
8896
 
8897
    /**
8898
     * Returns context of canvas where object selection is drawn
8899
     * @return {CanvasRenderingContext2D}
8900
     */
8901
    getSelectionContext: function() {
8902
      return this.contextTop;
8903
    },
8904
 
8905
    /**
8906
     * Returns &lt;canvas> element on which object selection is drawn
8907
     * @return {HTMLCanvasElement}
8908
     */
8909
    getSelectionElement: function () {
8910
      return this.upperCanvasEl;
8911
    },
8912
 
8913
    /**
8914
     * @private
8915
     * @param {Object} object
8916
     */
8917
    _setActiveObject: function(object) {
8918
      if (this._activeObject) {
8919
        this._activeObject.set('active', false);
8920
      }
8921
      this._activeObject = object;
8922
      object.set('active', true);
8923
    },
8924
 
8925
    /**
8926
     * Sets given object as the only active object on canvas
8927
     * @param {fabric.Object} object Object to set as an active one
8928
     * @param {Event} [e] Event (passed along when firing "object:selected")
8929
     * @return {fabric.Canvas} thisArg
8930
     * @chainable
8931
     */
8932
    setActiveObject: function (object, e) {
8933
      this._setActiveObject(object);
8934
      this.renderAll();
8935
      this.fire('object:selected', { target: object, e: e });
8936
      object.fire('selected', { e: e });
8937
      return this;
8938
    },
8939
 
8940
    /**
8941
     * Returns currently active object
8942
     * @return {fabric.Object} active object
8943
     */
8944
    getActiveObject: function () {
8945
      return this._activeObject;
8946
    },
8947
 
8948
    /**
8949
     * @private
8950
     */
8951
    _discardActiveObject: function() {
8952
      if (this._activeObject) {
8953
        this._activeObject.set('active', false);
8954
      }
8955
      this._activeObject = null;
8956
    },
8957
 
8958
    /**
8959
     * Discards currently active object
8960
     * @return {fabric.Canvas} thisArg
8961
     * @chainable
8962
     */
8963
    discardActiveObject: function (e) {
8964
      this._discardActiveObject();
8965
      this.renderAll();
8966
      this.fire('selection:cleared', { e: e });
8967
      return this;
8968
    },
8969
 
8970
    /**
8971
     * @private
8972
     * @param {fabric.Group} group
8973
     */
8974
    _setActiveGroup: function(group) {
8975
      this._activeGroup = group;
8976
      if (group) {
8977
        group.set('active', true);
8978
      }
8979
    },
8980
 
8981
    /**
8982
     * Sets active group to a speicified one
8983
     * @param {fabric.Group} group Group to set as a current one
8984
     * @return {fabric.Canvas} thisArg
8985
     * @chainable
8986
     */
8987
    setActiveGroup: function (group, e) {
8988
      this._setActiveGroup(group);
8989
      if (group) {
8990
        this.fire('object:selected', { target: group, e: e });
8991
        group.fire('selected', { e: e });
8992
      }
8993
      return this;
8994
    },
8995
 
8996
    /**
8997
     * Returns currently active group
8998
     * @return {fabric.Group} Current group
8999
     */
9000
    getActiveGroup: function () {
9001
      return this._activeGroup;
9002
    },
9003
 
9004
    /**
9005
     * @private
9006
     */
9007
    _discardActiveGroup: function() {
9008
      var g = this.getActiveGroup();
9009
      if (g) {
9010
        g.destroy();
9011
      }
9012
      this.setActiveGroup(null);
9013
    },
9014
 
9015
    /**
9016
     * Discards currently active group
9017
     * @return {fabric.Canvas} thisArg
9018
     */
9019
    discardActiveGroup: function (e) {
9020
      this._discardActiveGroup();
9021
      this.fire('selection:cleared', { e: e });
9022
      return this;
9023
    },
9024
 
9025
    /**
9026
     * Deactivates all objects on canvas, removing any active group or object
9027
     * @return {fabric.Canvas} thisArg
9028
     */
9029
    deactivateAll: function () {
9030
      var allObjects = this.getObjects(),
9031
          i = 0,
9032
          len = allObjects.length;
9033
      for ( ; i < len; i++) {
9034
        allObjects[i].set('active', false);
9035
      }
9036
      this._discardActiveGroup();
9037
      this._discardActiveObject();
9038
      return this;
9039
    },
9040
 
9041
    /**
9042
     * Deactivates all objects and dispatches appropriate events
9043
     * @return {fabric.Canvas} thisArg
9044
     */
9045
    deactivateAllWithDispatch: function (e) {
9046
      var activeObject = this.getActiveGroup() || this.getActiveObject();
9047
      if (activeObject) {
9048
        this.fire('before:selection:cleared', { target: activeObject, e: e });
9049
      }
9050
      this.deactivateAll();
9051
      if (activeObject) {
9052
        this.fire('selection:cleared', { e: e });
9053
      }
9054
      return this;
9055
    },
9056
 
9057
    /**
9058
     * Draws objects' controls (borders/controls)
9059
     * @param {CanvasRenderingContext2D} ctx Context to render controls on
9060
     */
9061
    drawControls: function(ctx) {
9062
      var activeGroup = this.getActiveGroup();
9063
      if (activeGroup) {
9064
        this._drawGroupControls(ctx, activeGroup);
9065
      }
9066
      else {
9067
        this._drawObjectsControls(ctx);
9068
      }
9069
    },
9070
 
9071
    /**
9072
     * @private
9073
     */
9074
    _drawGroupControls: function(ctx, activeGroup) {
9075
      activeGroup._renderControls(ctx);
9076
    },
9077
 
9078
    /**
9079
     * @private
9080
     */
9081
    _drawObjectsControls: function(ctx) {
9082
      for (var i = 0, len = this._objects.length; i < len; ++i) {
9083
        if (!this._objects[i] || !this._objects[i].active) {
9084
          continue;
9085
        }
9086
        this._objects[i]._renderControls(ctx);
9087
        this.lastRenderedObjectWithControlsAboveOverlay = this._objects[i];
9088
      }
9089
    }
9090
  });
9091
 
9092
  // copying static properties manually to work around Opera's bug,
9093
  // where "prototype" property is enumerable and overrides existing prototype
9094
  for (var prop in fabric.StaticCanvas) {
9095
    if (prop !== 'prototype') {
9096
      fabric.Canvas[prop] = fabric.StaticCanvas[prop];
9097
    }
9098
  }
9099
 
9100
  if (fabric.isTouchSupported) {
9101
    /** @ignore */
9102
    fabric.Canvas.prototype._setCursorFromEvent = function() { };
9103
  }
9104
 
9105
  /**
9106
   * @class fabric.Element
9107
   * @alias fabric.Canvas
9108
   * @deprecated Use {@link fabric.Canvas} instead.
9109
   * @constructor
9110
   */
9111
  fabric.Element = fabric.Canvas;
9112
})();
9113
 
9114
 
9115
(function() {
9116
 
9117
  var cursorOffset = {
9118
    mt: 0, // n
9119
    tr: 1, // ne
9120
    mr: 2, // e
9121
    br: 3, // se
9122
    mb: 4, // s
9123
    bl: 5, // sw
9124
    ml: 6, // w
9125
    tl: 7 // nw
9126
  },
9127
  addListener = fabric.util.addListener,
9128
  removeListener = fabric.util.removeListener;
9129
 
9130
  fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ {
9131
 
9132
    /**
9133
     * Map of cursor style values for each of the object controls
9134
     * @private
9135
     */
9136
    cursorMap: [
9137
      'n-resize',
9138
      'ne-resize',
9139
      'e-resize',
9140
      'se-resize',
9141
      's-resize',
9142
      'sw-resize',
9143
      'w-resize',
9144
      'nw-resize'
9145
    ],
9146
 
9147
    /**
9148
     * Adds mouse listeners to canvas
9149
     * @private
9150
     */
9151
    _initEventListeners: function () {
9152
 
9153
      this._bindEvents();
9154
 
9155
      addListener(fabric.window, 'resize', this._onResize);
9156
 
9157
      // mouse events
9158
      addListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
9159
      addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
9160
      addListener(this.upperCanvasEl, 'mousewheel', this._onMouseWheel);
9161
 
9162
      // touch events
9163
      addListener(this.upperCanvasEl, 'touchstart', this._onMouseDown);
9164
      addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);
9165
 
9166
      if (typeof eventjs !== 'undefined' && 'add' in eventjs) {
9167
        eventjs.add(this.upperCanvasEl, 'gesture', this._onGesture);
9168
        eventjs.add(this.upperCanvasEl, 'drag', this._onDrag);
9169
        eventjs.add(this.upperCanvasEl, 'orientation', this._onOrientationChange);
9170
        eventjs.add(this.upperCanvasEl, 'shake', this._onShake);
9171
        eventjs.add(this.upperCanvasEl, 'longpress', this._onLongPress);
9172
      }
9173
    },
9174
 
9175
    /**
9176
     * @private
9177
     */
9178
    _bindEvents: function() {
9179
      this._onMouseDown = this._onMouseDown.bind(this);
9180
      this._onMouseMove = this._onMouseMove.bind(this);
9181
      this._onMouseUp = this._onMouseUp.bind(this);
9182
      this._onResize = this._onResize.bind(this);
9183
      this._onGesture = this._onGesture.bind(this);
9184
      this._onDrag = this._onDrag.bind(this);
9185
      this._onShake = this._onShake.bind(this);
9186
      this._onLongPress = this._onLongPress.bind(this);
9187
      this._onOrientationChange = this._onOrientationChange.bind(this);
9188
      this._onMouseWheel = this._onMouseWheel.bind(this);
9189
    },
9190
 
9191
    /**
9192
     * Removes all event listeners
9193
     */
9194
    removeListeners: function() {
9195
      removeListener(fabric.window, 'resize', this._onResize);
9196
 
9197
      removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
9198
      removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
9199
      removeListener(this.upperCanvasEl, 'mousewheel', this._onMouseWheel);
9200
 
9201
      removeListener(this.upperCanvasEl, 'touchstart', this._onMouseDown);
9202
      removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);
9203
 
9204
      if (typeof eventjs !== 'undefined' && 'remove' in eventjs) {
9205
        eventjs.remove(this.upperCanvasEl, 'gesture', this._onGesture);
9206
        eventjs.remove(this.upperCanvasEl, 'drag', this._onDrag);
9207
        eventjs.remove(this.upperCanvasEl, 'orientation', this._onOrientationChange);
9208
        eventjs.remove(this.upperCanvasEl, 'shake', this._onShake);
9209
        eventjs.remove(this.upperCanvasEl, 'longpress', this._onLongPress);
9210
      }
9211
    },
9212
 
9213
    /**
9214
     * @private
9215
     * @param {Event} [e] Event object fired on Event.js gesture
9216
     * @param {Event} [self] Inner Event object
9217
     */
9218
    _onGesture: function(e, self) {
9219
      this.__onTransformGesture && this.__onTransformGesture(e, self);
9220
    },
9221
 
9222
    /**
9223
     * @private
9224
     * @param {Event} [e] Event object fired on Event.js drag
9225
     * @param {Event} [self] Inner Event object
9226
     */
9227
    _onDrag: function(e, self) {
9228
      this.__onDrag && this.__onDrag(e, self);
9229
    },
9230
 
9231
    /**
9232
     * @private
9233
     * @param {Event} [e] Event object fired on Event.js wheel event
9234
     * @param {Event} [self] Inner Event object
9235
     */
9236
    _onMouseWheel: function(e, self) {
9237
      this.__onMouseWheel && this.__onMouseWheel(e, self);
9238
    },
9239
 
9240
    /**
9241
     * @private
9242
     * @param {Event} [e] Event object fired on Event.js orientation change
9243
     * @param {Event} [self] Inner Event object
9244
     */
9245
    _onOrientationChange: function(e, self) {
9246
      this.__onOrientationChange && this.__onOrientationChange(e, self);
9247
    },
9248
 
9249
    /**
9250
     * @private
9251
     * @param {Event} [e] Event object fired on Event.js shake
9252
     * @param {Event} [self] Inner Event object
9253
     */
9254
    _onShake: function(e, self) {
9255
      this.__onShake && this.__onShake(e, self);
9256
    },
9257
    /**
9258
     * @private
9259
     * @param {Event} [e] Event object fired on Event.js shake
9260
     * @param {Event} [self] Inner Event object
9261
     */
9262
    _onLongPress: function(e, self) {
9263
      this.__onLongPress && this.__onLongPress(e, self);
9264
    },
9265
 
9266
    /**
9267
     * @private
9268
     * @param {Event} e Event object fired on mousedown
9269
     */
9270
    _onMouseDown: function (e) {
9271
      this.__onMouseDown(e);
9272
 
9273
      addListener(fabric.document, 'touchend', this._onMouseUp);
9274
      addListener(fabric.document, 'touchmove', this._onMouseMove);
9275
 
9276
      removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
9277
      removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);
9278
 
9279
      if (e.type === 'touchstart') {
9280
        // Unbind mousedown to prevent double triggers from touch devices
9281
        removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
9282
      }
9283
      else {
9284
        addListener(fabric.document, 'mouseup', this._onMouseUp);
9285
        addListener(fabric.document, 'mousemove', this._onMouseMove);
9286
      }
9287
    },
9288
 
9289
    /**
9290
     * @private
9291
     * @param {Event} e Event object fired on mouseup
9292
     */
9293
    _onMouseUp: function (e) {
9294
      this.__onMouseUp(e);
9295
 
9296
      removeListener(fabric.document, 'mouseup', this._onMouseUp);
9297
      removeListener(fabric.document, 'touchend', this._onMouseUp);
9298
 
9299
      removeListener(fabric.document, 'mousemove', this._onMouseMove);
9300
      removeListener(fabric.document, 'touchmove', this._onMouseMove);
9301
 
9302
      addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
9303
      addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);
9304
 
9305
      if (e.type === 'touchend') {
9306
        // Wait 400ms before rebinding mousedown to prevent double triggers
9307
        // from touch devices
9308
        var _this = this;
9309
        setTimeout(function() {
9310
          addListener(_this.upperCanvasEl, 'mousedown', _this._onMouseDown);
9311
        }, 400);
9312
      }
9313
    },
9314
 
9315
    /**
9316
     * @private
9317
     * @param {Event} e Event object fired on mousemove
9318
     */
9319
    _onMouseMove: function (e) {
9320
      !this.allowTouchScrolling && e.preventDefault && e.preventDefault();
9321
      this.__onMouseMove(e);
9322
    },
9323
 
9324
    /**
9325
     * @private
9326
     */
9327
    _onResize: function () {
9328
      this.calcOffset();
9329
    },
9330
 
9331
    /**
9332
     * Decides whether the canvas should be redrawn in mouseup and mousedown events.
9333
     * @private
9334
     * @param {Object} target
9335
     * @param {Object} pointer
9336
     */
9337
    _shouldRender: function(target, pointer) {
9338
      var activeObject = this.getActiveGroup() || this.getActiveObject();
9339
 
9340
      return !!(
9341
        (target && (
9342
          target.isMoving ||
9343
          target !== activeObject))
9344
        ||
9345
        (!target && !!activeObject)
9346
        ||
9347
        (!target && !activeObject && !this._groupSelector)
9348
        ||
9349
        (pointer &&
9350
          this._previousPointer &&
9351
          this.selection && (
9352
          pointer.x !== this._previousPointer.x ||
9353
          pointer.y !== this._previousPointer.y))
9354
      );
9355
    },
9356
 
9357
    /**
9358
     * Method that defines the actions when mouse is released on canvas.
9359
     * The method resets the currentTransform parameters, store the image corner
9360
     * position in the image object and render the canvas on top.
9361
     * @private
9362
     * @param {Event} e Event object fired on mouseup
9363
     */
9364
    __onMouseUp: function (e) {
9365
      var target;
9366
 
9367
      if (this.isDrawingMode && this._isCurrentlyDrawing) {
9368
        this._onMouseUpInDrawingMode(e);
9369
        return;
9370
      }
9371
 
9372
      if (this._currentTransform) {
9373
        this._finalizeCurrentTransform();
9374
        target = this._currentTransform.target;
9375
      }
9376
      else {
9377
        target = this.findTarget(e, true);
9378
      }
9379
 
9380
      var shouldRender = this._shouldRender(target, this.getPointer(e));
9381
 
9382
      this._maybeGroupObjects(e);
9383
 
9384
      if (target) {
9385
        target.isMoving = false;
9386
      }
9387
 
9388
      shouldRender && this.renderAll();
9389
 
9390
      this._handleCursorAndEvent(e, target);
9391
    },
9392
 
9393
    _handleCursorAndEvent: function(e, target) {
9394
      this._setCursorFromEvent(e, target);
9395
 
9396
      // TODO: why are we doing this?
9397
      var _this = this;
9398
      setTimeout(function () {
9399
        _this._setCursorFromEvent(e, target);
9400
      }, 50);
9401
 
9402
      this.fire('mouse:up', { target: target, e: e });
9403
      target && target.fire('mouseup', { e: e });
9404
    },
9405
 
9406
    /**
9407
     * @private
9408
     */
9409
    _finalizeCurrentTransform: function() {
9410
 
9411
      var transform = this._currentTransform,
9412
          target = transform.target;
9413
 
9414
      if (target._scaling) {
9415
        target._scaling = false;
9416
      }
9417
 
9418
      target.setCoords();
9419
 
9420
      // only fire :modified event if target coordinates were changed during mousedown-mouseup
9421
      if (this.stateful && target.hasStateChanged()) {
9422
        this.fire('object:modified', { target: target });
9423
        target.fire('modified');
9424
      }
9425
 
9426
      this._restoreOriginXY(target);
9427
    },
9428
 
9429
    /**
9430
     * @private
9431
     * @param {Object} target Object to restore
9432
     */
9433
    _restoreOriginXY: function(target) {
9434
      if (this._previousOriginX && this._previousOriginY) {
9435
 
9436
        var originPoint = target.translateToOriginPoint(
9437
          target.getCenterPoint(),
9438
          this._previousOriginX,
9439
          this._previousOriginY);
9440
 
9441
        target.originX = this._previousOriginX;
9442
        target.originY = this._previousOriginY;
9443
 
9444
        target.left = originPoint.x;
9445
        target.top = originPoint.y;
9446
 
9447
        this._previousOriginX = null;
9448
        this._previousOriginY = null;
9449
      }
9450
    },
9451
 
9452
    /**
9453
     * @private
9454
     * @param {Event} e Event object fired on mousedown
9455
     */
9456
    _onMouseDownInDrawingMode: function(e) {
9457
      this._isCurrentlyDrawing = true;
9458
      this.discardActiveObject(e).renderAll();
9459
      if (this.clipTo) {
9460
        fabric.util.clipContext(this, this.contextTop);
9461
      }
9462
      var ivt = fabric.util.invertTransform(this.viewportTransform),
9463
          pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt);
9464
      this.freeDrawingBrush.onMouseDown(pointer);
9465
      this.fire('mouse:down', { e: e });
9466
 
9467
      var target = this.findTarget(e);
9468
      if (typeof target !== 'undefined') {
9469
        target.fire('mousedown', { e: e, target: target });
9470
      }
9471
    },
9472
 
9473
    /**
9474
     * @private
9475
     * @param {Event} e Event object fired on mousemove
9476
     */
9477
    _onMouseMoveInDrawingMode: function(e) {
9478
      if (this._isCurrentlyDrawing) {
9479
        var ivt = fabric.util.invertTransform(this.viewportTransform),
9480
            pointer = fabric.util.transformPoint(this.getPointer(e, true), ivt);
9481
        this.freeDrawingBrush.onMouseMove(pointer);
9482
      }
9483
      this.setCursor(this.freeDrawingCursor);
9484
      this.fire('mouse:move', { e: e });
9485
 
9486
      var target = this.findTarget(e);
9487
      if (typeof target !== 'undefined') {
9488
        target.fire('mousemove', { e: e, target: target });
9489
      }
9490
    },
9491
 
9492
    /**
9493
     * @private
9494
     * @param {Event} e Event object fired on mouseup
9495
     */
9496
    _onMouseUpInDrawingMode: function(e) {
9497
      this._isCurrentlyDrawing = false;
9498
      if (this.clipTo) {
9499
        this.contextTop.restore();
9500
      }
9501
      this.freeDrawingBrush.onMouseUp();
9502
      this.fire('mouse:up', { e: e });
9503
 
9504
      var target = this.findTarget(e);
9505
      if (typeof target !== 'undefined') {
9506
        target.fire('mouseup', { e: e, target: target });
9507
      }
9508
    },
9509
 
9510
    /**
9511
     * Method that defines the actions when mouse is clic ked on canvas.
9512
     * The method inits the currentTransform parameters and renders all the
9513
     * canvas so the current image can be placed on the top canvas and the rest
9514
     * in on the container one.
9515
     * @private
9516
     * @param {Event} e Event object fired on mousedown
9517
     */
9518
    __onMouseDown: function (e) {
9519
 
9520
      // accept only left clicks
9521
      var isLeftClick  = 'which' in e ? e.which === 1 : e.button === 1;
9522
      if (!isLeftClick && !fabric.isTouchSupported) {
9523
        return;
9524
      }
9525
 
9526
      if (this.isDrawingMode) {
9527
        this._onMouseDownInDrawingMode(e);
9528
        return;
9529
      }
9530
 
9531
      // ignore if some object is being transformed at this moment
9532
      if (this._currentTransform) {
9533
        return;
9534
      }
9535
 
9536
      var target = this.findTarget(e),
9537
          pointer = this.getPointer(e, true);
9538
 
9539
      // save pointer for check in __onMouseUp event
9540
      this._previousPointer = pointer;
9541
 
9542
      var shouldRender = this._shouldRender(target, pointer),
9543
          shouldGroup = this._shouldGroup(e, target);
9544
 
9545
      if (this._shouldClearSelection(e, target)) {
9546
        this._clearSelection(e, target, pointer);
9547
      }
9548
      else if (shouldGroup) {
9549
        this._handleGrouping(e, target);
9550
        target = this.getActiveGroup();
9551
      }
9552
 
9553
      if (target && target.selectable && !shouldGroup) {
9554
        this._beforeTransform(e, target);
9555
        this._setupCurrentTransform(e, target);
9556
      }
9557
      // we must renderAll so that active image is placed on the top canvas
9558
      shouldRender && this.renderAll();
9559
 
9560
      this.fire('mouse:down', { target: target, e: e });
9561
      target && target.fire('mousedown', { e: e });
9562
    },
9563
 
9564
    /**
9565
     * @private
9566
     */
9567
    _beforeTransform: function(e, target) {
9568
      this.stateful && target.saveState();
9569
 
9570
      // determine if it's a drag or rotate case
9571
      if (target._findTargetCorner(this.getPointer(e))) {
9572
        this.onBeforeScaleRotate(target);
9573
      }
9574
 
9575
      if (target !== this.getActiveGroup() && target !== this.getActiveObject()) {
9576
        this.deactivateAll();
9577
        this.setActiveObject(target, e);
9578
      }
9579
    },
9580
 
9581
    /**
9582
     * @private
9583
     */
9584
    _clearSelection: function(e, target, pointer) {
9585
      this.deactivateAllWithDispatch(e);
9586
 
9587
      if (target && target.selectable) {
9588
        this.setActiveObject(target, e);
9589
      }
9590
      else if (this.selection) {
9591
        this._groupSelector = {
9592
          ex: pointer.x,
9593
          ey: pointer.y,
9594
          top: 0,
9595
          left: 0
9596
        };
9597
      }
9598
    },
9599
 
9600
    /**
9601
     * @private
9602
     * @param {Object} target Object for that origin is set to center
9603
     */
9604
    _setOriginToCenter: function(target) {
9605
      this._previousOriginX = this._currentTransform.target.originX;
9606
      this._previousOriginY = this._currentTransform.target.originY;
9607
 
9608
      var center = target.getCenterPoint();
9609
 
9610
      target.originX = 'center';
9611
      target.originY = 'center';
9612
 
9613
      target.left = center.x;
9614
      target.top = center.y;
9615
 
9616
      this._currentTransform.left = target.left;
9617
      this._currentTransform.top = target.top;
9618
    },
9619
 
9620
    /**
9621
     * @private
9622
     * @param {Object} target Object for that center is set to origin
9623
     */
9624
    _setCenterToOrigin: function(target) {
9625
      var originPoint = target.translateToOriginPoint(
9626
        target.getCenterPoint(),
9627
        this._previousOriginX,
9628
        this._previousOriginY);
9629
 
9630
      target.originX = this._previousOriginX;
9631
      target.originY = this._previousOriginY;
9632
 
9633
      target.left = originPoint.x;
9634
      target.top = originPoint.y;
9635
 
9636
      this._previousOriginX = null;
9637
      this._previousOriginY = null;
9638
    },
9639
 
9640
    /**
9641
      * Method that defines the actions when mouse is hovering the canvas.
9642
      * The currentTransform parameter will definde whether the user is rotating/scaling/translating
9643
      * an image or neither of them (only hovering). A group selection is also possible and would cancel
9644
      * all any other type of action.
9645
      * In case of an image transformation only the top canvas will be rendered.
9646
      * @private
9647
      * @param {Event} e Event object fired on mousemove
9648
      */
9649
    __onMouseMove: function (e) {
9650
 
9651
      var target, pointer;
9652
 
9653
      if (this.isDrawingMode) {
9654
        this._onMouseMoveInDrawingMode(e);
9655
        return;
9656
      }
9657
      if (typeof e.touches !== 'undefined' && e.touches.length > 1) {
9658
        return;
9659
      }
9660
 
9661
      var groupSelector = this._groupSelector;
9662
 
9663
      // We initially clicked in an empty area, so we draw a box for multiple selection
9664
      if (groupSelector) {
9665
        pointer = this.getPointer(e, true);
9666
 
9667
        groupSelector.left = pointer.x - groupSelector.ex;
9668
        groupSelector.top = pointer.y - groupSelector.ey;
9669
 
9670
        this.renderTop();
9671
      }
9672
      else if (!this._currentTransform) {
9673
 
9674
        target = this.findTarget(e);
9675
 
9676
        if (!target || target && !target.selectable) {
9677
          this.setCursor(this.defaultCursor);
9678
        }
9679
        else {
9680
          this._setCursorFromEvent(e, target);
9681
        }
9682
      }
9683
      else {
9684
        this._transformObject(e);
9685
      }
9686
 
9687
      this.fire('mouse:move', { target: target, e: e });
9688
      target && target.fire('mousemove', { e: e });
9689
    },
9690
 
9691
    /**
9692
     * @private
9693
     * @param {Event} e Event fired on mousemove
9694
     */
9695
    _transformObject: function(e) {
9696
      var pointer = this.getPointer(e),
9697
          transform = this._currentTransform;
9698
 
9699
      transform.reset = false,
9700
      transform.target.isMoving = true;
9701
 
9702
      this._beforeScaleTransform(e, transform);
9703
      this._performTransformAction(e, transform, pointer);
9704
 
9705
      this.renderAll();
9706
    },
9707
 
9708
    /**
9709
     * @private
9710
     */
9711
    _performTransformAction: function(e, transform, pointer) {
9712
      var x = pointer.x,
9713
          y = pointer.y,
9714
          target = transform.target,
9715
          action = transform.action;
9716
 
9717
      if (action === 'rotate') {
9718
        this._rotateObject(x, y);
9719
        this._fire('rotating', target, e);
9720
      }
9721
      else if (action === 'scale') {
9722
        this._onScale(e, transform, x, y);
9723
        this._fire('scaling', target, e);
9724
      }
9725
      else if (action === 'scaleX') {
9726
        this._scaleObject(x, y, 'x');
9727
        this._fire('scaling', target, e);
9728
      }
9729
      else if (action === 'scaleY') {
9730
        this._scaleObject(x, y, 'y');
9731
        this._fire('scaling', target, e);
9732
      }
9733
      else {
9734
        this._translateObject(x, y);
9735
        this._fire('moving', target, e);
9736
        this.setCursor(this.moveCursor);
9737
      }
9738
    },
9739
 
9740
    /**
9741
     * @private
9742
     */
9743
    _fire: function(eventName, target, e) {
9744
      this.fire('object:' + eventName, { target: target, e: e });
9745
      target.fire(eventName, { e: e });
9746
    },
9747
 
9748
    /**
9749
     * @private
9750
     */
9751
    _beforeScaleTransform: function(e, transform) {
9752
      if (transform.action === 'scale' || transform.action === 'scaleX' || transform.action === 'scaleY') {
9753
        var centerTransform = this._shouldCenterTransform(e, transform.target);
9754
 
9755
        // Switch from a normal resize to center-based
9756
        if ((centerTransform && (transform.originX !== 'center' || transform.originY !== 'center')) ||
9757
           // Switch from center-based resize to normal one
9758
           (!centerTransform && transform.originX === 'center' && transform.originY === 'center')
9759
        ) {
9760
          this._resetCurrentTransform(e);
9761
          transform.reset = true;
9762
        }
9763
      }
9764
    },
9765
 
9766
    /**
9767
     * @private
9768
     */
9769
    _onScale: function(e, transform, x, y) {
9770
      // rotate object only if shift key is not pressed
9771
      // and if it is not a group we are transforming
9772
      if ((e.shiftKey || this.uniScaleTransform) && !transform.target.get('lockUniScaling')) {
9773
        transform.currentAction = 'scale';
9774
        this._scaleObject(x, y);
9775
      }
9776
      else {
9777
        // Switch from a normal resize to proportional
9778
        if (!transform.reset && transform.currentAction === 'scale') {
9779
          this._resetCurrentTransform(e, transform.target);
9780
        }
9781
 
9782
        transform.currentAction = 'scaleEqually';
9783
        this._scaleObject(x, y, 'equally');
9784
      }
9785
    },
9786
 
9787
    /**
9788
     * Sets the cursor depending on where the canvas is being hovered.
9789
     * Note: very buggy in Opera
9790
     * @param {Event} e Event object
9791
     * @param {Object} target Object that the mouse is hovering, if so.
9792
     */
9793
    _setCursorFromEvent: function (e, target) {
9794
      if (!target || !target.selectable) {
9795
        this.setCursor(this.defaultCursor);
9796
        return false;
9797
      }
9798
      else {
9799
        var activeGroup = this.getActiveGroup(),
9800
            // only show proper corner when group selection is not active
9801
            corner = target._findTargetCorner
9802
                      && (!activeGroup || !activeGroup.contains(target))
9803
                      && target._findTargetCorner(this.getPointer(e, true));
9804
 
9805
        if (!corner) {
9806
          this.setCursor(target.hoverCursor || this.hoverCursor);
9807
        }
9808
        else {
9809
          this._setCornerCursor(corner, target);
9810
        }
9811
      }
9812
      return true;
9813
    },
9814
 
9815
    /**
9816
     * @private
9817
     */
9818
    _setCornerCursor: function(corner, target) {
9819
      if (corner in cursorOffset) {
9820
        this.setCursor(this._getRotatedCornerCursor(corner, target));
9821
      }
9822
      else if (corner === 'mtr' && target.hasRotatingPoint) {
9823
        this.setCursor(this.rotationCursor);
9824
      }
9825
      else {
9826
        this.setCursor(this.defaultCursor);
9827
        return false;
9828
      }
9829
    },
9830
 
9831
    /**
9832
     * @private
9833
     */
9834
    _getRotatedCornerCursor: function(corner, target) {
9835
      var n = Math.round((target.getAngle() % 360) / 45);
9836
 
9837
      if (n < 0) {
9838
        n += 8; // full circle ahead
9839
      }
9840
      n += cursorOffset[corner];
9841
      // normalize n to be from 0 to 7
9842
      n %= 8;
9843
 
9844
      return this.cursorMap[n];
9845
    }
9846
  });
9847
})();
9848
 
9849
 
9850
(function() {
9851
 
9852
  var min = Math.min,
9853
      max = Math.max;
9854
 
9855
  fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ {
9856
 
9857
    /**
9858
     * @private
9859
     * @param {Event} e Event object
9860
     * @param {fabric.Object} target
9861
     * @return {Boolean}
9862
     */
9863
    _shouldGroup: function(e, target) {
9864
      var activeObject = this.getActiveObject();
9865
      return e.shiftKey &&
9866
            (this.getActiveGroup() || (activeObject && activeObject !== target))
9867
            && this.selection;
9868
    },
9869
 
9870
    /**
9871
     * @private
9872
     * @param {Event} e Event object
9873
     * @param {fabric.Object} target
9874
     */
9875
    _handleGrouping: function (e, target) {
9876
 
9877
      if (target === this.getActiveGroup()) {
9878
 
9879
        // if it's a group, find target again, this time skipping group
9880
        target = this.findTarget(e, true);
9881
 
9882
        // if even object is not found, bail out
9883
        if (!target || target.isType('group')) {
9884
          return;
9885
        }
9886
      }
9887
      if (this.getActiveGroup()) {
9888
        this._updateActiveGroup(target, e);
9889
      }
9890
      else {
9891
        this._createActiveGroup(target, e);
9892
      }
9893
 
9894
      if (this._activeGroup) {
9895
        this._activeGroup.saveCoords();
9896
      }
9897
    },
9898
 
9899
    /**
9900
     * @private
9901
     */
9902
    _updateActiveGroup: function(target, e) {
9903
      var activeGroup = this.getActiveGroup();
9904
 
9905
      if (activeGroup.contains(target)) {
9906
 
9907
        activeGroup.removeWithUpdate(target);
9908
        this._resetObjectTransform(activeGroup);
9909
        target.set('active', false);
9910
 
9911
        if (activeGroup.size() === 1) {
9912
          // remove group alltogether if after removal it only contains 1 object
9913
          this.discardActiveGroup(e);
9914
          // activate last remaining object
9915
          this.setActiveObject(activeGroup.item(0));
9916
          return;
9917
        }
9918
      }
9919
      else {
9920
        activeGroup.addWithUpdate(target);
9921
        this._resetObjectTransform(activeGroup);
9922
      }
9923
      this.fire('selection:created', { target: activeGroup, e: e });
9924
      activeGroup.set('active', true);
9925
    },
9926
 
9927
    /**
9928
     * @private
9929
     */
9930
    _createActiveGroup: function(target, e) {
9931
 
9932
      if (this._activeObject && target !== this._activeObject) {
9933
 
9934
        var group = this._createGroup(target);
9935
        group.addWithUpdate();
9936
 
9937
        this.setActiveGroup(group);
9938
        this._activeObject = null;
9939
 
9940
        this.fire('selection:created', { target: group, e: e });
9941
      }
9942
 
9943
      target.set('active', true);
9944
    },
9945
 
9946
    /**
9947
     * @private
9948
     * @param {Object} target
9949
     */
9950
    _createGroup: function(target) {
9951
 
9952
      var objects = this.getObjects(),
9953
          isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target),
9954
          groupObjects = isActiveLower
9955
            ? [ this._activeObject, target ]
9956
            : [ target, this._activeObject ];
9957
 
9958
      return new fabric.Group(groupObjects, {
9959
        canvas: this
9960
      });
9961
    },
9962
 
9963
    /**
9964
     * @private
9965
     * @param {Event} e mouse event
9966
     */
9967
    _groupSelectedObjects: function (e) {
9968
 
9969
      var group = this._collectObjects();
9970
 
9971
      // do not create group for 1 element only
9972
      if (group.length === 1) {
9973
        this.setActiveObject(group[0], e);
9974
      }
9975
      else if (group.length > 1) {
9976
        group = new fabric.Group(group.reverse(), {
9977
          canvas: this
9978
        });
9979
        group.addWithUpdate();
9980
        this.setActiveGroup(group, e);
9981
        group.saveCoords();
9982
        this.fire('selection:created', { target: group });
9983
        this.renderAll();
9984
      }
9985
    },
9986
 
9987
    /**
9988
     * @private
9989
     */
9990
    _collectObjects: function() {
9991
      var group = [ ],
9992
          currentObject,
9993
          x1 = this._groupSelector.ex,
9994
          y1 = this._groupSelector.ey,
9995
          x2 = x1 + this._groupSelector.left,
9996
          y2 = y1 + this._groupSelector.top,
9997
          selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)),
9998
          selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2)),
9999
          isClick = x1 === x2 && y1 === y2;
10000
 
10001
      for (var i = this._objects.length; i--; ) {
10002
        currentObject = this._objects[i];
10003
 
10004
        if (!currentObject || !currentObject.selectable || !currentObject.visible) {
10005
          continue;
10006
        }
10007
 
10008
        if (currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2) ||
10009
            currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2) ||
10010
            currentObject.containsPoint(selectionX1Y1) ||
10011
            currentObject.containsPoint(selectionX2Y2)
10012
        ) {
10013
          currentObject.set('active', true);
10014
          group.push(currentObject);
10015
 
10016
          // only add one object if it's a click
10017
          if (isClick) {
10018
            break;
10019
          }
10020
        }
10021
      }
10022
 
10023
      return group;
10024
    },
10025
 
10026
    /**
10027
     * @private
10028
     */
10029
    _maybeGroupObjects: function(e) {
10030
      if (this.selection && this._groupSelector) {
10031
        this._groupSelectedObjects(e);
10032
      }
10033
 
10034
      var activeGroup = this.getActiveGroup();
10035
      if (activeGroup) {
10036
        activeGroup.setObjectsCoords().setCoords();
10037
        activeGroup.isMoving = false;
10038
        this.setCursor(this.defaultCursor);
10039
      }
10040
 
10041
      // clear selection and current transformation
10042
      this._groupSelector = null;
10043
      this._currentTransform = null;
10044
    }
10045
  });
10046
 
10047
})();
10048
 
10049
 
10050
fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ {
10051
 
10052
  /**
10053
   * Exports canvas element to a dataurl image. Note that when multiplier is used, cropping is scaled appropriately
10054
   * @param {Object} [options] Options object
10055
   * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png"
10056
   * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg.
10057
   * @param {Number} [options.multiplier=1] Multiplier to scale by
10058
   * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14
10059
   * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14
10060
   * @param {Number} [options.width] Cropping width. Introduced in v1.2.14
10061
   * @param {Number} [options.height] Cropping height. Introduced in v1.2.14
10062
   * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format
10063
   * @see {@link http://jsfiddle.net/fabricjs/NfZVb/|jsFiddle demo}
10064
   * @example <caption>Generate jpeg dataURL with lower quality</caption>
10065
   * var dataURL = canvas.toDataURL({
10066
   *   format: 'jpeg',
10067
   *   quality: 0.8
10068
   * });
10069
   * @example <caption>Generate cropped png dataURL (clipping of canvas)</caption>
10070
   * var dataURL = canvas.toDataURL({
10071
   *   format: 'png',
10072
   *   left: 100,
10073
   *   top: 100,
10074
   *   width: 200,
10075
   *   height: 200
10076
   * });
10077
   * @example <caption>Generate double scaled png dataURL</caption>
10078
   * var dataURL = canvas.toDataURL({
10079
   *   format: 'png',
10080
   *   multiplier: 2
10081
   * });
10082
   */
10083
  toDataURL: function (options) {
10084
    options || (options = { });
10085
 
10086
    var format = options.format || 'png',
10087
        quality = options.quality || 1,
10088
        multiplier = options.multiplier || 1,
10089
        cropping = {
10090
          left: options.left,
10091
          top: options.top,
10092
          width: options.width,
10093
          height: options.height
10094
        };
10095
 
10096
    if (multiplier !== 1) {
10097
      return this.__toDataURLWithMultiplier(format, quality, cropping, multiplier);
10098
    }
10099
    else {
10100
      return this.__toDataURL(format, quality, cropping);
10101
    }
10102
  },
10103
 
10104
  /**
10105
   * @private
10106
   */
10107
  __toDataURL: function(format, quality, cropping) {
10108
 
10109
    this.renderAll(true);
10110
 
10111
    var canvasEl = this.upperCanvasEl || this.lowerCanvasEl,
10112
        croppedCanvasEl = this.__getCroppedCanvas(canvasEl, cropping);
10113
 
10114
    // to avoid common confusion https://github.com/kangax/fabric.js/issues/806
10115
    if (format === 'jpg') {
10116
      format = 'jpeg';
10117
    }
10118
 
10119
    var data = (fabric.StaticCanvas.supports('toDataURLWithQuality'))
10120
              ? (croppedCanvasEl || canvasEl).toDataURL('image/' + format, quality)
10121
              : (croppedCanvasEl || canvasEl).toDataURL('image/' + format);
10122
 
10123
    this.contextTop && this.clearContext(this.contextTop);
10124
    this.renderAll();
10125
 
10126
    if (croppedCanvasEl) {
10127
      croppedCanvasEl = null;
10128
    }
10129
 
10130
    return data;
10131
  },
10132
 
10133
  /**
10134
   * @private
10135
   */
10136
  __getCroppedCanvas: function(canvasEl, cropping) {
10137
 
10138
    var croppedCanvasEl,
10139
        croppedCtx,
10140
        shouldCrop = 'left' in cropping ||
10141
                     'top' in cropping ||
10142
                     'width' in cropping ||
10143
                     'height' in cropping;
10144
 
10145
    if (shouldCrop) {
10146
 
10147
      croppedCanvasEl = fabric.util.createCanvasElement();
10148
      croppedCtx = croppedCanvasEl.getContext('2d');
10149
 
10150
      croppedCanvasEl.width = cropping.width || this.width;
10151
      croppedCanvasEl.height = cropping.height || this.height;
10152
 
10153
      croppedCtx.drawImage(canvasEl, -cropping.left || 0, -cropping.top || 0);
10154
    }
10155
 
10156
    return croppedCanvasEl;
10157
  },
10158
 
10159
  /**
10160
   * @private
10161
   */
10162
  __toDataURLWithMultiplier: function(format, quality, cropping, multiplier) {
10163
 
10164
    var origWidth = this.getWidth(),
10165
        origHeight = this.getHeight(),
10166
        scaledWidth = origWidth * multiplier,
10167
        scaledHeight = origHeight * multiplier,
10168
        activeObject = this.getActiveObject(),
10169
        activeGroup = this.getActiveGroup(),
10170
 
10171
        ctx = this.contextTop || this.contextContainer;
10172
 
10173
    if (multiplier > 1) {
10174
      this.setWidth(scaledWidth).setHeight(scaledHeight);
10175
    }
10176
    ctx.scale(multiplier, multiplier);
10177
 
10178
    if (cropping.left) {
10179
      cropping.left *= multiplier;
10180
    }
10181
    if (cropping.top) {
10182
      cropping.top *= multiplier;
10183
    }
10184
    if (cropping.width) {
10185
      cropping.width *= multiplier;
10186
    }
10187
    else if (multiplier < 1) {
10188
      cropping.width = scaledWidth;
10189
    }
10190
    if (cropping.height) {
10191
      cropping.height *= multiplier;
10192
    }
10193
    else if (multiplier < 1) {
10194
      cropping.height = scaledHeight;
10195
    }
10196
 
10197
    if (activeGroup) {
10198
      // not removing group due to complications with restoring it with correct state afterwords
10199
      this._tempRemoveBordersControlsFromGroup(activeGroup);
10200
    }
10201
    else if (activeObject && this.deactivateAll) {
10202
      this.deactivateAll();
10203
    }
10204
 
10205
    this.renderAll(true);
10206
 
10207
    var data = this.__toDataURL(format, quality, cropping);
10208
 
10209
    // restoring width, height for `renderAll` to draw
10210
    // background properly (while context is scaled)
10211
    this.width = origWidth;
10212
    this.height = origHeight;
10213
 
10214
    ctx.scale(1 / multiplier,  1 / multiplier);
10215
    this.setWidth(origWidth).setHeight(origHeight);
10216
 
10217
    if (activeGroup) {
10218
      this._restoreBordersControlsOnGroup(activeGroup);
10219
    }
10220
    else if (activeObject && this.setActiveObject) {
10221
      this.setActiveObject(activeObject);
10222
    }
10223
 
10224
    this.contextTop && this.clearContext(this.contextTop);
10225
    this.renderAll();
10226
 
10227
    return data;
10228
  },
10229
 
10230
  /**
10231
   * Exports canvas element to a dataurl image (allowing to change image size via multiplier).
10232
   * @deprecated since 1.0.13
10233
   * @param {String} format (png|jpeg)
10234
   * @param {Number} multiplier
10235
   * @param {Number} quality (0..1)
10236
   * @return {String}
10237
   */
10238
  toDataURLWithMultiplier: function (format, multiplier, quality) {
10239
    return this.toDataURL({
10240
      format: format,
10241
      multiplier: multiplier,
10242
      quality: quality
10243
    });
10244
  },
10245
 
10246
  /**
10247
   * @private
10248
   */
10249
  _tempRemoveBordersControlsFromGroup: function(group) {
10250
    group.origHasControls = group.hasControls;
10251
    group.origBorderColor = group.borderColor;
10252
 
10253
    group.hasControls = true;
10254
    group.borderColor = 'rgba(0,0,0,0)';
10255
 
10256
    group.forEachObject(function(o) {
10257
      o.origBorderColor = o.borderColor;
10258
      o.borderColor = 'rgba(0,0,0,0)';
10259
    });
10260
  },
10261
 
10262
  /**
10263
   * @private
10264
   */
10265
  _restoreBordersControlsOnGroup: function(group) {
10266
    group.hideControls = group.origHideControls;
10267
    group.borderColor = group.origBorderColor;
10268
 
10269
    group.forEachObject(function(o) {
10270
      o.borderColor = o.origBorderColor;
10271
      delete o.origBorderColor;
10272
    });
10273
  }
10274
});
10275
 
10276
 
10277
fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ {
10278
 
10279
  /**
10280
   * Populates canvas with data from the specified dataless JSON.
10281
   * JSON format must conform to the one of {@link fabric.Canvas#toDatalessJSON}
10282
   * @deprecated since 1.2.2
10283
   * @param {String|Object} json JSON string or object
10284
   * @param {Function} callback Callback, invoked when json is parsed
10285
   *                            and corresponding objects (e.g: {@link fabric.Image})
10286
   *                            are initialized
10287
   * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created.
10288
   * @return {fabric.Canvas} instance
10289
   * @chainable
10290
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#deserialization}
10291
   */
10292
  loadFromDatalessJSON: function (json, callback, reviver) {
10293
    return this.loadFromJSON(json, callback, reviver);
10294
  },
10295
 
10296
  /**
10297
   * Populates canvas with data from the specified JSON.
10298
   * JSON format must conform to the one of {@link fabric.Canvas#toJSON}
10299
   * @param {String|Object} json JSON string or object
10300
   * @param {Function} callback Callback, invoked when json is parsed
10301
   *                            and corresponding objects (e.g: {@link fabric.Image})
10302
   *                            are initialized
10303
   * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created.
10304
   * @return {fabric.Canvas} instance
10305
   * @chainable
10306
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#deserialization}
10307
   * @see {@link http://jsfiddle.net/fabricjs/fmgXt/|jsFiddle demo}
10308
   * @example <caption>loadFromJSON</caption>
10309
   * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas));
10310
   * @example <caption>loadFromJSON with reviver</caption>
10311
   * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas), function(o, object) {
10312
   *   // `o` = json object
10313
   *   // `object` = fabric.Object instance
10314
   *   // ... do some stuff ...
10315
   * });
10316
   */
10317
  loadFromJSON: function (json, callback, reviver) {
10318
    if (!json) {
10319
      return;
10320
    }
10321
 
10322
    // serialize if it wasn't already
10323
    var serialized = (typeof json === 'string')
10324
      ? JSON.parse(json)
10325
      : json;
10326
 
10327
    this.clear();
10328
 
10329
    var _this = this;
10330
    this._enlivenObjects(serialized.objects, function () {
10331
      _this._setBgOverlay(serialized, callback);
10332
    }, reviver);
10333
 
10334
    return this;
10335
  },
10336
 
10337
  /**
10338
   * @private
10339
   * @param {Object} serialized Object with background and overlay information
10340
   * @param {Function} callback Invoked after all background and overlay images/patterns loaded
10341
   */
10342
  _setBgOverlay: function(serialized, callback) {
10343
    var _this = this,
10344
        loaded = {
10345
          backgroundColor: false,
10346
          overlayColor: false,
10347
          backgroundImage: false,
10348
          overlayImage: false
10349
        };
10350
 
10351
    if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background && !serialized.overlay) {
10352
      callback && callback();
10353
      return;
10354
    }
10355
 
10356
    var cbIfLoaded = function () {
10357
      if (loaded.backgroundImage && loaded.overlayImage && loaded.backgroundColor && loaded.overlayColor) {
10358
        _this.renderAll();
10359
        callback && callback();
10360
      }
10361
    };
10362
 
10363
    this.__setBgOverlay('backgroundImage', serialized.backgroundImage, loaded, cbIfLoaded);
10364
    this.__setBgOverlay('overlayImage', serialized.overlayImage, loaded, cbIfLoaded);
10365
    this.__setBgOverlay('backgroundColor', serialized.background, loaded, cbIfLoaded);
10366
    this.__setBgOverlay('overlayColor', serialized.overlay, loaded, cbIfLoaded);
10367
 
10368
    cbIfLoaded();
10369
  },
10370
 
10371
  /**
10372
   * @private
10373
   * @param {String} property Property to set (backgroundImage, overlayImage, backgroundColor, overlayColor)
10374
   * @param {(Object|String)} value Value to set
10375
   * @param {Object} loaded Set loaded property to true if property is set
10376
   * @param {Object} callback Callback function to invoke after property is set
10377
   */
10378
  __setBgOverlay: function(property, value, loaded, callback) {
10379
    var _this = this;
10380
 
10381
    if (!value) {
10382
      loaded[property] = true;
10383
      return;
10384
    }
10385
 
10386
    if (property === 'backgroundImage' || property === 'overlayImage') {
10387
      fabric.Image.fromObject(value, function(img) {
10388
        _this[property] = img;
10389
        loaded[property] = true;
10390
        callback && callback();
10391
      });
10392
    }
10393
    else {
10394
      this['set' + fabric.util.string.capitalize(property, true)](value, function() {
10395
        loaded[property] = true;
10396
        callback && callback();
10397
      });
10398
    }
10399
  },
10400
 
10401
  /**
10402
   * @private
10403
   * @param {Array} objects
10404
   * @param {Function} callback
10405
   * @param {Function} [reviver]
10406
   */
10407
  _enlivenObjects: function (objects, callback, reviver) {
10408
    var _this = this;
10409
 
10410
    if (!objects || objects.length === 0) {
10411
      callback && callback();
10412
      return;
10413
    }
10414
 
10415
    var renderOnAddRemove = this.renderOnAddRemove;
10416
    this.renderOnAddRemove = false;
10417
 
10418
    fabric.util.enlivenObjects(objects, function(enlivenedObjects) {
10419
      enlivenedObjects.forEach(function(obj, index) {
10420
        _this.insertAt(obj, index, true);
10421
      });
10422
 
10423
      _this.renderOnAddRemove = renderOnAddRemove;
10424
      callback && callback();
10425
    }, null, reviver);
10426
  },
10427
 
10428
  /**
10429
   * @private
10430
   * @param {String} format
10431
   * @param {Function} callback
10432
   */
10433
  _toDataURL: function (format, callback) {
10434
    this.clone(function (clone) {
10435
      callback(clone.toDataURL(format));
10436
    });
10437
  },
10438
 
10439
  /**
10440
   * @private
10441
   * @param {String} format
10442
   * @param {Number} multiplier
10443
   * @param {Function} callback
10444
   */
10445
  _toDataURLWithMultiplier: function (format, multiplier, callback) {
10446
    this.clone(function (clone) {
10447
      callback(clone.toDataURLWithMultiplier(format, multiplier));
10448
    });
10449
  },
10450
 
10451
  /**
10452
   * Clones canvas instance
10453
   * @param {Object} [callback] Receives cloned instance as a first argument
10454
   * @param {Array} [properties] Array of properties to include in the cloned canvas and children
10455
   */
10456
  clone: function (callback, properties) {
10457
    var data = JSON.stringify(this.toJSON(properties));
10458
    this.cloneWithoutData(function(clone) {
10459
      clone.loadFromJSON(data, function() {
10460
        callback && callback(clone);
10461
      });
10462
    });
10463
  },
10464
 
10465
  /**
10466
   * Clones canvas instance without cloning existing data.
10467
   * This essentially copies canvas dimensions, clipping properties, etc.
10468
   * but leaves data empty (so that you can populate it with your own)
10469
   * @param {Object} [callback] Receives cloned instance as a first argument
10470
   */
10471
  cloneWithoutData: function(callback) {
10472
    var el = fabric.document.createElement('canvas');
10473
 
10474
    el.width = this.getWidth();
10475
    el.height = this.getHeight();
10476
 
10477
    var clone = new fabric.Canvas(el);
10478
    clone.clipTo = this.clipTo;
10479
    if (this.backgroundImage) {
10480
      clone.setBackgroundImage(this.backgroundImage.src, function() {
10481
        clone.renderAll();
10482
        callback && callback(clone);
10483
      });
10484
      clone.backgroundImageOpacity = this.backgroundImageOpacity;
10485
      clone.backgroundImageStretch = this.backgroundImageStretch;
10486
    }
10487
    else {
10488
      callback && callback(clone);
10489
    }
10490
  }
10491
});
10492
 
10493
 
10494
(function(global) {
10495
 
10496
  'use strict';
10497
 
10498
  var fabric = global.fabric || (global.fabric = { }),
10499
      extend = fabric.util.object.extend,
10500
      toFixed = fabric.util.toFixed,
10501
      capitalize = fabric.util.string.capitalize,
10502
      degreesToRadians = fabric.util.degreesToRadians,
10503
      supportsLineDash = fabric.StaticCanvas.supports('setLineDash');
10504
 
10505
  if (fabric.Object) {
10506
    return;
10507
  }
10508
 
10509
  /**
10510
   * Root object class from which all 2d shape classes inherit from
10511
   * @class fabric.Object
10512
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#objects}
10513
   * @see {@link fabric.Object#initialize} for constructor definition
10514
   *
10515
   * @fires added
10516
   * @fires removed
10517
   *
10518
   * @fires selected
10519
   * @fires modified
10520
   * @fires rotating
10521
   * @fires scaling
10522
   * @fires moving
10523
   *
10524
   * @fires mousedown
10525
   * @fires mouseup
10526
   */
10527
  fabric.Object = fabric.util.createClass(/** @lends fabric.Object.prototype */ {
10528
 
10529
    /**
10530
     * Retrieves object's {@link fabric.Object#clipTo|clipping function}
10531
     * @method getClipTo
10532
     * @memberOf fabric.Object.prototype
10533
     * @return {Function}
10534
     */
10535
 
10536
    /**
10537
     * Sets object's {@link fabric.Object#clipTo|clipping function}
10538
     * @method setClipTo
10539
     * @memberOf fabric.Object.prototype
10540
     * @param {Function} clipTo Clipping function
10541
     * @return {fabric.Object} thisArg
10542
     * @chainable
10543
     */
10544
 
10545
    /**
10546
     * Retrieves object's {@link fabric.Object#transformMatrix|transformMatrix}
10547
     * @method getTransformMatrix
10548
     * @memberOf fabric.Object.prototype
10549
     * @return {Array} transformMatrix
10550
     */
10551
 
10552
    /**
10553
     * Sets object's {@link fabric.Object#transformMatrix|transformMatrix}
10554
     * @method setTransformMatrix
10555
     * @memberOf fabric.Object.prototype
10556
     * @param {Array} transformMatrix
10557
     * @return {fabric.Object} thisArg
10558
     * @chainable
10559
     */
10560
 
10561
    /**
10562
     * Retrieves object's {@link fabric.Object#visible|visible} state
10563
     * @method getVisible
10564
     * @memberOf fabric.Object.prototype
10565
     * @return {Boolean} True if visible
10566
     */
10567
 
10568
    /**
10569
     * Sets object's {@link fabric.Object#visible|visible} state
10570
     * @method setVisible
10571
     * @memberOf fabric.Object.prototype
10572
     * @param {Boolean} value visible value
10573
     * @return {fabric.Object} thisArg
10574
     * @chainable
10575
     */
10576
 
10577
    /**
10578
     * Retrieves object's {@link fabric.Object#shadow|shadow}
10579
     * @method getShadow
10580
     * @memberOf fabric.Object.prototype
10581
     * @return {Object} Shadow instance
10582
     */
10583
 
10584
    /**
10585
     * Retrieves object's {@link fabric.Object#stroke|stroke}
10586
     * @method getStroke
10587
     * @memberOf fabric.Object.prototype
10588
     * @return {String} stroke value
10589
     */
10590
 
10591
    /**
10592
     * Sets object's {@link fabric.Object#stroke|stroke}
10593
     * @method setStroke
10594
     * @memberOf fabric.Object.prototype
10595
     * @param {String} value stroke value
10596
     * @return {fabric.Object} thisArg
10597
     * @chainable
10598
     */
10599
 
10600
    /**
10601
     * Retrieves object's {@link fabric.Object#strokeWidth|strokeWidth}
10602
     * @method getStrokeWidth
10603
     * @memberOf fabric.Object.prototype
10604
     * @return {Number} strokeWidth value
10605
     */
10606
 
10607
    /**
10608
     * Sets object's {@link fabric.Object#strokeWidth|strokeWidth}
10609
     * @method setStrokeWidth
10610
     * @memberOf fabric.Object.prototype
10611
     * @param {Number} value strokeWidth value
10612
     * @return {fabric.Object} thisArg
10613
     * @chainable
10614
     */
10615
 
10616
    /**
10617
     * Retrieves object's {@link fabric.Object#originX|originX}
10618
     * @method getOriginX
10619
     * @memberOf fabric.Object.prototype
10620
     * @return {String} originX value
10621
     */
10622
 
10623
    /**
10624
     * Sets object's {@link fabric.Object#originX|originX}
10625
     * @method setOriginX
10626
     * @memberOf fabric.Object.prototype
10627
     * @param {String} value originX value
10628
     * @return {fabric.Object} thisArg
10629
     * @chainable
10630
     */
10631
 
10632
    /**
10633
     * Retrieves object's {@link fabric.Object#originY|originY}
10634
     * @method getOriginY
10635
     * @memberOf fabric.Object.prototype
10636
     * @return {String} originY value
10637
     */
10638
 
10639
    /**
10640
     * Sets object's {@link fabric.Object#originY|originY}
10641
     * @method setOriginY
10642
     * @memberOf fabric.Object.prototype
10643
     * @param {String} value originY value
10644
     * @return {fabric.Object} thisArg
10645
     * @chainable
10646
     */
10647
 
10648
    /**
10649
     * Retrieves object's {@link fabric.Object#fill|fill}
10650
     * @method getFill
10651
     * @memberOf fabric.Object.prototype
10652
     * @return {String} Fill value
10653
     */
10654
 
10655
    /**
10656
     * Sets object's {@link fabric.Object#fill|fill}
10657
     * @method setFill
10658
     * @memberOf fabric.Object.prototype
10659
     * @param {String} value Fill value
10660
     * @return {fabric.Object} thisArg
10661
     * @chainable
10662
     */
10663
 
10664
    /**
10665
     * Retrieves object's {@link fabric.Object#opacity|opacity}
10666
     * @method getOpacity
10667
     * @memberOf fabric.Object.prototype
10668
     * @return {Number} Opacity value (0-1)
10669
     */
10670
 
10671
    /**
10672
     * Sets object's {@link fabric.Object#opacity|opacity}
10673
     * @method setOpacity
10674
     * @memberOf fabric.Object.prototype
10675
     * @param {Number} value Opacity value (0-1)
10676
     * @return {fabric.Object} thisArg
10677
     * @chainable
10678
     */
10679
 
10680
    /**
10681
     * Retrieves object's {@link fabric.Object#angle|angle} (in degrees)
10682
     * @method getAngle
10683
     * @memberOf fabric.Object.prototype
10684
     * @return {Number}
10685
     */
10686
 
10687
    /**
10688
     * Retrieves object's {@link fabric.Object#top|top position}
10689
     * @method getTop
10690
     * @memberOf fabric.Object.prototype
10691
     * @return {Number} Top value (in pixels)
10692
     */
10693
 
10694
    /**
10695
     * Sets object's {@link fabric.Object#top|top position}
10696
     * @method setTop
10697
     * @memberOf fabric.Object.prototype
10698
     * @param {Number} value Top value (in pixels)
10699
     * @return {fabric.Object} thisArg
10700
     * @chainable
10701
     */
10702
 
10703
    /**
10704
     * Retrieves object's {@link fabric.Object#left|left position}
10705
     * @method getLeft
10706
     * @memberOf fabric.Object.prototype
10707
     * @return {Number} Left value (in pixels)
10708
     */
10709
 
10710
    /**
10711
     * Sets object's {@link fabric.Object#left|left position}
10712
     * @method setLeft
10713
     * @memberOf fabric.Object.prototype
10714
     * @param {Number} value Left value (in pixels)
10715
     * @return {fabric.Object} thisArg
10716
     * @chainable
10717
     */
10718
 
10719
    /**
10720
     * Retrieves object's {@link fabric.Object#scaleX|scaleX} value
10721
     * @method getScaleX
10722
     * @memberOf fabric.Object.prototype
10723
     * @return {Number} scaleX value
10724
     */
10725
 
10726
    /**
10727
     * Sets object's {@link fabric.Object#scaleX|scaleX} value
10728
     * @method setScaleX
10729
     * @memberOf fabric.Object.prototype
10730
     * @param {Number} value scaleX value
10731
     * @return {fabric.Object} thisArg
10732
     * @chainable
10733
     */
10734
 
10735
    /**
10736
     * Retrieves object's {@link fabric.Object#scaleY|scaleY} value
10737
     * @method getScaleY
10738
     * @memberOf fabric.Object.prototype
10739
     * @return {Number} scaleY value
10740
     */
10741
 
10742
    /**
10743
     * Sets object's {@link fabric.Object#scaleY|scaleY} value
10744
     * @method setScaleY
10745
     * @memberOf fabric.Object.prototype
10746
     * @param {Number} value scaleY value
10747
     * @return {fabric.Object} thisArg
10748
     * @chainable
10749
     */
10750
 
10751
    /**
10752
     * Retrieves object's {@link fabric.Object#flipX|flipX} value
10753
     * @method getFlipX
10754
     * @memberOf fabric.Object.prototype
10755
     * @return {Boolean} flipX value
10756
     */
10757
 
10758
    /**
10759
     * Sets object's {@link fabric.Object#flipX|flipX} value
10760
     * @method setFlipX
10761
     * @memberOf fabric.Object.prototype
10762
     * @param {Boolean} value flipX value
10763
     * @return {fabric.Object} thisArg
10764
     * @chainable
10765
     */
10766
 
10767
    /**
10768
     * Retrieves object's {@link fabric.Object#flipY|flipY} value
10769
     * @method getFlipY
10770
     * @memberOf fabric.Object.prototype
10771
     * @return {Boolean} flipY value
10772
     */
10773
 
10774
    /**
10775
     * Sets object's {@link fabric.Object#flipY|flipY} value
10776
     * @method setFlipY
10777
     * @memberOf fabric.Object.prototype
10778
     * @param {Boolean} value flipY value
10779
     * @return {fabric.Object} thisArg
10780
     * @chainable
10781
     */
10782
 
10783
    /**
10784
     * Type of an object (rect, circle, path, etc.).
10785
     * Note that this property is meant to be read-only and not meant to be modified.
10786
     * If you modify, certain parts of Fabric (such as JSON loading) won't work correctly.
10787
     * @type String
10788
     * @default
10789
     */
10790
    type:                     'object',
10791
 
10792
    /**
10793
     * Horizontal origin of transformation of an object (one of "left", "right", "center")
10794
     * See http://jsfiddle.net/1ow02gea/40/ on how originX/originY affect objects in groups
10795
     * @type String
10796
     * @default
10797
     */
10798
    originX:                  'left',
10799
 
10800
    /**
10801
     * Vertical origin of transformation of an object (one of "top", "bottom", "center")
10802
     * See http://jsfiddle.net/1ow02gea/40/ on how originX/originY affect objects in groups
10803
     * @type String
10804
     * @default
10805
     */
10806
    originY:                  'top',
10807
 
10808
    /**
10809
     * Top position of an object. Note that by default it's relative to object center. You can change this by setting originY={top/center/bottom}
10810
     * @type Number
10811
     * @default
10812
     */
10813
    top:                      0,
10814
 
10815
    /**
10816
     * Left position of an object. Note that by default it's relative to object center. You can change this by setting originX={left/center/right}
10817
     * @type Number
10818
     * @default
10819
     */
10820
    left:                     0,
10821
 
10822
    /**
10823
     * Object width
10824
     * @type Number
10825
     * @default
10826
     */
10827
    width:                    0,
10828
 
10829
    /**
10830
     * Object height
10831
     * @type Number
10832
     * @default
10833
     */
10834
    height:                   0,
10835
 
10836
    /**
10837
     * Object scale factor (horizontal)
10838
     * @type Number
10839
     * @default
10840
     */
10841
    scaleX:                   1,
10842
 
10843
    /**
10844
     * Object scale factor (vertical)
10845
     * @type Number
10846
     * @default
10847
     */
10848
    scaleY:                   1,
10849
 
10850
    /**
10851
     * When true, an object is rendered as flipped horizontally
10852
     * @type Boolean
10853
     * @default
10854
     */
10855
    flipX:                    false,
10856
 
10857
    /**
10858
     * When true, an object is rendered as flipped vertically
10859
     * @type Boolean
10860
     * @default
10861
     */
10862
    flipY:                    false,
10863
 
10864
    /**
10865
     * Opacity of an object
10866
     * @type Number
10867
     * @default
10868
     */
10869
    opacity:                  1,
10870
 
10871
    /**
10872
     * Angle of rotation of an object (in degrees)
10873
     * @type Number
10874
     * @default
10875
     */
10876
    angle:                    0,
10877
 
10878
    /**
10879
     * Size of object's controlling corners (in pixels)
10880
     * @type Number
10881
     * @default
10882
     */
10883
    cornerSize:               12,
10884
 
10885
    /**
10886
     * When true, object's controlling corners are rendered as transparent inside (i.e. stroke instead of fill)
10887
     * @type Boolean
10888
     * @default
10889
     */
10890
    transparentCorners:       true,
10891
 
10892
    /**
10893
     * Default cursor value used when hovering over this object on canvas
10894
     * @type String
10895
     * @default
10896
     */
10897
    hoverCursor:              null,
10898
 
10899
    /**
10900
     * Padding between object and its controlling borders (in pixels)
10901
     * @type Number
10902
     * @default
10903
     */
10904
    padding:                  0,
10905
 
10906
    /**
10907
     * Color of controlling borders of an object (when it's active)
10908
     * @type String
10909
     * @default
10910
     */
10911
    borderColor:              'rgba(102,153,255,0.75)',
10912
 
10913
    /**
10914
     * Color of controlling corners of an object (when it's active)
10915
     * @type String
10916
     * @default
10917
     */
10918
    cornerColor:              'rgba(102,153,255,0.5)',
10919
 
10920
    /**
10921
     * When true, this object will use center point as the origin of transformation
10922
     * when being scaled via the controls.
10923
     * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean).
10924
     * @since 1.3.4
10925
     * @type Boolean
10926
     * @default
10927
     */
10928
    centeredScaling:          false,
10929
 
10930
    /**
10931
     * When true, this object will use center point as the origin of transformation
10932
     * when being rotated via the controls.
10933
     * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean).
10934
     * @since 1.3.4
10935
     * @type Boolean
10936
     * @default
10937
     */
10938
    centeredRotation:         true,
10939
 
10940
    /**
10941
     * Color of object's fill
10942
     * @type String
10943
     * @default
10944
     */
10945
    fill:                     'rgb(0,0,0)',
10946
 
10947
    /**
10948
     * Fill rule used to fill an object
10949
     * accepted values are nonzero, evenodd
10950
     * <b>Backwards incompatibility note:</b> This property was used for setting globalCompositeOperation until v1.4.12 (use `fabric.Object#globalCompositeOperation` instead)
10951
     * @type String
10952
     * @default
10953
     */
10954
    fillRule:                 'nonzero',
10955
 
10956
    /**
10957
     * Composite rule used for canvas globalCompositeOperation
10958
     * @type String
10959
     * @default
10960
     */
10961
    globalCompositeOperation: 'source-over',
10962
 
10963
    /**
10964
     * Background color of an object. Only works with text objects at the moment.
10965
     * @type String
10966
     * @default
10967
     */
10968
    backgroundColor:          '',
10969
 
10970
    /**
10971
     * When defined, an object is rendered via stroke and this property specifies its color
10972
     * @type String
10973
     * @default
10974
     */
10975
    stroke:                   null,
10976
 
10977
    /**
10978
     * Width of a stroke used to render this object
10979
     * @type Number
10980
     * @default
10981
     */
10982
    strokeWidth:              1,
10983
 
10984
    /**
10985
     * Array specifying dash pattern of an object's stroke (stroke must be defined)
10986
     * @type Array
10987
     */
10988
    strokeDashArray:          null,
10989
 
10990
    /**
10991
     * Line endings style of an object's stroke (one of "butt", "round", "square")
10992
     * @type String
10993
     * @default
10994
     */
10995
    strokeLineCap:            'butt',
10996
 
10997
    /**
10998
     * Corner style of an object's stroke (one of "bevil", "round", "miter")
10999
     * @type String
11000
     * @default
11001
     */
11002
    strokeLineJoin:           'miter',
11003
 
11004
    /**
11005
     * Maximum miter length (used for strokeLineJoin = "miter") of an object's stroke
11006
     * @type Number
11007
     * @default
11008
     */
11009
    strokeMiterLimit:         10,
11010
 
11011
    /**
11012
     * Shadow object representing shadow of this shape
11013
     * @type fabric.Shadow
11014
     * @default
11015
     */
11016
    shadow:                   null,
11017
 
11018
    /**
11019
     * Opacity of object's controlling borders when object is active and moving
11020
     * @type Number
11021
     * @default
11022
     */
11023
    borderOpacityWhenMoving:  0.4,
11024
 
11025
    /**
11026
     * Scale factor of object's controlling borders
11027
     * @type Number
11028
     * @default
11029
     */
11030
    borderScaleFactor:        1,
11031
 
11032
    /**
11033
     * Transform matrix (similar to SVG's transform matrix)
11034
     * @type Array
11035
     */
11036
    transformMatrix:          null,
11037
 
11038
    /**
11039
     * Minimum allowed scale value of an object
11040
     * @type Number
11041
     * @default
11042
     */
11043
    minScaleLimit:            0.01,
11044
 
11045
    /**
11046
     * When set to `false`, an object can not be selected for modification (using either point-click-based or group-based selection).
11047
     * But events still fire on it.
11048
     * @type Boolean
11049
     * @default
11050
     */
11051
    selectable:               true,
11052
 
11053
    /**
11054
     * When set to `false`, an object can not be a target of events. All events propagate through it. Introduced in v1.3.4
11055
     * @type Boolean
11056
     * @default
11057
     */
11058
    evented:                  true,
11059
 
11060
    /**
11061
     * When set to `false`, an object is not rendered on canvas
11062
     * @type Boolean
11063
     * @default
11064
     */
11065
    visible:                  true,
11066
 
11067
    /**
11068
     * When set to `false`, object's controls are not displayed and can not be used to manipulate object
11069
     * @type Boolean
11070
     * @default
11071
     */
11072
    hasControls:              true,
11073
 
11074
    /**
11075
     * When set to `false`, object's controlling borders are not rendered
11076
     * @type Boolean
11077
     * @default
11078
     */
11079
    hasBorders:               true,
11080
 
11081
    /**
11082
     * When set to `false`, object's controlling rotating point will not be visible or selectable
11083
     * @type Boolean
11084
     * @default
11085
     */
11086
    hasRotatingPoint:         true,
11087
 
11088
    /**
11089
     * Offset for object's controlling rotating point (when enabled via `hasRotatingPoint`)
11090
     * @type Number
11091
     * @default
11092
     */
11093
    rotatingPointOffset:      40,
11094
 
11095
    /**
11096
     * When set to `true`, objects are "found" on canvas on per-pixel basis rather than according to bounding box
11097
     * @type Boolean
11098
     * @default
11099
     */
11100
    perPixelTargetFind:       false,
11101
 
11102
    /**
11103
     * When `false`, default object's values are not included in its serialization
11104
     * @type Boolean
11105
     * @default
11106
     */
11107
    includeDefaultValues:     true,
11108
 
11109
    /**
11110
     * Function that determines clipping of an object (context is passed as a first argument)
11111
     * Note that context origin is at the object's center point (not left/top corner)
11112
     * @type Function
11113
     */
11114
    clipTo:                   null,
11115
 
11116
    /**
11117
     * When `true`, object horizontal movement is locked
11118
     * @type Boolean
11119
     * @default
11120
     */
11121
    lockMovementX:            false,
11122
 
11123
    /**
11124
     * When `true`, object vertical movement is locked
11125
     * @type Boolean
11126
     * @default
11127
     */
11128
    lockMovementY:            false,
11129
 
11130
    /**
11131
     * When `true`, object rotation is locked
11132
     * @type Boolean
11133
     * @default
11134
     */
11135
    lockRotation:             false,
11136
 
11137
    /**
11138
     * When `true`, object horizontal scaling is locked
11139
     * @type Boolean
11140
     * @default
11141
     */
11142
    lockScalingX:             false,
11143
 
11144
    /**
11145
     * When `true`, object vertical scaling is locked
11146
     * @type Boolean
11147
     * @default
11148
     */
11149
    lockScalingY:             false,
11150
 
11151
    /**
11152
     * When `true`, object non-uniform scaling is locked
11153
     * @type Boolean
11154
     * @default
11155
     */
11156
    lockUniScaling:           false,
11157
 
11158
    /**
11159
     * When `true`, object cannot be flipped by scaling into negative values
11160
     * @type Boolean
11161
     * @default
11162
     */
11163
 
11164
    lockScalingFlip:          false,
11165
    /**
11166
     * List of properties to consider when checking if state
11167
     * of an object is changed (fabric.Object#hasStateChanged)
11168
     * as well as for history (undo/redo) purposes
11169
     * @type Array
11170
     */
11171
    stateProperties:  (
11172
      'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' +
11173
      'stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit ' +
11174
      'angle opacity fill fillRule globalCompositeOperation shadow clipTo visible backgroundColor'
11175
    ).split(' '),
11176
 
11177
    /**
11178
     * Constructor
11179
     * @param {Object} [options] Options object
11180
     */
11181
    initialize: function(options) {
11182
      if (options) {
11183
        this.setOptions(options);
11184
      }
11185
    },
11186
 
11187
    /**
11188
     * @private
11189
     * @param {Object} [options] Options object
11190
     */
11191
    _initGradient: function(options) {
11192
      if (options.fill && options.fill.colorStops && !(options.fill instanceof fabric.Gradient)) {
11193
        this.set('fill', new fabric.Gradient(options.fill));
11194
      }
11195
    },
11196
 
11197
    /**
11198
     * @private
11199
     * @param {Object} [options] Options object
11200
     */
11201
    _initPattern: function(options) {
11202
      if (options.fill && options.fill.source && !(options.fill instanceof fabric.Pattern)) {
11203
        this.set('fill', new fabric.Pattern(options.fill));
11204
      }
11205
      if (options.stroke && options.stroke.source && !(options.stroke instanceof fabric.Pattern)) {
11206
        this.set('stroke', new fabric.Pattern(options.stroke));
11207
      }
11208
    },
11209
 
11210
    /**
11211
     * @private
11212
     * @param {Object} [options] Options object
11213
     */
11214
    _initClipping: function(options) {
11215
      if (!options.clipTo || typeof options.clipTo !== 'string') {
11216
        return;
11217
      }
11218
 
11219
      var functionBody = fabric.util.getFunctionBody(options.clipTo);
11220
      if (typeof functionBody !== 'undefined') {
11221
        this.clipTo = new Function('ctx', functionBody);
11222
      }
11223
    },
11224
 
11225
    /**
11226
     * Sets object's properties from options
11227
     * @param {Object} [options] Options object
11228
     */
11229
    setOptions: function(options) {
11230
      for (var prop in options) {
11231
        this.set(prop, options[prop]);
11232
      }
11233
      this._initGradient(options);
11234
      this._initPattern(options);
11235
      this._initClipping(options);
11236
    },
11237
 
11238
    /**
11239
     * Transforms context when rendering an object
11240
     * @param {CanvasRenderingContext2D} ctx Context
11241
     * @param {Boolean} fromLeft When true, context is transformed to object's top/left corner. This is used when rendering text on Node
11242
     */
11243
    transform: function(ctx, fromLeft) {
11244
      var center = fromLeft ? this._getLeftTopCoords() : this.getCenterPoint();
11245
      ctx.translate(center.x, center.y);
11246
      ctx.rotate(degreesToRadians(this.angle));
11247
      ctx.scale(
11248
        this.scaleX * (this.flipX ? -1 : 1),
11249
        this.scaleY * (this.flipY ? -1 : 1)
11250
      );
11251
    },
11252
 
11253
    /**
11254
     * Returns an object representation of an instance
11255
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
11256
     * @return {Object} Object representation of an instance
11257
     */
11258
    toObject: function(propertiesToInclude) {
11259
      var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS,
11260
 
11261
          object = {
11262
            type:                     this.type,
11263
            originX:                  this.originX,
11264
            originY:                  this.originY,
11265
            left:                     toFixed(this.left, NUM_FRACTION_DIGITS),
11266
            top:                      toFixed(this.top, NUM_FRACTION_DIGITS),
11267
            width:                    toFixed(this.width, NUM_FRACTION_DIGITS),
11268
            height:                   toFixed(this.height, NUM_FRACTION_DIGITS),
11269
            fill:                     (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill,
11270
            stroke:                   (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke,
11271
            strokeWidth:              toFixed(this.strokeWidth, NUM_FRACTION_DIGITS),
11272
            strokeDashArray:          this.strokeDashArray,
11273
            strokeLineCap:            this.strokeLineCap,
11274
            strokeLineJoin:           this.strokeLineJoin,
11275
            strokeMiterLimit:         toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS),
11276
            scaleX:                   toFixed(this.scaleX, NUM_FRACTION_DIGITS),
11277
            scaleY:                   toFixed(this.scaleY, NUM_FRACTION_DIGITS),
11278
            angle:                    toFixed(this.getAngle(), NUM_FRACTION_DIGITS),
11279
            flipX:                    this.flipX,
11280
            flipY:                    this.flipY,
11281
            opacity:                  toFixed(this.opacity, NUM_FRACTION_DIGITS),
11282
            shadow:                   (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow,
11283
            visible:                  this.visible,
11284
            clipTo:                   this.clipTo && String(this.clipTo),
11285
            backgroundColor:          this.backgroundColor,
11286
            fillRule:                 this.fillRule,
11287
            globalCompositeOperation: this.globalCompositeOperation
11288
          };
11289
 
11290
      if (!this.includeDefaultValues) {
11291
        object = this._removeDefaultValues(object);
11292
      }
11293
 
11294
      fabric.util.populateWithProperties(this, object, propertiesToInclude);
11295
 
11296
      return object;
11297
    },
11298
 
11299
    /**
11300
     * Returns (dataless) object representation of an instance
11301
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
11302
     * @return {Object} Object representation of an instance
11303
     */
11304
    toDatalessObject: function(propertiesToInclude) {
11305
      // will be overwritten by subclasses
11306
      return this.toObject(propertiesToInclude);
11307
    },
11308
 
11309
    /**
11310
     * @private
11311
     * @param {Object} object
11312
     */
11313
    _removeDefaultValues: function(object) {
11314
      var prototype = fabric.util.getKlass(object.type).prototype,
11315
          stateProperties = prototype.stateProperties;
11316
 
11317
      stateProperties.forEach(function(prop) {
11318
        if (object[prop] === prototype[prop]) {
11319
          delete object[prop];
11320
        }
11321
      });
11322
 
11323
      return object;
11324
    },
11325
 
11326
    /**
11327
     * Returns a string representation of an instance
11328
     * @return {String}
11329
     */
11330
    toString: function() {
11331
      return '#<fabric.' + capitalize(this.type) + '>';
11332
    },
11333
 
11334
    /**
11335
     * Basic getter
11336
     * @param {String} property Property name
11337
     * @return {Any} value of a property
11338
     */
11339
    get: function(property) {
11340
      return this[property];
11341
    },
11342
 
11343
    /**
11344
     * @private
11345
     */
11346
    _setObject: function(obj) {
11347
      for (var prop in obj) {
11348
        this._set(prop, obj[prop]);
11349
      }
11350
    },
11351
 
11352
    /**
11353
     * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`.
11354
     * @param {String|Object} key Property name or object (if object, iterate over the object properties)
11355
     * @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one)
11356
     * @return {fabric.Object} thisArg
11357
     * @chainable
11358
     */
11359
    set: function(key, value) {
11360
      if (typeof key === 'object') {
11361
        this._setObject(key);
11362
      }
11363
      else {
11364
        if (typeof value === 'function' && key !== 'clipTo') {
11365
          this._set(key, value(this.get(key)));
11366
        }
11367
        else {
11368
          this._set(key, value);
11369
        }
11370
      }
11371
      return this;
11372
    },
11373
 
11374
    /**
11375
     * @private
11376
     * @param {String} key
11377
     * @param {Any} value
11378
     * @return {fabric.Object} thisArg
11379
     */
11380
    _set: function(key, value) {
11381
      var shouldConstrainValue = (key === 'scaleX' || key === 'scaleY');
11382
 
11383
      if (shouldConstrainValue) {
11384
        value = this._constrainScale(value);
11385
      }
11386
      if (key === 'scaleX' && value < 0) {
11387
        this.flipX = !this.flipX;
11388
        value *= -1;
11389
      }
11390
      else if (key === 'scaleY' && value < 0) {
11391
        this.flipY = !this.flipY;
11392
        value *= -1;
11393
      }
11394
      else if (key === 'width' || key === 'height') {
11395
        this.minScaleLimit = toFixed(Math.min(0.1, 1/Math.max(this.width, this.height)), 2);
11396
      }
11397
      else if (key === 'shadow' && value && !(value instanceof fabric.Shadow)) {
11398
        value = new fabric.Shadow(value);
11399
      }
11400
 
11401
      this[key] = value;
11402
 
11403
      return this;
11404
    },
11405
 
11406
    /**
11407
     * Toggles specified property from `true` to `false` or from `false` to `true`
11408
     * @param {String} property Property to toggle
11409
     * @return {fabric.Object} thisArg
11410
     * @chainable
11411
     */
11412
    toggle: function(property) {
11413
      var value = this.get(property);
11414
      if (typeof value === 'boolean') {
11415
        this.set(property, !value);
11416
      }
11417
      return this;
11418
    },
11419
 
11420
    /**
11421
     * Sets sourcePath of an object
11422
     * @param {String} value Value to set sourcePath to
11423
     * @return {fabric.Object} thisArg
11424
     * @chainable
11425
     */
11426
    setSourcePath: function(value) {
11427
      this.sourcePath = value;
11428
      return this;
11429
    },
11430
 
11431
    /**
11432
     * Retrieves viewportTransform from Object's canvas if possible
11433
     * @method getViewportTransform
11434
     * @memberOf fabric.Object.prototype
11435
     * @return {Boolean} flipY value // TODO
11436
     */
11437
    getViewportTransform: function() {
11438
      if (this.canvas && this.canvas.viewportTransform) {
11439
        return this.canvas.viewportTransform;
11440
      }
11441
      return [1, 0, 0, 1, 0, 0];
11442
    },
11443
 
11444
    /**
11445
     * Renders an object on a specified context
11446
     * @param {CanvasRenderingContext2D} ctx Context to render on
11447
     * @param {Boolean} [noTransform] When true, context is not transformed
11448
     */
11449
    render: function(ctx, noTransform) {
11450
      // do not render if width/height are zeros or object is not visible
11451
      if ((this.width === 0 && this.height === 0) || !this.visible) {
11452
        return;
11453
      }
11454
 
11455
      ctx.save();
11456
 
11457
      //setup fill rule for current object
11458
      this._setupCompositeOperation(ctx);
11459
      if (!noTransform) {
11460
        this.transform(ctx);
11461
      }
11462
      this._setStrokeStyles(ctx);
11463
      this._setFillStyles(ctx);
11464
      if (this.transformMatrix) {
11465
        ctx.transform.apply(ctx, this.transformMatrix);
11466
      }
11467
      this._setOpacity(ctx);
11468
      this._setShadow(ctx);
11469
      this.clipTo && fabric.util.clipContext(this, ctx);
11470
      this._render(ctx, noTransform);
11471
      this.clipTo && ctx.restore();
11472
      this._removeShadow(ctx);
11473
      this._restoreCompositeOperation(ctx);
11474
 
11475
      ctx.restore();
11476
    },
11477
 
11478
    /* @private
11479
     * @param {CanvasRenderingContext2D} ctx Context to render on
11480
     */
11481
    _setOpacity: function(ctx) {
11482
      if (this.group) {
11483
        this.group._setOpacity(ctx);
11484
      }
11485
      ctx.globalAlpha *= this.opacity;
11486
    },
11487
 
11488
    _setStrokeStyles: function(ctx) {
11489
      if (this.stroke) {
11490
        ctx.lineWidth = this.strokeWidth;
11491
        ctx.lineCap = this.strokeLineCap;
11492
        ctx.lineJoin = this.strokeLineJoin;
11493
        ctx.miterLimit = this.strokeMiterLimit;
11494
        ctx.strokeStyle = this.stroke.toLive
11495
          ? this.stroke.toLive(ctx, this)
11496
          : this.stroke;
11497
      }
11498
    },
11499
 
11500
    _setFillStyles: function(ctx) {
11501
      if (this.fill) {
11502
        ctx.fillStyle = this.fill.toLive
11503
          ? this.fill.toLive(ctx, this)
11504
          : this.fill;
11505
      }
11506
    },
11507
 
11508
    /**
11509
     * Renders controls and borders for the object
11510
     * @param {CanvasRenderingContext2D} ctx Context to render on
11511
     * @param {Boolean} [noTransform] When true, context is not transformed
11512
     */
11513
    _renderControls: function(ctx, noTransform) {
11514
      if (!this.active || noTransform) {
11515
        return;
11516
      }
11517
      var vpt = this.getViewportTransform();
11518
      ctx.save();
11519
      var center;
11520
      if (this.group) {
11521
        center = fabric.util.transformPoint(this.group.getCenterPoint(), vpt);
11522
        ctx.translate(center.x, center.y);
11523
        ctx.rotate(degreesToRadians(this.group.angle));
11524
      }
11525
      center = fabric.util.transformPoint(this.getCenterPoint(), vpt, null != this.group);
11526
      if (this.group) {
11527
        center.x *= this.group.scaleX;
11528
        center.y *= this.group.scaleY;
11529
      }
11530
      ctx.translate(center.x, center.y);
11531
      ctx.rotate(degreesToRadians(this.angle));
11532
      this.drawBorders(ctx);
11533
      this.drawControls(ctx);
11534
      ctx.restore();
11535
    },
11536
 
11537
    /**
11538
     * @private
11539
     * @param {CanvasRenderingContext2D} ctx Context to render on
11540
     */
11541
    _setShadow: function(ctx) {
11542
      if (!this.shadow) {
11543
        return;
11544
      }
11545
 
11546
      var multX = (this.canvas && this.canvas.viewportTransform[0]) || 1,
11547
          multY = (this.canvas && this.canvas.viewportTransform[3]) || 1;
11548
 
11549
      ctx.shadowColor = this.shadow.color;
11550
      ctx.shadowBlur = this.shadow.blur * (multX + multY) * (this.scaleX + this.scaleY) / 4;
11551
      ctx.shadowOffsetX = this.shadow.offsetX * multX * this.scaleX;
11552
      ctx.shadowOffsetY = this.shadow.offsetY * multY * this.scaleY;
11553
    },
11554
 
11555
    /**
11556
     * @private
11557
     * @param {CanvasRenderingContext2D} ctx Context to render on
11558
     */
11559
    _removeShadow: function(ctx) {
11560
      if (!this.shadow) {
11561
        return;
11562
      }
11563
 
11564
      ctx.shadowColor = '';
11565
      ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0;
11566
    },
11567
 
11568
    /**
11569
     * @private
11570
     * @param {CanvasRenderingContext2D} ctx Context to render on
11571
     */
11572
    _renderFill: function(ctx) {
11573
      if (!this.fill) {
11574
        return;
11575
      }
11576
 
11577
      ctx.save();
11578
      if (this.fill.gradientTransform) {
11579
        var g = this.fill.gradientTransform;
11580
        ctx.transform.apply(ctx, g);
11581
      }
11582
      if (this.fill.toLive) {
11583
        ctx.translate(
11584
          -this.width / 2 + this.fill.offsetX || 0,
11585
          -this.height / 2 + this.fill.offsetY || 0);
11586
      }
11587
      if (this.fillRule === 'evenodd') {
11588
        ctx.fill('evenodd');
11589
      }
11590
      else {
11591
        ctx.fill();
11592
      }
11593
      ctx.restore();
11594
      if (this.shadow && !this.shadow.affectStroke) {
11595
        this._removeShadow(ctx);
11596
      }
11597
    },
11598
 
11599
    /**
11600
     * @private
11601
     * @param {CanvasRenderingContext2D} ctx Context to render on
11602
     */
11603
    _renderStroke: function(ctx) {
11604
      if (!this.stroke || this.strokeWidth === 0) {
11605
        return;
11606
      }
11607
 
11608
      ctx.save();
11609
      if (this.strokeDashArray) {
11610
        // Spec requires the concatenation of two copies the dash list when the number of elements is odd
11611
        if (1 & this.strokeDashArray.length) {
11612
          this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray);
11613
        }
11614
        if (supportsLineDash) {
11615
          ctx.setLineDash(this.strokeDashArray);
11616
          this._stroke && this._stroke(ctx);
11617
        }
11618
        else {
11619
          this._renderDashedStroke && this._renderDashedStroke(ctx);
11620
        }
11621
        ctx.stroke();
11622
      }
11623
      else {
11624
        if (this.stroke.gradientTransform) {
11625
          var g = this.stroke.gradientTransform;
11626
          ctx.transform.apply(ctx, g);
11627
        }
11628
        this._stroke ? this._stroke(ctx) : ctx.stroke();
11629
      }
11630
      this._removeShadow(ctx);
11631
      ctx.restore();
11632
    },
11633
 
11634
    /**
11635
     * Clones an instance
11636
     * @param {Function} callback Callback is invoked with a clone as a first argument
11637
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
11638
     * @return {fabric.Object} clone of an instance
11639
     */
11640
    clone: function(callback, propertiesToInclude) {
11641
      if (this.constructor.fromObject) {
11642
        return this.constructor.fromObject(this.toObject(propertiesToInclude), callback);
11643
      }
11644
      return new fabric.Object(this.toObject(propertiesToInclude));
11645
    },
11646
 
11647
    /**
11648
     * Creates an instance of fabric.Image out of an object
11649
     * @param {Function} callback callback, invoked with an instance as a first argument
11650
     * @return {fabric.Object} thisArg
11651
     */
11652
    cloneAsImage: function(callback) {
11653
      var dataUrl = this.toDataURL();
11654
      fabric.util.loadImage(dataUrl, function(img) {
11655
        if (callback) {
11656
          callback(new fabric.Image(img));
11657
        }
11658
      });
11659
      return this;
11660
    },
11661
 
11662
    /**
11663
     * Converts an object into a data-url-like string
11664
     * @param {Object} options Options object
11665
     * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png"
11666
     * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg.
11667
     * @param {Number} [options.multiplier=1] Multiplier to scale by
11668
     * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14
11669
     * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14
11670
     * @param {Number} [options.width] Cropping width. Introduced in v1.2.14
11671
     * @param {Number} [options.height] Cropping height. Introduced in v1.2.14
11672
     * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format
11673
     */
11674
    toDataURL: function(options) {
11675
      options || (options = { });
11676
 
11677
      var el = fabric.util.createCanvasElement(),
11678
          boundingRect = this.getBoundingRect();
11679
 
11680
      el.width = boundingRect.width;
11681
      el.height = boundingRect.height;
11682
 
11683
      fabric.util.wrapElement(el, 'div');
11684
      var canvas = new fabric.StaticCanvas(el);
11685
 
11686
      // to avoid common confusion https://github.com/kangax/fabric.js/issues/806
11687
      if (options.format === 'jpg') {
11688
        options.format = 'jpeg';
11689
      }
11690
 
11691
      if (options.format === 'jpeg') {
11692
        canvas.backgroundColor = '#fff';
11693
      }
11694
 
11695
      var origParams = {
11696
        active: this.get('active'),
11697
        left: this.getLeft(),
11698
        top: this.getTop()
11699
      };
11700
 
11701
      this.set('active', false);
11702
      this.setPositionByOrigin(new fabric.Point(el.width / 2, el.height / 2), 'center', 'center');
11703
 
11704
      var originalCanvas = this.canvas;
11705
      canvas.add(this);
11706
      var data = canvas.toDataURL(options);
11707
 
11708
      this.set(origParams).setCoords();
11709
      this.canvas = originalCanvas;
11710
 
11711
      canvas.dispose();
11712
      canvas = null;
11713
 
11714
      return data;
11715
    },
11716
 
11717
    /**
11718
     * Returns true if specified type is identical to the type of an instance
11719
     * @param {String} type Type to check against
11720
     * @return {Boolean}
11721
     */
11722
    isType: function(type) {
11723
      return this.type === type;
11724
    },
11725
 
11726
    /**
11727
     * Returns complexity of an instance
11728
     * @return {Number} complexity of this instance
11729
     */
11730
    complexity: function() {
11731
      return 0;
11732
    },
11733
 
11734
    /**
11735
     * Returns a JSON representation of an instance
11736
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
11737
     * @return {Object} JSON
11738
     */
11739
    toJSON: function(propertiesToInclude) {
11740
      // delegate, not alias
11741
      return this.toObject(propertiesToInclude);
11742
    },
11743
 
11744
    /**
11745
     * Sets gradient (fill or stroke) of an object
11746
     * <b>Backwards incompatibility note:</b> This method was named "setGradientFill" until v1.1.0
11747
     * @param {String} property Property name 'stroke' or 'fill'
11748
     * @param {Object} [options] Options object
11749
     * @param {String} [options.type] Type of gradient 'radial' or 'linear'
11750
     * @param {Number} [options.x1=0] x-coordinate of start point
11751
     * @param {Number} [options.y1=0] y-coordinate of start point
11752
     * @param {Number} [options.x2=0] x-coordinate of end point
11753
     * @param {Number} [options.y2=0] y-coordinate of end point
11754
     * @param {Number} [options.r1=0] Radius of start point (only for radial gradients)
11755
     * @param {Number} [options.r2=0] Radius of end point (only for radial gradients)
11756
     * @param {Object} [options.colorStops] Color stops object eg. {0: 'ff0000', 1: '000000'}
11757
     * @return {fabric.Object} thisArg
11758
     * @chainable
11759
     * @see {@link http://jsfiddle.net/fabricjs/58y8b/|jsFiddle demo}
11760
     * @example <caption>Set linear gradient</caption>
11761
     * object.setGradient('fill', {
11762
     *   type: 'linear',
11763
     *   x1: -object.width / 2,
11764
     *   y1: 0,
11765
     *   x2: object.width / 2,
11766
     *   y2: 0,
11767
     *   colorStops: {
11768
     *     0: 'red',
11769
     *     0.5: '#005555',
11770
     *     1: 'rgba(0,0,255,0.5)'
11771
     *   }
11772
     * });
11773
     * canvas.renderAll();
11774
     * @example <caption>Set radial gradient</caption>
11775
     * object.setGradient('fill', {
11776
     *   type: 'radial',
11777
     *   x1: 0,
11778
     *   y1: 0,
11779
     *   x2: 0,
11780
     *   y2: 0,
11781
     *   r1: object.width / 2,
11782
     *   r2: 10,
11783
     *   colorStops: {
11784
     *     0: 'red',
11785
     *     0.5: '#005555',
11786
     *     1: 'rgba(0,0,255,0.5)'
11787
     *   }
11788
     * });
11789
     * canvas.renderAll();
11790
     */
11791
    setGradient: function(property, options) {
11792
      options || (options = { });
11793
 
11794
      var gradient = { colorStops: [] };
11795
 
11796
      gradient.type = options.type || (options.r1 || options.r2 ? 'radial' : 'linear');
11797
      gradient.coords = {
11798
        x1: options.x1,
11799
        y1: options.y1,
11800
        x2: options.x2,
11801
        y2: options.y2
11802
      };
11803
 
11804
      if (options.r1 || options.r2) {
11805
        gradient.coords.r1 = options.r1;
11806
        gradient.coords.r2 = options.r2;
11807
      }
11808
 
11809
      for (var position in options.colorStops) {
11810
        var color = new fabric.Color(options.colorStops[position]);
11811
        gradient.colorStops.push({
11812
          offset: position,
11813
          color: color.toRgb(),
11814
          opacity: color.getAlpha()
11815
        });
11816
      }
11817
 
11818
      return this.set(property, fabric.Gradient.forObject(this, gradient));
11819
    },
11820
 
11821
    /**
11822
     * Sets pattern fill of an object
11823
     * @param {Object} options Options object
11824
     * @param {(String|HTMLImageElement)} options.source Pattern source
11825
     * @param {String} [options.repeat=repeat] Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat)
11826
     * @param {Number} [options.offsetX=0] Pattern horizontal offset from object's left/top corner
11827
     * @param {Number} [options.offsetY=0] Pattern vertical offset from object's left/top corner
11828
     * @return {fabric.Object} thisArg
11829
     * @chainable
11830
     * @see {@link http://jsfiddle.net/fabricjs/QT3pa/|jsFiddle demo}
11831
     * @example <caption>Set pattern</caption>
11832
     * fabric.util.loadImage('http://fabricjs.com/assets/escheresque_ste.png', function(img) {
11833
     *   object.setPatternFill({
11834
     *     source: img,
11835
     *     repeat: 'repeat'
11836
     *   });
11837
     *   canvas.renderAll();
11838
     * });
11839
     */
11840
    setPatternFill: function(options) {
11841
      return this.set('fill', new fabric.Pattern(options));
11842
    },
11843
 
11844
    /**
11845
     * Sets {@link fabric.Object#shadow|shadow} of an object
11846
     * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)")
11847
     * @param {String} [options.color=rgb(0,0,0)] Shadow color
11848
     * @param {Number} [options.blur=0] Shadow blur
11849
     * @param {Number} [options.offsetX=0] Shadow horizontal offset
11850
     * @param {Number} [options.offsetY=0] Shadow vertical offset
11851
     * @return {fabric.Object} thisArg
11852
     * @chainable
11853
     * @see {@link http://jsfiddle.net/fabricjs/7gvJG/|jsFiddle demo}
11854
     * @example <caption>Set shadow with string notation</caption>
11855
     * object.setShadow('2px 2px 10px rgba(0,0,0,0.2)');
11856
     * canvas.renderAll();
11857
     * @example <caption>Set shadow with object notation</caption>
11858
     * object.setShadow({
11859
     *   color: 'red',
11860
     *   blur: 10,
11861
     *   offsetX: 20,
11862
     *   offsetY: 20
11863
     * });
11864
     * canvas.renderAll();
11865
     */
11866
    setShadow: function(options) {
11867
      return this.set('shadow', options ? new fabric.Shadow(options) : null);
11868
    },
11869
 
11870
    /**
11871
     * Sets "color" of an instance (alias of `set('fill', &hellip;)`)
11872
     * @param {String} color Color value
11873
     * @return {fabric.Object} thisArg
11874
     * @chainable
11875
     */
11876
    setColor: function(color) {
11877
      this.set('fill', color);
11878
      return this;
11879
    },
11880
 
11881
    /**
11882
     * Sets "angle" of an instance
11883
     * @param {Number} angle Angle value (in degrees)
11884
     * @return {fabric.Object} thisArg
11885
     * @chainable
11886
     */
11887
    setAngle: function(angle) {
11888
      var shouldCenterOrigin = (this.originX !== 'center' || this.originY !== 'center') && this.centeredRotation;
11889
 
11890
      if (shouldCenterOrigin) {
11891
        this._setOriginToCenter();
11892
      }
11893
 
11894
      this.set('angle', angle);
11895
 
11896
      if (shouldCenterOrigin) {
11897
        this._resetOrigin();
11898
      }
11899
 
11900
      return this;
11901
    },
11902
 
11903
    /**
11904
     * Centers object horizontally on canvas to which it was added last.
11905
     * You might need to call `setCoords` on an object after centering, to update controls area.
11906
     * @return {fabric.Object} thisArg
11907
     * @chainable
11908
     */
11909
    centerH: function () {
11910
      this.canvas.centerObjectH(this);
11911
      return this;
11912
    },
11913
 
11914
    /**
11915
     * Centers object vertically on canvas to which it was added last.
11916
     * You might need to call `setCoords` on an object after centering, to update controls area.
11917
     * @return {fabric.Object} thisArg
11918
     * @chainable
11919
     */
11920
    centerV: function () {
11921
      this.canvas.centerObjectV(this);
11922
      return this;
11923
    },
11924
 
11925
    /**
11926
     * Centers object vertically and horizontally on canvas to which is was added last
11927
     * You might need to call `setCoords` on an object after centering, to update controls area.
11928
     * @return {fabric.Object} thisArg
11929
     * @chainable
11930
     */
11931
    center: function () {
11932
      this.canvas.centerObject(this);
11933
      return this;
11934
    },
11935
 
11936
    /**
11937
     * Removes object from canvas to which it was added last
11938
     * @return {fabric.Object} thisArg
11939
     * @chainable
11940
     */
11941
    remove: function() {
11942
      this.canvas.remove(this);
11943
      return this;
11944
    },
11945
 
11946
    /**
11947
     * Returns coordinates of a pointer relative to an object
11948
     * @param {Event} e Event to operate upon
11949
     * @param {Object} [pointer] Pointer to operate upon (instead of event)
11950
     * @return {Object} Coordinates of a pointer (x, y)
11951
     */
11952
    getLocalPointer: function(e, pointer) {
11953
      pointer = pointer || this.canvas.getPointer(e);
11954
      var objectLeftTop = this.translateToOriginPoint(this.getCenterPoint(), 'left', 'top');
11955
      return {
11956
        x: pointer.x - objectLeftTop.x,
11957
        y: pointer.y - objectLeftTop.y
11958
      };
11959
    },
11960
 
11961
    /**
11962
     * Sets canvas globalCompositeOperation for specific object
11963
     * custom composition operation for the particular object can be specifed using globalCompositeOperation property
11964
     * @param {CanvasRenderingContext2D} ctx Rendering canvas context
11965
     */
11966
    _setupCompositeOperation: function (ctx) {
11967
      if (this.globalCompositeOperation) {
11968
        this._prevGlobalCompositeOperation = ctx.globalCompositeOperation;
11969
        ctx.globalCompositeOperation = this.globalCompositeOperation;
11970
      }
11971
    },
11972
 
11973
    /**
11974
     * Restores previously saved canvas globalCompositeOperation after obeject rendering
11975
     * @param {CanvasRenderingContext2D} ctx Rendering canvas context
11976
     */
11977
    _restoreCompositeOperation: function (ctx) {
11978
      if (this.globalCompositeOperation && this._prevGlobalCompositeOperation) {
11979
        ctx.globalCompositeOperation = this._prevGlobalCompositeOperation;
11980
      }
11981
    }
11982
  });
11983
 
11984
  fabric.util.createAccessors(fabric.Object);
11985
 
11986
  /**
11987
   * Alias for {@link fabric.Object.prototype.setAngle}
11988
   * @alias rotate -> setAngle
11989
   * @memberof fabric.Object
11990
   */
11991
  fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle;
11992
 
11993
  extend(fabric.Object.prototype, fabric.Observable);
11994
 
11995
  /**
11996
   * Defines the number of fraction digits to use when serializing object values.
11997
   * You can use it to increase/decrease precision of such values like left, top, scaleX, scaleY, etc.
11998
   * @static
11999
   * @memberof fabric.Object
12000
   * @constant
12001
   * @type Number
12002
   */
12003
  fabric.Object.NUM_FRACTION_DIGITS = 2;
12004
 
12005
  /**
12006
   * Unique id used internally when creating SVG elements
12007
   * @static
12008
   * @memberof fabric.Object
12009
   * @type Number
12010
   */
12011
  fabric.Object.__uid = 0;
12012
 
12013
})(typeof exports !== 'undefined' ? exports : this);
12014
 
12015
 
12016
(function() {
12017
 
12018
  var degreesToRadians = fabric.util.degreesToRadians;
12019
 
12020
  fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
12021
 
12022
    /**
12023
     * Translates the coordinates from origin to center coordinates (based on the object's dimensions)
12024
     * @param {fabric.Point} point The point which corresponds to the originX and originY params
12025
     * @param {String} originX Horizontal origin: 'left', 'center' or 'right'
12026
     * @param {String} originY Vertical origin: 'top', 'center' or 'bottom'
12027
     * @return {fabric.Point}
12028
     */
12029
    translateToCenterPoint: function(point, originX, originY) {
12030
      var cx = point.x,
12031
          cy = point.y,
12032
          strokeWidth = this.stroke ? this.strokeWidth : 0;
12033
 
12034
      if (originX === 'left') {
12035
        cx = point.x + (this.getWidth() + strokeWidth * this.scaleX) / 2;
12036
      }
12037
      else if (originX === 'right') {
12038
        cx = point.x - (this.getWidth() + strokeWidth * this.scaleX) / 2;
12039
      }
12040
 
12041
      if (originY === 'top') {
12042
        cy = point.y + (this.getHeight() + strokeWidth * this.scaleY) / 2;
12043
      }
12044
      else if (originY === 'bottom') {
12045
        cy = point.y - (this.getHeight() + strokeWidth * this.scaleY) / 2;
12046
      }
12047
 
12048
      // Apply the reverse rotation to the point (it's already scaled properly)
12049
      return fabric.util.rotatePoint(new fabric.Point(cx, cy), point, degreesToRadians(this.angle));
12050
    },
12051
 
12052
    /**
12053
     * Translates the coordinates from center to origin coordinates (based on the object's dimensions)
12054
     * @param {fabric.Point} center The point which corresponds to center of the object
12055
     * @param {String} originX Horizontal origin: 'left', 'center' or 'right'
12056
     * @param {String} originY Vertical origin: 'top', 'center' or 'bottom'
12057
     * @return {fabric.Point}
12058
     */
12059
    translateToOriginPoint: function(center, originX, originY) {
12060
      var x = center.x,
12061
          y = center.y,
12062
          strokeWidth = this.stroke ? this.strokeWidth : 0;
12063
 
12064
      // Get the point coordinates
12065
      if (originX === 'left') {
12066
        x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2;
12067
      }
12068
      else if (originX === 'right') {
12069
        x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2;
12070
      }
12071
      if (originY === 'top') {
12072
        y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2;
12073
      }
12074
      else if (originY === 'bottom') {
12075
        y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2;
12076
      }
12077
 
12078
      // Apply the rotation to the point (it's already scaled properly)
12079
      return fabric.util.rotatePoint(new fabric.Point(x, y), center, degreesToRadians(this.angle));
12080
    },
12081
 
12082
    /**
12083
     * Returns the real center coordinates of the object
12084
     * @return {fabric.Point}
12085
     */
12086
    getCenterPoint: function() {
12087
      var leftTop = new fabric.Point(this.left, this.top);
12088
      return this.translateToCenterPoint(leftTop, this.originX, this.originY);
12089
    },
12090
 
12091
    /**
12092
     * Returns the coordinates of the object based on center coordinates
12093
     * @param {fabric.Point} point The point which corresponds to the originX and originY params
12094
     * @return {fabric.Point}
12095
     */
12096
    // getOriginPoint: function(center) {
12097
    //   return this.translateToOriginPoint(center, this.originX, this.originY);
12098
    // },
12099
 
12100
    /**
12101
     * Returns the coordinates of the object as if it has a different origin
12102
     * @param {String} originX Horizontal origin: 'left', 'center' or 'right'
12103
     * @param {String} originY Vertical origin: 'top', 'center' or 'bottom'
12104
     * @return {fabric.Point}
12105
     */
12106
    getPointByOrigin: function(originX, originY) {
12107
      var center = this.getCenterPoint();
12108
      return this.translateToOriginPoint(center, originX, originY);
12109
    },
12110
 
12111
    /**
12112
     * Returns the point in local coordinates
12113
     * @param {fabric.Point} point The point relative to the global coordinate system
12114
     * @param {String} originX Horizontal origin: 'left', 'center' or 'right'
12115
     * @param {String} originY Vertical origin: 'top', 'center' or 'bottom'
12116
     * @return {fabric.Point}
12117
     */
12118
    toLocalPoint: function(point, originX, originY) {
12119
      var center = this.getCenterPoint(),
12120
          strokeWidth = this.stroke ? this.strokeWidth : 0,
12121
          x, y;
12122
 
12123
      if (originX && originY) {
12124
        if (originX === 'left') {
12125
          x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2;
12126
        }
12127
        else if (originX === 'right') {
12128
          x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2;
12129
        }
12130
        else {
12131
          x = center.x;
12132
        }
12133
 
12134
        if (originY === 'top') {
12135
          y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2;
12136
        }
12137
        else if (originY === 'bottom') {
12138
          y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2;
12139
        }
12140
        else {
12141
          y = center.y;
12142
        }
12143
      }
12144
      else {
12145
        x = this.left;
12146
        y = this.top;
12147
      }
12148
 
12149
      return fabric.util.rotatePoint(new fabric.Point(point.x, point.y), center, -degreesToRadians(this.angle))
12150
        .subtractEquals(new fabric.Point(x, y));
12151
    },
12152
 
12153
    /**
12154
     * Returns the point in global coordinates
12155
     * @param {fabric.Point} The point relative to the local coordinate system
12156
     * @return {fabric.Point}
12157
     */
12158
    // toGlobalPoint: function(point) {
12159
    //   return fabric.util.rotatePoint(point, this.getCenterPoint(), degreesToRadians(this.angle)).addEquals(new fabric.Point(this.left, this.top));
12160
    // },
12161
 
12162
    /**
12163
     * Sets the position of the object taking into consideration the object's origin
12164
     * @param {fabric.Point} pos The new position of the object
12165
     * @param {String} originX Horizontal origin: 'left', 'center' or 'right'
12166
     * @param {String} originY Vertical origin: 'top', 'center' or 'bottom'
12167
     * @return {void}
12168
     */
12169
    setPositionByOrigin: function(pos, originX, originY) {
12170
      var center = this.translateToCenterPoint(pos, originX, originY),
12171
          position = this.translateToOriginPoint(center, this.originX, this.originY);
12172
 
12173
      this.set('left', position.x);
12174
      this.set('top', position.y);
12175
    },
12176
 
12177
    /**
12178
     * @param {String} to One of 'left', 'center', 'right'
12179
     */
12180
    adjustPosition: function(to) {
12181
      var angle = degreesToRadians(this.angle),
12182
          hypotHalf = this.getWidth() / 2,
12183
          xHalf = Math.cos(angle) * hypotHalf,
12184
          yHalf = Math.sin(angle) * hypotHalf,
12185
          hypotFull = this.getWidth(),
12186
          xFull = Math.cos(angle) * hypotFull,
12187
          yFull = Math.sin(angle) * hypotFull;
12188
 
12189
      if (this.originX === 'center' && to === 'left' ||
12190
          this.originX === 'right' && to === 'center') {
12191
        // move half left
12192
        this.left -= xHalf;
12193
        this.top -= yHalf;
12194
      }
12195
      else if (this.originX === 'left' && to === 'center' ||
12196
               this.originX === 'center' && to === 'right') {
12197
        // move half right
12198
        this.left += xHalf;
12199
        this.top += yHalf;
12200
      }
12201
      else if (this.originX === 'left' && to === 'right') {
12202
        // move full right
12203
        this.left += xFull;
12204
        this.top += yFull;
12205
      }
12206
      else if (this.originX === 'right' && to === 'left') {
12207
        // move full left
12208
        this.left -= xFull;
12209
        this.top -= yFull;
12210
      }
12211
 
12212
      this.setCoords();
12213
      this.originX = to;
12214
    },
12215
 
12216
    /**
12217
     * Sets the origin/position of the object to it's center point
12218
     * @private
12219
     * @return {void}
12220
     */
12221
    _setOriginToCenter: function() {
12222
      this._originalOriginX = this.originX;
12223
      this._originalOriginY = this.originY;
12224
 
12225
      var center = this.getCenterPoint();
12226
 
12227
      this.originX = 'center';
12228
      this.originY = 'center';
12229
 
12230
      this.left = center.x;
12231
      this.top = center.y;
12232
    },
12233
 
12234
    /**
12235
     * Resets the origin/position of the object to it's original origin
12236
     * @private
12237
     * @return {void}
12238
     */
12239
    _resetOrigin: function() {
12240
      var originPoint = this.translateToOriginPoint(
12241
        this.getCenterPoint(),
12242
        this._originalOriginX,
12243
        this._originalOriginY);
12244
 
12245
      this.originX = this._originalOriginX;
12246
      this.originY = this._originalOriginY;
12247
 
12248
      this.left = originPoint.x;
12249
      this.top = originPoint.y;
12250
 
12251
      this._originalOriginX = null;
12252
      this._originalOriginY = null;
12253
    },
12254
 
12255
    /**
12256
     * @private
12257
     */
12258
    _getLeftTopCoords: function() {
12259
      return this.translateToOriginPoint(this.getCenterPoint(), 'left', 'center');
12260
    }
12261
  });
12262
 
12263
})();
12264
 
12265
 
12266
(function() {
12267
 
12268
  var degreesToRadians = fabric.util.degreesToRadians;
12269
 
12270
  fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
12271
 
12272
    /**
12273
     * Object containing coordinates of object's controls
12274
     * @type Object
12275
     * @default
12276
     */
12277
    oCoords: null,
12278
 
12279
    /**
12280
     * Checks if object intersects with an area formed by 2 points
12281
     * @param {Object} pointTL top-left point of area
12282
     * @param {Object} pointBR bottom-right point of area
12283
     * @return {Boolean} true if object intersects with an area formed by 2 points
12284
     */
12285
    intersectsWithRect: function(pointTL, pointBR) {
12286
      var oCoords = this.oCoords,
12287
          tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y),
12288
          tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y),
12289
          bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y),
12290
          br = new fabric.Point(oCoords.br.x, oCoords.br.y),
12291
          intersection = fabric.Intersection.intersectPolygonRectangle(
12292
            [tl, tr, br, bl],
12293
            pointTL,
12294
            pointBR
12295
          );
12296
      return intersection.status === 'Intersection';
12297
    },
12298
 
12299
    /**
12300
     * Checks if object intersects with another object
12301
     * @param {Object} other Object to test
12302
     * @return {Boolean} true if object intersects with another object
12303
     */
12304
    intersectsWithObject: function(other) {
12305
      // extracts coords
12306
      function getCoords(oCoords) {
12307
        return {
12308
          tl: new fabric.Point(oCoords.tl.x, oCoords.tl.y),
12309
          tr: new fabric.Point(oCoords.tr.x, oCoords.tr.y),
12310
          bl: new fabric.Point(oCoords.bl.x, oCoords.bl.y),
12311
          br: new fabric.Point(oCoords.br.x, oCoords.br.y)
12312
        };
12313
      }
12314
      var thisCoords = getCoords(this.oCoords),
12315
          otherCoords = getCoords(other.oCoords),
12316
          intersection = fabric.Intersection.intersectPolygonPolygon(
12317
            [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl],
12318
            [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl]
12319
          );
12320
 
12321
      return intersection.status === 'Intersection';
12322
    },
12323
 
12324
    /**
12325
     * Checks if object is fully contained within area of another object
12326
     * @param {Object} other Object to test
12327
     * @return {Boolean} true if object is fully contained within area of another object
12328
     */
12329
    isContainedWithinObject: function(other) {
12330
      var boundingRect = other.getBoundingRect(),
12331
          point1 = new fabric.Point(boundingRect.left, boundingRect.top),
12332
          point2 = new fabric.Point(boundingRect.left + boundingRect.width, boundingRect.top + boundingRect.height);
12333
 
12334
      return this.isContainedWithinRect(point1, point2);
12335
    },
12336
 
12337
    /**
12338
     * Checks if object is fully contained within area formed by 2 points
12339
     * @param {Object} pointTL top-left point of area
12340
     * @param {Object} pointBR bottom-right point of area
12341
     * @return {Boolean} true if object is fully contained within area formed by 2 points
12342
     */
12343
    isContainedWithinRect: function(pointTL, pointBR) {
12344
      var boundingRect = this.getBoundingRect();
12345
 
12346
      return (
12347
        boundingRect.left >= pointTL.x &&
12348
        boundingRect.left + boundingRect.width <= pointBR.x &&
12349
        boundingRect.top >= pointTL.y &&
12350
        boundingRect.top + boundingRect.height <= pointBR.y
12351
      );
12352
    },
12353
 
12354
    /**
12355
     * Checks if point is inside the object
12356
     * @param {fabric.Point} point Point to check against
12357
     * @return {Boolean} true if point is inside the object
12358
     */
12359
    containsPoint: function(point) {
12360
      var lines = this._getImageLines(this.oCoords),
12361
          xPoints = this._findCrossPoints(point, lines);
12362
 
12363
      // if xPoints is odd then point is inside the object
12364
      return (xPoints !== 0 && xPoints % 2 === 1);
12365
    },
12366
 
12367
    /**
12368
     * Method that returns an object with the object edges in it, given the coordinates of the corners
12369
     * @private
12370
     * @param {Object} oCoords Coordinates of the object corners
12371
     */
12372
    _getImageLines: function(oCoords) {
12373
      return {
12374
        topline: {
12375
          o: oCoords.tl,
12376
          d: oCoords.tr
12377
        },
12378
        rightline: {
12379
          o: oCoords.tr,
12380
          d: oCoords.br
12381
        },
12382
        bottomline: {
12383
          o: oCoords.br,
12384
          d: oCoords.bl
12385
        },
12386
        leftline: {
12387
          o: oCoords.bl,
12388
          d: oCoords.tl
12389
        }
12390
      };
12391
    },
12392
 
12393
    /**
12394
     * Helper method to determine how many cross points are between the 4 object edges
12395
     * and the horizontal line determined by a point on canvas
12396
     * @private
12397
     * @param {fabric.Point} point Point to check
12398
     * @param {Object} oCoords Coordinates of the object being evaluated
12399
     */
12400
    _findCrossPoints: function(point, oCoords) {
12401
      var b1, b2, a1, a2, xi, yi,
12402
          xcount = 0,
12403
          iLine;
12404
 
12405
      for (var lineKey in oCoords) {
12406
        iLine = oCoords[lineKey];
12407
        // optimisation 1: line below point. no cross
12408
        if ((iLine.o.y < point.y) && (iLine.d.y < point.y)) {
12409
          continue;
12410
        }
12411
        // optimisation 2: line above point. no cross
12412
        if ((iLine.o.y >= point.y) && (iLine.d.y >= point.y)) {
12413
          continue;
12414
        }
12415
        // optimisation 3: vertical line case
12416
        if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= point.x)) {
12417
          xi = iLine.o.x;
12418
          yi = point.y;
12419
        }
12420
        // calculate the intersection point
12421
        else {
12422
          b1 = 0;
12423
          b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x);
12424
          a1 = point.y - b1 * point.x;
12425
          a2 = iLine.o.y - b2 * iLine.o.x;
12426
 
12427
          xi = - (a1 - a2) / (b1 - b2);
12428
          yi = a1 + b1 * xi;
12429
        }
12430
        // dont count xi < point.x cases
12431
        if (xi >= point.x) {
12432
          xcount += 1;
12433
        }
12434
        // optimisation 4: specific for square images
12435
        if (xcount === 2) {
12436
          break;
12437
        }
12438
      }
12439
      return xcount;
12440
    },
12441
 
12442
    /**
12443
     * Returns width of an object's bounding rectangle
12444
     * @deprecated since 1.0.4
12445
     * @return {Number} width value
12446
     */
12447
    getBoundingRectWidth: function() {
12448
      return this.getBoundingRect().width;
12449
    },
12450
 
12451
    /**
12452
     * Returns height of an object's bounding rectangle
12453
     * @deprecated since 1.0.4
12454
     * @return {Number} height value
12455
     */
12456
    getBoundingRectHeight: function() {
12457
      return this.getBoundingRect().height;
12458
    },
12459
 
12460
    /**
12461
     * Returns coordinates of object's bounding rectangle (left, top, width, height)
12462
     * @return {Object} Object with left, top, width, height properties
12463
     */
12464
    getBoundingRect: function() {
12465
      this.oCoords || this.setCoords();
12466
 
12467
      var xCoords = [this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x],
12468
          minX = fabric.util.array.min(xCoords),
12469
          maxX = fabric.util.array.max(xCoords),
12470
          width = Math.abs(minX - maxX),
12471
 
12472
          yCoords = [this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y],
12473
          minY = fabric.util.array.min(yCoords),
12474
          maxY = fabric.util.array.max(yCoords),
12475
          height = Math.abs(minY - maxY);
12476
 
12477
      return {
12478
        left: minX,
12479
        top: minY,
12480
        width: width,
12481
        height: height
12482
      };
12483
    },
12484
 
12485
    /**
12486
     * Returns width of an object
12487
     * @return {Number} width value
12488
     */
12489
    getWidth: function() {
12490
      return this.width * this.scaleX;
12491
    },
12492
 
12493
    /**
12494
     * Returns height of an object
12495
     * @return {Number} height value
12496
     */
12497
    getHeight: function() {
12498
      return this.height * this.scaleY;
12499
    },
12500
 
12501
    /**
12502
     * Makes sure the scale is valid and modifies it if necessary
12503
     * @private
12504
     * @param {Number} value
12505
     * @return {Number}
12506
     */
12507
    _constrainScale: function(value) {
12508
      if (Math.abs(value) < this.minScaleLimit) {
12509
        if (value < 0) {
12510
          return -this.minScaleLimit;
12511
        }
12512
        else {
12513
          return this.minScaleLimit;
12514
        }
12515
      }
12516
      return value;
12517
    },
12518
 
12519
    /**
12520
     * Scales an object (equally by x and y)
12521
     * @param {Number} value Scale factor
12522
     * @return {fabric.Object} thisArg
12523
     * @chainable
12524
     */
12525
    scale: function(value) {
12526
      value = this._constrainScale(value);
12527
 
12528
      if (value < 0) {
12529
        this.flipX = !this.flipX;
12530
        this.flipY = !this.flipY;
12531
        value *= -1;
12532
      }
12533
 
12534
      this.scaleX = value;
12535
      this.scaleY = value;
12536
      this.setCoords();
12537
      return this;
12538
    },
12539
 
12540
    /**
12541
     * Scales an object to a given width, with respect to bounding box (scaling by x/y equally)
12542
     * @param {Number} value New width value
12543
     * @return {fabric.Object} thisArg
12544
     * @chainable
12545
     */
12546
    scaleToWidth: function(value) {
12547
      // adjust to bounding rect factor so that rotated shapes would fit as well
12548
      var boundingRectFactor = this.getBoundingRectWidth() / this.getWidth();
12549
      return this.scale(value / this.width / boundingRectFactor);
12550
    },
12551
 
12552
    /**
12553
     * Scales an object to a given height, with respect to bounding box (scaling by x/y equally)
12554
     * @param {Number} value New height value
12555
     * @return {fabric.Object} thisArg
12556
     * @chainable
12557
     */
12558
    scaleToHeight: function(value) {
12559
      // adjust to bounding rect factor so that rotated shapes would fit as well
12560
      var boundingRectFactor = this.getBoundingRectHeight() / this.getHeight();
12561
      return this.scale(value / this.height / boundingRectFactor);
12562
    },
12563
 
12564
    /**
12565
     * Sets corner position coordinates based on current angle, width and height
12566
     * See https://github.com/kangax/fabric.js/wiki/When-to-call-setCoords
12567
     * @return {fabric.Object} thisArg
12568
     * @chainable
12569
     */
12570
    setCoords: function() {
12571
      var theta = degreesToRadians(this.angle),
12572
          vpt = this.getViewportTransform(),
12573
          f = function (p) {
12574
            return fabric.util.transformPoint(p, vpt);
12575
          },
12576
          p = this._calculateCurrentDimensions(false),
12577
          currentWidth = p.x, currentHeight = p.y;
12578
 
12579
      // If width is negative, make postive. Fixes path selection issue
12580
      if (currentWidth < 0) {
12581
        currentWidth = Math.abs(currentWidth);
12582
      }
12583
 
12584
      var _hypotenuse = Math.sqrt(
12585
            Math.pow(currentWidth / 2, 2) +
12586
            Math.pow(currentHeight / 2, 2)),
12587
 
12588
          _angle = Math.atan(
12589
            isFinite(currentHeight / currentWidth)
12590
              ? currentHeight / currentWidth
12591
              : 0),
12592
 
12593
          // offset added for rotate and scale actions
12594
          offsetX = Math.cos(_angle + theta) * _hypotenuse,
12595
          offsetY = Math.sin(_angle + theta) * _hypotenuse,
12596
          sinTh = Math.sin(theta),
12597
          cosTh = Math.cos(theta),
12598
          coords = this.getCenterPoint(),
12599
          wh = new fabric.Point(currentWidth, currentHeight),
12600
          _tl =   new fabric.Point(coords.x - offsetX, coords.y - offsetY),
12601
          _tr =   new fabric.Point(_tl.x + (wh.x * cosTh),   _tl.y + (wh.x * sinTh)),
12602
          bl =  f(new fabric.Point(_tl.x - (wh.y * sinTh),   _tl.y + (wh.y * cosTh))),
12603
          br  = f(new fabric.Point(_tr.x - (wh.y * sinTh),   _tr.y + (wh.y * cosTh))),
12604
          tl  = f(_tl),
12605
          tr  = f(_tr),
12606
          ml  = new fabric.Point((tl.x + bl.x)/2, (tl.y + bl.y)/2),
12607
          mt  = new fabric.Point((tr.x + tl.x)/2, (tr.y + tl.y)/2),
12608
          mr  = new fabric.Point((br.x + tr.x)/2, (br.y + tr.y)/2),
12609
          mb  = new fabric.Point((br.x + bl.x)/2, (br.y + bl.y)/2),
12610
          mtr = new fabric.Point(mt.x + sinTh * this.rotatingPointOffset, mt.y - cosTh * this.rotatingPointOffset);
12611
      // debugging
12612
 
12613
      /* setTimeout(function() {
12614
         canvas.contextTop.fillStyle = 'green';
12615
         canvas.contextTop.fillRect(mb.x, mb.y, 3, 3);
12616
         canvas.contextTop.fillRect(bl.x, bl.y, 3, 3);
12617
         canvas.contextTop.fillRect(br.x, br.y, 3, 3);
12618
         canvas.contextTop.fillRect(tl.x, tl.y, 3, 3);
12619
         canvas.contextTop.fillRect(tr.x, tr.y, 3, 3);
12620
         canvas.contextTop.fillRect(ml.x, ml.y, 3, 3);
12621
         canvas.contextTop.fillRect(mr.x, mr.y, 3, 3);
12622
         canvas.contextTop.fillRect(mt.x, mt.y, 3, 3);
12623
         canvas.contextTop.fillRect(mtr.x, mtr.y, 3, 3);
12624
       }, 50); */
12625
 
12626
      this.oCoords = {
12627
        // corners
12628
        tl: tl, tr: tr, br: br, bl: bl,
12629
        // middle
12630
        ml: ml, mt: mt, mr: mr, mb: mb,
12631
        // rotating point
12632
        mtr: mtr
12633
      };
12634
 
12635
      // set coordinates of the draggable boxes in the corners used to scale/rotate the image
12636
      this._setCornerCoords && this._setCornerCoords();
12637
 
12638
      return this;
12639
    }
12640
  });
12641
})();
12642
 
12643
 
12644
fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
12645
 
12646
  /**
12647
   * Moves an object to the bottom of the stack of drawn objects
12648
   * @return {fabric.Object} thisArg
12649
   * @chainable
12650
   */
12651
  sendToBack: function() {
12652
    if (this.group) {
12653
      fabric.StaticCanvas.prototype.sendToBack.call(this.group, this);
12654
    }
12655
    else {
12656
      this.canvas.sendToBack(this);
12657
    }
12658
    return this;
12659
  },
12660
 
12661
  /**
12662
   * Moves an object to the top of the stack of drawn objects
12663
   * @return {fabric.Object} thisArg
12664
   * @chainable
12665
   */
12666
  bringToFront: function() {
12667
    if (this.group) {
12668
      fabric.StaticCanvas.prototype.bringToFront.call(this.group, this);
12669
    }
12670
    else {
12671
      this.canvas.bringToFront(this);
12672
    }
12673
    return this;
12674
  },
12675
 
12676
  /**
12677
   * Moves an object down in stack of drawn objects
12678
   * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object
12679
   * @return {fabric.Object} thisArg
12680
   * @chainable
12681
   */
12682
  sendBackwards: function(intersecting) {
12683
    if (this.group) {
12684
      fabric.StaticCanvas.prototype.sendBackwards.call(this.group, this, intersecting);
12685
    }
12686
    else {
12687
      this.canvas.sendBackwards(this, intersecting);
12688
    }
12689
    return this;
12690
  },
12691
 
12692
  /**
12693
   * Moves an object up in stack of drawn objects
12694
   * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object
12695
   * @return {fabric.Object} thisArg
12696
   * @chainable
12697
   */
12698
  bringForward: function(intersecting) {
12699
    if (this.group) {
12700
      fabric.StaticCanvas.prototype.bringForward.call(this.group, this, intersecting);
12701
    }
12702
    else {
12703
      this.canvas.bringForward(this, intersecting);
12704
    }
12705
    return this;
12706
  },
12707
 
12708
  /**
12709
   * Moves an object to specified level in stack of drawn objects
12710
   * @param {Number} index New position of object
12711
   * @return {fabric.Object} thisArg
12712
   * @chainable
12713
   */
12714
  moveTo: function(index) {
12715
    if (this.group) {
12716
      fabric.StaticCanvas.prototype.moveTo.call(this.group, this, index);
12717
    }
12718
    else {
12719
      this.canvas.moveTo(this, index);
12720
    }
12721
    return this;
12722
  }
12723
});
12724
 
12725
 
12726
/* _TO_SVG_START_ */
12727
fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
12728
 
12729
  /**
12730
   * Returns styles-string for svg-export
12731
   * @return {String}
12732
   */
12733
  getSvgStyles: function() {
12734
 
12735
    var fill = this.fill
12736
          ? (this.fill.toLive ? 'url(#SVGID_' + this.fill.id + ')' : this.fill)
12737
          : 'none',
12738
        fillRule = this.fillRule,
12739
        stroke = this.stroke
12740
          ? (this.stroke.toLive ? 'url(#SVGID_' + this.stroke.id + ')' : this.stroke)
12741
          : 'none',
12742
 
12743
        strokeWidth = this.strokeWidth ? this.strokeWidth : '0',
12744
        strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : '',
12745
        strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt',
12746
        strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter',
12747
        strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4',
12748
        opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1',
12749
 
12750
        visibility = this.visible ? '' : ' visibility: hidden;',
12751
        filter = this.shadow ? 'filter: url(#SVGID_' + this.shadow.id + ');' : '';
12752
 
12753
    return [
12754
      'stroke: ', stroke, '; ',
12755
      'stroke-width: ', strokeWidth, '; ',
12756
      'stroke-dasharray: ', strokeDashArray, '; ',
12757
      'stroke-linecap: ', strokeLineCap, '; ',
12758
      'stroke-linejoin: ', strokeLineJoin, '; ',
12759
      'stroke-miterlimit: ', strokeMiterLimit, '; ',
12760
      'fill: ', fill, '; ',
12761
      'fill-rule: ', fillRule, '; ',
12762
      'opacity: ', opacity, ';',
12763
      filter,
12764
      visibility
12765
    ].join('');
12766
  },
12767
 
12768
  /**
12769
   * Returns transform-string for svg-export
12770
   * @return {String}
12771
   */
12772
  getSvgTransform: function() {
12773
    if (this.group && this.group.type === 'path-group') {
12774
      return '';
12775
    }
12776
    var toFixed = fabric.util.toFixed,
12777
        angle = this.getAngle(),
12778
        vpt = !this.canvas || this.canvas.svgViewportTransformation ? this.getViewportTransform() : [1, 0, 0, 1, 0, 0],
12779
        center = fabric.util.transformPoint(this.getCenterPoint(), vpt),
12780
 
12781
        NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS,
12782
 
12783
        translatePart = this.type === 'path-group' ? '' : 'translate(' +
12784
                          toFixed(center.x, NUM_FRACTION_DIGITS) +
12785
                          ' ' +
12786
                          toFixed(center.y, NUM_FRACTION_DIGITS) +
12787
                        ')',
12788
 
12789
        anglePart = angle !== 0
12790
          ? (' rotate(' + toFixed(angle, NUM_FRACTION_DIGITS) + ')')
12791
          : '',
12792
 
12793
        scalePart = (this.scaleX === 1 && this.scaleY === 1 && vpt[0] === 1 && vpt[3] === 1)
12794
          ? '' :
12795
          (' scale(' +
12796
            toFixed(this.scaleX * vpt[0], NUM_FRACTION_DIGITS) +
12797
            ' ' +
12798
            toFixed(this.scaleY * vpt[3], NUM_FRACTION_DIGITS) +
12799
          ')'),
12800
 
12801
        addTranslateX = this.type === 'path-group' ? this.width * vpt[0] : 0,
12802
 
12803
        flipXPart = this.flipX ? ' matrix(-1 0 0 1 ' + addTranslateX + ' 0) ' : '',
12804
 
12805
        addTranslateY = this.type === 'path-group' ? this.height * vpt[3] : 0,
12806
 
12807
        flipYPart = this.flipY ? ' matrix(1 0 0 -1 0 ' + addTranslateY + ')' : '';
12808
 
12809
    return [
12810
      translatePart, anglePart, scalePart, flipXPart, flipYPart
12811
    ].join('');
12812
  },
12813
 
12814
  /**
12815
   * Returns transform-string for svg-export from the transform matrix of single elements
12816
   * @return {String}
12817
   */
12818
  getSvgTransformMatrix: function() {
12819
    return this.transformMatrix ? ' matrix(' + this.transformMatrix.join(' ') + ') ' : '';
12820
  },
12821
 
12822
  /**
12823
   * @private
12824
   */
12825
  _createBaseSVGMarkup: function() {
12826
    var markup = [ ];
12827
 
12828
    if (this.fill && this.fill.toLive) {
12829
      markup.push(this.fill.toSVG(this, false));
12830
    }
12831
    if (this.stroke && this.stroke.toLive) {
12832
      markup.push(this.stroke.toSVG(this, false));
12833
    }
12834
    if (this.shadow) {
12835
      markup.push(this.shadow.toSVG(this));
12836
    }
12837
    return markup;
12838
  }
12839
});
12840
/* _TO_SVG_END_ */
12841
 
12842
 
12843
/*
12844
  Depends on `stateProperties`
12845
*/
12846
fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
12847
 
12848
  /**
12849
   * Returns true if object state (one of its state properties) was changed
12850
   * @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called
12851
   */
12852
  hasStateChanged: function() {
12853
    return this.stateProperties.some(function(prop) {
12854
      return this.get(prop) !== this.originalState[prop];
12855
    }, this);
12856
  },
12857
 
12858
  /**
12859
   * Saves state of an object
12860
   * @param {Object} [options] Object with additional `stateProperties` array to include when saving state
12861
   * @return {fabric.Object} thisArg
12862
   */
12863
  saveState: function(options) {
12864
    this.stateProperties.forEach(function(prop) {
12865
      this.originalState[prop] = this.get(prop);
12866
    }, this);
12867
 
12868
    if (options && options.stateProperties) {
12869
      options.stateProperties.forEach(function(prop) {
12870
        this.originalState[prop] = this.get(prop);
12871
      }, this);
12872
    }
12873
 
12874
    return this;
12875
  },
12876
 
12877
  /**
12878
   * Setups state of an object
12879
   * @return {fabric.Object} thisArg
12880
   */
12881
  setupState: function() {
12882
    this.originalState = { };
12883
    this.saveState();
12884
 
12885
    return this;
12886
  }
12887
});
12888
 
12889
 
12890
(function() {
12891
 
12892
  var degreesToRadians = fabric.util.degreesToRadians,
12893
      //jscs:disable requireCamelCaseOrUpperCaseIdentifiers
12894
      isVML = function() { return typeof G_vmlCanvasManager !== 'undefined'; };
12895
  //jscs:enable requireCamelCaseOrUpperCaseIdentifiers
12896
 
12897
  fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
12898
 
12899
    /**
12900
     * The object interactivity controls.
12901
     * @private
12902
     */
12903
    _controlsVisibility: null,
12904
 
12905
    /**
12906
     * Determines which corner has been clicked
12907
     * @private
12908
     * @param {Object} pointer The pointer indicating the mouse position
12909
     * @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found
12910
     */
12911
    _findTargetCorner: function(pointer) {
12912
      if (!this.hasControls || !this.active) {
12913
        return false;
12914
      }
12915
 
12916
      var ex = pointer.x,
12917
          ey = pointer.y,
12918
          xPoints,
12919
          lines;
12920
 
12921
      for (var i in this.oCoords) {
12922
 
12923
        if (!this.isControlVisible(i)) {
12924
          continue;
12925
        }
12926
 
12927
        if (i === 'mtr' && !this.hasRotatingPoint) {
12928
          continue;
12929
        }
12930
 
12931
        if (this.get('lockUniScaling') &&
12932
           (i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) {
12933
          continue;
12934
        }
12935
 
12936
        lines = this._getImageLines(this.oCoords[i].corner);
12937
 
12938
        // debugging
12939
 
12940
        // canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2);
12941
        // canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2);
12942
 
12943
        // canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2);
12944
        // canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2);
12945
 
12946
        // canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2);
12947
        // canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2);
12948
 
12949
        // canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2);
12950
        // canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2);
12951
 
12952
        xPoints = this._findCrossPoints({ x: ex, y: ey }, lines);
12953
        if (xPoints !== 0 && xPoints % 2 === 1) {
12954
          this.__corner = i;
12955
          return i;
12956
        }
12957
      }
12958
      return false;
12959
    },
12960
 
12961
    /**
12962
     * Sets the coordinates of the draggable boxes in the corners of
12963
     * the image used to scale/rotate it.
12964
     * @private
12965
     */
12966
    _setCornerCoords: function() {
12967
      var coords = this.oCoords,
12968
          newTheta = degreesToRadians(45 - this.angle),
12969
          cornerHypotenuse = Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2,
12970
          cosHalfOffset = cornerHypotenuse * Math.cos(newTheta),
12971
          sinHalfOffset = cornerHypotenuse * Math.sin(newTheta),
12972
          x, y;
12973
 
12974
      for (var point in coords) {
12975
        x = coords[point].x;
12976
        y = coords[point].y;
12977
        coords[point].corner = {
12978
          tl: {
12979
            x: x - sinHalfOffset,
12980
            y: y - cosHalfOffset
12981
          },
12982
          tr: {
12983
            x: x + cosHalfOffset,
12984
            y: y - sinHalfOffset
12985
          },
12986
          bl: {
12987
            x: x - cosHalfOffset,
12988
            y: y + sinHalfOffset
12989
          },
12990
          br: {
12991
            x: x + sinHalfOffset,
12992
            y: y + cosHalfOffset
12993
          }
12994
        };
12995
      }
12996
    },
12997
 
12998
    _calculateCurrentDimensions: function(shouldTransform)  {
12999
      var vpt = this.getViewportTransform(),
13000
          strokeWidth = this.strokeWidth,
13001
          w = this.width,
13002
          h = this.height,
13003
          capped = this.strokeLineCap === 'round' || this.strokeLineCap === 'square',
13004
          vLine = this.type === 'line' && this.width === 0,
13005
          hLine = this.type === 'line' && this.height === 0,
13006
          sLine = vLine || hLine,
13007
          strokeW = (capped && hLine) || !sLine,
13008
          strokeH = (capped && vLine) || !sLine;
13009
 
13010
      if (vLine) {
13011
        w = strokeWidth;
13012
      }
13013
      else if (hLine) {
13014
        h = strokeWidth;
13015
      }
13016
      if (strokeW) {
13017
        w += (w < 0 ? -strokeWidth : strokeWidth);
13018
      }
13019
      if (strokeH) {
13020
        h += (h < 0 ? -strokeWidth : strokeWidth);
13021
      }
13022
 
13023
      w = w * this.scaleX + 2 * this.padding;
13024
      h = h * this.scaleY + 2 * this.padding;
13025
 
13026
      if (shouldTransform) {
13027
        return fabric.util.transformPoint(new fabric.Point(w, h), vpt, true);
13028
      }
13029
      return { x: w, y: h };
13030
    },
13031
 
13032
    /**
13033
     * Draws borders of an object's bounding box.
13034
     * Requires public properties: width, height
13035
     * Requires public options: padding, borderColor
13036
     * @param {CanvasRenderingContext2D} ctx Context to draw on
13037
     * @return {fabric.Object} thisArg
13038
     * @chainable
13039
     */
13040
    drawBorders: function(ctx) {
13041
      if (!this.hasBorders) {
13042
        return this;
13043
      }
13044
 
13045
      ctx.save();
13046
 
13047
      ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
13048
      ctx.strokeStyle = this.borderColor;
13049
      ctx.lineWidth = 1 / this.borderScaleFactor;
13050
 
13051
      var wh = this._calculateCurrentDimensions(true),
13052
          width = wh.x,
13053
          height = wh.y;
13054
      if (this.group) {
13055
        width = width * this.group.scaleX;
13056
        height = height * this.group.scaleY;
13057
      }
13058
 
13059
      ctx.strokeRect(
13060
        ~~(-(width / 2)) - 0.5, // offset needed to make lines look sharper
13061
        ~~(-(height / 2)) - 0.5,
13062
        ~~(width) + 1, // double offset needed to make lines look sharper
13063
        ~~(height) + 1
13064
      );
13065
 
13066
      if (this.hasRotatingPoint && this.isControlVisible('mtr') && !this.get('lockRotation') && this.hasControls) {
13067
 
13068
        var rotateHeight = -height / 2;
13069
 
13070
        ctx.beginPath();
13071
        ctx.moveTo(0, rotateHeight);
13072
        ctx.lineTo(0, rotateHeight - this.rotatingPointOffset);
13073
        ctx.closePath();
13074
        ctx.stroke();
13075
      }
13076
 
13077
      ctx.restore();
13078
      return this;
13079
    },
13080
 
13081
    /**
13082
     * Draws corners of an object's bounding box.
13083
     * Requires public properties: width, height
13084
     * Requires public options: cornerSize, padding
13085
     * @param {CanvasRenderingContext2D} ctx Context to draw on
13086
     * @return {fabric.Object} thisArg
13087
     * @chainable
13088
     */
13089
    drawControls: function(ctx) {
13090
      if (!this.hasControls) {
13091
        return this;
13092
      }
13093
 
13094
      var wh = this._calculateCurrentDimensions(true),
13095
          width = wh.x,
13096
          height = wh.y,
13097
          left = -(width / 2),
13098
          top = -(height / 2),
13099
          scaleOffset = this.cornerSize / 2,
13100
          methodName = this.transparentCorners ? 'strokeRect' : 'fillRect';
13101
 
13102
      ctx.save();
13103
 
13104
      ctx.lineWidth = 1;
13105
 
13106
      ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
13107
      ctx.strokeStyle = ctx.fillStyle = this.cornerColor;
13108
 
13109
      // top-left
13110
      this._drawControl('tl', ctx, methodName,
13111
        left - scaleOffset,
13112
        top - scaleOffset);
13113
 
13114
      // top-right
13115
      this._drawControl('tr', ctx, methodName,
13116
        left + width - scaleOffset,
13117
        top - scaleOffset);
13118
 
13119
      // bottom-left
13120
      this._drawControl('bl', ctx, methodName,
13121
        left - scaleOffset,
13122
        top + height - scaleOffset);
13123
 
13124
      // bottom-right
13125
      this._drawControl('br', ctx, methodName,
13126
        left + width - scaleOffset,
13127
        top + height - scaleOffset);
13128
 
13129
      if (!this.get('lockUniScaling')) {
13130
 
13131
        // middle-top
13132
        this._drawControl('mt', ctx, methodName,
13133
          left + width/2 - scaleOffset,
13134
          top - scaleOffset);
13135
 
13136
        // middle-bottom
13137
        this._drawControl('mb', ctx, methodName,
13138
          left + width/2 - scaleOffset,
13139
          top + height - scaleOffset);
13140
 
13141
        // middle-right
13142
        this._drawControl('mr', ctx, methodName,
13143
          left + width - scaleOffset,
13144
          top + height/2 - scaleOffset);
13145
 
13146
        // middle-left
13147
        this._drawControl('ml', ctx, methodName,
13148
          left - scaleOffset,
13149
          top + height/2 - scaleOffset);
13150
      }
13151
 
13152
      // middle-top-rotate
13153
      if (this.hasRotatingPoint) {
13154
        this._drawControl('mtr', ctx, methodName,
13155
          left + width/2 - scaleOffset,
13156
          top - this.rotatingPointOffset - scaleOffset);
13157
      }
13158
 
13159
      ctx.restore();
13160
 
13161
      return this;
13162
    },
13163
 
13164
    /**
13165
     * @private
13166
     */
13167
    _drawControl: function(control, ctx, methodName, left, top) {
13168
      if (!this.isControlVisible(control)) {
13169
        return;
13170
      }
13171
      var size = this.cornerSize;
13172
      isVML() || this.transparentCorners || ctx.clearRect(left, top, size, size);
13173
      ctx[methodName](left, top, size, size);
13174
    },
13175
 
13176
    /**
13177
     * Returns true if the specified control is visible, false otherwise.
13178
     * @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'.
13179
     * @returns {Boolean} true if the specified control is visible, false otherwise
13180
     */
13181
    isControlVisible: function(controlName) {
13182
      return this._getControlsVisibility()[controlName];
13183
    },
13184
 
13185
    /**
13186
     * Sets the visibility of the specified control.
13187
     * @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'.
13188
     * @param {Boolean} visible true to set the specified control visible, false otherwise
13189
     * @return {fabric.Object} thisArg
13190
     * @chainable
13191
     */
13192
    setControlVisible: function(controlName, visible) {
13193
      this._getControlsVisibility()[controlName] = visible;
13194
      return this;
13195
    },
13196
 
13197
    /**
13198
     * Sets the visibility state of object controls.
13199
     * @param {Object} [options] Options object
13200
     * @param {Boolean} [options.bl] true to enable the bottom-left control, false to disable it
13201
     * @param {Boolean} [options.br] true to enable the bottom-right control, false to disable it
13202
     * @param {Boolean} [options.mb] true to enable the middle-bottom control, false to disable it
13203
     * @param {Boolean} [options.ml] true to enable the middle-left control, false to disable it
13204
     * @param {Boolean} [options.mr] true to enable the middle-right control, false to disable it
13205
     * @param {Boolean} [options.mt] true to enable the middle-top control, false to disable it
13206
     * @param {Boolean} [options.tl] true to enable the top-left control, false to disable it
13207
     * @param {Boolean} [options.tr] true to enable the top-right control, false to disable it
13208
     * @param {Boolean} [options.mtr] true to enable the middle-top-rotate control, false to disable it
13209
     * @return {fabric.Object} thisArg
13210
     * @chainable
13211
     */
13212
    setControlsVisibility: function(options) {
13213
      options || (options = { });
13214
 
13215
      for (var p in options) {
13216
        this.setControlVisible(p, options[p]);
13217
      }
13218
      return this;
13219
    },
13220
 
13221
    /**
13222
     * Returns the instance of the control visibility set for this object.
13223
     * @private
13224
     * @returns {Object}
13225
     */
13226
    _getControlsVisibility: function() {
13227
      if (!this._controlsVisibility) {
13228
        this._controlsVisibility = {
13229
          tl: true,
13230
          tr: true,
13231
          br: true,
13232
          bl: true,
13233
          ml: true,
13234
          mt: true,
13235
          mr: true,
13236
          mb: true,
13237
          mtr: true
13238
        };
13239
      }
13240
      return this._controlsVisibility;
13241
    }
13242
  });
13243
})();
13244
 
13245
 
13246
fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ {
13247
 
13248
  /**
13249
   * Animation duration (in ms) for fx* methods
13250
   * @type Number
13251
   * @default
13252
   */
13253
  FX_DURATION: 500,
13254
 
13255
  /**
13256
   * Centers object horizontally with animation.
13257
   * @param {fabric.Object} object Object to center
13258
   * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
13259
   * @param {Function} [callbacks.onComplete] Invoked on completion
13260
   * @param {Function} [callbacks.onChange] Invoked on every step of animation
13261
   * @return {fabric.Canvas} thisArg
13262
   * @chainable
13263
   */
13264
  fxCenterObjectH: function (object, callbacks) {
13265
    callbacks = callbacks || { };
13266
 
13267
    var empty = function() { },
13268
        onComplete = callbacks.onComplete || empty,
13269
        onChange = callbacks.onChange || empty,
13270
        _this = this;
13271
 
13272
    fabric.util.animate({
13273
      startValue: object.get('left'),
13274
      endValue: this.getCenter().left,
13275
      duration: this.FX_DURATION,
13276
      onChange: function(value) {
13277
        object.set('left', value);
13278
        _this.renderAll();
13279
        onChange();
13280
      },
13281
      onComplete: function() {
13282
        object.setCoords();
13283
        onComplete();
13284
      }
13285
    });
13286
 
13287
    return this;
13288
  },
13289
 
13290
  /**
13291
   * Centers object vertically with animation.
13292
   * @param {fabric.Object} object Object to center
13293
   * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
13294
   * @param {Function} [callbacks.onComplete] Invoked on completion
13295
   * @param {Function} [callbacks.onChange] Invoked on every step of animation
13296
   * @return {fabric.Canvas} thisArg
13297
   * @chainable
13298
   */
13299
  fxCenterObjectV: function (object, callbacks) {
13300
    callbacks = callbacks || { };
13301
 
13302
    var empty = function() { },
13303
        onComplete = callbacks.onComplete || empty,
13304
        onChange = callbacks.onChange || empty,
13305
        _this = this;
13306
 
13307
    fabric.util.animate({
13308
      startValue: object.get('top'),
13309
      endValue: this.getCenter().top,
13310
      duration: this.FX_DURATION,
13311
      onChange: function(value) {
13312
        object.set('top', value);
13313
        _this.renderAll();
13314
        onChange();
13315
      },
13316
      onComplete: function() {
13317
        object.setCoords();
13318
        onComplete();
13319
      }
13320
    });
13321
 
13322
    return this;
13323
  },
13324
 
13325
  /**
13326
   * Same as `fabric.Canvas#remove` but animated
13327
   * @param {fabric.Object} object Object to remove
13328
   * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
13329
   * @param {Function} [callbacks.onComplete] Invoked on completion
13330
   * @param {Function} [callbacks.onChange] Invoked on every step of animation
13331
   * @return {fabric.Canvas} thisArg
13332
   * @chainable
13333
   */
13334
  fxRemove: function (object, callbacks) {
13335
    callbacks = callbacks || { };
13336
 
13337
    var empty = function() { },
13338
        onComplete = callbacks.onComplete || empty,
13339
        onChange = callbacks.onChange || empty,
13340
        _this = this;
13341
 
13342
    fabric.util.animate({
13343
      startValue: object.get('opacity'),
13344
      endValue: 0,
13345
      duration: this.FX_DURATION,
13346
      onStart: function() {
13347
        object.set('active', false);
13348
      },
13349
      onChange: function(value) {
13350
        object.set('opacity', value);
13351
        _this.renderAll();
13352
        onChange();
13353
      },
13354
      onComplete: function () {
13355
        _this.remove(object);
13356
        onComplete();
13357
      }
13358
    });
13359
 
13360
    return this;
13361
  }
13362
});
13363
 
13364
fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
13365
  /**
13366
   * Animates object's properties
13367
   * @param {String|Object} property Property to animate (if string) or properties to animate (if object)
13368
   * @param {Number|Object} value Value to animate property to (if string was given first) or options object
13369
   * @return {fabric.Object} thisArg
13370
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#animation}
13371
   * @chainable
13372
   *
13373
   * As object — multiple properties
13374
   *
13375
   * object.animate({ left: ..., top: ... });
13376
   * object.animate({ left: ..., top: ... }, { duration: ... });
13377
   *
13378
   * As string — one property
13379
   *
13380
   * object.animate('left', ...);
13381
   * object.animate('left', { duration: ... });
13382
   *
13383
   */
13384
  animate: function() {
13385
    if (arguments[0] && typeof arguments[0] === 'object') {
13386
      var propsToAnimate = [ ], prop, skipCallbacks;
13387
      for (prop in arguments[0]) {
13388
        propsToAnimate.push(prop);
13389
      }
13390
      for (var i = 0, len = propsToAnimate.length; i < len; i++) {
13391
        prop = propsToAnimate[i];
13392
        skipCallbacks = i !== len - 1;
13393
        this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks);
13394
      }
13395
    }
13396
    else {
13397
      this._animate.apply(this, arguments);
13398
    }
13399
    return this;
13400
  },
13401
 
13402
  /**
13403
   * @private
13404
   * @param {String} property Property to animate
13405
   * @param {String} to Value to animate to
13406
   * @param {Object} [options] Options object
13407
   * @param {Boolean} [skipCallbacks] When true, callbacks like onchange and oncomplete are not invoked
13408
   */
13409
  _animate: function(property, to, options, skipCallbacks) {
13410
    var _this = this, propPair;
13411
 
13412
    to = to.toString();
13413
 
13414
    if (!options) {
13415
      options = { };
13416
    }
13417
    else {
13418
      options = fabric.util.object.clone(options);
13419
    }
13420
 
13421
    if (~property.indexOf('.')) {
13422
      propPair = property.split('.');
13423
    }
13424
 
13425
    var currentValue = propPair
13426
      ? this.get(propPair[0])[propPair[1]]
13427
      : this.get(property);
13428
 
13429
    if (!('from' in options)) {
13430
      options.from = currentValue;
13431
    }
13432
 
13433
    if (~to.indexOf('=')) {
13434
      to = currentValue + parseFloat(to.replace('=', ''));
13435
    }
13436
    else {
13437
      to = parseFloat(to);
13438
    }
13439
 
13440
    fabric.util.animate({
13441
      startValue: options.from,
13442
      endValue: to,
13443
      byValue: options.by,
13444
      easing: options.easing,
13445
      duration: options.duration,
13446
      abort: options.abort && function() {
13447
        return options.abort.call(_this);
13448
      },
13449
      onChange: function(value) {
13450
        if (propPair) {
13451
          _this[propPair[0]][propPair[1]] = value;
13452
        }
13453
        else {
13454
          _this.set(property, value);
13455
        }
13456
        if (skipCallbacks) {
13457
          return;
13458
        }
13459
        options.onChange && options.onChange();
13460
      },
13461
      onComplete: function() {
13462
        if (skipCallbacks) {
13463
          return;
13464
        }
13465
 
13466
        _this.setCoords();
13467
        options.onComplete && options.onComplete();
13468
      }
13469
    });
13470
  }
13471
});
13472
 
13473
 
13474
(function(global) {
13475
 
13476
  'use strict';
13477
 
13478
  var fabric = global.fabric || (global.fabric = { }),
13479
      extend = fabric.util.object.extend,
13480
      coordProps = { x1: 1, x2: 1, y1: 1, y2: 1 },
13481
      supportsLineDash = fabric.StaticCanvas.supports('setLineDash');
13482
 
13483
  if (fabric.Line) {
13484
    fabric.warn('fabric.Line is already defined');
13485
    return;
13486
  }
13487
 
13488
  /**
13489
   * Line class
13490
   * @class fabric.Line
13491
   * @extends fabric.Object
13492
   * @see {@link fabric.Line#initialize} for constructor definition
13493
   */
13494
  fabric.Line = fabric.util.createClass(fabric.Object, /** @lends fabric.Line.prototype */ {
13495
 
13496
    /**
13497
     * Type of an object
13498
     * @type String
13499
     * @default
13500
     */
13501
    type: 'line',
13502
 
13503
    /**
13504
     * x value or first line edge
13505
     * @type Number
13506
     * @default
13507
     */
13508
    x1: 0,
13509
 
13510
    /**
13511
     * y value or first line edge
13512
     * @type Number
13513
     * @default
13514
     */
13515
    y1: 0,
13516
 
13517
    /**
13518
     * x value or second line edge
13519
     * @type Number
13520
     * @default
13521
     */
13522
    x2: 0,
13523
 
13524
    /**
13525
     * y value or second line edge
13526
     * @type Number
13527
     * @default
13528
     */
13529
    y2: 0,
13530
 
13531
    /**
13532
     * Constructor
13533
     * @param {Array} [points] Array of points
13534
     * @param {Object} [options] Options object
13535
     * @return {fabric.Line} thisArg
13536
     */
13537
    initialize: function(points, options) {
13538
      options = options || { };
13539
 
13540
      if (!points) {
13541
        points = [0, 0, 0, 0];
13542
      }
13543
 
13544
      this.callSuper('initialize', options);
13545
 
13546
      this.set('x1', points[0]);
13547
      this.set('y1', points[1]);
13548
      this.set('x2', points[2]);
13549
      this.set('y2', points[3]);
13550
 
13551
      this._setWidthHeight(options);
13552
    },
13553
 
13554
    /**
13555
     * @private
13556
     * @param {Object} [options] Options
13557
     */
13558
    _setWidthHeight: function(options) {
13559
      options || (options = { });
13560
 
13561
      this.width = Math.abs(this.x2 - this.x1);
13562
      this.height = Math.abs(this.y2 - this.y1);
13563
 
13564
      this.left = 'left' in options
13565
        ? options.left
13566
        : this._getLeftToOriginX();
13567
 
13568
      this.top = 'top' in options
13569
        ? options.top
13570
        : this._getTopToOriginY();
13571
    },
13572
 
13573
    /**
13574
     * @private
13575
     * @param {String} key
13576
     * @param {Any} value
13577
     */
13578
    _set: function(key, value) {
13579
      this.callSuper('_set', key, value);
13580
      if (typeof coordProps[key] !== 'undefined') {
13581
        this._setWidthHeight();
13582
      }
13583
      return this;
13584
    },
13585
 
13586
    /**
13587
     * @private
13588
     * @return {Number} leftToOriginX Distance from left edge of canvas to originX of Line.
13589
     */
13590
    _getLeftToOriginX: makeEdgeToOriginGetter(
13591
      { // property names
13592
        origin: 'originX',
13593
        axis1: 'x1',
13594
        axis2: 'x2',
13595
        dimension: 'width'
13596
      },
13597
      { // possible values of origin
13598
        nearest: 'left',
13599
        center: 'center',
13600
        farthest: 'right'
13601
      }
13602
    ),
13603
 
13604
    /**
13605
     * @private
13606
     * @return {Number} topToOriginY Distance from top edge of canvas to originY of Line.
13607
     */
13608
    _getTopToOriginY: makeEdgeToOriginGetter(
13609
      { // property names
13610
        origin: 'originY',
13611
        axis1: 'y1',
13612
        axis2: 'y2',
13613
        dimension: 'height'
13614
      },
13615
      { // possible values of origin
13616
        nearest: 'top',
13617
        center: 'center',
13618
        farthest: 'bottom'
13619
      }
13620
    ),
13621
 
13622
    /**
13623
     * @private
13624
     * @param {CanvasRenderingContext2D} ctx Context to render on
13625
     */
13626
    _render: function(ctx, noTransform) {
13627
      ctx.beginPath();
13628
 
13629
      if (noTransform) {
13630
        //  Line coords are distances from left-top of canvas to origin of line.
13631
        //  To render line in a path-group, we need to translate them to
13632
        //  distances from center of path-group to center of line.
13633
        var cp = this.getCenterPoint();
13634
        ctx.translate(
13635
          cp.x - this.strokeWidth / 2,
13636
          cp.y - this.strokeWidth / 2
13637
        );
13638
      }
13639
 
13640
      if (!this.strokeDashArray || this.strokeDashArray && supportsLineDash) {
13641
        // move from center (of virtual box) to its left/top corner
13642
        // we can't assume x1, y1 is top left and x2, y2 is bottom right
13643
        var p = this.calcLinePoints();
13644
        ctx.moveTo(p.x1, p.y1);
13645
        ctx.lineTo(p.x2, p.y2);
13646
      }
13647
 
13648
      ctx.lineWidth = this.strokeWidth;
13649
 
13650
      // TODO: test this
13651
      // make sure setting "fill" changes color of a line
13652
      // (by copying fillStyle to strokeStyle, since line is stroked, not filled)
13653
      var origStrokeStyle = ctx.strokeStyle;
13654
      ctx.strokeStyle = this.stroke || ctx.fillStyle;
13655
      this.stroke && this._renderStroke(ctx);
13656
      ctx.strokeStyle = origStrokeStyle;
13657
    },
13658
 
13659
    /**
13660
     * @private
13661
     * @param {CanvasRenderingContext2D} ctx Context to render on
13662
     */
13663
    _renderDashedStroke: function(ctx) {
13664
      var p = this.calcLinePoints();
13665
 
13666
      ctx.beginPath();
13667
      fabric.util.drawDashedLine(ctx, p.x1, p.y1, p.x2, p.y2, this.strokeDashArray);
13668
      ctx.closePath();
13669
    },
13670
 
13671
    /**
13672
     * Returns object representation of an instance
13673
     * @methd toObject
13674
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
13675
     * @return {Object} object representation of an instance
13676
     */
13677
    toObject: function(propertiesToInclude) {
13678
      return extend(this.callSuper('toObject', propertiesToInclude), this.calcLinePoints());
13679
    },
13680
 
13681
    /**
13682
     * Recalculates line points given width and height
13683
     * @private
13684
     */
13685
    calcLinePoints: function() {
13686
      var xMult = this.x1 <= this.x2 ? -1 : 1,
13687
          yMult = this.y1 <= this.y2 ? -1 : 1,
13688
          x1 = (xMult * this.width * 0.5),
13689
          y1 = (yMult * this.height * 0.5),
13690
          x2 = (xMult * this.width * -0.5),
13691
          y2 = (yMult * this.height * -0.5);
13692
 
13693
      return {
13694
        x1: x1,
13695
        x2: x2,
13696
        y1: y1,
13697
        y2: y2
13698
      };
13699
    },
13700
 
13701
    /* _TO_SVG_START_ */
13702
    /**
13703
     * Returns SVG representation of an instance
13704
     * @param {Function} [reviver] Method for further parsing of svg representation.
13705
     * @return {String} svg representation of an instance
13706
     */
13707
    toSVG: function(reviver) {
13708
      var markup = this._createBaseSVGMarkup(),
13709
          p = { x1: this.x1, x2: this.x2, y1: this.y1, y2: this.y2 };
13710
 
13711
      if (!(this.group && this.group.type === 'path-group')) {
13712
        p = this.calcLinePoints();
13713
      }
13714
      markup.push(
13715
        '<line ',
13716
          'x1="', p.x1,
13717
          '" y1="', p.y1,
13718
          '" x2="', p.x2,
13719
          '" y2="', p.y2,
13720
          '" style="', this.getSvgStyles(),
13721
          '" transform="', this.getSvgTransform(),
13722
          this.getSvgTransformMatrix(),
13723
        '"/>\n'
13724
      );
13725
 
13726
      return reviver ? reviver(markup.join('')) : markup.join('');
13727
    },
13728
    /* _TO_SVG_END_ */
13729
 
13730
    /**
13731
     * Returns complexity of an instance
13732
     * @return {Number} complexity
13733
     */
13734
    complexity: function() {
13735
      return 1;
13736
    }
13737
  });
13738
 
13739
  /* _FROM_SVG_START_ */
13740
  /**
13741
   * List of attribute names to account for when parsing SVG element (used by {@link fabric.Line.fromElement})
13742
   * @static
13743
   * @memberOf fabric.Line
13744
   * @see http://www.w3.org/TR/SVG/shapes.html#LineElement
13745
   */
13746
  fabric.Line.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x1 y1 x2 y2'.split(' '));
13747
 
13748
  /**
13749
   * Returns fabric.Line instance from an SVG element
13750
   * @static
13751
   * @memberOf fabric.Line
13752
   * @param {SVGElement} element Element to parse
13753
   * @param {Object} [options] Options object
13754
   * @return {fabric.Line} instance of fabric.Line
13755
   */
13756
  fabric.Line.fromElement = function(element, options) {
13757
    var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES),
13758
        points = [
13759
          parsedAttributes.x1 || 0,
13760
          parsedAttributes.y1 || 0,
13761
          parsedAttributes.x2 || 0,
13762
          parsedAttributes.y2 || 0
13763
        ];
13764
    return new fabric.Line(points, extend(parsedAttributes, options));
13765
  };
13766
  /* _FROM_SVG_END_ */
13767
 
13768
  /**
13769
   * Returns fabric.Line instance from an object representation
13770
   * @static
13771
   * @memberOf fabric.Line
13772
   * @param {Object} object Object to create an instance from
13773
   * @return {fabric.Line} instance of fabric.Line
13774
   */
13775
  fabric.Line.fromObject = function(object) {
13776
    var points = [object.x1, object.y1, object.x2, object.y2];
13777
    return new fabric.Line(points, object);
13778
  };
13779
 
13780
  /**
13781
   * Produces a function that calculates distance from canvas edge to Line origin.
13782
   */
13783
  function makeEdgeToOriginGetter(propertyNames, originValues) {
13784
    var origin = propertyNames.origin,
13785
        axis1 = propertyNames.axis1,
13786
        axis2 = propertyNames.axis2,
13787
        dimension = propertyNames.dimension,
13788
        nearest = originValues.nearest,
13789
        center = originValues.center,
13790
        farthest = originValues.farthest;
13791
 
13792
    return function() {
13793
      switch (this.get(origin)) {
13794
      case nearest:
13795
        return Math.min(this.get(axis1), this.get(axis2));
13796
      case center:
13797
        return Math.min(this.get(axis1), this.get(axis2)) + (0.5 * this.get(dimension));
13798
      case farthest:
13799
        return Math.max(this.get(axis1), this.get(axis2));
13800
      }
13801
    };
13802
 
13803
  }
13804
 
13805
})(typeof exports !== 'undefined' ? exports : this);
13806
 
13807
 
13808
(function(global) {
13809
 
13810
  'use strict';
13811
 
13812
  var fabric = global.fabric || (global.fabric = { }),
13813
      pi = Math.PI,
13814
      extend = fabric.util.object.extend;
13815
 
13816
  if (fabric.Circle) {
13817
    fabric.warn('fabric.Circle is already defined.');
13818
    return;
13819
  }
13820
 
13821
  /**
13822
   * Circle class
13823
   * @class fabric.Circle
13824
   * @extends fabric.Object
13825
   * @see {@link fabric.Circle#initialize} for constructor definition
13826
   */
13827
  fabric.Circle = fabric.util.createClass(fabric.Object, /** @lends fabric.Circle.prototype */ {
13828
 
13829
    /**
13830
     * Type of an object
13831
     * @type String
13832
     * @default
13833
     */
13834
    type: 'circle',
13835
 
13836
    /**
13837
     * Radius of this circle
13838
     * @type Number
13839
     * @default
13840
     */
13841
    radius: 0,
13842
 
13843
    /**
13844
     * Start angle of the circle, moving clockwise
13845
     * @type Number
13846
     * @default 0
13847
     */
13848
    startAngle: 0,
13849
 
13850
    /**
13851
     * End angle of the circle
13852
     * @type Number
13853
     * @default 2Pi
13854
     */
13855
    endAngle: pi * 2,
13856
 
13857
    /**
13858
     * Constructor
13859
     * @param {Object} [options] Options object
13860
     * @return {fabric.Circle} thisArg
13861
     */
13862
    initialize: function(options) {
13863
      options = options || { };
13864
 
13865
      this.callSuper('initialize', options);
13866
      this.set('radius', options.radius || 0);
13867
      this.startAngle = options.startAngle || this.startAngle;
13868
      this.endAngle = options.endAngle || this.endAngle;
13869
    },
13870
 
13871
    /**
13872
     * @private
13873
     * @param {String} key
13874
     * @param {Any} value
13875
     * @return {fabric.Circle} thisArg
13876
     */
13877
    _set: function(key, value) {
13878
      this.callSuper('_set', key, value);
13879
 
13880
      if (key === 'radius') {
13881
        this.setRadius(value);
13882
      }
13883
 
13884
      return this;
13885
    },
13886
 
13887
    /**
13888
     * Returns object representation of an instance
13889
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
13890
     * @return {Object} object representation of an instance
13891
     */
13892
    toObject: function(propertiesToInclude) {
13893
      return extend(this.callSuper('toObject', propertiesToInclude), {
13894
        radius: this.get('radius'),
13895
        startAngle: this.startAngle,
13896
        endAngle: this.endAngle
13897
      });
13898
    },
13899
 
13900
    /* _TO_SVG_START_ */
13901
    /**
13902
     * Returns svg representation of an instance
13903
     * @param {Function} [reviver] Method for further parsing of svg representation.
13904
     * @return {String} svg representation of an instance
13905
     */
13906
    toSVG: function(reviver) {
13907
      var markup = this._createBaseSVGMarkup(), x = 0, y = 0,
13908
      angle = (this.endAngle - this.startAngle) % ( 2 * pi);
13909
 
13910
      if (angle === 0) {
13911
        if (this.group && this.group.type === 'path-group') {
13912
          x = this.left + this.radius;
13913
          y = this.top + this.radius;
13914
        }
13915
        markup.push(
13916
          '<circle ',
13917
            'cx="' + x + '" cy="' + y + '" ',
13918
            'r="', this.radius,
13919
            '" style="', this.getSvgStyles(),
13920
            '" transform="', this.getSvgTransform(),
13921
            ' ', this.getSvgTransformMatrix(),
13922
          '"/>\n'
13923
        );
13924
      }
13925
      else {
13926
        var startX = Math.cos(this.startAngle) * this.radius,
13927
            startY = Math.sin(this.startAngle) * this.radius,
13928
            endX = Math.cos(this.endAngle) * this.radius,
13929
            endY = Math.sin(this.endAngle) * this.radius,
13930
            largeFlag = angle > pi ? '1' : '0';
13931
 
13932
        markup.push(
13933
          '<path d="M ' + startX + ' ' + startY,
13934
          ' A ' + this.radius + ' ' + this.radius,
13935
          ' 0 ', + largeFlag + ' 1', ' ' + endX + ' ' + endY,
13936
          '" style="', this.getSvgStyles(),
13937
          '" transform="', this.getSvgTransform(),
13938
          ' ', this.getSvgTransformMatrix(),
13939
          '"/>\n'
13940
        );
13941
      }
13942
 
13943
      return reviver ? reviver(markup.join('')) : markup.join('');
13944
    },
13945
    /* _TO_SVG_END_ */
13946
 
13947
    /**
13948
     * @private
13949
     * @param {CanvasRenderingContext2D} ctx context to render on
13950
     * @param {Boolean} [noTransform] When true, context is not transformed
13951
     */
13952
    _render: function(ctx, noTransform) {
13953
      ctx.beginPath();
13954
      ctx.arc(noTransform ? this.left + this.radius : 0,
13955
              noTransform ? this.top + this.radius : 0,
13956
              this.radius,
13957
              this.startAngle,
13958
              this.endAngle, false);
13959
      this._renderFill(ctx);
13960
      this._renderStroke(ctx);
13961
    },
13962
 
13963
    /**
13964
     * Returns horizontal radius of an object (according to how an object is scaled)
13965
     * @return {Number}
13966
     */
13967
    getRadiusX: function() {
13968
      return this.get('radius') * this.get('scaleX');
13969
    },
13970
 
13971
    /**
13972
     * Returns vertical radius of an object (according to how an object is scaled)
13973
     * @return {Number}
13974
     */
13975
    getRadiusY: function() {
13976
      return this.get('radius') * this.get('scaleY');
13977
    },
13978
 
13979
    /**
13980
     * Sets radius of an object (and updates width accordingly)
13981
     * @return {Number}
13982
     */
13983
    setRadius: function(value) {
13984
      this.radius = value;
13985
      this.set('width', value * 2).set('height', value * 2);
13986
    },
13987
 
13988
    /**
13989
     * Returns complexity of an instance
13990
     * @return {Number} complexity of this instance
13991
     */
13992
    complexity: function() {
13993
      return 1;
13994
    }
13995
  });
13996
 
13997
  /* _FROM_SVG_START_ */
13998
  /**
13999
   * List of attribute names to account for when parsing SVG element (used by {@link fabric.Circle.fromElement})
14000
   * @static
14001
   * @memberOf fabric.Circle
14002
   * @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement
14003
   */
14004
  fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy r'.split(' '));
14005
 
14006
  /**
14007
   * Returns {@link fabric.Circle} instance from an SVG element
14008
   * @static
14009
   * @memberOf fabric.Circle
14010
   * @param {SVGElement} element Element to parse
14011
   * @param {Object} [options] Options object
14012
   * @throws {Error} If value of `r` attribute is missing or invalid
14013
   * @return {fabric.Circle} Instance of fabric.Circle
14014
   */
14015
  fabric.Circle.fromElement = function(element, options) {
14016
    options || (options = { });
14017
 
14018
    var parsedAttributes = fabric.parseAttributes(element, fabric.Circle.ATTRIBUTE_NAMES);
14019
 
14020
    if (!isValidRadius(parsedAttributes)) {
14021
      throw new Error('value of `r` attribute is required and can not be negative');
14022
    }
14023
 
14024
    parsedAttributes.left = parsedAttributes.left || 0;
14025
    parsedAttributes.top = parsedAttributes.top || 0;
14026
 
14027
    var obj = new fabric.Circle(extend(parsedAttributes, options));
14028
 
14029
    obj.left -= obj.radius;
14030
    obj.top -= obj.radius;
14031
    return obj;
14032
  };
14033
 
14034
  /**
14035
   * @private
14036
   */
14037
  function isValidRadius(attributes) {
14038
    return (('radius' in attributes) && (attributes.radius >= 0));
14039
  }
14040
  /* _FROM_SVG_END_ */
14041
 
14042
  /**
14043
   * Returns {@link fabric.Circle} instance from an object representation
14044
   * @static
14045
   * @memberOf fabric.Circle
14046
   * @param {Object} object Object to create an instance from
14047
   * @return {Object} Instance of fabric.Circle
14048
   */
14049
  fabric.Circle.fromObject = function(object) {
14050
    return new fabric.Circle(object);
14051
  };
14052
 
14053
})(typeof exports !== 'undefined' ? exports : this);
14054
 
14055
 
14056
(function(global) {
14057
 
14058
  'use strict';
14059
 
14060
  var fabric = global.fabric || (global.fabric = { });
14061
 
14062
  if (fabric.Triangle) {
14063
    fabric.warn('fabric.Triangle is already defined');
14064
    return;
14065
  }
14066
 
14067
  /**
14068
   * Triangle class
14069
   * @class fabric.Triangle
14070
   * @extends fabric.Object
14071
   * @return {fabric.Triangle} thisArg
14072
   * @see {@link fabric.Triangle#initialize} for constructor definition
14073
   */
14074
  fabric.Triangle = fabric.util.createClass(fabric.Object, /** @lends fabric.Triangle.prototype */ {
14075
 
14076
    /**
14077
     * Type of an object
14078
     * @type String
14079
     * @default
14080
     */
14081
    type: 'triangle',
14082
 
14083
    /**
14084
     * Constructor
14085
     * @param {Object} [options] Options object
14086
     * @return {Object} thisArg
14087
     */
14088
    initialize: function(options) {
14089
      options = options || { };
14090
 
14091
      this.callSuper('initialize', options);
14092
 
14093
      this.set('width', options.width || 100)
14094
          .set('height', options.height || 100);
14095
    },
14096
 
14097
    /**
14098
     * @private
14099
     * @param {CanvasRenderingContext2D} ctx Context to render on
14100
     */
14101
    _render: function(ctx) {
14102
      var widthBy2 = this.width / 2,
14103
          heightBy2 = this.height / 2;
14104
 
14105
      ctx.beginPath();
14106
      ctx.moveTo(-widthBy2, heightBy2);
14107
      ctx.lineTo(0, -heightBy2);
14108
      ctx.lineTo(widthBy2, heightBy2);
14109
      ctx.closePath();
14110
 
14111
      this._renderFill(ctx);
14112
      this._renderStroke(ctx);
14113
    },
14114
 
14115
    /**
14116
     * @private
14117
     * @param {CanvasRenderingContext2D} ctx Context to render on
14118
     */
14119
    _renderDashedStroke: function(ctx) {
14120
      var widthBy2 = this.width / 2,
14121
          heightBy2 = this.height / 2;
14122
 
14123
      ctx.beginPath();
14124
      fabric.util.drawDashedLine(ctx, -widthBy2, heightBy2, 0, -heightBy2, this.strokeDashArray);
14125
      fabric.util.drawDashedLine(ctx, 0, -heightBy2, widthBy2, heightBy2, this.strokeDashArray);
14126
      fabric.util.drawDashedLine(ctx, widthBy2, heightBy2, -widthBy2, heightBy2, this.strokeDashArray);
14127
      ctx.closePath();
14128
    },
14129
 
14130
    /* _TO_SVG_START_ */
14131
    /**
14132
     * Returns SVG representation of an instance
14133
     * @param {Function} [reviver] Method for further parsing of svg representation.
14134
     * @return {String} svg representation of an instance
14135
     */
14136
    toSVG: function(reviver) {
14137
      var markup = this._createBaseSVGMarkup(),
14138
          widthBy2 = this.width / 2,
14139
          heightBy2 = this.height / 2,
14140
          points = [
14141
            -widthBy2 + ' ' + heightBy2,
14142
            '0 ' + -heightBy2,
14143
            widthBy2 + ' ' + heightBy2
14144
          ]
14145
          .join(',');
14146
 
14147
      markup.push(
14148
        '<polygon ',
14149
          'points="', points,
14150
          '" style="', this.getSvgStyles(),
14151
          '" transform="', this.getSvgTransform(),
14152
        '"/>'
14153
      );
14154
 
14155
      return reviver ? reviver(markup.join('')) : markup.join('');
14156
    },
14157
    /* _TO_SVG_END_ */
14158
 
14159
    /**
14160
     * Returns complexity of an instance
14161
     * @return {Number} complexity of this instance
14162
     */
14163
    complexity: function() {
14164
      return 1;
14165
    }
14166
  });
14167
 
14168
  /**
14169
   * Returns fabric.Triangle instance from an object representation
14170
   * @static
14171
   * @memberOf fabric.Triangle
14172
   * @param {Object} object Object to create an instance from
14173
   * @return {Object} instance of Canvas.Triangle
14174
   */
14175
  fabric.Triangle.fromObject = function(object) {
14176
    return new fabric.Triangle(object);
14177
  };
14178
 
14179
})(typeof exports !== 'undefined' ? exports : this);
14180
 
14181
 
14182
(function(global) {
14183
 
14184
  'use strict';
14185
 
14186
  var fabric = global.fabric || (global.fabric = { }),
14187
      piBy2   = Math.PI * 2,
14188
      extend = fabric.util.object.extend;
14189
 
14190
  if (fabric.Ellipse) {
14191
    fabric.warn('fabric.Ellipse is already defined.');
14192
    return;
14193
  }
14194
 
14195
  /**
14196
   * Ellipse class
14197
   * @class fabric.Ellipse
14198
   * @extends fabric.Object
14199
   * @return {fabric.Ellipse} thisArg
14200
   * @see {@link fabric.Ellipse#initialize} for constructor definition
14201
   */
14202
  fabric.Ellipse = fabric.util.createClass(fabric.Object, /** @lends fabric.Ellipse.prototype */ {
14203
 
14204
    /**
14205
     * Type of an object
14206
     * @type String
14207
     * @default
14208
     */
14209
    type: 'ellipse',
14210
 
14211
    /**
14212
     * Horizontal radius
14213
     * @type Number
14214
     * @default
14215
     */
14216
    rx:   0,
14217
 
14218
    /**
14219
     * Vertical radius
14220
     * @type Number
14221
     * @default
14222
     */
14223
    ry:   0,
14224
 
14225
    /**
14226
     * Constructor
14227
     * @param {Object} [options] Options object
14228
     * @return {fabric.Ellipse} thisArg
14229
     */
14230
    initialize: function(options) {
14231
      options = options || { };
14232
 
14233
      this.callSuper('initialize', options);
14234
 
14235
      this.set('rx', options.rx || 0);
14236
      this.set('ry', options.ry || 0);
14237
    },
14238
 
14239
    /**
14240
     * @private
14241
     * @param {String} key
14242
     * @param {Any} value
14243
     * @return {fabric.Ellipse} thisArg
14244
     */
14245
    _set: function(key, value) {
14246
      this.callSuper('_set', key, value);
14247
      switch (key) {
14248
 
14249
        case 'rx':
14250
          this.rx = value;
14251
          this.set('width', value * 2);
14252
          break;
14253
 
14254
        case 'ry':
14255
          this.ry = value;
14256
          this.set('height', value * 2);
14257
          break;
14258
 
14259
      }
14260
      return this;
14261
    },
14262
 
14263
    /**
14264
     * Returns horizontal radius of an object (according to how an object is scaled)
14265
     * @return {Number}
14266
     */
14267
    getRx: function() {
14268
      return this.get('rx') * this.get('scaleX');
14269
    },
14270
 
14271
    /**
14272
     * Returns Vertical radius of an object (according to how an object is scaled)
14273
     * @return {Number}
14274
     */
14275
    getRy: function() {
14276
      return this.get('ry') * this.get('scaleY');
14277
    },
14278
 
14279
    /**
14280
     * Returns object representation of an instance
14281
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
14282
     * @return {Object} object representation of an instance
14283
     */
14284
    toObject: function(propertiesToInclude) {
14285
      return extend(this.callSuper('toObject', propertiesToInclude), {
14286
        rx: this.get('rx'),
14287
        ry: this.get('ry')
14288
      });
14289
    },
14290
 
14291
    /* _TO_SVG_START_ */
14292
    /**
14293
     * Returns svg representation of an instance
14294
     * @param {Function} [reviver] Method for further parsing of svg representation.
14295
     * @return {String} svg representation of an instance
14296
     */
14297
    toSVG: function(reviver) {
14298
      var markup = this._createBaseSVGMarkup(), x = 0, y = 0;
14299
      if (this.group && this.group.type === 'path-group') {
14300
        x = this.left + this.rx;
14301
        y = this.top + this.ry;
14302
      }
14303
      markup.push(
14304
        '<ellipse ',
14305
          'cx="', x, '" cy="', y, '" ',
14306
          'rx="', this.rx,
14307
          '" ry="', this.ry,
14308
          '" style="', this.getSvgStyles(),
14309
          '" transform="', this.getSvgTransform(),
14310
          this.getSvgTransformMatrix(),
14311
        '"/>\n'
14312
      );
14313
 
14314
      return reviver ? reviver(markup.join('')) : markup.join('');
14315
    },
14316
    /* _TO_SVG_END_ */
14317
 
14318
    /**
14319
     * @private
14320
     * @param {CanvasRenderingContext2D} ctx context to render on
14321
     * @param {Boolean} [noTransform] When true, context is not transformed
14322
     */
14323
    _render: function(ctx, noTransform) {
14324
      ctx.beginPath();
14325
      ctx.save();
14326
      ctx.transform(1, 0, 0, this.ry/this.rx, 0, 0);
14327
      ctx.arc(
14328
        noTransform ? this.left + this.rx : 0,
14329
        noTransform ? (this.top + this.ry) * this.rx/this.ry : 0,
14330
        this.rx,
14331
        0,
14332
        piBy2,
14333
        false);
14334
      ctx.restore();
14335
      this._renderFill(ctx);
14336
      this._renderStroke(ctx);
14337
    },
14338
 
14339
    /**
14340
     * Returns complexity of an instance
14341
     * @return {Number} complexity
14342
     */
14343
    complexity: function() {
14344
      return 1;
14345
    }
14346
  });
14347
 
14348
  /* _FROM_SVG_START_ */
14349
  /**
14350
   * List of attribute names to account for when parsing SVG element (used by {@link fabric.Ellipse.fromElement})
14351
   * @static
14352
   * @memberOf fabric.Ellipse
14353
   * @see http://www.w3.org/TR/SVG/shapes.html#EllipseElement
14354
   */
14355
  fabric.Ellipse.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy rx ry'.split(' '));
14356
 
14357
  /**
14358
   * Returns {@link fabric.Ellipse} instance from an SVG element
14359
   * @static
14360
   * @memberOf fabric.Ellipse
14361
   * @param {SVGElement} element Element to parse
14362
   * @param {Object} [options] Options object
14363
   * @return {fabric.Ellipse}
14364
   */
14365
  fabric.Ellipse.fromElement = function(element, options) {
14366
    options || (options = { });
14367
 
14368
    var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES);
14369
 
14370
    parsedAttributes.left = parsedAttributes.left || 0;
14371
    parsedAttributes.top = parsedAttributes.top || 0;
14372
 
14373
    var ellipse = new fabric.Ellipse(extend(parsedAttributes, options));
14374
 
14375
    ellipse.top -= ellipse.ry;
14376
    ellipse.left -= ellipse.rx;
14377
    return ellipse;
14378
  };
14379
  /* _FROM_SVG_END_ */
14380
 
14381
  /**
14382
   * Returns {@link fabric.Ellipse} instance from an object representation
14383
   * @static
14384
   * @memberOf fabric.Ellipse
14385
   * @param {Object} object Object to create an instance from
14386
   * @return {fabric.Ellipse}
14387
   */
14388
  fabric.Ellipse.fromObject = function(object) {
14389
    return new fabric.Ellipse(object);
14390
  };
14391
 
14392
})(typeof exports !== 'undefined' ? exports : this);
14393
 
14394
 
14395
(function(global) {
14396
 
14397
  'use strict';
14398
 
14399
  var fabric = global.fabric || (global.fabric = { }),
14400
      extend = fabric.util.object.extend;
14401
 
14402
  if (fabric.Rect) {
14403
    console.warn('fabric.Rect is already defined');
14404
    return;
14405
  }
14406
 
14407
  var stateProperties = fabric.Object.prototype.stateProperties.concat();
14408
  stateProperties.push('rx', 'ry', 'x', 'y');
14409
 
14410
  /**
14411
   * Rectangle class
14412
   * @class fabric.Rect
14413
   * @extends fabric.Object
14414
   * @return {fabric.Rect} thisArg
14415
   * @see {@link fabric.Rect#initialize} for constructor definition
14416
   */
14417
  fabric.Rect = fabric.util.createClass(fabric.Object, /** @lends fabric.Rect.prototype */ {
14418
 
14419
    /**
14420
     * List of properties to consider when checking if state of an object is changed ({@link fabric.Object#hasStateChanged})
14421
     * as well as for history (undo/redo) purposes
14422
     * @type Array
14423
     */
14424
    stateProperties: stateProperties,
14425
 
14426
    /**
14427
     * Type of an object
14428
     * @type String
14429
     * @default
14430
     */
14431
    type: 'rect',
14432
 
14433
    /**
14434
     * Horizontal border radius
14435
     * @type Number
14436
     * @default
14437
     */
14438
    rx:   0,
14439
 
14440
    /**
14441
     * Vertical border radius
14442
     * @type Number
14443
     * @default
14444
     */
14445
    ry:   0,
14446
 
14447
    /**
14448
     * Used to specify dash pattern for stroke on this object
14449
     * @type Array
14450
     */
14451
    strokeDashArray: null,
14452
 
14453
    /**
14454
     * Constructor
14455
     * @param {Object} [options] Options object
14456
     * @return {Object} thisArg
14457
     */
14458
    initialize: function(options) {
14459
      options = options || { };
14460
 
14461
      this.callSuper('initialize', options);
14462
      this._initRxRy();
14463
 
14464
    },
14465
 
14466
    /**
14467
     * Initializes rx/ry attributes
14468
     * @private
14469
     */
14470
    _initRxRy: function() {
14471
      if (this.rx && !this.ry) {
14472
        this.ry = this.rx;
14473
      }
14474
      else if (this.ry && !this.rx) {
14475
        this.rx = this.ry;
14476
      }
14477
    },
14478
 
14479
    /**
14480
     * @private
14481
     * @param {CanvasRenderingContext2D} ctx Context to render on
14482
     */
14483
    _render: function(ctx, noTransform) {
14484
 
14485
      // optimize 1x1 case (used in spray brush)
14486
      if (this.width === 1 && this.height === 1) {
14487
        ctx.fillRect(0, 0, 1, 1);
14488
        return;
14489
      }
14490
 
14491
      var rx = this.rx ? Math.min(this.rx, this.width / 2) : 0,
14492
          ry = this.ry ? Math.min(this.ry, this.height / 2) : 0,
14493
          w = this.width,
14494
          h = this.height,
14495
          x = noTransform ? this.left : -this.width / 2,
14496
          y = noTransform ? this.top : -this.height / 2,
14497
          isRounded = rx !== 0 || ry !== 0,
14498
          k = 1 - 0.5522847498 /* "magic number" for bezier approximations of arcs (http://itc.ktu.lt/itc354/Riskus354.pdf) */;
14499
 
14500
      ctx.beginPath();
14501
 
14502
      ctx.moveTo(x + rx, y);
14503
 
14504
      ctx.lineTo(x + w - rx, y);
14505
      isRounded && ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry);
14506
 
14507
      ctx.lineTo(x + w, y + h - ry);
14508
      isRounded && ctx.bezierCurveTo(x + w, y + h - k * ry, x + w - k * rx, y + h, x + w - rx, y + h);
14509
 
14510
      ctx.lineTo(x + rx, y + h);
14511
      isRounded && ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry);
14512
 
14513
      ctx.lineTo(x, y + ry);
14514
      isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y);
14515
 
14516
      ctx.closePath();
14517
 
14518
      this._renderFill(ctx);
14519
      this._renderStroke(ctx);
14520
    },
14521
 
14522
    /**
14523
     * @private
14524
     * @param {CanvasRenderingContext2D} ctx Context to render on
14525
     */
14526
    _renderDashedStroke: function(ctx) {
14527
      var x = -this.width / 2,
14528
          y = -this.height / 2,
14529
          w = this.width,
14530
          h = this.height;
14531
 
14532
      ctx.beginPath();
14533
      fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray);
14534
      fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray);
14535
      fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray);
14536
      fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray);
14537
      ctx.closePath();
14538
    },
14539
 
14540
    /**
14541
     * Returns object representation of an instance
14542
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
14543
     * @return {Object} object representation of an instance
14544
     */
14545
    toObject: function(propertiesToInclude) {
14546
      var object = extend(this.callSuper('toObject', propertiesToInclude), {
14547
        rx: this.get('rx') || 0,
14548
        ry: this.get('ry') || 0
14549
      });
14550
      if (!this.includeDefaultValues) {
14551
        this._removeDefaultValues(object);
14552
      }
14553
      return object;
14554
    },
14555
 
14556
    /* _TO_SVG_START_ */
14557
    /**
14558
     * Returns svg representation of an instance
14559
     * @param {Function} [reviver] Method for further parsing of svg representation.
14560
     * @return {String} svg representation of an instance
14561
     */
14562
    toSVG: function(reviver) {
14563
      var markup = this._createBaseSVGMarkup(), x = this.left, y = this.top;
14564
      if (!(this.group && this.group.type === 'path-group')) {
14565
        x = -this.width / 2;
14566
        y = -this.height / 2;
14567
      }
14568
      markup.push(
14569
        '<rect ',
14570
          'x="', x, '" y="', y,
14571
          '" rx="', this.get('rx'), '" ry="', this.get('ry'),
14572
          '" width="', this.width, '" height="', this.height,
14573
          '" style="', this.getSvgStyles(),
14574
          '" transform="', this.getSvgTransform(),
14575
          this.getSvgTransformMatrix(),
14576
        '"/>\n');
14577
 
14578
      return reviver ? reviver(markup.join('')) : markup.join('');
14579
    },
14580
    /* _TO_SVG_END_ */
14581
 
14582
    /**
14583
     * Returns complexity of an instance
14584
     * @return {Number} complexity
14585
     */
14586
    complexity: function() {
14587
      return 1;
14588
    }
14589
  });
14590
 
14591
  /* _FROM_SVG_START_ */
14592
  /**
14593
   * List of attribute names to account for when parsing SVG element (used by `fabric.Rect.fromElement`)
14594
   * @static
14595
   * @memberOf fabric.Rect
14596
   * @see: http://www.w3.org/TR/SVG/shapes.html#RectElement
14597
   */
14598
  fabric.Rect.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y rx ry width height'.split(' '));
14599
 
14600
  /**
14601
   * Returns {@link fabric.Rect} instance from an SVG element
14602
   * @static
14603
   * @memberOf fabric.Rect
14604
   * @param {SVGElement} element Element to parse
14605
   * @param {Object} [options] Options object
14606
   * @return {fabric.Rect} Instance of fabric.Rect
14607
   */
14608
  fabric.Rect.fromElement = function(element, options) {
14609
    if (!element) {
14610
      return null;
14611
    }
14612
    options = options || { };
14613
 
14614
    var parsedAttributes = fabric.parseAttributes(element, fabric.Rect.ATTRIBUTE_NAMES);
14615
 
14616
    parsedAttributes.left = parsedAttributes.left || 0;
14617
    parsedAttributes.top  = parsedAttributes.top  || 0;
14618
    var rect = new fabric.Rect(extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes));
14619
    rect.visible = rect.width > 0 && rect.height > 0;
14620
    return rect;
14621
  };
14622
  /* _FROM_SVG_END_ */
14623
 
14624
  /**
14625
   * Returns {@link fabric.Rect} instance from an object representation
14626
   * @static
14627
   * @memberOf fabric.Rect
14628
   * @param {Object} object Object to create an instance from
14629
   * @return {Object} instance of fabric.Rect
14630
   */
14631
  fabric.Rect.fromObject = function(object) {
14632
    return new fabric.Rect(object);
14633
  };
14634
 
14635
})(typeof exports !== 'undefined' ? exports : this);
14636
 
14637
 
14638
(function(global) {
14639
 
14640
  'use strict';
14641
 
14642
  var fabric = global.fabric || (global.fabric = { });
14643
 
14644
  if (fabric.Polyline) {
14645
    fabric.warn('fabric.Polyline is already defined');
14646
    return;
14647
  }
14648
 
14649
  /**
14650
   * Polyline class
14651
   * @class fabric.Polyline
14652
   * @extends fabric.Object
14653
   * @see {@link fabric.Polyline#initialize} for constructor definition
14654
   */
14655
  fabric.Polyline = fabric.util.createClass(fabric.Object, /** @lends fabric.Polyline.prototype */ {
14656
 
14657
    /**
14658
     * Type of an object
14659
     * @type String
14660
     * @default
14661
     */
14662
    type: 'polyline',
14663
 
14664
    /**
14665
     * Points array
14666
     * @type Array
14667
     * @default
14668
     */
14669
    points: null,
14670
 
14671
    /**
14672
     * Minimum X from points values, necessary to offset points
14673
     * @type Number
14674
     * @default
14675
     */
14676
    minX: 0,
14677
 
14678
    /**
14679
     * Minimum Y from points values, necessary to offset points
14680
     * @type Number
14681
     * @default
14682
     */
14683
    minY: 0,
14684
 
14685
    /**
14686
     * Constructor
14687
     * @param {Array} points Array of points (where each point is an object with x and y)
14688
     * @param {Object} [options] Options object
14689
     * @param {Boolean} [skipOffset] Whether points offsetting should be skipped
14690
     * @return {fabric.Polyline} thisArg
14691
     * @example
14692
     * var poly = new fabric.Polyline([
14693
     *     { x: 10, y: 10 },
14694
     *     { x: 50, y: 30 },
14695
     *     { x: 40, y: 70 },
14696
     *     { x: 60, y: 50 },
14697
     *     { x: 100, y: 150 },
14698
     *     { x: 40, y: 100 }
14699
     *   ], {
14700
     *   stroke: 'red',
14701
     *   left: 100,
14702
     *   top: 100
14703
     * });
14704
     */
14705
    initialize: function(points, options) {
14706
      return fabric.Polygon.prototype.initialize.call(this, points, options);
14707
    },
14708
 
14709
    /**
14710
     * @private
14711
     */
14712
    _calcDimensions: function() {
14713
      return fabric.Polygon.prototype._calcDimensions.call(this);
14714
    },
14715
 
14716
    /**
14717
     * @private
14718
     */
14719
    _applyPointOffset: function() {
14720
      return fabric.Polygon.prototype._applyPointOffset.call(this);
14721
    },
14722
 
14723
    /**
14724
     * Returns object representation of an instance
14725
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
14726
     * @return {Object} Object representation of an instance
14727
     */
14728
    toObject: function(propertiesToInclude) {
14729
      return fabric.Polygon.prototype.toObject.call(this, propertiesToInclude);
14730
    },
14731
 
14732
    /* _TO_SVG_START_ */
14733
    /**
14734
     * Returns SVG representation of an instance
14735
     * @param {Function} [reviver] Method for further parsing of svg representation.
14736
     * @return {String} svg representation of an instance
14737
     */
14738
    toSVG: function(reviver) {
14739
      return fabric.Polygon.prototype.toSVG.call(this, reviver);
14740
    },
14741
    /* _TO_SVG_END_ */
14742
 
14743
    /**
14744
     * @private
14745
     * @param {CanvasRenderingContext2D} ctx Context to render on
14746
     */
14747
    _render: function(ctx) {
14748
      if (!fabric.Polygon.prototype.commonRender.call(this, ctx)) {
14749
        return;
14750
      }
14751
      this._renderFill(ctx);
14752
      this._renderStroke(ctx);
14753
    },
14754
 
14755
    /**
14756
     * @private
14757
     * @param {CanvasRenderingContext2D} ctx Context to render on
14758
     */
14759
    _renderDashedStroke: function(ctx) {
14760
      var p1, p2;
14761
 
14762
      ctx.beginPath();
14763
      for (var i = 0, len = this.points.length; i < len; i++) {
14764
        p1 = this.points[i];
14765
        p2 = this.points[i + 1] || p1;
14766
        fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray);
14767
      }
14768
    },
14769
 
14770
    /**
14771
     * Returns complexity of an instance
14772
     * @return {Number} complexity of this instance
14773
     */
14774
    complexity: function() {
14775
      return this.get('points').length;
14776
    }
14777
  });
14778
 
14779
  /* _FROM_SVG_START_ */
14780
  /**
14781
   * List of attribute names to account for when parsing SVG element (used by {@link fabric.Polyline.fromElement})
14782
   * @static
14783
   * @memberOf fabric.Polyline
14784
   * @see: http://www.w3.org/TR/SVG/shapes.html#PolylineElement
14785
   */
14786
  fabric.Polyline.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat();
14787
 
14788
  /**
14789
   * Returns fabric.Polyline instance from an SVG element
14790
   * @static
14791
   * @memberOf fabric.Polyline
14792
   * @param {SVGElement} element Element to parse
14793
   * @param {Object} [options] Options object
14794
   * @return {fabric.Polyline} Instance of fabric.Polyline
14795
   */
14796
  fabric.Polyline.fromElement = function(element, options) {
14797
    if (!element) {
14798
      return null;
14799
    }
14800
    options || (options = { });
14801
 
14802
    var points = fabric.parsePointsAttribute(element.getAttribute('points')),
14803
        parsedAttributes = fabric.parseAttributes(element, fabric.Polyline.ATTRIBUTE_NAMES);
14804
 
14805
    return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options));
14806
  };
14807
  /* _FROM_SVG_END_ */
14808
 
14809
  /**
14810
   * Returns fabric.Polyline instance from an object representation
14811
   * @static
14812
   * @memberOf fabric.Polyline
14813
   * @param {Object} object Object to create an instance from
14814
   * @return {fabric.Polyline} Instance of fabric.Polyline
14815
   */
14816
  fabric.Polyline.fromObject = function(object) {
14817
    var points = object.points;
14818
    return new fabric.Polyline(points, object, true);
14819
  };
14820
 
14821
})(typeof exports !== 'undefined' ? exports : this);
14822
 
14823
 
14824
(function(global) {
14825
 
14826
  'use strict';
14827
 
14828
  var fabric = global.fabric || (global.fabric = { }),
14829
      extend = fabric.util.object.extend,
14830
      min = fabric.util.array.min,
14831
      max = fabric.util.array.max,
14832
      toFixed = fabric.util.toFixed;
14833
 
14834
  if (fabric.Polygon) {
14835
    fabric.warn('fabric.Polygon is already defined');
14836
    return;
14837
  }
14838
 
14839
  /**
14840
   * Polygon class
14841
   * @class fabric.Polygon
14842
   * @extends fabric.Object
14843
   * @see {@link fabric.Polygon#initialize} for constructor definition
14844
   */
14845
  fabric.Polygon = fabric.util.createClass(fabric.Object, /** @lends fabric.Polygon.prototype */ {
14846
 
14847
    /**
14848
     * Type of an object
14849
     * @type String
14850
     * @default
14851
     */
14852
    type: 'polygon',
14853
 
14854
    /**
14855
     * Points array
14856
     * @type Array
14857
     * @default
14858
     */
14859
    points: null,
14860
 
14861
    /**
14862
     * Minimum X from points values, necessary to offset points
14863
     * @type Number
14864
     * @default
14865
     */
14866
    minX: 0,
14867
 
14868
    /**
14869
     * Minimum Y from points values, necessary to offset points
14870
     * @type Number
14871
     * @default
14872
     */
14873
    minY: 0,
14874
 
14875
    /**
14876
     * Constructor
14877
     * @param {Array} points Array of points
14878
     * @param {Object} [options] Options object
14879
     * @return {fabric.Polygon} thisArg
14880
     */
14881
    initialize: function(points, options) {
14882
      options = options || { };
14883
      this.points = points || [ ];
14884
      this.callSuper('initialize', options);
14885
      this._calcDimensions();
14886
      if (!('top' in options)) {
14887
        this.top = this.minY;
14888
      }
14889
      if (!('left' in options)) {
14890
        this.left = this.minX;
14891
      }
14892
    },
14893
 
14894
    /**
14895
     * @private
14896
     */
14897
    _calcDimensions: function() {
14898
 
14899
      var points = this.points,
14900
          minX = min(points, 'x'),
14901
          minY = min(points, 'y'),
14902
          maxX = max(points, 'x'),
14903
          maxY = max(points, 'y');
14904
 
14905
      this.width = (maxX - minX) || 0;
14906
      this.height = (maxY - minY) || 0;
14907
 
14908
      this.minX = minX || 0,
14909
      this.minY = minY || 0;
14910
    },
14911
 
14912
    /**
14913
     * @private
14914
     */
14915
    _applyPointOffset: function() {
14916
      // change points to offset polygon into a bounding box
14917
      // executed one time
14918
      this.points.forEach(function(p) {
14919
        p.x -= (this.minX + this.width / 2);
14920
        p.y -= (this.minY + this.height / 2);
14921
      }, this);
14922
    },
14923
 
14924
    /**
14925
     * Returns object representation of an instance
14926
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
14927
     * @return {Object} Object representation of an instance
14928
     */
14929
    toObject: function(propertiesToInclude) {
14930
      return extend(this.callSuper('toObject', propertiesToInclude), {
14931
        points: this.points.concat()
14932
      });
14933
    },
14934
 
14935
    /* _TO_SVG_START_ */
14936
    /**
14937
     * Returns svg representation of an instance
14938
     * @param {Function} [reviver] Method for further parsing of svg representation.
14939
     * @return {String} svg representation of an instance
14940
     */
14941
    toSVG: function(reviver) {
14942
      var points = [],
14943
          markup = this._createBaseSVGMarkup();
14944
 
14945
      for (var i = 0, len = this.points.length; i < len; i++) {
14946
        points.push(toFixed(this.points[i].x, 2), ',', toFixed(this.points[i].y, 2), ' ');
14947
      }
14948
 
14949
      markup.push(
14950
        '<', this.type, ' ',
14951
          'points="', points.join(''),
14952
          '" style="', this.getSvgStyles(),
14953
          '" transform="', this.getSvgTransform(),
14954
          ' ', this.getSvgTransformMatrix(),
14955
        '"/>\n'
14956
      );
14957
 
14958
      return reviver ? reviver(markup.join('')) : markup.join('');
14959
    },
14960
    /* _TO_SVG_END_ */
14961
 
14962
    /**
14963
     * @private
14964
     * @param {CanvasRenderingContext2D} ctx Context to render on
14965
     */
14966
    _render: function(ctx) {
14967
      if (!this.commonRender(ctx)) {
14968
        return;
14969
      }
14970
      this._renderFill(ctx);
14971
      if (this.stroke || this.strokeDashArray) {
14972
        ctx.closePath();
14973
        this._renderStroke(ctx);
14974
      }
14975
    },
14976
 
14977
    /**
14978
     * @private
14979
     * @param {CanvasRenderingContext2D} ctx Context to render on
14980
     */
14981
    commonRender: function(ctx) {
14982
      var point, len = this.points.length;
14983
 
14984
      if (!len || isNaN(this.points[len - 1].y)) {
14985
        // do not draw if no points or odd points
14986
        // NaN comes from parseFloat of a empty string in parser
14987
        return false;
14988
      }
14989
 
14990
      ctx.beginPath();
14991
 
14992
      if (this._applyPointOffset) {
14993
        if (!(this.group && this.group.type === 'path-group')) {
14994
          this._applyPointOffset();
14995
        }
14996
        this._applyPointOffset = null;
14997
      }
14998
 
14999
      ctx.moveTo(this.points[0].x, this.points[0].y);
15000
      for (var i = 0; i < len; i++) {
15001
        point = this.points[i];
15002
        ctx.lineTo(point.x, point.y);
15003
      }
15004
      return true;
15005
    },
15006
 
15007
    /**
15008
     * @private
15009
     * @param {CanvasRenderingContext2D} ctx Context to render on
15010
     */
15011
    _renderDashedStroke: function(ctx) {
15012
      fabric.Polyline.prototype._renderDashedStroke.call(this, ctx);
15013
      ctx.closePath();
15014
    },
15015
 
15016
    /**
15017
     * Returns complexity of an instance
15018
     * @return {Number} complexity of this instance
15019
     */
15020
    complexity: function() {
15021
      return this.points.length;
15022
    }
15023
  });
15024
 
15025
  /* _FROM_SVG_START_ */
15026
  /**
15027
   * List of attribute names to account for when parsing SVG element (used by `fabric.Polygon.fromElement`)
15028
   * @static
15029
   * @memberOf fabric.Polygon
15030
   * @see: http://www.w3.org/TR/SVG/shapes.html#PolygonElement
15031
   */
15032
  fabric.Polygon.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat();
15033
 
15034
  /**
15035
   * Returns {@link fabric.Polygon} instance from an SVG element
15036
   * @static
15037
   * @memberOf fabric.Polygon
15038
   * @param {SVGElement} element Element to parse
15039
   * @param {Object} [options] Options object
15040
   * @return {fabric.Polygon} Instance of fabric.Polygon
15041
   */
15042
  fabric.Polygon.fromElement = function(element, options) {
15043
    if (!element) {
15044
      return null;
15045
    }
15046
 
15047
    options || (options = { });
15048
 
15049
    var points = fabric.parsePointsAttribute(element.getAttribute('points')),
15050
        parsedAttributes = fabric.parseAttributes(element, fabric.Polygon.ATTRIBUTE_NAMES);
15051
 
15052
    return new fabric.Polygon(points, extend(parsedAttributes, options));
15053
  };
15054
  /* _FROM_SVG_END_ */
15055
 
15056
  /**
15057
   * Returns fabric.Polygon instance from an object representation
15058
   * @static
15059
   * @memberOf fabric.Polygon
15060
   * @param {Object} object Object to create an instance from
15061
   * @return {fabric.Polygon} Instance of fabric.Polygon
15062
   */
15063
  fabric.Polygon.fromObject = function(object) {
15064
    return new fabric.Polygon(object.points, object, true);
15065
  };
15066
 
15067
})(typeof exports !== 'undefined' ? exports : this);
15068
 
15069
 
15070
(function(global) {
15071
 
15072
  'use strict';
15073
 
15074
  var fabric = global.fabric || (global.fabric = { }),
15075
      min = fabric.util.array.min,
15076
      max = fabric.util.array.max,
15077
      extend = fabric.util.object.extend,
15078
      _toString = Object.prototype.toString,
15079
      drawArc = fabric.util.drawArc,
15080
      commandLengths = {
15081
        m: 2,
15082
        l: 2,
15083
        h: 1,
15084
        v: 1,
15085
        c: 6,
15086
        s: 4,
15087
        q: 4,
15088
        t: 2,
15089
        a: 7
15090
      },
15091
      repeatedCommands = {
15092
        m: 'l',
15093
        M: 'L'
15094
      };
15095
 
15096
  if (fabric.Path) {
15097
    fabric.warn('fabric.Path is already defined');
15098
    return;
15099
  }
15100
 
15101
  /**
15102
   * Path class
15103
   * @class fabric.Path
15104
   * @extends fabric.Object
15105
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#path_and_pathgroup}
15106
   * @see {@link fabric.Path#initialize} for constructor definition
15107
   */
15108
  fabric.Path = fabric.util.createClass(fabric.Object, /** @lends fabric.Path.prototype */ {
15109
 
15110
    /**
15111
     * Type of an object
15112
     * @type String
15113
     * @default
15114
     */
15115
    type: 'path',
15116
 
15117
    /**
15118
     * Array of path points
15119
     * @type Array
15120
     * @default
15121
     */
15122
    path: null,
15123
 
15124
    /**
15125
     * Minimum X from points values, necessary to offset points
15126
     * @type Number
15127
     * @default
15128
     */
15129
    minX: 0,
15130
 
15131
    /**
15132
     * Minimum Y from points values, necessary to offset points
15133
     * @type Number
15134
     * @default
15135
     */
15136
    minY: 0,
15137
 
15138
    /**
15139
     * Constructor
15140
     * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens)
15141
     * @param {Object} [options] Options object
15142
     * @return {fabric.Path} thisArg
15143
     */
15144
    initialize: function(path, options) {
15145
      options = options || { };
15146
 
15147
      this.setOptions(options);
15148
 
15149
      if (!path) {
15150
        throw new Error('`path` argument is required');
15151
      }
15152
 
15153
      var fromArray = _toString.call(path) === '[object Array]';
15154
 
15155
      this.path = fromArray
15156
        ? path
15157
        // one of commands (m,M,l,L,q,Q,c,C,etc.) followed by non-command characters (i.e. command values)
15158
        : path.match && path.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi);
15159
 
15160
      if (!this.path) {
15161
        return;
15162
      }
15163
 
15164
      if (!fromArray) {
15165
        this.path = this._parsePath();
15166
      }
15167
 
15168
      this._setPositionDimensions();
15169
 
15170
      if (options.sourcePath) {
15171
        this.setSourcePath(options.sourcePath);
15172
      }
15173
    },
15174
 
15175
    /**
15176
     * @private
15177
     */
15178
    _setPositionDimensions: function() {
15179
      var calcDim = this._parseDimensions();
15180
 
15181
      this.minX = calcDim.left;
15182
      this.minY = calcDim.top;
15183
      this.width = calcDim.width;
15184
      this.height = calcDim.height;
15185
 
15186
      calcDim.left += this.originX === 'center'
15187
        ? this.width / 2
15188
        : this.originX === 'right'
15189
          ? this.width
15190
          : 0;
15191
 
15192
      calcDim.top += this.originY === 'center'
15193
        ? this.height / 2
15194
        : this.originY === 'bottom'
15195
          ? this.height
15196
          : 0;
15197
 
15198
      this.top = this.top || calcDim.top;
15199
      this.left = this.left || calcDim.left;
15200
 
15201
      this.pathOffset = this.pathOffset || {
15202
        x: this.minX + this.width / 2,
15203
        y: this.minY + this.height / 2
15204
      };
15205
    },
15206
 
15207
    /**
15208
     * @private
15209
     * @param {CanvasRenderingContext2D} ctx context to render path on
15210
     */
15211
    _render: function(ctx) {
15212
      var current, // current instruction
15213
          previous = null,
15214
          subpathStartX = 0,
15215
          subpathStartY = 0,
15216
          x = 0, // current x
15217
          y = 0, // current y
15218
          controlX = 0, // current control point x
15219
          controlY = 0, // current control point y
15220
          tempX,
15221
          tempY,
15222
          l = -this.pathOffset.x,
15223
          t = -this.pathOffset.y;
15224
 
15225
      if (this.group && this.group.type === 'path-group') {
15226
        l = 0;
15227
        t = 0;
15228
      }
15229
 
15230
      ctx.beginPath();
15231
 
15232
      for (var i = 0, len = this.path.length; i < len; ++i) {
15233
 
15234
        current = this.path[i];
15235
 
15236
        switch (current[0]) { // first letter
15237
 
15238
          case 'l': // lineto, relative
15239
            x += current[1];
15240
            y += current[2];
15241
            ctx.lineTo(x + l, y + t);
15242
            break;
15243
 
15244
          case 'L': // lineto, absolute
15245
            x = current[1];
15246
            y = current[2];
15247
            ctx.lineTo(x + l, y + t);
15248
            break;
15249
 
15250
          case 'h': // horizontal lineto, relative
15251
            x += current[1];
15252
            ctx.lineTo(x + l, y + t);
15253
            break;
15254
 
15255
          case 'H': // horizontal lineto, absolute
15256
            x = current[1];
15257
            ctx.lineTo(x + l, y + t);
15258
            break;
15259
 
15260
          case 'v': // vertical lineto, relative
15261
            y += current[1];
15262
            ctx.lineTo(x + l, y + t);
15263
            break;
15264
 
15265
          case 'V': // verical lineto, absolute
15266
            y = current[1];
15267
            ctx.lineTo(x + l, y + t);
15268
            break;
15269
 
15270
          case 'm': // moveTo, relative
15271
            x += current[1];
15272
            y += current[2];
15273
            subpathStartX = x;
15274
            subpathStartY = y;
15275
            ctx.moveTo(x + l, y + t);
15276
            break;
15277
 
15278
          case 'M': // moveTo, absolute
15279
            x = current[1];
15280
            y = current[2];
15281
            subpathStartX = x;
15282
            subpathStartY = y;
15283
            ctx.moveTo(x + l, y + t);
15284
            break;
15285
 
15286
          case 'c': // bezierCurveTo, relative
15287
            tempX = x + current[5];
15288
            tempY = y + current[6];
15289
            controlX = x + current[3];
15290
            controlY = y + current[4];
15291
            ctx.bezierCurveTo(
15292
              x + current[1] + l, // x1
15293
              y + current[2] + t, // y1
15294
              controlX + l, // x2
15295
              controlY + t, // y2
15296
              tempX + l,
15297
              tempY + t
15298
            );
15299
            x = tempX;
15300
            y = tempY;
15301
            break;
15302
 
15303
          case 'C': // bezierCurveTo, absolute
15304
            x = current[5];
15305
            y = current[6];
15306
            controlX = current[3];
15307
            controlY = current[4];
15308
            ctx.bezierCurveTo(
15309
              current[1] + l,
15310
              current[2] + t,
15311
              controlX + l,
15312
              controlY + t,
15313
              x + l,
15314
              y + t
15315
            );
15316
            break;
15317
 
15318
          case 's': // shorthand cubic bezierCurveTo, relative
15319
 
15320
            // transform to absolute x,y
15321
            tempX = x + current[3];
15322
            tempY = y + current[4];
15323
 
15324
            if (previous[0].match(/[CcSs]/) === null) {
15325
              // If there is no previous command or if the previous command was not a C, c, S, or s,
15326
              // the control point is coincident with the current point
15327
              controlX = x;
15328
              controlY = y;
15329
            }
15330
            else {
15331
              // calculate reflection of previous control points
15332
              controlX = 2 * x - controlX;
15333
              controlY = 2 * y - controlY;
15334
            }
15335
 
15336
            ctx.bezierCurveTo(
15337
              controlX + l,
15338
              controlY + t,
15339
              x + current[1] + l,
15340
              y + current[2] + t,
15341
              tempX + l,
15342
              tempY + t
15343
            );
15344
            // set control point to 2nd one of this command
15345
            // "... the first control point is assumed to be
15346
            // the reflection of the second control point on
15347
            // the previous command relative to the current point."
15348
            controlX = x + current[1];
15349
            controlY = y + current[2];
15350
 
15351
            x = tempX;
15352
            y = tempY;
15353
            break;
15354
 
15355
          case 'S': // shorthand cubic bezierCurveTo, absolute
15356
            tempX = current[3];
15357
            tempY = current[4];
15358
            if (previous[0].match(/[CcSs]/) === null) {
15359
              // If there is no previous command or if the previous command was not a C, c, S, or s,
15360
              // the control point is coincident with the current point
15361
              controlX = x;
15362
              controlY = y;
15363
            }
15364
            else {
15365
              // calculate reflection of previous control points
15366
              controlX = 2 * x - controlX;
15367
              controlY = 2 * y - controlY;
15368
            }
15369
            ctx.bezierCurveTo(
15370
              controlX + l,
15371
              controlY + t,
15372
              current[1] + l,
15373
              current[2] + t,
15374
              tempX + l,
15375
              tempY + t
15376
            );
15377
            x = tempX;
15378
            y = tempY;
15379
 
15380
            // set control point to 2nd one of this command
15381
            // "... the first control point is assumed to be
15382
            // the reflection of the second control point on
15383
            // the previous command relative to the current point."
15384
            controlX = current[1];
15385
            controlY = current[2];
15386
 
15387
            break;
15388
 
15389
          case 'q': // quadraticCurveTo, relative
15390
            // transform to absolute x,y
15391
            tempX = x + current[3];
15392
            tempY = y + current[4];
15393
 
15394
            controlX = x + current[1];
15395
            controlY = y + current[2];
15396
 
15397
            ctx.quadraticCurveTo(
15398
              controlX + l,
15399
              controlY + t,
15400
              tempX + l,
15401
              tempY + t
15402
            );
15403
            x = tempX;
15404
            y = tempY;
15405
            break;
15406
 
15407
          case 'Q': // quadraticCurveTo, absolute
15408
            tempX = current[3];
15409
            tempY = current[4];
15410
 
15411
            ctx.quadraticCurveTo(
15412
              current[1] + l,
15413
              current[2] + t,
15414
              tempX + l,
15415
              tempY + t
15416
            );
15417
            x = tempX;
15418
            y = tempY;
15419
            controlX = current[1];
15420
            controlY = current[2];
15421
            break;
15422
 
15423
          case 't': // shorthand quadraticCurveTo, relative
15424
 
15425
            // transform to absolute x,y
15426
            tempX = x + current[1];
15427
            tempY = y + current[2];
15428
 
15429
            if (previous[0].match(/[QqTt]/) === null) {
15430
              // If there is no previous command or if the previous command was not a Q, q, T or t,
15431
              // assume the control point is coincident with the current point
15432
              controlX = x;
15433
              controlY = y;
15434
            }
15435
            else {
15436
              // calculate reflection of previous control point
15437
              controlX = 2 * x - controlX;
15438
              controlY = 2 * y - controlY;
15439
            }
15440
 
15441
            ctx.quadraticCurveTo(
15442
              controlX + l,
15443
              controlY + t,
15444
              tempX + l,
15445
              tempY + t
15446
            );
15447
            x = tempX;
15448
            y = tempY;
15449
 
15450
            break;
15451
 
15452
          case 'T':
15453
            tempX = current[1];
15454
            tempY = current[2];
15455
 
15456
            if (previous[0].match(/[QqTt]/) === null) {
15457
              // If there is no previous command or if the previous command was not a Q, q, T or t,
15458
              // assume the control point is coincident with the current point
15459
              controlX = x;
15460
              controlY = y;
15461
            }
15462
            else {
15463
              // calculate reflection of previous control point
15464
              controlX = 2 * x - controlX;
15465
              controlY = 2 * y - controlY;
15466
            }
15467
            ctx.quadraticCurveTo(
15468
              controlX + l,
15469
              controlY + t,
15470
              tempX + l,
15471
              tempY + t
15472
            );
15473
            x = tempX;
15474
            y = tempY;
15475
            break;
15476
 
15477
          case 'a':
15478
            // TODO: optimize this
15479
            drawArc(ctx, x + l, y + t, [
15480
              current[1],
15481
              current[2],
15482
              current[3],
15483
              current[4],
15484
              current[5],
15485
              current[6] + x + l,
15486
              current[7] + y + t
15487
            ]);
15488
            x += current[6];
15489
            y += current[7];
15490
            break;
15491
 
15492
          case 'A':
15493
            // TODO: optimize this
15494
            drawArc(ctx, x + l, y + t, [
15495
              current[1],
15496
              current[2],
15497
              current[3],
15498
              current[4],
15499
              current[5],
15500
              current[6] + l,
15501
              current[7] + t
15502
            ]);
15503
            x = current[6];
15504
            y = current[7];
15505
            break;
15506
 
15507
          case 'z':
15508
          case 'Z':
15509
            x = subpathStartX;
15510
            y = subpathStartY;
15511
            ctx.closePath();
15512
            break;
15513
        }
15514
        previous = current;
15515
      }
15516
      this._renderFill(ctx);
15517
      this._renderStroke(ctx);
15518
    },
15519
 
15520
    /**
15521
     * Returns string representation of an instance
15522
     * @return {String} string representation of an instance
15523
     */
15524
    toString: function() {
15525
      return '#<fabric.Path (' + this.complexity() +
15526
        '): { "top": ' + this.top + ', "left": ' + this.left + ' }>';
15527
    },
15528
 
15529
    /**
15530
     * Returns object representation of an instance
15531
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
15532
     * @return {Object} object representation of an instance
15533
     */
15534
    toObject: function(propertiesToInclude) {
15535
      var o = extend(this.callSuper('toObject', propertiesToInclude), {
15536
        path: this.path.map(function(item) { return item.slice() }),
15537
        pathOffset: this.pathOffset
15538
      });
15539
      if (this.sourcePath) {
15540
        o.sourcePath = this.sourcePath;
15541
      }
15542
      if (this.transformMatrix) {
15543
        o.transformMatrix = this.transformMatrix;
15544
      }
15545
      return o;
15546
    },
15547
 
15548
    /**
15549
     * Returns dataless object representation of an instance
15550
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
15551
     * @return {Object} object representation of an instance
15552
     */
15553
    toDatalessObject: function(propertiesToInclude) {
15554
      var o = this.toObject(propertiesToInclude);
15555
      if (this.sourcePath) {
15556
        o.path = this.sourcePath;
15557
      }
15558
      delete o.sourcePath;
15559
      return o;
15560
    },
15561
 
15562
    /* _TO_SVG_START_ */
15563
    /**
15564
     * Returns svg representation of an instance
15565
     * @param {Function} [reviver] Method for further parsing of svg representation.
15566
     * @return {String} svg representation of an instance
15567
     */
15568
    toSVG: function(reviver) {
15569
      var chunks = [],
15570
          markup = this._createBaseSVGMarkup(), addTransform = '';
15571
 
15572
      for (var i = 0, len = this.path.length; i < len; i++) {
15573
        chunks.push(this.path[i].join(' '));
15574
      }
15575
      var path = chunks.join(' ');
15576
      if (!(this.group && this.group.type === 'path-group')) {
15577
        addTransform = ' translate(' + (-this.pathOffset.x) + ', ' + (-this.pathOffset.y) + ') ';
15578
      }
15579
      markup.push(
15580
        //jscs:disable validateIndentation
15581
        '<path ',
15582
          'd="', path,
15583
          '" style="', this.getSvgStyles(),
15584
          '" transform="', this.getSvgTransform(), addTransform,
15585
          this.getSvgTransformMatrix(), '" stroke-linecap="round" ',
15586
        '/>\n'
15587
        //jscs:enable validateIndentation
15588
      );
15589
 
15590
      return reviver ? reviver(markup.join('')) : markup.join('');
15591
    },
15592
    /* _TO_SVG_END_ */
15593
 
15594
    /**
15595
     * Returns number representation of an instance complexity
15596
     * @return {Number} complexity of this instance
15597
     */
15598
    complexity: function() {
15599
      return this.path.length;
15600
    },
15601
 
15602
    /**
15603
     * @private
15604
     */
15605
    _parsePath: function() {
15606
      var result = [ ],
15607
          coords = [ ],
15608
          currentPath,
15609
          parsed,
15610
          re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/ig,
15611
          match,
15612
          coordsStr;
15613
 
15614
      for (var i = 0, coordsParsed, len = this.path.length; i < len; i++) {
15615
        currentPath = this.path[i];
15616
 
15617
        coordsStr = currentPath.slice(1).trim();
15618
        coords.length = 0;
15619
 
15620
        while ((match = re.exec(coordsStr))) {
15621
          coords.push(match[0]);
15622
        }
15623
 
15624
        coordsParsed = [ currentPath.charAt(0) ];
15625
 
15626
        for (var j = 0, jlen = coords.length; j < jlen; j++) {
15627
          parsed = parseFloat(coords[j]);
15628
          if (!isNaN(parsed)) {
15629
            coordsParsed.push(parsed);
15630
          }
15631
        }
15632
 
15633
        var command = coordsParsed[0],
15634
            commandLength = commandLengths[command.toLowerCase()],
15635
            repeatedCommand = repeatedCommands[command] || command;
15636
 
15637
        if (coordsParsed.length - 1 > commandLength) {
15638
          for (var k = 1, klen = coordsParsed.length; k < klen; k += commandLength) {
15639
            result.push([ command ].concat(coordsParsed.slice(k, k + commandLength)));
15640
            command = repeatedCommand;
15641
          }
15642
        }
15643
        else {
15644
          result.push(coordsParsed);
15645
        }
15646
      }
15647
 
15648
      return result;
15649
    },
15650
 
15651
    /**
15652
     * @private
15653
     */
15654
    _parseDimensions: function() {
15655
 
15656
      var aX = [],
15657
          aY = [],
15658
          current, // current instruction
15659
          previous = null,
15660
          subpathStartX = 0,
15661
          subpathStartY = 0,
15662
          x = 0, // current x
15663
          y = 0, // current y
15664
          controlX = 0, // current control point x
15665
          controlY = 0, // current control point y
15666
          tempX,
15667
          tempY,
15668
          bounds;
15669
 
15670
      for (var i = 0, len = this.path.length; i < len; ++i) {
15671
 
15672
        current = this.path[i];
15673
 
15674
        switch (current[0]) { // first letter
15675
 
15676
          case 'l': // lineto, relative
15677
            x += current[1];
15678
            y += current[2];
15679
            bounds = [ ];
15680
            break;
15681
 
15682
          case 'L': // lineto, absolute
15683
            x = current[1];
15684
            y = current[2];
15685
            bounds = [ ];
15686
            break;
15687
 
15688
          case 'h': // horizontal lineto, relative
15689
            x += current[1];
15690
            bounds = [ ];
15691
            break;
15692
 
15693
          case 'H': // horizontal lineto, absolute
15694
            x = current[1];
15695
            bounds = [ ];
15696
            break;
15697
 
15698
          case 'v': // vertical lineto, relative
15699
            y += current[1];
15700
            bounds = [ ];
15701
            break;
15702
 
15703
          case 'V': // verical lineto, absolute
15704
            y = current[1];
15705
            bounds = [ ];
15706
            break;
15707
 
15708
          case 'm': // moveTo, relative
15709
            x += current[1];
15710
            y += current[2];
15711
            subpathStartX = x;
15712
            subpathStartY = y;
15713
            bounds = [ ];
15714
            break;
15715
 
15716
          case 'M': // moveTo, absolute
15717
            x = current[1];
15718
            y = current[2];
15719
            subpathStartX = x;
15720
            subpathStartY = y;
15721
            bounds = [ ];
15722
            break;
15723
 
15724
          case 'c': // bezierCurveTo, relative
15725
            tempX = x + current[5];
15726
            tempY = y + current[6];
15727
            controlX = x + current[3];
15728
            controlY = y + current[4];
15729
            bounds = fabric.util.getBoundsOfCurve(x, y,
15730
              x + current[1], // x1
15731
              y + current[2], // y1
15732
              controlX, // x2
15733
              controlY, // y2
15734
              tempX,
15735
              tempY
15736
            );
15737
            x = tempX;
15738
            y = tempY;
15739
            break;
15740
 
15741
          case 'C': // bezierCurveTo, absolute
15742
            x = current[5];
15743
            y = current[6];
15744
            controlX = current[3];
15745
            controlY = current[4];
15746
            bounds = fabric.util.getBoundsOfCurve(x, y,
15747
              current[1],
15748
              current[2],
15749
              controlX,
15750
              controlY,
15751
              x,
15752
              y
15753
            );
15754
            break;
15755
 
15756
          case 's': // shorthand cubic bezierCurveTo, relative
15757
 
15758
            // transform to absolute x,y
15759
            tempX = x + current[3];
15760
            tempY = y + current[4];
15761
 
15762
            if (previous[0].match(/[CcSs]/) === null) {
15763
              // If there is no previous command or if the previous command was not a C, c, S, or s,
15764
              // the control point is coincident with the current point
15765
              controlX = x;
15766
              controlY = y;
15767
            }
15768
            else {
15769
              // calculate reflection of previous control points
15770
              controlX = 2 * x - controlX;
15771
              controlY = 2 * y - controlY;
15772
            }
15773
 
15774
            bounds = fabric.util.getBoundsOfCurve(x, y,
15775
              controlX,
15776
              controlY,
15777
              x + current[1],
15778
              y + current[2],
15779
              tempX,
15780
              tempY
15781
            );
15782
            // set control point to 2nd one of this command
15783
            // "... the first control point is assumed to be
15784
            // the reflection of the second control point on
15785
            // the previous command relative to the current point."
15786
            controlX = x + current[1];
15787
            controlY = y + current[2];
15788
            x = tempX;
15789
            y = tempY;
15790
            break;
15791
 
15792
          case 'S': // shorthand cubic bezierCurveTo, absolute
15793
            tempX = current[3];
15794
            tempY = current[4];
15795
            if (previous[0].match(/[CcSs]/) === null) {
15796
              // If there is no previous command or if the previous command was not a C, c, S, or s,
15797
              // the control point is coincident with the current point
15798
              controlX = x;
15799
              controlY = y;
15800
            }
15801
            else {
15802
              // calculate reflection of previous control points
15803
              controlX = 2 * x - controlX;
15804
              controlY = 2 * y - controlY;
15805
            }
15806
            bounds = fabric.util.getBoundsOfCurve(x, y,
15807
              controlX,
15808
              controlY,
15809
              current[1],
15810
              current[2],
15811
              tempX,
15812
              tempY
15813
            );
15814
            x = tempX;
15815
            y = tempY;
15816
            // set control point to 2nd one of this command
15817
            // "... the first control point is assumed to be
15818
            // the reflection of the second control point on
15819
            // the previous command relative to the current point."
15820
            controlX = current[1];
15821
            controlY = current[2];
15822
            break;
15823
 
15824
          case 'q': // quadraticCurveTo, relative
15825
            // transform to absolute x,y
15826
            tempX = x + current[3];
15827
            tempY = y + current[4];
15828
            controlX = x + current[1];
15829
            controlY = y + current[2];
15830
            bounds = fabric.util.getBoundsOfCurve(x, y,
15831
              controlX,
15832
              controlY,
15833
              controlX,
15834
              controlY,
15835
              tempX,
15836
              tempY
15837
            );
15838
            x = tempX;
15839
            y = tempY;
15840
            break;
15841
 
15842
          case 'Q': // quadraticCurveTo, absolute
15843
            controlX = current[1];
15844
            controlY = current[2];
15845
            bounds = fabric.util.getBoundsOfCurve(x, y,
15846
              controlX,
15847
              controlY,
15848
              controlX,
15849
              controlY,
15850
              current[3],
15851
              current[4]
15852
            );
15853
            x = current[3];
15854
            y = current[4];
15855
            break;
15856
 
15857
          case 't': // shorthand quadraticCurveTo, relative
15858
            // transform to absolute x,y
15859
            tempX = x + current[1];
15860
            tempY = y + current[2];
15861
            if (previous[0].match(/[QqTt]/) === null) {
15862
              // If there is no previous command or if the previous command was not a Q, q, T or t,
15863
              // assume the control point is coincident with the current point
15864
              controlX = x;
15865
              controlY = y;
15866
            }
15867
            else {
15868
              // calculate reflection of previous control point
15869
              controlX = 2 * x - controlX;
15870
              controlY = 2 * y - controlY;
15871
            }
15872
 
15873
            bounds = fabric.util.getBoundsOfCurve(x, y,
15874
              controlX,
15875
              controlY,
15876
              controlX,
15877
              controlY,
15878
              tempX,
15879
              tempY
15880
            );
15881
            x = tempX;
15882
            y = tempY;
15883
 
15884
            break;
15885
 
15886
          case 'T':
15887
            tempX = current[1];
15888
            tempY = current[2];
15889
 
15890
            if (previous[0].match(/[QqTt]/) === null) {
15891
              // If there is no previous command or if the previous command was not a Q, q, T or t,
15892
              // assume the control point is coincident with the current point
15893
              controlX = x;
15894
              controlY = y;
15895
            }
15896
            else {
15897
              // calculate reflection of previous control point
15898
              controlX = 2 * x - controlX;
15899
              controlY = 2 * y - controlY;
15900
            }
15901
            bounds = fabric.util.getBoundsOfCurve(x, y,
15902
              controlX,
15903
              controlY,
15904
              controlX,
15905
              controlY,
15906
              tempX,
15907
              tempY
15908
            );
15909
            x = tempX;
15910
            y = tempY;
15911
            break;
15912
 
15913
          case 'a':
15914
            // TODO: optimize this
15915
            bounds = fabric.util.getBoundsOfArc(x, y,
15916
              current[1],
15917
              current[2],
15918
              current[3],
15919
              current[4],
15920
              current[5],
15921
              current[6] + x,
15922
              current[7] + y
15923
            );
15924
            x += current[6];
15925
            y += current[7];
15926
            break;
15927
 
15928
          case 'A':
15929
            // TODO: optimize this
15930
            bounds = fabric.util.getBoundsOfArc(x, y,
15931
              current[1],
15932
              current[2],
15933
              current[3],
15934
              current[4],
15935
              current[5],
15936
              current[6],
15937
              current[7]
15938
            );
15939
            x = current[6];
15940
            y = current[7];
15941
            break;
15942
 
15943
          case 'z':
15944
          case 'Z':
15945
            x = subpathStartX;
15946
            y = subpathStartY;
15947
            break;
15948
        }
15949
        previous = current;
15950
        bounds.forEach(function (point) {
15951
          aX.push(point.x);
15952
          aY.push(point.y);
15953
        });
15954
        aX.push(x);
15955
        aY.push(y);
15956
      }
15957
 
15958
      var minX = min(aX),
15959
          minY = min(aY),
15960
          maxX = max(aX),
15961
          maxY = max(aY),
15962
          deltaX = maxX - minX,
15963
          deltaY = maxY - minY,
15964
 
15965
          o = {
15966
            left: minX,
15967
            top: minY,
15968
            width: deltaX,
15969
            height: deltaY
15970
          };
15971
 
15972
      return o;
15973
    }
15974
  });
15975
 
15976
  /**
15977
   * Creates an instance of fabric.Path from an object
15978
   * @static
15979
   * @memberOf fabric.Path
15980
   * @param {Object} object
15981
   * @param {Function} callback Callback to invoke when an fabric.Path instance is created
15982
   */
15983
  fabric.Path.fromObject = function(object, callback) {
15984
    if (typeof object.path === 'string') {
15985
      fabric.loadSVGFromURL(object.path, function (elements) {
15986
        var path = elements[0],
15987
            pathUrl = object.path;
15988
 
15989
        delete object.path;
15990
 
15991
        fabric.util.object.extend(path, object);
15992
        path.setSourcePath(pathUrl);
15993
 
15994
        callback(path);
15995
      });
15996
    }
15997
    else {
15998
      callback(new fabric.Path(object.path, object));
15999
    }
16000
  };
16001
 
16002
  /* _FROM_SVG_START_ */
16003
  /**
16004
   * List of attribute names to account for when parsing SVG element (used by `fabric.Path.fromElement`)
16005
   * @static
16006
   * @memberOf fabric.Path
16007
   * @see http://www.w3.org/TR/SVG/paths.html#PathElement
16008
   */
16009
  fabric.Path.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(['d']);
16010
 
16011
  /**
16012
   * Creates an instance of fabric.Path from an SVG <path> element
16013
   * @static
16014
   * @memberOf fabric.Path
16015
   * @param {SVGElement} element to parse
16016
   * @param {Function} callback Callback to invoke when an fabric.Path instance is created
16017
   * @param {Object} [options] Options object
16018
   */
16019
  fabric.Path.fromElement = function(element, callback, options) {
16020
    var parsedAttributes = fabric.parseAttributes(element, fabric.Path.ATTRIBUTE_NAMES);
16021
    callback && callback(new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options)));
16022
  };
16023
  /* _FROM_SVG_END_ */
16024
 
16025
  /**
16026
   * Indicates that instances of this type are async
16027
   * @static
16028
   * @memberOf fabric.Path
16029
   * @type Boolean
16030
   * @default
16031
   */
16032
  fabric.Path.async = true;
16033
 
16034
})(typeof exports !== 'undefined' ? exports : this);
16035
 
16036
 
16037
(function(global) {
16038
 
16039
  'use strict';
16040
 
16041
  var fabric = global.fabric || (global.fabric = { }),
16042
      extend = fabric.util.object.extend,
16043
      invoke = fabric.util.array.invoke,
16044
      parentToObject = fabric.Object.prototype.toObject;
16045
 
16046
  if (fabric.PathGroup) {
16047
    fabric.warn('fabric.PathGroup is already defined');
16048
    return;
16049
  }
16050
 
16051
  /**
16052
   * Path group class
16053
   * @class fabric.PathGroup
16054
   * @extends fabric.Path
16055
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#path_and_pathgroup}
16056
   * @see {@link fabric.PathGroup#initialize} for constructor definition
16057
   */
16058
  fabric.PathGroup = fabric.util.createClass(fabric.Path, /** @lends fabric.PathGroup.prototype */ {
16059
 
16060
    /**
16061
     * Type of an object
16062
     * @type String
16063
     * @default
16064
     */
16065
    type: 'path-group',
16066
 
16067
    /**
16068
     * Fill value
16069
     * @type String
16070
     * @default
16071
     */
16072
    fill: '',
16073
 
16074
    /**
16075
     * Constructor
16076
     * @param {Array} paths
16077
     * @param {Object} [options] Options object
16078
     * @return {fabric.PathGroup} thisArg
16079
     */
16080
    initialize: function(paths, options) {
16081
 
16082
      options = options || { };
16083
      this.paths = paths || [ ];
16084
 
16085
      for (var i = this.paths.length; i--;) {
16086
        this.paths[i].group = this;
16087
      }
16088
 
16089
      if (options.toBeParsed) {
16090
        this.parseDimensionsFromPaths(options);
16091
        delete options.toBeParsed;
16092
      }
16093
      this.setOptions(options);
16094
      this.setCoords();
16095
 
16096
      if (options.sourcePath) {
16097
        this.setSourcePath(options.sourcePath);
16098
      }
16099
    },
16100
 
16101
    /**
16102
     * Calculate width and height based on paths contained
16103
     */
16104
    parseDimensionsFromPaths: function(options) {
16105
      var points, p, xC = [ ], yC = [ ], path, height, width,
16106
          m = this.transformMatrix;
16107
      for (var j = this.paths.length; j--;) {
16108
        path = this.paths[j];
16109
        height = path.height + path.strokeWidth;
16110
        width = path.width + path.strokeWidth;
16111
        points = [
16112
          { x: path.left, y: path.top },
16113
          { x: path.left + width, y: path.top },
16114
          { x: path.left, y: path.top + height },
16115
          { x: path.left + width, y: path.top + height }
16116
        ];
16117
        for (var i = 0; i < points.length; i++) {
16118
          p = points[i];
16119
          if (m) {
16120
            p = fabric.util.transformPoint(p, m, false);
16121
          }
16122
          xC.push(p.x);
16123
          yC.push(p.y);
16124
        }
16125
      }
16126
      options.width = Math.max.apply(null, xC);
16127
      options.height = Math.max.apply(null, yC);
16128
    },
16129
 
16130
    /**
16131
     * Renders this group on a specified context
16132
     * @param {CanvasRenderingContext2D} ctx Context to render this instance on
16133
     */
16134
    render: function(ctx) {
16135
      // do not render if object is not visible
16136
      if (!this.visible) {
16137
        return;
16138
      }
16139
 
16140
      ctx.save();
16141
 
16142
      if (this.transformMatrix) {
16143
        ctx.transform.apply(ctx, this.transformMatrix);
16144
      }
16145
      this.transform(ctx);
16146
 
16147
      this._setShadow(ctx);
16148
      this.clipTo && fabric.util.clipContext(this, ctx);
16149
      ctx.translate(-this.width/2, -this.height/2);
16150
      for (var i = 0, l = this.paths.length; i < l; ++i) {
16151
        this.paths[i].render(ctx, true);
16152
      }
16153
      this.clipTo && ctx.restore();
16154
      this._removeShadow(ctx);
16155
      ctx.restore();
16156
    },
16157
 
16158
    /**
16159
     * Sets certain property to a certain value
16160
     * @param {String} prop
16161
     * @param {Any} value
16162
     * @return {fabric.PathGroup} thisArg
16163
     */
16164
    _set: function(prop, value) {
16165
 
16166
      if (prop === 'fill' && value && this.isSameColor()) {
16167
        var i = this.paths.length;
16168
        while (i--) {
16169
          this.paths[i]._set(prop, value);
16170
        }
16171
      }
16172
 
16173
      return this.callSuper('_set', prop, value);
16174
    },
16175
 
16176
    /**
16177
     * Returns object representation of this path group
16178
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
16179
     * @return {Object} object representation of an instance
16180
     */
16181
    toObject: function(propertiesToInclude) {
16182
      var o = extend(parentToObject.call(this, propertiesToInclude), {
16183
        paths: invoke(this.getObjects(), 'toObject', propertiesToInclude)
16184
      });
16185
      if (this.sourcePath) {
16186
        o.sourcePath = this.sourcePath;
16187
      }
16188
      return o;
16189
    },
16190
 
16191
    /**
16192
     * Returns dataless object representation of this path group
16193
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
16194
     * @return {Object} dataless object representation of an instance
16195
     */
16196
    toDatalessObject: function(propertiesToInclude) {
16197
      var o = this.toObject(propertiesToInclude);
16198
      if (this.sourcePath) {
16199
        o.paths = this.sourcePath;
16200
      }
16201
      return o;
16202
    },
16203
 
16204
    /* _TO_SVG_START_ */
16205
    /**
16206
     * Returns svg representation of an instance
16207
     * @param {Function} [reviver] Method for further parsing of svg representation.
16208
     * @return {String} svg representation of an instance
16209
     */
16210
    toSVG: function(reviver) {
16211
      var objects = this.getObjects(),
16212
          p = this.getPointByOrigin('left', 'top'),
16213
          translatePart = 'translate(' + p.x + ' ' + p.y + ')',
16214
          markup = [
16215
            //jscs:disable validateIndentation
16216
            '<g ',
16217
              'style="', this.getSvgStyles(), '" ',
16218
              'transform="', this.getSvgTransformMatrix(), translatePart, this.getSvgTransform(), '" ',
16219
            '>\n'
16220
            //jscs:enable validateIndentation
16221
          ];
16222
 
16223
      for (var i = 0, len = objects.length; i < len; i++) {
16224
        markup.push(objects[i].toSVG(reviver));
16225
      }
16226
      markup.push('</g>\n');
16227
 
16228
      return reviver ? reviver(markup.join('')) : markup.join('');
16229
    },
16230
    /* _TO_SVG_END_ */
16231
 
16232
    /**
16233
     * Returns a string representation of this path group
16234
     * @return {String} string representation of an object
16235
     */
16236
    toString: function() {
16237
      return '#<fabric.PathGroup (' + this.complexity() +
16238
        '): { top: ' + this.top + ', left: ' + this.left + ' }>';
16239
    },
16240
 
16241
    /**
16242
     * Returns true if all paths in this group are of same color
16243
     * @return {Boolean} true if all paths are of the same color (`fill`)
16244
     */
16245
    isSameColor: function() {
16246
      var firstPathFill = (this.getObjects()[0].get('fill') || '').toLowerCase();
16247
      return this.getObjects().every(function(path) {
16248
        return (path.get('fill') || '').toLowerCase() === firstPathFill;
16249
      });
16250
    },
16251
 
16252
    /**
16253
     * Returns number representation of object's complexity
16254
     * @return {Number} complexity
16255
     */
16256
    complexity: function() {
16257
      return this.paths.reduce(function(total, path) {
16258
        return total + ((path && path.complexity) ? path.complexity() : 0);
16259
      }, 0);
16260
    },
16261
 
16262
    /**
16263
     * Returns all paths in this path group
16264
     * @return {Array} array of path objects included in this path group
16265
     */
16266
    getObjects: function() {
16267
      return this.paths;
16268
    }
16269
  });
16270
 
16271
  /**
16272
   * Creates fabric.PathGroup instance from an object representation
16273
   * @static
16274
   * @memberOf fabric.PathGroup
16275
   * @param {Object} object Object to create an instance from
16276
   * @param {Function} callback Callback to invoke when an fabric.PathGroup instance is created
16277
   */
16278
  fabric.PathGroup.fromObject = function(object, callback) {
16279
    if (typeof object.paths === 'string') {
16280
      fabric.loadSVGFromURL(object.paths, function (elements) {
16281
 
16282
        var pathUrl = object.paths;
16283
        delete object.paths;
16284
 
16285
        var pathGroup = fabric.util.groupSVGElements(elements, object, pathUrl);
16286
 
16287
        callback(pathGroup);
16288
      });
16289
    }
16290
    else {
16291
      fabric.util.enlivenObjects(object.paths, function(enlivenedObjects) {
16292
        delete object.paths;
16293
        callback(new fabric.PathGroup(enlivenedObjects, object));
16294
      });
16295
    }
16296
  };
16297
 
16298
  /**
16299
   * Indicates that instances of this type are async
16300
   * @static
16301
   * @memberOf fabric.PathGroup
16302
   * @type Boolean
16303
   * @default
16304
   */
16305
  fabric.PathGroup.async = true;
16306
 
16307
})(typeof exports !== 'undefined' ? exports : this);
16308
 
16309
 
16310
(function(global) {
16311
 
16312
  'use strict';
16313
 
16314
  var fabric = global.fabric || (global.fabric = { }),
16315
      extend = fabric.util.object.extend,
16316
      min = fabric.util.array.min,
16317
      max = fabric.util.array.max,
16318
      invoke = fabric.util.array.invoke;
16319
 
16320
  if (fabric.Group) {
16321
    return;
16322
  }
16323
 
16324
  // lock-related properties, for use in fabric.Group#get
16325
  // to enable locking behavior on group
16326
  // when one of its objects has lock-related properties set
16327
  var _lockProperties = {
16328
    lockMovementX:  true,
16329
    lockMovementY:  true,
16330
    lockRotation:   true,
16331
    lockScalingX:   true,
16332
    lockScalingY:   true,
16333
    lockUniScaling: true
16334
  };
16335
 
16336
  /**
16337
   * Group class
16338
   * @class fabric.Group
16339
   * @extends fabric.Object
16340
   * @mixes fabric.Collection
16341
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#groups}
16342
   * @see {@link fabric.Group#initialize} for constructor definition
16343
   */
16344
  fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, /** @lends fabric.Group.prototype */ {
16345
 
16346
    /**
16347
     * Type of an object
16348
     * @type String
16349
     * @default
16350
     */
16351
    type: 'group',
16352
 
16353
    /**
16354
     * Constructor
16355
     * @param {Object} objects Group objects
16356
     * @param {Object} [options] Options object
16357
     * @return {Object} thisArg
16358
     */
16359
    initialize: function(objects, options) {
16360
      options = options || { };
16361
 
16362
      this._objects = objects || [];
16363
      for (var i = this._objects.length; i--; ) {
16364
        this._objects[i].group = this;
16365
      }
16366
 
16367
      this.originalState = { };
16368
      this.callSuper('initialize');
16369
 
16370
      if (options.originX) {
16371
        this.originX = options.originX;
16372
      }
16373
 
16374
      if (options.originY) {
16375
        this.originY = options.originY;
16376
      }
16377
 
16378
      this._calcBounds();
16379
      this._updateObjectsCoords();
16380
 
16381
      this.callSuper('initialize', options);
16382
 
16383
      this.setCoords();
16384
      this.saveCoords();
16385
    },
16386
 
16387
    /**
16388
     * @private
16389
     */
16390
    _updateObjectsCoords: function() {
16391
      this.forEachObject(this._updateObjectCoords, this);
16392
    },
16393
 
16394
    /**
16395
     * @private
16396
     */
16397
    _updateObjectCoords: function(object) {
16398
      var objectLeft = object.getLeft(),
16399
          objectTop = object.getTop(),
16400
          center = this.getCenterPoint();
16401
 
16402
      object.set({
16403
        originalLeft: objectLeft,
16404
        originalTop: objectTop,
16405
        left: objectLeft - center.x,
16406
        top: objectTop - center.y
16407
      });
16408
 
16409
      object.setCoords();
16410
 
16411
      // do not display corners of objects enclosed in a group
16412
      object.__origHasControls = object.hasControls;
16413
      object.hasControls = false;
16414
    },
16415
 
16416
    /**
16417
     * Returns string represenation of a group
16418
     * @return {String}
16419
     */
16420
    toString: function() {
16421
      return '#<fabric.Group: (' + this.complexity() + ')>';
16422
    },
16423
 
16424
    /**
16425
     * Adds an object to a group; Then recalculates group's dimension, position.
16426
     * @param {Object} object
16427
     * @return {fabric.Group} thisArg
16428
     * @chainable
16429
     */
16430
    addWithUpdate: function(object) {
16431
      this._restoreObjectsState();
16432
      if (object) {
16433
        this._objects.push(object);
16434
        object.group = this;
16435
      }
16436
      // since _restoreObjectsState set objects inactive
16437
      this.forEachObject(this._setObjectActive, this);
16438
      this._calcBounds();
16439
      this._updateObjectsCoords();
16440
      return this;
16441
    },
16442
 
16443
    /**
16444
     * @private
16445
     */
16446
    _setObjectActive: function(object) {
16447
      object.set('active', true);
16448
      object.group = this;
16449
    },
16450
 
16451
    /**
16452
     * Removes an object from a group; Then recalculates group's dimension, position.
16453
     * @param {Object} object
16454
     * @return {fabric.Group} thisArg
16455
     * @chainable
16456
     */
16457
    removeWithUpdate: function(object) {
16458
      this._moveFlippedObject(object);
16459
      this._restoreObjectsState();
16460
 
16461
      // since _restoreObjectsState set objects inactive
16462
      this.forEachObject(this._setObjectActive, this);
16463
 
16464
      this.remove(object);
16465
      this._calcBounds();
16466
      this._updateObjectsCoords();
16467
 
16468
      return this;
16469
    },
16470
 
16471
    /**
16472
     * @private
16473
     */
16474
    _onObjectAdded: function(object) {
16475
      object.group = this;
16476
    },
16477
 
16478
    /**
16479
     * @private
16480
     */
16481
    _onObjectRemoved: function(object) {
16482
      delete object.group;
16483
      object.set('active', false);
16484
    },
16485
 
16486
    /**
16487
     * Properties that are delegated to group objects when reading/writing
16488
     * @param {Object} delegatedProperties
16489
     */
16490
    delegatedProperties: {
16491
      fill:             true,
16492
      opacity:          true,
16493
      fontFamily:       true,
16494
      fontWeight:       true,
16495
      fontSize:         true,
16496
      fontStyle:        true,
16497
      lineHeight:       true,
16498
      textDecoration:   true,
16499
      textAlign:        true,
16500
      backgroundColor:  true
16501
    },
16502
 
16503
    /**
16504
     * @private
16505
     */
16506
    _set: function(key, value) {
16507
      if (key in this.delegatedProperties) {
16508
        var i = this._objects.length;
16509
        while (i--) {
16510
          this._objects[i].set(key, value);
16511
        }
16512
      }
16513
      this.callSuper('_set', key, value);
16514
    },
16515
 
16516
    /**
16517
     * Returns object representation of an instance
16518
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
16519
     * @return {Object} object representation of an instance
16520
     */
16521
    toObject: function(propertiesToInclude) {
16522
      return extend(this.callSuper('toObject', propertiesToInclude), {
16523
        objects: invoke(this._objects, 'toObject', propertiesToInclude)
16524
      });
16525
    },
16526
 
16527
    /**
16528
     * Renders instance on a given context
16529
     * @param {CanvasRenderingContext2D} ctx context to render instance on
16530
     */
16531
    render: function(ctx) {
16532
      // do not render if object is not visible
16533
      if (!this.visible) {
16534
        return;
16535
      }
16536
 
16537
      ctx.save();
16538
      this.clipTo && fabric.util.clipContext(this, ctx);
16539
      this.transform(ctx);
16540
      // the array is now sorted in order of highest first, so start from end
16541
      for (var i = 0, len = this._objects.length; i < len; i++) {
16542
        this._renderObject(this._objects[i], ctx);
16543
      }
16544
 
16545
      this.clipTo && ctx.restore();
16546
 
16547
      ctx.restore();
16548
    },
16549
 
16550
    /**
16551
     * Renders controls and borders for the object
16552
     * @param {CanvasRenderingContext2D} ctx Context to render on
16553
     * @param {Boolean} [noTransform] When true, context is not transformed
16554
     */
16555
    _renderControls: function(ctx, noTransform) {
16556
      this.callSuper('_renderControls', ctx, noTransform);
16557
      for (var i = 0, len = this._objects.length; i < len; i++) {
16558
        this._objects[i]._renderControls(ctx);
16559
      }
16560
    },
16561
 
16562
    /**
16563
     * @private
16564
     */
16565
    _renderObject: function(object, ctx) {
16566
      var originalHasRotatingPoint = object.hasRotatingPoint;
16567
 
16568
      // do not render if object is not visible
16569
      if (!object.visible) {
16570
        return;
16571
      }
16572
 
16573
      object.hasRotatingPoint = false;
16574
 
16575
      object.render(ctx);
16576
 
16577
      object.hasRotatingPoint = originalHasRotatingPoint;
16578
    },
16579
 
16580
    /**
16581
     * Retores original state of each of group objects (original state is that which was before group was created).
16582
     * @private
16583
     * @return {fabric.Group} thisArg
16584
     * @chainable
16585
     */
16586
    _restoreObjectsState: function() {
16587
      this._objects.forEach(this._restoreObjectState, this);
16588
      return this;
16589
    },
16590
 
16591
    /**
16592
     * Realises the transform from this group onto the supplied object
16593
     * i.e. it tells you what would happen if the supplied object was in
16594
     * the group, and then the group was destroyed. It mutates the supplied
16595
     * object.
16596
     * @param {fabric.Object} object
16597
     * @return {fabric.Object} transformedObject
16598
    */
16599
    realizeTransform: function(object) {
16600
      this._moveFlippedObject(object);
16601
      this._setObjectPosition(object);
16602
      return object;
16603
    },
16604
    /**
16605
     * Moves a flipped object to the position where it's displayed
16606
     * @private
16607
     * @param {fabric.Object} object
16608
     * @return {fabric.Group} thisArg
16609
     */
16610
    _moveFlippedObject: function(object) {
16611
      var oldOriginX = object.get('originX'),
16612
          oldOriginY = object.get('originY'),
16613
          center = object.getCenterPoint();
16614
 
16615
      object.set({
16616
        originX: 'center',
16617
        originY: 'center',
16618
        left: center.x,
16619
        top: center.y
16620
      });
16621
 
16622
      this._toggleFlipping(object);
16623
 
16624
      var newOrigin = object.getPointByOrigin(oldOriginX, oldOriginY);
16625
 
16626
      object.set({
16627
        originX: oldOriginX,
16628
        originY: oldOriginY,
16629
        left: newOrigin.x,
16630
        top: newOrigin.y
16631
      });
16632
 
16633
      return this;
16634
    },
16635
 
16636
    /**
16637
     * @private
16638
     */
16639
    _toggleFlipping: function(object) {
16640
      if (this.flipX) {
16641
        object.toggle('flipX');
16642
        object.set('left', -object.get('left'));
16643
        object.setAngle(-object.getAngle());
16644
      }
16645
      if (this.flipY) {
16646
        object.toggle('flipY');
16647
        object.set('top', -object.get('top'));
16648
        object.setAngle(-object.getAngle());
16649
      }
16650
    },
16651
 
16652
    /**
16653
     * Restores original state of a specified object in group
16654
     * @private
16655
     * @param {fabric.Object} object
16656
     * @return {fabric.Group} thisArg
16657
     */
16658
    _restoreObjectState: function(object) {
16659
      this._setObjectPosition(object);
16660
 
16661
      object.setCoords();
16662
      object.hasControls = object.__origHasControls;
16663
      delete object.__origHasControls;
16664
      object.set('active', false);
16665
      object.setCoords();
16666
      delete object.group;
16667
 
16668
      return this;
16669
    },
16670
 
16671
    /**
16672
     * @private
16673
     */
16674
    _setObjectPosition: function(object) {
16675
      var center = this.getCenterPoint(),
16676
          rotated = this._getRotatedLeftTop(object);
16677
 
16678
      object.set({
16679
        angle: object.getAngle() + this.getAngle(),
16680
        left: center.x + rotated.left,
16681
        top: center.y + rotated.top,
16682
        scaleX: object.get('scaleX') * this.get('scaleX'),
16683
        scaleY: object.get('scaleY') * this.get('scaleY')
16684
      });
16685
    },
16686
 
16687
    /**
16688
     * @private
16689
     */
16690
    _getRotatedLeftTop: function(object) {
16691
      var groupAngle = this.getAngle() * (Math.PI / 180);
16692
      return {
16693
        left: (-Math.sin(groupAngle) * object.getTop() * this.get('scaleY') +
16694
                Math.cos(groupAngle) * object.getLeft() * this.get('scaleX')),
16695
 
16696
        top:  (Math.cos(groupAngle) * object.getTop() * this.get('scaleY') +
16697
               Math.sin(groupAngle) * object.getLeft() * this.get('scaleX'))
16698
      };
16699
    },
16700
 
16701
    /**
16702
     * Destroys a group (restoring state of its objects)
16703
     * @return {fabric.Group} thisArg
16704
     * @chainable
16705
     */
16706
    destroy: function() {
16707
      this._objects.forEach(this._moveFlippedObject, this);
16708
      return this._restoreObjectsState();
16709
    },
16710
 
16711
    /**
16712
     * Saves coordinates of this instance (to be used together with `hasMoved`)
16713
     * @saveCoords
16714
     * @return {fabric.Group} thisArg
16715
     * @chainable
16716
     */
16717
    saveCoords: function() {
16718
      this._originalLeft = this.get('left');
16719
      this._originalTop = this.get('top');
16720
      return this;
16721
    },
16722
 
16723
    /**
16724
     * Checks whether this group was moved (since `saveCoords` was called last)
16725
     * @return {Boolean} true if an object was moved (since fabric.Group#saveCoords was called)
16726
     */
16727
    hasMoved: function() {
16728
      return this._originalLeft !== this.get('left') ||
16729
             this._originalTop !== this.get('top');
16730
    },
16731
 
16732
    /**
16733
     * Sets coordinates of all group objects
16734
     * @return {fabric.Group} thisArg
16735
     * @chainable
16736
     */
16737
    setObjectsCoords: function() {
16738
      this.forEachObject(function(object) {
16739
        object.setCoords();
16740
      });
16741
      return this;
16742
    },
16743
 
16744
    /**
16745
     * @private
16746
     */
16747
    _calcBounds: function(onlyWidthHeight) {
16748
      var aX = [],
16749
          aY = [],
16750
          o, prop,
16751
          props = ['tr', 'br', 'bl', 'tl'];
16752
 
16753
      for (var i = 0, len = this._objects.length; i < len; ++i) {
16754
        o = this._objects[i];
16755
        o.setCoords();
16756
        for (var j = 0; j < props.length; j++) {
16757
          prop = props[j];
16758
          aX.push(o.oCoords[prop].x);
16759
          aY.push(o.oCoords[prop].y);
16760
        }
16761
      }
16762
 
16763
      this.set(this._getBounds(aX, aY, onlyWidthHeight));
16764
    },
16765
 
16766
    /**
16767
     * @private
16768
     */
16769
    _getBounds: function(aX, aY, onlyWidthHeight) {
16770
      var ivt = fabric.util.invertTransform(this.getViewportTransform()),
16771
          minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt),
16772
          maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt),
16773
          obj = {
16774
            width: (maxXY.x - minXY.x) || 0,
16775
            height: (maxXY.y - minXY.y) || 0
16776
          };
16777
 
16778
      if (!onlyWidthHeight) {
16779
        obj.left = minXY.x || 0;
16780
        obj.top = minXY.y || 0;
16781
        if (this.originX === 'center') {
16782
          obj.left += obj.width / 2;
16783
        }
16784
        if (this.originX === 'right') {
16785
          obj.left += obj.width;
16786
        }
16787
        if (this.originY === 'center') {
16788
          obj.top += obj.height / 2;
16789
        }
16790
        if (this.originY === 'bottom') {
16791
          obj.top += obj.height;
16792
        }
16793
      }
16794
      return obj;
16795
    },
16796
 
16797
    /* _TO_SVG_START_ */
16798
    /**
16799
     * Returns svg representation of an instance
16800
     * @param {Function} [reviver] Method for further parsing of svg representation.
16801
     * @return {String} svg representation of an instance
16802
     */
16803
    toSVG: function(reviver) {
16804
      var markup = [
16805
        //jscs:disable validateIndentation
16806
        '<g ',
16807
          'transform="', this.getSvgTransform(),
16808
        '">\n'
16809
        //jscs:enable validateIndentation
16810
      ];
16811
 
16812
      for (var i = 0, len = this._objects.length; i < len; i++) {
16813
        markup.push(this._objects[i].toSVG(reviver));
16814
      }
16815
 
16816
      markup.push('</g>\n');
16817
 
16818
      return reviver ? reviver(markup.join('')) : markup.join('');
16819
    },
16820
    /* _TO_SVG_END_ */
16821
 
16822
    /**
16823
     * Returns requested property
16824
     * @param {String} prop Property to get
16825
     * @return {Any}
16826
     */
16827
    get: function(prop) {
16828
      if (prop in _lockProperties) {
16829
        if (this[prop]) {
16830
          return this[prop];
16831
        }
16832
        else {
16833
          for (var i = 0, len = this._objects.length; i < len; i++) {
16834
            if (this._objects[i][prop]) {
16835
              return true;
16836
            }
16837
          }
16838
          return false;
16839
        }
16840
      }
16841
      else {
16842
        if (prop in this.delegatedProperties) {
16843
          return this._objects[0] && this._objects[0].get(prop);
16844
        }
16845
        return this[prop];
16846
      }
16847
    }
16848
  });
16849
 
16850
  /**
16851
   * Returns {@link fabric.Group} instance from an object representation
16852
   * @static
16853
   * @memberOf fabric.Group
16854
   * @param {Object} object Object to create a group from
16855
   * @param {Function} [callback] Callback to invoke when an group instance is created
16856
   * @return {fabric.Group} An instance of fabric.Group
16857
   */
16858
  fabric.Group.fromObject = function(object, callback) {
16859
    fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) {
16860
      delete object.objects;
16861
      callback && callback(new fabric.Group(enlivenedObjects, object));
16862
    });
16863
  };
16864
 
16865
  /**
16866
   * Indicates that instances of this type are async
16867
   * @static
16868
   * @memberOf fabric.Group
16869
   * @type Boolean
16870
   * @default
16871
   */
16872
  fabric.Group.async = true;
16873
 
16874
})(typeof exports !== 'undefined' ? exports : this);
16875
 
16876
 
16877
(function(global) {
16878
 
16879
  'use strict';
16880
 
16881
  var extend = fabric.util.object.extend;
16882
 
16883
  if (!global.fabric) {
16884
    global.fabric = { };
16885
  }
16886
 
16887
  if (global.fabric.Image) {
16888
    fabric.warn('fabric.Image is already defined.');
16889
    return;
16890
  }
16891
 
16892
  /**
16893
   * Image class
16894
   * @class fabric.Image
16895
   * @extends fabric.Object
16896
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#images}
16897
   * @see {@link fabric.Image#initialize} for constructor definition
16898
   */
16899
  fabric.Image = fabric.util.createClass(fabric.Object, /** @lends fabric.Image.prototype */ {
16900
 
16901
    /**
16902
     * Type of an object
16903
     * @type String
16904
     * @default
16905
     */
16906
    type: 'image',
16907
 
16908
    /**
16909
     * crossOrigin value (one of "", "anonymous", "allow-credentials")
16910
     * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes
16911
     * @type String
16912
     * @default
16913
     */
16914
    crossOrigin: '',
16915
 
16916
    /**
16917
     * AlignX value, part of preserveAspectRatio (one of "none", "mid", "min", "max")
16918
     * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
16919
     * This parameter defines how the picture is aligned to its viewport when image element width differs from image width.
16920
     * @type String
16921
     * @default
16922
     */
16923
    alignX: 'none',
16924
 
16925
    /**
16926
     * AlignY value, part of preserveAspectRatio (one of "none", "mid", "min", "max")
16927
     * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
16928
     * This parameter defines how the picture is aligned to its viewport when image element height differs from image height.
16929
     * @type String
16930
     * @default
16931
     */
16932
    alignY: 'none',
16933
 
16934
    /**
16935
     * meetOrSlice value, part of preserveAspectRatio  (one of "meet", "slice").
16936
     * if meet the image is always fully visibile, if slice the viewport is always filled with image.
16937
     * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
16938
     * @type String
16939
     * @default
16940
     */
16941
    meetOrSlice: 'meet',
16942
 
16943
    /**
16944
     * private
16945
     * contains last value of scaleX to detect
16946
     * if the Image got resized after the last Render
16947
     * @type Number
16948
     */
16949
    _lastScaleX: 1,
16950
 
16951
    /**
16952
     * private
16953
     * contains last value of scaleY to detect
16954
     * if the Image got resized after the last Render
16955
     * @type Number
16956
     */
16957
    _lastScaleY: 1,
16958
 
16959
    /**
16960
     * Constructor
16961
     * @param {HTMLImageElement | String} element Image element
16962
     * @param {Object} [options] Options object
16963
     * @return {fabric.Image} thisArg
16964
     */
16965
    initialize: function(element, options) {
16966
      options || (options = { });
16967
 
16968
      this.filters = [ ];
16969
      this.resizeFilters = [ ];
16970
      this.callSuper('initialize', options);
16971
 
16972
      this._initElement(element, options);
16973
      this._initConfig(options);
16974
 
16975
      if (options.filters) {
16976
        this.filters = options.filters;
16977
        this.applyFilters();
16978
      }
16979
    },
16980
 
16981
    /**
16982
     * Returns image element which this instance if based on
16983
     * @return {HTMLImageElement} Image element
16984
     */
16985
    getElement: function() {
16986
      return this._element;
16987
    },
16988
 
16989
    /**
16990
     * Sets image element for this instance to a specified one.
16991
     * If filters defined they are applied to new image.
16992
     * You might need to call `canvas.renderAll` and `object.setCoords` after replacing, to render new image and update controls area.
16993
     * @param {HTMLImageElement} element
16994
     * @param {Function} [callback] Callback is invoked when all filters have been applied and new image is generated
16995
     * @param {Object} [options] Options object
16996
     * @return {fabric.Image} thisArg
16997
     * @chainable
16998
     */
16999
    setElement: function(element, callback, options) {
17000
      this._element = element;
17001
      this._originalElement = element;
17002
      this._initConfig(options);
17003
 
17004
      if (this.filters.length !== 0) {
17005
        this.applyFilters(callback);
17006
      }
17007
      else if (callback) {
17008
        callback();
17009
      }
17010
 
17011
      return this;
17012
    },
17013
 
17014
    /**
17015
     * Sets crossOrigin value (on an instance and corresponding image element)
17016
     * @return {fabric.Image} thisArg
17017
     * @chainable
17018
     */
17019
    setCrossOrigin: function(value) {
17020
      this.crossOrigin = value;
17021
      this._element.crossOrigin = value;
17022
 
17023
      return this;
17024
    },
17025
 
17026
    /**
17027
     * Returns original size of an image
17028
     * @return {Object} Object with "width" and "height" properties
17029
     */
17030
    getOriginalSize: function() {
17031
      var element = this.getElement();
17032
      return {
17033
        width: element.width,
17034
        height: element.height
17035
      };
17036
    },
17037
 
17038
    /**
17039
     * @private
17040
     * @param {CanvasRenderingContext2D} ctx Context to render on
17041
     */
17042
    _stroke: function(ctx) {
17043
      ctx.save();
17044
      this._setStrokeStyles(ctx);
17045
      ctx.beginPath();
17046
      ctx.strokeRect(-this.width / 2, -this.height / 2, this.width, this.height);
17047
      ctx.closePath();
17048
      ctx.restore();
17049
    },
17050
 
17051
    /**
17052
     * @private
17053
     * @param {CanvasRenderingContext2D} ctx Context to render on
17054
     */
17055
    _renderDashedStroke: function(ctx) {
17056
      var x = -this.width / 2,
17057
          y = -this.height / 2,
17058
          w = this.width,
17059
          h = this.height;
17060
 
17061
      ctx.save();
17062
      this._setStrokeStyles(ctx);
17063
 
17064
      ctx.beginPath();
17065
      fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray);
17066
      fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray);
17067
      fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray);
17068
      fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray);
17069
      ctx.closePath();
17070
      ctx.restore();
17071
    },
17072
 
17073
    /**
17074
     * Returns object representation of an instance
17075
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
17076
     * @return {Object} Object representation of an instance
17077
     */
17078
    toObject: function(propertiesToInclude) {
17079
      return extend(this.callSuper('toObject', propertiesToInclude), {
17080
        src: this._originalElement.src || this._originalElement._src,
17081
        filters: this.filters.map(function(filterObj) {
17082
          return filterObj && filterObj.toObject();
17083
        }),
17084
        crossOrigin: this.crossOrigin,
17085
        alignX: this.alignX,
17086
        alignY: this.alignY,
17087
        meetOrSlice: this.meetOrSlice
17088
      });
17089
    },
17090
 
17091
    /* _TO_SVG_START_ */
17092
    /**
17093
     * Returns SVG representation of an instance
17094
     * @param {Function} [reviver] Method for further parsing of svg representation.
17095
     * @return {String} svg representation of an instance
17096
     */
17097
    toSVG: function(reviver) {
17098
      var markup = [], x = -this.width / 2, y = -this.height / 2,
17099
          preserveAspectRatio = 'none';
17100
      if (this.group && this.group.type === 'path-group') {
17101
        x = this.left;
17102
        y = this.top;
17103
      }
17104
      if (this.alignX !== 'none' && this.alignY !== 'none') {
17105
        preserveAspectRatio = 'x' + this.alignX + 'Y' + this.alignY + ' ' + this.meetOrSlice;
17106
      }
17107
      markup.push(
17108
        '<g transform="', this.getSvgTransform(), this.getSvgTransformMatrix(), '">\n',
17109
          '<image xlink:href="', this.getSvgSrc(),
17110
            '" x="', x, '" y="', y,
17111
            '" style="', this.getSvgStyles(),
17112
            // we're essentially moving origin of transformation from top/left corner to the center of the shape
17113
            // by wrapping it in container <g> element with actual transformation, then offsetting object to the top/left
17114
            // so that object's center aligns with container's left/top
17115
            '" width="', this.width,
17116
            '" height="', this.height,
17117
            '" preserveAspectRatio="', preserveAspectRatio, '"',
17118
          '></image>\n'
17119
      );
17120
 
17121
      if (this.stroke || this.strokeDashArray) {
17122
        var origFill = this.fill;
17123
        this.fill = null;
17124
        markup.push(
17125
          '<rect ',
17126
            'x="', x, '" y="', y,
17127
            '" width="', this.width, '" height="', this.height,
17128
            '" style="', this.getSvgStyles(),
17129
          '"/>\n'
17130
        );
17131
        this.fill = origFill;
17132
      }
17133
 
17134
      markup.push('</g>\n');
17135
 
17136
      return reviver ? reviver(markup.join('')) : markup.join('');
17137
    },
17138
    /* _TO_SVG_END_ */
17139
 
17140
    /**
17141
     * Returns source of an image
17142
     * @return {String} Source of an image
17143
     */
17144
    getSrc: function() {
17145
      if (this.getElement()) {
17146
        return this.getElement().src || this.getElement()._src;
17147
      }
17148
    },
17149
 
17150
    /**
17151
     * Sets source of an image
17152
     * @param {String} src Source string (URL)
17153
     * @param {Function} [callback] Callback is invoked when image has been loaded (and all filters have been applied)
17154
     * @param {Object} [options] Options object
17155
     * @return {fabric.Image} thisArg
17156
     * @chainable
17157
     */
17158
    setSrc: function(src, callback, options) {
17159
      fabric.util.loadImage(src, function(img) {
17160
        return this.setElement(img, callback, options);
17161
      }, this, options && options.crossOrigin);
17162
    },
17163
 
17164
    /**
17165
     * Returns string representation of an instance
17166
     * @return {String} String representation of an instance
17167
     */
17168
    toString: function() {
17169
      return '#<fabric.Image: { src: "' + this.getSrc() + '" }>';
17170
    },
17171
 
17172
    /**
17173
     * Returns a clone of an instance
17174
     * @param {Function} callback Callback is invoked with a clone as a first argument
17175
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
17176
     */
17177
    clone: function(callback, propertiesToInclude) {
17178
      this.constructor.fromObject(this.toObject(propertiesToInclude), callback);
17179
    },
17180
 
17181
    /**
17182
     * Applies filters assigned to this image (from "filters" array)
17183
     * @method applyFilters
17184
     * @param {Function} callback Callback is invoked when all filters have been applied and new image is generated
17185
     * @return {fabric.Image} thisArg
17186
     * @chainable
17187
     */
17188
    applyFilters: function(callback, filters, imgElement, forResizing) {
17189
 
17190
      filters = filters || this.filters;
17191
      imgElement = imgElement || this._originalElement;
17192
 
17193
      if (!imgElement) {
17194
        return;
17195
      }
17196
 
17197
      var imgEl = imgElement,
17198
          canvasEl = fabric.util.createCanvasElement(),
17199
          replacement = fabric.util.createImage(),
17200
          _this = this;
17201
 
17202
      canvasEl.width = imgEl.width;
17203
      canvasEl.height = imgEl.height;
17204
      canvasEl.getContext('2d').drawImage(imgEl, 0, 0, imgEl.width, imgEl.height);
17205
 
17206
      if (filters.length === 0) {
17207
        this._element = imgElement;
17208
        callback && callback();
17209
        return canvasEl;
17210
      }
17211
      filters.forEach(function(filter) {
17212
        filter && filter.applyTo(canvasEl, filter.scaleX || _this.scaleX, filter.scaleY || _this.scaleY);
17213
        if (!forResizing && filter && filter.type === 'Resize') {
17214
          _this.width *= filter.scaleX;
17215
          _this.height *= filter.scaleY;
17216
        }
17217
      });
17218
 
17219
      /** @ignore */
17220
      replacement.width = canvasEl.width;
17221
      replacement.height = canvasEl.height;
17222
 
17223
      if (fabric.isLikelyNode) {
17224
        replacement.src = canvasEl.toBuffer(undefined, fabric.Image.pngCompression);
17225
        // onload doesn't fire in some node versions, so we invoke callback manually
17226
        _this._element = replacement;
17227
        !forResizing && (_this._filteredEl = replacement);
17228
        callback && callback();
17229
      }
17230
      else {
17231
        replacement.onload = function() {
17232
          _this._element = replacement;
17233
          !forResizing && (_this._filteredEl = replacement);
17234
          callback && callback();
17235
          replacement.onload = canvasEl = imgEl = null;
17236
        };
17237
        replacement.src = canvasEl.toDataURL('image/png');
17238
      }
17239
      return canvasEl;
17240
    },
17241
    /**
17242
     * @private
17243
     * @param {CanvasRenderingContext2D} ctx Context to render on
17244
     */
17245
    _render: function(ctx, noTransform) {
17246
      var x, y, imageMargins = this._findMargins(), elementToDraw;
17247
 
17248
      x = (noTransform ? this.left : -this.width / 2);
17249
      y = (noTransform ? this.top : -this.height / 2);
17250
 
17251
      if (this.meetOrSlice === 'slice') {
17252
        ctx.beginPath();
17253
        ctx.rect(x, y, this.width, this.height);
17254
        ctx.clip();
17255
      }
17256
 
17257
      if (this.isMoving === false && this.resizeFilters.length && this._needsResize()) {
17258
        this._lastScaleX = this.scaleX;
17259
        this._lastScaleY = this.scaleY;
17260
        elementToDraw = this.applyFilters(null, this.resizeFilters, this._filteredEl || this._originalElement, true);
17261
      }
17262
      else {
17263
        elementToDraw = this._element;
17264
      }
17265
      elementToDraw && ctx.drawImage(elementToDraw,
17266
                                     x + imageMargins.marginX,
17267
                                     y + imageMargins.marginY,
17268
                                     imageMargins.width,
17269
                                     imageMargins.height
17270
                                    );
17271
 
17272
      this._renderStroke(ctx);
17273
    },
17274
    /**
17275
     * @private, needed to check if image needs resize
17276
     */
17277
    _needsResize: function() {
17278
      return (this.scaleX !== this._lastScaleX || this.scaleY !== this._lastScaleY);
17279
    },
17280
 
17281
    /**
17282
     * @private
17283
     */
17284
    _findMargins: function() {
17285
      var width = this.width, height = this.height, scales,
17286
          scale, marginX = 0, marginY = 0;
17287
 
17288
      if (this.alignX !== 'none' || this.alignY !== 'none') {
17289
        scales = [this.width / this._element.width, this.height / this._element.height];
17290
        scale = this.meetOrSlice === 'meet'
17291
                ? Math.min.apply(null, scales) : Math.max.apply(null, scales);
17292
        width = this._element.width * scale;
17293
        height = this._element.height * scale;
17294
        if (this.alignX === 'Mid') {
17295
          marginX = (this.width - width) / 2;
17296
        }
17297
        if (this.alignX === 'Max') {
17298
          marginX = this.width - width;
17299
        }
17300
        if (this.alignY === 'Mid') {
17301
          marginY = (this.height - height) / 2;
17302
        }
17303
        if (this.alignY === 'Max') {
17304
          marginY = this.height - height;
17305
        }
17306
      }
17307
      return {
17308
        width:  width,
17309
        height: height,
17310
        marginX: marginX,
17311
        marginY: marginY
17312
      };
17313
    },
17314
 
17315
    /**
17316
     * @private
17317
     */
17318
    _resetWidthHeight: function() {
17319
      var element = this.getElement();
17320
 
17321
      this.set('width', element.width);
17322
      this.set('height', element.height);
17323
    },
17324
 
17325
    /**
17326
     * The Image class's initialization method. This method is automatically
17327
     * called by the constructor.
17328
     * @private
17329
     * @param {HTMLImageElement|String} element The element representing the image
17330
     */
17331
    _initElement: function(element) {
17332
      this.setElement(fabric.util.getById(element));
17333
      fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS);
17334
    },
17335
 
17336
    /**
17337
     * @private
17338
     * @param {Object} [options] Options object
17339
     */
17340
    _initConfig: function(options) {
17341
      options || (options = { });
17342
      this.setOptions(options);
17343
      this._setWidthHeight(options);
17344
      if (this._element && this.crossOrigin) {
17345
        this._element.crossOrigin = this.crossOrigin;
17346
      }
17347
    },
17348
 
17349
    /**
17350
     * @private
17351
     * @param {Object} object Object with filters property
17352
     * @param {Function} callback Callback to invoke when all fabric.Image.filters instances are created
17353
     */
17354
    _initFilters: function(object, callback) {
17355
      if (object.filters && object.filters.length) {
17356
        fabric.util.enlivenObjects(object.filters, function(enlivenedObjects) {
17357
          callback && callback(enlivenedObjects);
17358
        }, 'fabric.Image.filters');
17359
      }
17360
      else {
17361
        callback && callback();
17362
      }
17363
    },
17364
 
17365
    /**
17366
     * @private
17367
     * @param {Object} [options] Object with width/height properties
17368
     */
17369
    _setWidthHeight: function(options) {
17370
      this.width = 'width' in options
17371
        ? options.width
17372
        : (this.getElement()
17373
            ? this.getElement().width || 0
17374
            : 0);
17375
 
17376
      this.height = 'height' in options
17377
        ? options.height
17378
        : (this.getElement()
17379
            ? this.getElement().height || 0
17380
            : 0);
17381
    },
17382
 
17383
    /**
17384
     * Returns complexity of an instance
17385
     * @return {Number} complexity of this instance
17386
     */
17387
    complexity: function() {
17388
      return 1;
17389
    }
17390
  });
17391
 
17392
  /**
17393
   * Default CSS class name for canvas
17394
   * @static
17395
   * @type String
17396
   * @default
17397
   */
17398
  fabric.Image.CSS_CANVAS = 'canvas-img';
17399
 
17400
  /**
17401
   * Alias for getSrc
17402
   * @static
17403
   */
17404
  fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc;
17405
 
17406
  /**
17407
   * Creates an instance of fabric.Image from its object representation
17408
   * @static
17409
   * @param {Object} object Object to create an instance from
17410
   * @param {Function} [callback] Callback to invoke when an image instance is created
17411
   */
17412
  fabric.Image.fromObject = function(object, callback) {
17413
    fabric.util.loadImage(object.src, function(img) {
17414
      fabric.Image.prototype._initFilters.call(object, object, function(filters) {
17415
        object.filters = filters || [ ];
17416
        var instance = new fabric.Image(img, object);
17417
        callback && callback(instance);
17418
      });
17419
    }, null, object.crossOrigin);
17420
  };
17421
 
17422
  /**
17423
   * Creates an instance of fabric.Image from an URL string
17424
   * @static
17425
   * @param {String} url URL to create an image from
17426
   * @param {Function} [callback] Callback to invoke when image is created (newly created image is passed as a first argument)
17427
   * @param {Object} [imgOptions] Options object
17428
   */
17429
  fabric.Image.fromURL = function(url, callback, imgOptions) {
17430
    fabric.util.loadImage(url, function(img) {
17431
      callback && callback(new fabric.Image(img, imgOptions));
17432
    }, null, imgOptions && imgOptions.crossOrigin);
17433
  };
17434
 
17435
  /* _FROM_SVG_START_ */
17436
  /**
17437
   * List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement})
17438
   * @static
17439
   * @see {@link http://www.w3.org/TR/SVG/struct.html#ImageElement}
17440
   */
17441
  fabric.Image.ATTRIBUTE_NAMES =
17442
    fabric.SHARED_ATTRIBUTES.concat('x y width height preserveAspectRatio xlink:href'.split(' '));
17443
 
17444
  /**
17445
   * Returns {@link fabric.Image} instance from an SVG element
17446
   * @static
17447
   * @param {SVGElement} element Element to parse
17448
   * @param {Function} callback Callback to execute when fabric.Image object is created
17449
   * @param {Object} [options] Options object
17450
   * @return {fabric.Image} Instance of fabric.Image
17451
   */
17452
  fabric.Image.fromElement = function(element, callback, options) {
17453
    var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES),
17454
        align = 'xMidYMid', meetOrSlice = 'meet', alignX, alignY, aspectRatioAttrs;
17455
 
17456
    if (parsedAttributes.preserveAspectRatio) {
17457
      aspectRatioAttrs = parsedAttributes.preserveAspectRatio.split(' ');
17458
    }
17459
 
17460
    if (aspectRatioAttrs && aspectRatioAttrs.length) {
17461
      meetOrSlice = aspectRatioAttrs.pop();
17462
      if (meetOrSlice !== 'meet' && meetOrSlice !== 'slice') {
17463
        align = meetOrSlice;
17464
        meetOrSlice = 'meet';
17465
      }
17466
      else if (aspectRatioAttrs.length) {
17467
        align = aspectRatioAttrs.pop();
17468
      }
17469
    }
17470
    //divide align in alignX and alignY
17471
    alignX = align !== 'none' ? align.slice(1, 4) : 'none';
17472
    alignY = align !== 'none' ? align.slice(5, 8) : 'none';
17473
    parsedAttributes.alignX = alignX;
17474
    parsedAttributes.alignY = alignY;
17475
    parsedAttributes.meetOrSlice = meetOrSlice;
17476
    fabric.Image.fromURL(parsedAttributes['xlink:href'], callback,
17477
      extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes));
17478
  };
17479
  /* _FROM_SVG_END_ */
17480
 
17481
  /**
17482
   * Indicates that instances of this type are async
17483
   * @static
17484
   * @type Boolean
17485
   * @default
17486
   */
17487
  fabric.Image.async = true;
17488
 
17489
  /**
17490
   * Indicates compression level used when generating PNG under Node (in applyFilters). Any of 0-9
17491
   * @static
17492
   * @type Number
17493
   * @default
17494
   */
17495
  fabric.Image.pngCompression = 1;
17496
 
17497
})(typeof exports !== 'undefined' ? exports : this);
17498
 
17499
 
17500
fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
17501
 
17502
  /**
17503
   * @private
17504
   * @return {Number} angle value
17505
   */
17506
  _getAngleValueForStraighten: function() {
17507
    var angle = this.getAngle() % 360;
17508
    if (angle > 0) {
17509
      return Math.round((angle - 1) / 90) * 90;
17510
    }
17511
    return Math.round(angle / 90) * 90;
17512
  },
17513
 
17514
  /**
17515
   * Straightens an object (rotating it from current angle to one of 0, 90, 180, 270, etc. depending on which is closer)
17516
   * @return {fabric.Object} thisArg
17517
   * @chainable
17518
   */
17519
  straighten: function() {
17520
    this.setAngle(this._getAngleValueForStraighten());
17521
    return this;
17522
  },
17523
 
17524
  /**
17525
   * Same as {@link fabric.Object.prototype.straighten} but with animation
17526
   * @param {Object} callbacks Object with callback functions
17527
   * @param {Function} [callbacks.onComplete] Invoked on completion
17528
   * @param {Function} [callbacks.onChange] Invoked on every step of animation
17529
   * @return {fabric.Object} thisArg
17530
   * @chainable
17531
   */
17532
  fxStraighten: function(callbacks) {
17533
    callbacks = callbacks || { };
17534
 
17535
    var empty = function() { },
17536
        onComplete = callbacks.onComplete || empty,
17537
        onChange = callbacks.onChange || empty,
17538
        _this = this;
17539
 
17540
    fabric.util.animate({
17541
      startValue: this.get('angle'),
17542
      endValue: this._getAngleValueForStraighten(),
17543
      duration: this.FX_DURATION,
17544
      onChange: function(value) {
17545
        _this.setAngle(value);
17546
        onChange();
17547
      },
17548
      onComplete: function() {
17549
        _this.setCoords();
17550
        onComplete();
17551
      },
17552
      onStart: function() {
17553
        _this.set('active', false);
17554
      }
17555
    });
17556
 
17557
    return this;
17558
  }
17559
});
17560
 
17561
fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ {
17562
 
17563
  /**
17564
   * Straightens object, then rerenders canvas
17565
   * @param {fabric.Object} object Object to straighten
17566
   * @return {fabric.Canvas} thisArg
17567
   * @chainable
17568
   */
17569
  straightenObject: function (object) {
17570
    object.straighten();
17571
    this.renderAll();
17572
    return this;
17573
  },
17574
 
17575
  /**
17576
   * Same as {@link fabric.Canvas.prototype.straightenObject}, but animated
17577
   * @param {fabric.Object} object Object to straighten
17578
   * @return {fabric.Canvas} thisArg
17579
   * @chainable
17580
   */
17581
  fxStraightenObject: function (object) {
17582
    object.fxStraighten({
17583
      onChange: this.renderAll.bind(this)
17584
    });
17585
    return this;
17586
  }
17587
});
17588
 
17589
 
17590
/**
17591
 * @namespace fabric.Image.filters
17592
 * @memberOf fabric.Image
17593
 * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#image_filters}
17594
 * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
17595
 */
17596
fabric.Image.filters = fabric.Image.filters || { };
17597
 
17598
/**
17599
 * Root filter class from which all filter classes inherit from
17600
 * @class fabric.Image.filters.BaseFilter
17601
 * @memberOf fabric.Image.filters
17602
 */
17603
fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Image.filters.BaseFilter.prototype */ {
17604
 
17605
  /**
17606
   * Filter type
17607
   * @param {String} type
17608
   * @default
17609
   */
17610
  type: 'BaseFilter',
17611
 
17612
  /**
17613
   * Constructor
17614
   * @param {Object} [options] Options object
17615
   */
17616
  initialize: function(options) {
17617
    if (options) {
17618
      this.setOptions(options);
17619
    }
17620
  },
17621
 
17622
  /**
17623
   * Sets filter's properties from options
17624
   * @param {Object} [options] Options object
17625
   */
17626
  setOptions: function(options) {
17627
    for (var prop in options) {
17628
      this[prop] = options[prop];
17629
    }
17630
  },
17631
 
17632
  /**
17633
   * Returns object representation of an instance
17634
   * @return {Object} Object representation of an instance
17635
   */
17636
  toObject: function() {
17637
    return { type: this.type };
17638
  },
17639
 
17640
  /**
17641
   * Returns a JSON representation of an instance
17642
   * @return {Object} JSON
17643
   */
17644
  toJSON: function() {
17645
    // delegate, not alias
17646
    return this.toObject();
17647
  }
17648
});
17649
 
17650
 
17651
(function(global) {
17652
 
17653
  'use strict';
17654
 
17655
  var fabric  = global.fabric || (global.fabric = { }),
17656
      extend = fabric.util.object.extend;
17657
 
17658
  /**
17659
   * Brightness filter class
17660
   * @class fabric.Image.filters.Brightness
17661
   * @memberOf fabric.Image.filters
17662
   * @extends fabric.Image.filters.BaseFilter
17663
   * @see {@link fabric.Image.filters.Brightness#initialize} for constructor definition
17664
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
17665
   * @example
17666
   * var filter = new fabric.Image.filters.Brightness({
17667
   *   brightness: 200
17668
   * });
17669
   * object.filters.push(filter);
17670
   * object.applyFilters(canvas.renderAll.bind(canvas));
17671
   */
17672
  fabric.Image.filters.Brightness = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Brightness.prototype */ {
17673
 
17674
    /**
17675
     * Filter type
17676
     * @param {String} type
17677
     * @default
17678
     */
17679
    type: 'Brightness',
17680
 
17681
    /**
17682
     * Constructor
17683
     * @memberOf fabric.Image.filters.Brightness.prototype
17684
     * @param {Object} [options] Options object
17685
     * @param {Number} [options.brightness=0] Value to brighten the image up (0..255)
17686
     */
17687
    initialize: function(options) {
17688
      options = options || { };
17689
      this.brightness = options.brightness || 0;
17690
    },
17691
 
17692
    /**
17693
     * Applies filter to canvas element
17694
     * @param {Object} canvasEl Canvas element to apply filter to
17695
     */
17696
    applyTo: function(canvasEl) {
17697
      var context = canvasEl.getContext('2d'),
17698
          imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
17699
          data = imageData.data,
17700
          brightness = this.brightness;
17701
 
17702
      for (var i = 0, len = data.length; i < len; i += 4) {
17703
        data[i] += brightness;
17704
        data[i + 1] += brightness;
17705
        data[i + 2] += brightness;
17706
      }
17707
 
17708
      context.putImageData(imageData, 0, 0);
17709
    },
17710
 
17711
    /**
17712
     * Returns object representation of an instance
17713
     * @return {Object} Object representation of an instance
17714
     */
17715
    toObject: function() {
17716
      return extend(this.callSuper('toObject'), {
17717
        brightness: this.brightness
17718
      });
17719
    }
17720
  });
17721
 
17722
  /**
17723
   * Returns filter instance from an object representation
17724
   * @static
17725
   * @param {Object} object Object to create an instance from
17726
   * @return {fabric.Image.filters.Brightness} Instance of fabric.Image.filters.Brightness
17727
   */
17728
  fabric.Image.filters.Brightness.fromObject = function(object) {
17729
    return new fabric.Image.filters.Brightness(object);
17730
  };
17731
 
17732
})(typeof exports !== 'undefined' ? exports : this);
17733
 
17734
 
17735
(function(global) {
17736
 
17737
  'use strict';
17738
 
17739
  var fabric  = global.fabric || (global.fabric = { }),
17740
      extend = fabric.util.object.extend;
17741
 
17742
  /**
17743
   * Adapted from <a href="http://www.html5rocks.com/en/tutorials/canvas/imagefilters/">html5rocks article</a>
17744
   * @class fabric.Image.filters.Convolute
17745
   * @memberOf fabric.Image.filters
17746
   * @extends fabric.Image.filters.BaseFilter
17747
   * @see {@link fabric.Image.filters.Convolute#initialize} for constructor definition
17748
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
17749
   * @example <caption>Sharpen filter</caption>
17750
   * var filter = new fabric.Image.filters.Convolute({
17751
   *   matrix: [ 0, -1,  0,
17752
   *            -1,  5, -1,
17753
   *             0, -1,  0 ]
17754
   * });
17755
   * object.filters.push(filter);
17756
   * object.applyFilters(canvas.renderAll.bind(canvas));
17757
   * @example <caption>Blur filter</caption>
17758
   * var filter = new fabric.Image.filters.Convolute({
17759
   *   matrix: [ 1/9, 1/9, 1/9,
17760
   *             1/9, 1/9, 1/9,
17761
   *             1/9, 1/9, 1/9 ]
17762
   * });
17763
   * object.filters.push(filter);
17764
   * object.applyFilters(canvas.renderAll.bind(canvas));
17765
   * @example <caption>Emboss filter</caption>
17766
   * var filter = new fabric.Image.filters.Convolute({
17767
   *   matrix: [ 1,   1,  1,
17768
   *             1, 0.7, -1,
17769
   *            -1,  -1, -1 ]
17770
   * });
17771
   * object.filters.push(filter);
17772
   * object.applyFilters(canvas.renderAll.bind(canvas));
17773
   * @example <caption>Emboss filter with opaqueness</caption>
17774
   * var filter = new fabric.Image.filters.Convolute({
17775
   *   opaque: true,
17776
   *   matrix: [ 1,   1,  1,
17777
   *             1, 0.7, -1,
17778
   *            -1,  -1, -1 ]
17779
   * });
17780
   * object.filters.push(filter);
17781
   * object.applyFilters(canvas.renderAll.bind(canvas));
17782
   */
17783
  fabric.Image.filters.Convolute = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Convolute.prototype */ {
17784
 
17785
    /**
17786
     * Filter type
17787
     * @param {String} type
17788
     * @default
17789
     */
17790
    type: 'Convolute',
17791
 
17792
    /**
17793
     * Constructor
17794
     * @memberOf fabric.Image.filters.Convolute.prototype
17795
     * @param {Object} [options] Options object
17796
     * @param {Boolean} [options.opaque=false] Opaque value (true/false)
17797
     * @param {Array} [options.matrix] Filter matrix
17798
     */
17799
    initialize: function(options) {
17800
      options = options || { };
17801
 
17802
      this.opaque = options.opaque;
17803
      this.matrix = options.matrix || [
17804
        0, 0, 0,
17805
        0, 1, 0,
17806
        0, 0, 0
17807
      ];
17808
 
17809
      var canvasEl = fabric.util.createCanvasElement();
17810
      this.tmpCtx = canvasEl.getContext('2d');
17811
    },
17812
 
17813
    /**
17814
     * @private
17815
     */
17816
    _createImageData: function(w, h) {
17817
      return this.tmpCtx.createImageData(w, h);
17818
    },
17819
 
17820
    /**
17821
     * Applies filter to canvas element
17822
     * @param {Object} canvasEl Canvas element to apply filter to
17823
     */
17824
    applyTo: function(canvasEl) {
17825
 
17826
      var weights = this.matrix,
17827
          context = canvasEl.getContext('2d'),
17828
          pixels = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
17829
 
17830
          side = Math.round(Math.sqrt(weights.length)),
17831
          halfSide = Math.floor(side/2),
17832
          src = pixels.data,
17833
          sw = pixels.width,
17834
          sh = pixels.height,
17835
 
17836
          // pad output by the convolution matrix
17837
          w = sw,
17838
          h = sh,
17839
          output = this._createImageData(w, h),
17840
 
17841
          dst = output.data,
17842
 
17843
          // go through the destination image pixels
17844
          alphaFac = this.opaque ? 1 : 0;
17845
 
17846
      for (var y = 0; y < h; y++) {
17847
        for (var x = 0; x < w; x++) {
17848
          var sy = y,
17849
              sx = x,
17850
              dstOff = (y * w + x) * 4,
17851
              // calculate the weighed sum of the source image pixels that
17852
              // fall under the convolution matrix
17853
              r = 0, g = 0, b = 0, a = 0;
17854
 
17855
          for (var cy = 0; cy < side; cy++) {
17856
            for (var cx = 0; cx < side; cx++) {
17857
 
17858
              var scy = sy + cy - halfSide,
17859
                  scx = sx + cx - halfSide;
17860
 
17861
              /* jshint maxdepth:5 */
17862
              if (scy < 0 || scy > sh || scx < 0 || scx > sw) {
17863
                continue;
17864
              }
17865
 
17866
              var srcOff = (scy * sw + scx) * 4,
17867
                  wt = weights[cy * side + cx];
17868
 
17869
              r += src[srcOff] * wt;
17870
              g += src[srcOff + 1] * wt;
17871
              b += src[srcOff + 2] * wt;
17872
              a += src[srcOff + 3] * wt;
17873
            }
17874
          }
17875
          dst[dstOff] = r;
17876
          dst[dstOff + 1] = g;
17877
          dst[dstOff + 2] = b;
17878
          dst[dstOff + 3] = a + alphaFac * (255 - a);
17879
        }
17880
      }
17881
 
17882
      context.putImageData(output, 0, 0);
17883
    },
17884
 
17885
    /**
17886
     * Returns object representation of an instance
17887
     * @return {Object} Object representation of an instance
17888
     */
17889
    toObject: function() {
17890
      return extend(this.callSuper('toObject'), {
17891
        opaque: this.opaque,
17892
        matrix: this.matrix
17893
      });
17894
    }
17895
  });
17896
 
17897
  /**
17898
   * Returns filter instance from an object representation
17899
   * @static
17900
   * @param {Object} object Object to create an instance from
17901
   * @return {fabric.Image.filters.Convolute} Instance of fabric.Image.filters.Convolute
17902
   */
17903
  fabric.Image.filters.Convolute.fromObject = function(object) {
17904
    return new fabric.Image.filters.Convolute(object);
17905
  };
17906
 
17907
})(typeof exports !== 'undefined' ? exports : this);
17908
 
17909
 
17910
(function(global) {
17911
 
17912
  'use strict';
17913
 
17914
  var fabric  = global.fabric || (global.fabric = { }),
17915
      extend = fabric.util.object.extend;
17916
 
17917
  /**
17918
   * GradientTransparency filter class
17919
   * @class fabric.Image.filters.GradientTransparency
17920
   * @memberOf fabric.Image.filters
17921
   * @extends fabric.Image.filters.BaseFilter
17922
   * @see {@link fabric.Image.filters.GradientTransparency#initialize} for constructor definition
17923
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
17924
   * @example
17925
   * var filter = new fabric.Image.filters.GradientTransparency({
17926
   *   threshold: 200
17927
   * });
17928
   * object.filters.push(filter);
17929
   * object.applyFilters(canvas.renderAll.bind(canvas));
17930
   */
17931
  fabric.Image.filters.GradientTransparency = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.GradientTransparency.prototype */ {
17932
 
17933
    /**
17934
     * Filter type
17935
     * @param {String} type
17936
     * @default
17937
     */
17938
    type: 'GradientTransparency',
17939
 
17940
    /**
17941
     * Constructor
17942
     * @memberOf fabric.Image.filters.GradientTransparency.prototype
17943
     * @param {Object} [options] Options object
17944
     * @param {Number} [options.threshold=100] Threshold value
17945
     */
17946
    initialize: function(options) {
17947
      options = options || { };
17948
      this.threshold = options.threshold || 100;
17949
    },
17950
 
17951
    /**
17952
     * Applies filter to canvas element
17953
     * @param {Object} canvasEl Canvas element to apply filter to
17954
     */
17955
    applyTo: function(canvasEl) {
17956
      var context = canvasEl.getContext('2d'),
17957
          imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
17958
          data = imageData.data,
17959
          threshold = this.threshold,
17960
          total = data.length;
17961
 
17962
      for (var i = 0, len = data.length; i < len; i += 4) {
17963
        data[i + 3] = threshold + 255 * (total - i) / total;
17964
      }
17965
 
17966
      context.putImageData(imageData, 0, 0);
17967
    },
17968
 
17969
    /**
17970
     * Returns object representation of an instance
17971
     * @return {Object} Object representation of an instance
17972
     */
17973
    toObject: function() {
17974
      return extend(this.callSuper('toObject'), {
17975
        threshold: this.threshold
17976
      });
17977
    }
17978
  });
17979
 
17980
  /**
17981
   * Returns filter instance from an object representation
17982
   * @static
17983
   * @param {Object} object Object to create an instance from
17984
   * @return {fabric.Image.filters.GradientTransparency} Instance of fabric.Image.filters.GradientTransparency
17985
   */
17986
  fabric.Image.filters.GradientTransparency.fromObject = function(object) {
17987
    return new fabric.Image.filters.GradientTransparency(object);
17988
  };
17989
 
17990
})(typeof exports !== 'undefined' ? exports : this);
17991
 
17992
 
17993
(function(global) {
17994
 
17995
  'use strict';
17996
 
17997
  var fabric  = global.fabric || (global.fabric = { });
17998
 
17999
  /**
18000
   * Grayscale image filter class
18001
   * @class fabric.Image.filters.Grayscale
18002
   * @memberOf fabric.Image.filters
18003
   * @extends fabric.Image.filters.BaseFilter
18004
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
18005
   * @example
18006
   * var filter = new fabric.Image.filters.Grayscale();
18007
   * object.filters.push(filter);
18008
   * object.applyFilters(canvas.renderAll.bind(canvas));
18009
   */
18010
  fabric.Image.filters.Grayscale = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Grayscale.prototype */ {
18011
 
18012
    /**
18013
     * Filter type
18014
     * @param {String} type
18015
     * @default
18016
     */
18017
    type: 'Grayscale',
18018
 
18019
    /**
18020
     * Applies filter to canvas element
18021
     * @memberOf fabric.Image.filters.Grayscale.prototype
18022
     * @param {Object} canvasEl Canvas element to apply filter to
18023
     */
18024
    applyTo: function(canvasEl) {
18025
      var context = canvasEl.getContext('2d'),
18026
          imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
18027
          data = imageData.data,
18028
          len = imageData.width * imageData.height * 4,
18029
          index = 0,
18030
          average;
18031
 
18032
      while (index < len) {
18033
        average = (data[index] + data[index + 1] + data[index + 2]) / 3;
18034
        data[index]     = average;
18035
        data[index + 1] = average;
18036
        data[index + 2] = average;
18037
        index += 4;
18038
      }
18039
 
18040
      context.putImageData(imageData, 0, 0);
18041
    }
18042
  });
18043
 
18044
  /**
18045
   * Returns filter instance from an object representation
18046
   * @static
18047
   * @return {fabric.Image.filters.Grayscale} Instance of fabric.Image.filters.Grayscale
18048
   */
18049
  fabric.Image.filters.Grayscale.fromObject = function() {
18050
    return new fabric.Image.filters.Grayscale();
18051
  };
18052
 
18053
})(typeof exports !== 'undefined' ? exports : this);
18054
 
18055
 
18056
(function(global) {
18057
 
18058
  'use strict';
18059
 
18060
  var fabric  = global.fabric || (global.fabric = { });
18061
 
18062
  /**
18063
   * Invert filter class
18064
   * @class fabric.Image.filters.Invert
18065
   * @memberOf fabric.Image.filters
18066
   * @extends fabric.Image.filters.BaseFilter
18067
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
18068
   * @example
18069
   * var filter = new fabric.Image.filters.Invert();
18070
   * object.filters.push(filter);
18071
   * object.applyFilters(canvas.renderAll.bind(canvas));
18072
   */
18073
  fabric.Image.filters.Invert = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Invert.prototype */ {
18074
 
18075
    /**
18076
     * Filter type
18077
     * @param {String} type
18078
     * @default
18079
     */
18080
    type: 'Invert',
18081
 
18082
    /**
18083
     * Applies filter to canvas element
18084
     * @memberOf fabric.Image.filters.Invert.prototype
18085
     * @param {Object} canvasEl Canvas element to apply filter to
18086
     */
18087
    applyTo: function(canvasEl) {
18088
      var context = canvasEl.getContext('2d'),
18089
          imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
18090
          data = imageData.data,
18091
          iLen = data.length, i;
18092
 
18093
      for (i = 0; i < iLen; i+=4) {
18094
        data[i] = 255 - data[i];
18095
        data[i + 1] = 255 - data[i + 1];
18096
        data[i + 2] = 255 - data[i + 2];
18097
      }
18098
 
18099
      context.putImageData(imageData, 0, 0);
18100
    }
18101
  });
18102
 
18103
  /**
18104
   * Returns filter instance from an object representation
18105
   * @static
18106
   * @return {fabric.Image.filters.Invert} Instance of fabric.Image.filters.Invert
18107
   */
18108
  fabric.Image.filters.Invert.fromObject = function() {
18109
    return new fabric.Image.filters.Invert();
18110
  };
18111
 
18112
})(typeof exports !== 'undefined' ? exports : this);
18113
 
18114
 
18115
(function(global) {
18116
 
18117
  'use strict';
18118
 
18119
  var fabric  = global.fabric || (global.fabric = { }),
18120
      extend = fabric.util.object.extend;
18121
 
18122
  /**
18123
   * Mask filter class
18124
   * See http://resources.aleph-1.com/mask/
18125
   * @class fabric.Image.filters.Mask
18126
   * @memberOf fabric.Image.filters
18127
   * @extends fabric.Image.filters.BaseFilter
18128
   * @see {@link fabric.Image.filters.Mask#initialize} for constructor definition
18129
   */
18130
  fabric.Image.filters.Mask = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Mask.prototype */ {
18131
 
18132
    /**
18133
     * Filter type
18134
     * @param {String} type
18135
     * @default
18136
     */
18137
    type: 'Mask',
18138
 
18139
    /**
18140
     * Constructor
18141
     * @memberOf fabric.Image.filters.Mask.prototype
18142
     * @param {Object} [options] Options object
18143
     * @param {fabric.Image} [options.mask] Mask image object
18144
     * @param {Number} [options.channel=0] Rgb channel (0, 1, 2 or 3)
18145
     */
18146
    initialize: function(options) {
18147
      options = options || { };
18148
 
18149
      this.mask = options.mask;
18150
      this.channel = [ 0, 1, 2, 3 ].indexOf(options.channel) > -1 ? options.channel : 0;
18151
    },
18152
 
18153
    /**
18154
     * Applies filter to canvas element
18155
     * @param {Object} canvasEl Canvas element to apply filter to
18156
     */
18157
    applyTo: function(canvasEl) {
18158
      if (!this.mask) {
18159
        return;
18160
      }
18161
 
18162
      var context = canvasEl.getContext('2d'),
18163
          imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
18164
          data = imageData.data,
18165
          maskEl = this.mask.getElement(),
18166
          maskCanvasEl = fabric.util.createCanvasElement(),
18167
          channel = this.channel,
18168
          i,
18169
          iLen = imageData.width * imageData.height * 4;
18170
 
18171
      maskCanvasEl.width = maskEl.width;
18172
      maskCanvasEl.height = maskEl.height;
18173
 
18174
      maskCanvasEl.getContext('2d').drawImage(maskEl, 0, 0, maskEl.width, maskEl.height);
18175
 
18176
      var maskImageData = maskCanvasEl.getContext('2d').getImageData(0, 0, maskEl.width, maskEl.height),
18177
          maskData = maskImageData.data;
18178
 
18179
      for (i = 0; i < iLen; i += 4) {
18180
        data[i + 3] = maskData[i + channel];
18181
      }
18182
 
18183
      context.putImageData(imageData, 0, 0);
18184
    },
18185
 
18186
    /**
18187
     * Returns object representation of an instance
18188
     * @return {Object} Object representation of an instance
18189
     */
18190
    toObject: function() {
18191
      return extend(this.callSuper('toObject'), {
18192
        mask: this.mask.toObject(),
18193
        channel: this.channel
18194
      });
18195
    }
18196
  });
18197
 
18198
  /**
18199
   * Returns filter instance from an object representation
18200
   * @static
18201
   * @param {Object} object Object to create an instance from
18202
   * @param {Function} [callback] Callback to invoke when a mask filter instance is created
18203
   */
18204
  fabric.Image.filters.Mask.fromObject = function(object, callback) {
18205
    fabric.util.loadImage(object.mask.src, function(img) {
18206
      object.mask = new fabric.Image(img, object.mask);
18207
      callback && callback(new fabric.Image.filters.Mask(object));
18208
    });
18209
  };
18210
 
18211
  /**
18212
   * Indicates that instances of this type are async
18213
   * @static
18214
   * @type Boolean
18215
   * @default
18216
   */
18217
  fabric.Image.filters.Mask.async = true;
18218
 
18219
})(typeof exports !== 'undefined' ? exports : this);
18220
 
18221
 
18222
(function(global) {
18223
 
18224
  'use strict';
18225
 
18226
  var fabric  = global.fabric || (global.fabric = { }),
18227
      extend = fabric.util.object.extend;
18228
 
18229
  /**
18230
   * Noise filter class
18231
   * @class fabric.Image.filters.Noise
18232
   * @memberOf fabric.Image.filters
18233
   * @extends fabric.Image.filters.BaseFilter
18234
   * @see {@link fabric.Image.filters.Noise#initialize} for constructor definition
18235
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
18236
   * @example
18237
   * var filter = new fabric.Image.filters.Noise({
18238
   *   noise: 700
18239
   * });
18240
   * object.filters.push(filter);
18241
   * object.applyFilters(canvas.renderAll.bind(canvas));
18242
   */
18243
  fabric.Image.filters.Noise = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Noise.prototype */ {
18244
 
18245
    /**
18246
     * Filter type
18247
     * @param {String} type
18248
     * @default
18249
     */
18250
    type: 'Noise',
18251
 
18252
    /**
18253
     * Constructor
18254
     * @memberOf fabric.Image.filters.Noise.prototype
18255
     * @param {Object} [options] Options object
18256
     * @param {Number} [options.noise=0] Noise value
18257
     */
18258
    initialize: function(options) {
18259
      options = options || { };
18260
      this.noise = options.noise || 0;
18261
    },
18262
 
18263
    /**
18264
     * Applies filter to canvas element
18265
     * @param {Object} canvasEl Canvas element to apply filter to
18266
     */
18267
    applyTo: function(canvasEl) {
18268
      var context = canvasEl.getContext('2d'),
18269
          imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
18270
          data = imageData.data,
18271
          noise = this.noise, rand;
18272
 
18273
      for (var i = 0, len = data.length; i < len; i += 4) {
18274
 
18275
        rand = (0.5 - Math.random()) * noise;
18276
 
18277
        data[i] += rand;
18278
        data[i + 1] += rand;
18279
        data[i + 2] += rand;
18280
      }
18281
 
18282
      context.putImageData(imageData, 0, 0);
18283
    },
18284
 
18285
    /**
18286
     * Returns object representation of an instance
18287
     * @return {Object} Object representation of an instance
18288
     */
18289
    toObject: function() {
18290
      return extend(this.callSuper('toObject'), {
18291
        noise: this.noise
18292
      });
18293
    }
18294
  });
18295
 
18296
  /**
18297
   * Returns filter instance from an object representation
18298
   * @static
18299
   * @param {Object} object Object to create an instance from
18300
   * @return {fabric.Image.filters.Noise} Instance of fabric.Image.filters.Noise
18301
   */
18302
  fabric.Image.filters.Noise.fromObject = function(object) {
18303
    return new fabric.Image.filters.Noise(object);
18304
  };
18305
 
18306
})(typeof exports !== 'undefined' ? exports : this);
18307
 
18308
 
18309
(function(global) {
18310
 
18311
  'use strict';
18312
 
18313
  var fabric  = global.fabric || (global.fabric = { }),
18314
      extend = fabric.util.object.extend;
18315
 
18316
  /**
18317
   * Pixelate filter class
18318
   * @class fabric.Image.filters.Pixelate
18319
   * @memberOf fabric.Image.filters
18320
   * @extends fabric.Image.filters.BaseFilter
18321
   * @see {@link fabric.Image.filters.Pixelate#initialize} for constructor definition
18322
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
18323
   * @example
18324
   * var filter = new fabric.Image.filters.Pixelate({
18325
   *   blocksize: 8
18326
   * });
18327
   * object.filters.push(filter);
18328
   * object.applyFilters(canvas.renderAll.bind(canvas));
18329
   */
18330
  fabric.Image.filters.Pixelate = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Pixelate.prototype */ {
18331
 
18332
    /**
18333
     * Filter type
18334
     * @param {String} type
18335
     * @default
18336
     */
18337
    type: 'Pixelate',
18338
 
18339
    /**
18340
     * Constructor
18341
     * @memberOf fabric.Image.filters.Pixelate.prototype
18342
     * @param {Object} [options] Options object
18343
     * @param {Number} [options.blocksize=4] Blocksize for pixelate
18344
     */
18345
    initialize: function(options) {
18346
      options = options || { };
18347
      this.blocksize = options.blocksize || 4;
18348
    },
18349
 
18350
    /**
18351
     * Applies filter to canvas element
18352
     * @param {Object} canvasEl Canvas element to apply filter to
18353
     */
18354
    applyTo: function(canvasEl) {
18355
      var context = canvasEl.getContext('2d'),
18356
          imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
18357
          data = imageData.data,
18358
          iLen = imageData.height,
18359
          jLen = imageData.width,
18360
          index, i, j, r, g, b, a;
18361
 
18362
      for (i = 0; i < iLen; i += this.blocksize) {
18363
        for (j = 0; j < jLen; j += this.blocksize) {
18364
 
18365
          index = (i * 4) * jLen + (j * 4);
18366
 
18367
          r = data[index];
18368
          g = data[index + 1];
18369
          b = data[index + 2];
18370
          a = data[index + 3];
18371
 
18372
          /*
18373
           blocksize: 4
18374
 
18375
           [1,x,x,x,1]
18376
           [x,x,x,x,1]
18377
           [x,x,x,x,1]
18378
           [x,x,x,x,1]
18379
           [1,1,1,1,1]
18380
           */
18381
 
18382
          for (var _i = i, _ilen = i + this.blocksize; _i < _ilen; _i++) {
18383
            for (var _j = j, _jlen = j + this.blocksize; _j < _jlen; _j++) {
18384
              index = (_i * 4) * jLen + (_j * 4);
18385
              data[index] = r;
18386
              data[index + 1] = g;
18387
              data[index + 2] = b;
18388
              data[index + 3] = a;
18389
            }
18390
          }
18391
        }
18392
      }
18393
 
18394
      context.putImageData(imageData, 0, 0);
18395
    },
18396
 
18397
    /**
18398
     * Returns object representation of an instance
18399
     * @return {Object} Object representation of an instance
18400
     */
18401
    toObject: function() {
18402
      return extend(this.callSuper('toObject'), {
18403
        blocksize: this.blocksize
18404
      });
18405
    }
18406
  });
18407
 
18408
  /**
18409
   * Returns filter instance from an object representation
18410
   * @static
18411
   * @param {Object} object Object to create an instance from
18412
   * @return {fabric.Image.filters.Pixelate} Instance of fabric.Image.filters.Pixelate
18413
   */
18414
  fabric.Image.filters.Pixelate.fromObject = function(object) {
18415
    return new fabric.Image.filters.Pixelate(object);
18416
  };
18417
 
18418
})(typeof exports !== 'undefined' ? exports : this);
18419
 
18420
 
18421
(function(global) {
18422
 
18423
  'use strict';
18424
 
18425
  var fabric  = global.fabric || (global.fabric = { }),
18426
      extend = fabric.util.object.extend;
18427
 
18428
  /**
18429
   * Remove white filter class
18430
   * @class fabric.Image.filters.RemoveWhite
18431
   * @memberOf fabric.Image.filters
18432
   * @extends fabric.Image.filters.BaseFilter
18433
   * @see {@link fabric.Image.filters.RemoveWhite#initialize} for constructor definition
18434
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
18435
   * @example
18436
   * var filter = new fabric.Image.filters.RemoveWhite({
18437
   *   threshold: 40,
18438
   *   distance: 140
18439
   * });
18440
   * object.filters.push(filter);
18441
   * object.applyFilters(canvas.renderAll.bind(canvas));
18442
   */
18443
  fabric.Image.filters.RemoveWhite = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.RemoveWhite.prototype */ {
18444
 
18445
    /**
18446
     * Filter type
18447
     * @param {String} type
18448
     * @default
18449
     */
18450
    type: 'RemoveWhite',
18451
 
18452
    /**
18453
     * Constructor
18454
     * @memberOf fabric.Image.filters.RemoveWhite.prototype
18455
     * @param {Object} [options] Options object
18456
     * @param {Number} [options.threshold=30] Threshold value
18457
     * @param {Number} [options.distance=20] Distance value
18458
     */
18459
    initialize: function(options) {
18460
      options = options || { };
18461
      this.threshold = options.threshold || 30;
18462
      this.distance = options.distance || 20;
18463
    },
18464
 
18465
    /**
18466
     * Applies filter to canvas element
18467
     * @param {Object} canvasEl Canvas element to apply filter to
18468
     */
18469
    applyTo: function(canvasEl) {
18470
      var context = canvasEl.getContext('2d'),
18471
          imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
18472
          data = imageData.data,
18473
          threshold = this.threshold,
18474
          distance = this.distance,
18475
          limit = 255 - threshold,
18476
          abs = Math.abs,
18477
          r, g, b;
18478
 
18479
      for (var i = 0, len = data.length; i < len; i += 4) {
18480
        r = data[i];
18481
        g = data[i + 1];
18482
        b = data[i + 2];
18483
 
18484
        if (r > limit &&
18485
            g > limit &&
18486
            b > limit &&
18487
            abs(r - g) < distance &&
18488
            abs(r - b) < distance &&
18489
            abs(g - b) < distance
18490
        ) {
18491
          data[i + 3] = 1;
18492
        }
18493
      }
18494
 
18495
      context.putImageData(imageData, 0, 0);
18496
    },
18497
 
18498
    /**
18499
     * Returns object representation of an instance
18500
     * @return {Object} Object representation of an instance
18501
     */
18502
    toObject: function() {
18503
      return extend(this.callSuper('toObject'), {
18504
        threshold: this.threshold,
18505
        distance: this.distance
18506
      });
18507
    }
18508
  });
18509
 
18510
  /**
18511
   * Returns filter instance from an object representation
18512
   * @static
18513
   * @param {Object} object Object to create an instance from
18514
   * @return {fabric.Image.filters.RemoveWhite} Instance of fabric.Image.filters.RemoveWhite
18515
   */
18516
  fabric.Image.filters.RemoveWhite.fromObject = function(object) {
18517
    return new fabric.Image.filters.RemoveWhite(object);
18518
  };
18519
 
18520
})(typeof exports !== 'undefined' ? exports : this);
18521
 
18522
 
18523
(function(global) {
18524
 
18525
  'use strict';
18526
 
18527
  var fabric  = global.fabric || (global.fabric = { });
18528
 
18529
  /**
18530
   * Sepia filter class
18531
   * @class fabric.Image.filters.Sepia
18532
   * @memberOf fabric.Image.filters
18533
   * @extends fabric.Image.filters.BaseFilter
18534
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
18535
   * @example
18536
   * var filter = new fabric.Image.filters.Sepia();
18537
   * object.filters.push(filter);
18538
   * object.applyFilters(canvas.renderAll.bind(canvas));
18539
   */
18540
  fabric.Image.filters.Sepia = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Sepia.prototype */ {
18541
 
18542
    /**
18543
     * Filter type
18544
     * @param {String} type
18545
     * @default
18546
     */
18547
    type: 'Sepia',
18548
 
18549
    /**
18550
     * Applies filter to canvas element
18551
     * @memberOf fabric.Image.filters.Sepia.prototype
18552
     * @param {Object} canvasEl Canvas element to apply filter to
18553
     */
18554
    applyTo: function(canvasEl) {
18555
      var context = canvasEl.getContext('2d'),
18556
          imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
18557
          data = imageData.data,
18558
          iLen = data.length, i, avg;
18559
 
18560
      for (i = 0; i < iLen; i+=4) {
18561
        avg = 0.3  * data[i] + 0.59 * data[i + 1] + 0.11 * data[i + 2];
18562
        data[i] = avg + 100;
18563
        data[i + 1] = avg + 50;
18564
        data[i + 2] = avg + 255;
18565
      }
18566
 
18567
      context.putImageData(imageData, 0, 0);
18568
    }
18569
  });
18570
 
18571
  /**
18572
   * Returns filter instance from an object representation
18573
   * @static
18574
   * @return {fabric.Image.filters.Sepia} Instance of fabric.Image.filters.Sepia
18575
   */
18576
  fabric.Image.filters.Sepia.fromObject = function() {
18577
    return new fabric.Image.filters.Sepia();
18578
  };
18579
 
18580
})(typeof exports !== 'undefined' ? exports : this);
18581
 
18582
 
18583
(function(global) {
18584
 
18585
  'use strict';
18586
 
18587
  var fabric  = global.fabric || (global.fabric = { });
18588
 
18589
  /**
18590
   * Sepia2 filter class
18591
   * @class fabric.Image.filters.Sepia2
18592
   * @memberOf fabric.Image.filters
18593
   * @extends fabric.Image.filters.BaseFilter
18594
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
18595
   * @example
18596
   * var filter = new fabric.Image.filters.Sepia2();
18597
   * object.filters.push(filter);
18598
   * object.applyFilters(canvas.renderAll.bind(canvas));
18599
   */
18600
  fabric.Image.filters.Sepia2 = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Sepia2.prototype */ {
18601
 
18602
    /**
18603
     * Filter type
18604
     * @param {String} type
18605
     * @default
18606
     */
18607
    type: 'Sepia2',
18608
 
18609
    /**
18610
     * Applies filter to canvas element
18611
     * @memberOf fabric.Image.filters.Sepia.prototype
18612
     * @param {Object} canvasEl Canvas element to apply filter to
18613
     */
18614
    applyTo: function(canvasEl) {
18615
      var context = canvasEl.getContext('2d'),
18616
          imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
18617
          data = imageData.data,
18618
          iLen = data.length, i, r, g, b;
18619
 
18620
      for (i = 0; i < iLen; i+=4) {
18621
        r = data[i];
18622
        g = data[i + 1];
18623
        b = data[i + 2];
18624
 
18625
        data[i] = (r * 0.393 + g * 0.769 + b * 0.189 ) / 1.351;
18626
        data[i + 1] = (r * 0.349 + g * 0.686 + b * 0.168 ) / 1.203;
18627
        data[i + 2] = (r * 0.272 + g * 0.534 + b * 0.131 ) / 2.140;
18628
      }
18629
 
18630
      context.putImageData(imageData, 0, 0);
18631
    }
18632
  });
18633
 
18634
  /**
18635
   * Returns filter instance from an object representation
18636
   * @static
18637
   * @return {fabric.Image.filters.Sepia2} Instance of fabric.Image.filters.Sepia2
18638
   */
18639
  fabric.Image.filters.Sepia2.fromObject = function() {
18640
    return new fabric.Image.filters.Sepia2();
18641
  };
18642
 
18643
})(typeof exports !== 'undefined' ? exports : this);
18644
 
18645
 
18646
(function(global) {
18647
 
18648
  'use strict';
18649
 
18650
  var fabric  = global.fabric || (global.fabric = { }),
18651
      extend = fabric.util.object.extend;
18652
 
18653
  /**
18654
   * Tint filter class
18655
   * Adapted from <a href="https://github.com/mezzoblue/PaintbrushJS">https://github.com/mezzoblue/PaintbrushJS</a>
18656
   * @class fabric.Image.filters.Tint
18657
   * @memberOf fabric.Image.filters
18658
   * @extends fabric.Image.filters.BaseFilter
18659
   * @see {@link fabric.Image.filters.Tint#initialize} for constructor definition
18660
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
18661
   * @example <caption>Tint filter with hex color and opacity</caption>
18662
   * var filter = new fabric.Image.filters.Tint({
18663
   *   color: '#3513B0',
18664
   *   opacity: 0.5
18665
   * });
18666
   * object.filters.push(filter);
18667
   * object.applyFilters(canvas.renderAll.bind(canvas));
18668
   * @example <caption>Tint filter with rgba color</caption>
18669
   * var filter = new fabric.Image.filters.Tint({
18670
   *   color: 'rgba(53, 21, 176, 0.5)'
18671
   * });
18672
   * object.filters.push(filter);
18673
   * object.applyFilters(canvas.renderAll.bind(canvas));
18674
   */
18675
  fabric.Image.filters.Tint = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Tint.prototype */ {
18676
 
18677
    /**
18678
     * Filter type
18679
     * @param {String} type
18680
     * @default
18681
     */
18682
    type: 'Tint',
18683
 
18684
    /**
18685
     * Constructor
18686
     * @memberOf fabric.Image.filters.Tint.prototype
18687
     * @param {Object} [options] Options object
18688
     * @param {String} [options.color=#000000] Color to tint the image with
18689
     * @param {Number} [options.opacity] Opacity value that controls the tint effect's transparency (0..1)
18690
     */
18691
    initialize: function(options) {
18692
      options = options || { };
18693
 
18694
      this.color = options.color || '#000000';
18695
      this.opacity = typeof options.opacity !== 'undefined'
18696
                      ? options.opacity
18697
                      : new fabric.Color(this.color).getAlpha();
18698
    },
18699
 
18700
    /**
18701
     * Applies filter to canvas element
18702
     * @param {Object} canvasEl Canvas element to apply filter to
18703
     */
18704
    applyTo: function(canvasEl) {
18705
      var context = canvasEl.getContext('2d'),
18706
          imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
18707
          data = imageData.data,
18708
          iLen = data.length, i,
18709
          tintR, tintG, tintB,
18710
          r, g, b, alpha1,
18711
          source;
18712
 
18713
      source = new fabric.Color(this.color).getSource();
18714
 
18715
      tintR = source[0] * this.opacity;
18716
      tintG = source[1] * this.opacity;
18717
      tintB = source[2] * this.opacity;
18718
 
18719
      alpha1 = 1 - this.opacity;
18720
 
18721
      for (i = 0; i < iLen; i+=4) {
18722
        r = data[i];
18723
        g = data[i + 1];
18724
        b = data[i + 2];
18725
 
18726
        // alpha compositing
18727
        data[i] = tintR + r * alpha1;
18728
        data[i + 1] = tintG + g * alpha1;
18729
        data[i + 2] = tintB + b * alpha1;
18730
      }
18731
 
18732
      context.putImageData(imageData, 0, 0);
18733
    },
18734
 
18735
    /**
18736
     * Returns object representation of an instance
18737
     * @return {Object} Object representation of an instance
18738
     */
18739
    toObject: function() {
18740
      return extend(this.callSuper('toObject'), {
18741
        color: this.color,
18742
        opacity: this.opacity
18743
      });
18744
    }
18745
  });
18746
 
18747
  /**
18748
   * Returns filter instance from an object representation
18749
   * @static
18750
   * @param {Object} object Object to create an instance from
18751
   * @return {fabric.Image.filters.Tint} Instance of fabric.Image.filters.Tint
18752
   */
18753
  fabric.Image.filters.Tint.fromObject = function(object) {
18754
    return new fabric.Image.filters.Tint(object);
18755
  };
18756
 
18757
})(typeof exports !== 'undefined' ? exports : this);
18758
 
18759
 
18760
(function(global) {
18761
 
18762
  'use strict';
18763
 
18764
  var fabric  = global.fabric || (global.fabric = { }),
18765
      extend = fabric.util.object.extend;
18766
 
18767
  /**
18768
   * Multiply filter class
18769
   * Adapted from <a href="http://www.laurenscorijn.com/articles/colormath-basics">http://www.laurenscorijn.com/articles/colormath-basics</a>
18770
   * @class fabric.Image.filters.Multiply
18771
   * @memberOf fabric.Image.filters
18772
   * @extends fabric.Image.filters.BaseFilter
18773
   * @example <caption>Multiply filter with hex color</caption>
18774
   * var filter = new fabric.Image.filters.Multiply({
18775
   *   color: '#F0F'
18776
   * });
18777
   * object.filters.push(filter);
18778
   * object.applyFilters(canvas.renderAll.bind(canvas));
18779
   * @example <caption>Multiply filter with rgb color</caption>
18780
   * var filter = new fabric.Image.filters.Multiply({
18781
   *   color: 'rgb(53, 21, 176)'
18782
   * });
18783
   * object.filters.push(filter);
18784
   * object.applyFilters(canvas.renderAll.bind(canvas));
18785
   */
18786
  fabric.Image.filters.Multiply = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Multiply.prototype */ {
18787
 
18788
    /**
18789
     * Filter type
18790
     * @param {String} type
18791
     * @default
18792
     */
18793
    type: 'Multiply',
18794
 
18795
    /**
18796
     * Constructor
18797
     * @memberOf fabric.Image.filters.Multiply.prototype
18798
     * @param {Object} [options] Options object
18799
     * @param {String} [options.color=#000000] Color to multiply the image pixels with
18800
     */
18801
    initialize: function(options) {
18802
      options = options || { };
18803
 
18804
      this.color = options.color || '#000000';
18805
    },
18806
 
18807
    /**
18808
     * Applies filter to canvas element
18809
     * @param {Object} canvasEl Canvas element to apply filter to
18810
     */
18811
    applyTo: function(canvasEl) {
18812
      var context = canvasEl.getContext('2d'),
18813
          imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
18814
          data = imageData.data,
18815
          iLen = data.length, i,
18816
          source;
18817
 
18818
      source = new fabric.Color(this.color).getSource();
18819
 
18820
      for (i = 0; i < iLen; i+=4) {
18821
        data[i] *= source[0] / 255;
18822
        data[i + 1] *= source[1] / 255;
18823
        data[i + 2] *= source[2] / 255;
18824
      }
18825
 
18826
      context.putImageData(imageData, 0, 0);
18827
    },
18828
 
18829
    /**
18830
     * Returns object representation of an instance
18831
     * @return {Object} Object representation of an instance
18832
     */
18833
    toObject: function() {
18834
      return extend(this.callSuper('toObject'), {
18835
        color: this.color
18836
      });
18837
    }
18838
  });
18839
 
18840
  /**
18841
   * Returns filter instance from an object representation
18842
   * @static
18843
   * @param {Object} object Object to create an instance from
18844
   * @return {fabric.Image.filters.Multiply} Instance of fabric.Image.filters.Multiply
18845
   */
18846
  fabric.Image.filters.Multiply.fromObject = function(object) {
18847
    return new fabric.Image.filters.Multiply(object);
18848
  };
18849
 
18850
})(typeof exports !== 'undefined' ? exports : this);
18851
 
18852
 
18853
(function(global) {
18854
  'use strict';
18855
 
18856
  var fabric = global.fabric;
18857
 
18858
  /**
18859
   * Color Blend filter class
18860
   * @class fabric.Image.filter.Blend
18861
   * @memberOf fabric.Image.filters
18862
   * @extends fabric.Image.filters.BaseFilter
18863
   * @example
18864
   * var filter = new fabric.Image.filters.Blend({
18865
   *  color: '#000',
18866
   *  mode: 'multiply'
18867
   * });
18868
   *
18869
   * var filter = new fabric.Image.filters.Blend({
18870
   *  image: fabricImageObject,
18871
   *  mode: 'multiply',
18872
   *  alpha: 0.5
18873
   * });
18874
 
18875
   * object.filters.push(filter);
18876
   * object.applyFilters(canvas.renderAll.bind(canvas));
18877
   */
18878
  fabric.Image.filters.Blend = fabric.util.createClass({
18879
    type: 'Blend',
18880
 
18881
    initialize: function(options) {
18882
      options = options || {};
18883
      this.color = options.color || '#000';
18884
      this.image = options.image || false;
18885
      this.mode = options.mode || 'multiply';
18886
      this.alpha = options.alpha || 1;
18887
    },
18888
 
18889
    applyTo: function(canvasEl) {
18890
      var context = canvasEl.getContext('2d'),
18891
          imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
18892
          data = imageData.data,
18893
          tr, tg, tb,
18894
          r, g, b,
18895
          _r, _g, _b,
18896
          source,
18897
          isImage = false;
18898
 
18899
      if (this.image) {
18900
        // Blend images
18901
        isImage = true;
18902
 
18903
        var _el = fabric.util.createCanvasElement();
18904
        _el.width = this.image.width;
18905
        _el.height = this.image.height;
18906
 
18907
        var tmpCanvas = new fabric.StaticCanvas(_el);
18908
        tmpCanvas.add(this.image);
18909
        var context2 =  tmpCanvas.getContext('2d');
18910
        source = context2.getImageData(0, 0, tmpCanvas.width, tmpCanvas.height).data;
18911
      }
18912
      else {
18913
        // Blend color
18914
        source = new fabric.Color(this.color).getSource();
18915
 
18916
        tr = source[0] * this.alpha;
18917
        tg = source[1] * this.alpha;
18918
        tb = source[2] * this.alpha;
18919
      }
18920
 
18921
      for (var i = 0, len = data.length; i < len; i += 4) {
18922
 
18923
        r = data[i];
18924
        g = data[i + 1];
18925
        b = data[i + 2];
18926
 
18927
        if (isImage) {
18928
          tr = source[i] * this.alpha;
18929
          tg = source[i + 1] * this.alpha;
18930
          tb = source[i + 2] * this.alpha;
18931
        }
18932
 
18933
        switch (this.mode) {
18934
          case 'multiply':
18935
            data[i] = r * tr / 255;
18936
            data[i + 1] = g * tg / 255;
18937
            data[i + 2] = b * tb / 255;
18938
            break;
18939
          case 'screen':
18940
            data[i] = 1 - (1 - r) * (1 - tr);
18941
            data[i + 1] = 1 - (1 - g) * (1 - tg);
18942
            data[i + 2] = 1 - (1 - b) * (1 - tb);
18943
            break;
18944
          case 'add':
18945
            data[i] = Math.min(255, r + tr);
18946
            data[i + 1] = Math.min(255, g + tg);
18947
            data[i + 2] = Math.min(255, b + tb);
18948
            break;
18949
          case 'diff':
18950
          case 'difference':
18951
            data[i] = Math.abs(r - tr);
18952
            data[i + 1] = Math.abs(g - tg);
18953
            data[i + 2] = Math.abs(b - tb);
18954
            break;
18955
          case 'subtract':
18956
            _r = r - tr;
18957
            _g = g - tg;
18958
            _b = b - tb;
18959
 
18960
            data[i] = (_r < 0) ? 0 : _r;
18961
            data[i + 1] = (_g < 0) ? 0 : _g;
18962
            data[i + 2] = (_b < 0) ? 0 : _b;
18963
            break;
18964
          case 'darken':
18965
            data[i] = Math.min(r, tr);
18966
            data[i + 1] = Math.min(g, tg);
18967
            data[i + 2] = Math.min(b, tb);
18968
            break;
18969
          case 'lighten':
18970
            data[i] = Math.max(r, tr);
18971
            data[i + 1] = Math.max(g, tg);
18972
            data[i + 2] = Math.max(b, tb);
18973
            break;
18974
        }
18975
      }
18976
 
18977
      context.putImageData(imageData, 0, 0);
18978
    },
18979
 
18980
    /**
18981
     * Returns object representation of an instance
18982
     * @return {Object} Object representation of an instance
18983
     */
18984
    toObject: function() {
18985
      return {
18986
        color: this.color,
18987
        image: this.image,
18988
        mode: this.mode,
18989
        alpha: this.alpha
18990
      };
18991
    }
18992
  });
18993
 
18994
  fabric.Image.filters.Blend.fromObject = function(object) {
18995
    return new fabric.Image.filters.Blend(object);
18996
  };
18997
})(typeof exports !== 'undefined' ? exports : this);
18998
 
18999
 
19000
(function(global) {
19001
 
19002
  'use strict';
19003
 
19004
  var fabric  = global.fabric || (global.fabric = { }), pow = Math.pow, floor = Math.floor,
19005
      sqrt = Math.sqrt, abs = Math.abs, max = Math.max, round = Math.round, sin = Math.sin,
19006
      ceil = Math.ceil;
19007
 
19008
  /**
19009
   * Resize image filter class
19010
   * @class fabric.Image.filters.Resize
19011
   * @memberOf fabric.Image.filters
19012
   * @extends fabric.Image.filters.BaseFilter
19013
   * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo}
19014
   * @example
19015
   * var filter = new fabric.Image.filters.Resize();
19016
   * object.filters.push(filter);
19017
   * object.applyFilters(canvas.renderAll.bind(canvas));
19018
   */
19019
  fabric.Image.filters.Resize = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Resize.prototype */ {
19020
 
19021
    /**
19022
     * Filter type
19023
     * @param {String} type
19024
     * @default
19025
     */
19026
    type: 'Resize',
19027
 
19028
    /**
19029
     * Resize type
19030
     * @param {String} resizeType
19031
     * @default
19032
     */
19033
    resizeType: 'hermite',
19034
 
19035
    /**
19036
     * Scale factor for resizing, x axis
19037
     * @param {Number} scaleX
19038
     * @default
19039
     */
19040
    scaleX: 0,
19041
 
19042
    /**
19043
     * Scale factor for resizing, y axis
19044
     * @param {Number} scaleY
19045
     * @default
19046
     */
19047
    scaleY: 0,
19048
 
19049
    /**
19050
     * LanczosLobes parameter for lanczos filter
19051
     * @param {Number} lanczosLobes
19052
     * @default
19053
     */
19054
    lanczosLobes: 3,
19055
 
19056
    /**
19057
     * Applies filter to canvas element
19058
     * @memberOf fabric.Image.filters.Resize.prototype
19059
     * @param {Object} canvasEl Canvas element to apply filter to
19060
     */
19061
    applyTo: function(canvasEl, scaleX, scaleY) {
19062
 
19063
      this.rcpScaleX = 1 / scaleX;
19064
      this.rcpScaleY = 1 / scaleY;
19065
 
19066
      var oW = canvasEl.width, oH = canvasEl.height,
19067
          dW = round(oW * scaleX), dH = round(oH * scaleY),
19068
          imageData;
19069
 
19070
      if (this.resizeType === 'sliceHack') {
19071
        imageData = this.sliceByTwo(canvasEl, oW, oH, dW, dH);
19072
      }
19073
      if (this.resizeType === 'hermite') {
19074
        imageData = this.hermiteFastResize(canvasEl, oW, oH, dW, dH);
19075
      }
19076
      if (this.resizeType === 'bilinear') {
19077
        imageData = this.bilinearFiltering(canvasEl, oW, oH, dW, dH);
19078
      }
19079
      if (this.resizeType === 'lanczos') {
19080
        imageData = this.lanczosResize(canvasEl, oW, oH, dW, dH);
19081
      }
19082
      canvasEl.width = dW;
19083
      canvasEl.height = dH;
19084
      canvasEl.getContext('2d').putImageData(imageData, 0, 0);
19085
    },
19086
 
19087
    sliceByTwo: function(canvasEl, width, height, newWidth, newHeight) {
19088
      var context = canvasEl.getContext('2d'), imageData,
19089
          multW = 0.5, multH = 0.5, signW = 1, signH = 1,
19090
          doneW = false, doneH = false, stepW = width, stepH = height,
19091
          tmpCanvas = fabric.util.createCanvasElement(),
19092
          tmpCtx = tmpCanvas.getContext('2d');
19093
      newWidth = floor(newWidth);
19094
      newHeight = floor(newHeight);
19095
      tmpCanvas.width = max(newWidth, width);
19096
      tmpCanvas.height = max(newHeight, height);
19097
 
19098
      if (newWidth > width) {
19099
        multW = 2;
19100
        signW = -1;
19101
      }
19102
      if (newHeight > height) {
19103
        multH = 2;
19104
        signH = -1;
19105
      }
19106
      imageData = context.getImageData(0, 0, width, height);
19107
      canvasEl.width = max(newWidth, width);
19108
      canvasEl.height = max(newHeight, height);
19109
      context.putImageData(imageData, 0, 0);
19110
 
19111
      while (!doneW || !doneH) {
19112
        width = stepW;
19113
        height = stepH;
19114
        if (newWidth * signW < floor(stepW * multW * signW)) {
19115
          stepW = floor(stepW * multW);
19116
        }
19117
        else {
19118
          stepW = newWidth;
19119
          doneW = true;
19120
        }
19121
        if (newHeight * signH < floor(stepH * multH * signH)) {
19122
          stepH = floor(stepH * multH);
19123
        }
19124
        else {
19125
          stepH = newHeight;
19126
          doneH = true;
19127
        }
19128
        imageData = context.getImageData(0, 0, width, height);
19129
        tmpCtx.putImageData(imageData, 0, 0);
19130
        context.clearRect(0, 0, stepW, stepH);
19131
        context.drawImage(tmpCanvas, 0, 0, width, height, 0, 0, stepW, stepH);
19132
      }
19133
      return context.getImageData(0, 0, newWidth, newHeight);
19134
    },
19135
 
19136
    lanczosResize: function(canvasEl, oW, oH, dW, dH) {
19137
 
19138
      function lanczosCreate(lobes) {
19139
        return function(x) {
19140
          if (x > lobes) {
19141
            return 0;
19142
          }
19143
          x *= Math.PI;
19144
          if (abs(x) < 1e-16) {
19145
            return 1;
19146
          }
19147
          var xx = x / lobes;
19148
          return sin(x) * sin(xx) / x / xx;
19149
        };
19150
      }
19151
 
19152
      function process(u) {
19153
        var v, i, weight, idx, a, red, green,
19154
            blue, alpha, fX, fY;
19155
        center.x = (u + 0.5) * ratioX;
19156
        icenter.x = floor(center.x);
19157
        for (v = 0; v < dH; v++) {
19158
          center.y = (v + 0.5) * ratioY;
19159
          icenter.y = floor(center.y);
19160
          a = 0, red = 0, green = 0, blue = 0, alpha = 0;
19161
          for (i = icenter.x - range2X; i <= icenter.x + range2X; i++) {
19162
            if (i < 0 || i >= oW) {
19163
              continue;
19164
            }
19165
            fX = floor(1000 * abs(i - center.x));
19166
            if (!cacheLanc[fX]) {
19167
              cacheLanc[fX] = { };
19168
            }
19169
            for (var j = icenter.y - range2Y; j <= icenter.y + range2Y; j++) {
19170
              if (j < 0 || j >= oH) {
19171
                continue;
19172
              }
19173
              fY = floor(1000 * abs(j - center.y));
19174
              if (!cacheLanc[fX][fY]) {
19175
                cacheLanc[fX][fY] = lanczos(sqrt(pow(fX * rcpRatioX, 2) + pow(fY * rcpRatioY, 2)) / 1000);
19176
              }
19177
              weight = cacheLanc[fX][fY];
19178
              if (weight > 0) {
19179
                idx = (j * oW + i) * 4;
19180
                a += weight;
19181
                red += weight * srcData[idx];
19182
                green += weight * srcData[idx + 1];
19183
                blue += weight * srcData[idx + 2];
19184
                alpha += weight * srcData[idx + 3];
19185
              }
19186
            }
19187
          }
19188
          idx = (v * dW + u) * 4;
19189
          destData[idx] = red / a;
19190
          destData[idx + 1] = green / a;
19191
          destData[idx + 2] = blue / a;
19192
          destData[idx + 3] = alpha / a;
19193
        }
19194
 
19195
        if (++u < dW) {
19196
          return process(u);
19197
        }
19198
        else {
19199
          return destImg;
19200
        }
19201
      }
19202
 
19203
      var context = canvasEl.getContext('2d'),
19204
          srcImg = context.getImageData(0, 0, oW, oH),
19205
          destImg = context.getImageData(0, 0, dW, dH),
19206
          srcData = srcImg.data, destData = destImg.data,
19207
          lanczos = lanczosCreate(this.lanczosLobes),
19208
          ratioX = this.rcpScaleX, ratioY = this.rcpScaleY,
19209
          rcpRatioX = 2 / this.rcpScaleX, rcpRatioY = 2 / this.rcpScaleY,
19210
          range2X = ceil(ratioX * this.lanczosLobes / 2),
19211
          range2Y = ceil(ratioY * this.lanczosLobes / 2),
19212
          cacheLanc = { }, center = { }, icenter = { };
19213
 
19214
      return process(0);
19215
    },
19216
 
19217
    bilinearFiltering: function(canvasEl, w, h, w2, h2) {
19218
      var a, b, c, d, x, y, i, j, xDiff, yDiff, chnl,
19219
          color, offset = 0, origPix, ratioX = this.rcpScaleX,
19220
          ratioY = this.rcpScaleY, context = canvasEl.getContext('2d'),
19221
          w4 = 4 * (w - 1), img = context.getImageData(0, 0, w, h),
19222
          pixels = img.data, destImage = context.getImageData(0, 0, w2, h2),
19223
          destPixels = destImage.data;
19224
      for (i = 0; i < h2; i++) {
19225
        for (j = 0; j < w2; j++) {
19226
          x = floor(ratioX * j);
19227
          y = floor(ratioY * i);
19228
          xDiff = ratioX * j - x;
19229
          yDiff = ratioY * i - y;
19230
          origPix = 4 * (y * w + x);
19231
 
19232
          for (chnl = 0; chnl < 4; chnl++) {
19233
            a = pixels[origPix + chnl];
19234
            b = pixels[origPix + 4 + chnl];
19235
            c = pixels[origPix + w4 + chnl];
19236
            d = pixels[origPix + w4 + 4 + chnl];
19237
            color = a * (1 - xDiff) * (1 - yDiff) + b * xDiff * (1 - yDiff) +
19238
                    c * yDiff * (1 - xDiff) + d * xDiff * yDiff;
19239
            destPixels[offset++] = color;
19240
          }
19241
        }
19242
      }
19243
      return destImage;
19244
    },
19245
 
19246
    hermiteFastResize: function(canvasEl, oW, oH, dW, dH) {
19247
      var ratioW = this.rcpScaleX, ratioH = this.rcpScaleY,
19248
          ratioWHalf = ceil(ratioW / 2),
19249
          ratioHHalf = ceil(ratioH / 2),
19250
          context = canvasEl.getContext('2d'),
19251
          img = context.getImageData(0, 0, oW, oH), data = img.data,
19252
          img2 = context.getImageData(0, 0, dW, dH), data2 = img2.data;
19253
      for (var j = 0; j < dH; j++) {
19254
        for (var i = 0; i < dW; i++) {
19255
          var x2 = (i + j * dW) * 4, weight = 0, weights = 0, weightsAlpha = 0,
19256
              gxR = 0, gxG = 0, gxB = 0, gxA = 0, centerY = (j + 0.5) * ratioH;
19257
          for (var yy = floor(j * ratioH); yy < (j + 1) * ratioH; yy++) {
19258
            var dy = abs(centerY - (yy + 0.5)) / ratioHHalf,
19259
                centerX = (i + 0.5) * ratioW, w0 = dy * dy;
19260
            for (var xx = floor(i * ratioW); xx < (i + 1) * ratioW; xx++) {
19261
              var dx = abs(centerX - (xx + 0.5)) / ratioWHalf,
19262
                  w = sqrt(w0 + dx * dx);
19263
              /*jshint maxdepth:5 */
19264
              if (w > 1 && w < -1) {
19265
                continue;
19266
              }
19267
              //hermite filter
19268
              weight = 2 * w * w * w - 3 * w * w + 1;
19269
              if (weight > 0) {
19270
                dx = 4 * (xx + yy * oW);
19271
                //alpha
19272
                gxA += weight * data[dx + 3];
19273
                weightsAlpha += weight;
19274
                //colors
19275
                /*jshint maxdepth:6 */
19276
                if (data[dx + 3] < 255) {
19277
                  weight = weight * data[dx + 3] / 250;
19278
                }
19279
                /*jshint maxdepth:5 */
19280
                gxR += weight * data[dx];
19281
                gxG += weight * data[dx + 1];
19282
                gxB += weight * data[dx + 2];
19283
                weights += weight;
19284
              }
19285
              /*jshint maxdepth:4 */
19286
            }
19287
          }
19288
          data2[x2] = gxR / weights;
19289
          data2[x2 + 1] = gxG / weights;
19290
          data2[x2 + 2] = gxB / weights;
19291
          data2[x2 + 3] = gxA / weightsAlpha;
19292
        }
19293
      }
19294
      return img2;
19295
    },
19296
 
19297
    /**
19298
     * Returns object representation of an instance
19299
     * @return {Object} Object representation of an instance
19300
     */
19301
    toObject: function() {
19302
      return {
19303
        type: this.type,
19304
        scaleX: this.scaleX,
19305
        scaley: this.scaleY,
19306
        resizeType: this.resizeType,
19307
        lanczosLobes: this.lanczosLobes
19308
      };
19309
    }
19310
  });
19311
 
19312
  /**
19313
   * Returns filter instance from an object representation
19314
   * @static
19315
   * @return {fabric.Image.filters.Resize} Instance of fabric.Image.filters.Resize
19316
   */
19317
  fabric.Image.filters.Resize.fromObject = function() {
19318
    return new fabric.Image.filters.Resize();
19319
  };
19320
 
19321
})(typeof exports !== 'undefined' ? exports : this);
19322
 
19323
 
19324
(function(global) {
19325
 
19326
  'use strict';
19327
 
19328
  var fabric = global.fabric || (global.fabric = { }),
19329
      extend = fabric.util.object.extend,
19330
      clone = fabric.util.object.clone,
19331
      toFixed = fabric.util.toFixed,
19332
      supportsLineDash = fabric.StaticCanvas.supports('setLineDash');
19333
 
19334
  if (fabric.Text) {
19335
    fabric.warn('fabric.Text is already defined');
19336
    return;
19337
  }
19338
 
19339
  var stateProperties = fabric.Object.prototype.stateProperties.concat();
19340
  stateProperties.push(
19341
    'fontFamily',
19342
    'fontWeight',
19343
    'fontSize',
19344
    'text',
19345
    'textDecoration',
19346
    'textAlign',
19347
    'fontStyle',
19348
    'lineHeight',
19349
    'textBackgroundColor'
19350
  );
19351
 
19352
  /**
19353
   * Text class
19354
   * @class fabric.Text
19355
   * @extends fabric.Object
19356
   * @return {fabric.Text} thisArg
19357
   * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#text}
19358
   * @see {@link fabric.Text#initialize} for constructor definition
19359
   */
19360
  fabric.Text = fabric.util.createClass(fabric.Object, /** @lends fabric.Text.prototype */ {
19361
 
19362
    /**
19363
     * Properties which when set cause object to change dimensions
19364
     * @type Object
19365
     * @private
19366
     */
19367
    _dimensionAffectingProps: {
19368
      fontSize: true,
19369
      fontWeight: true,
19370
      fontFamily: true,
19371
      fontStyle: true,
19372
      lineHeight: true,
19373
      stroke: true,
19374
      strokeWidth: true,
19375
      text: true,
19376
      textAlign: true
19377
    },
19378
 
19379
    /**
19380
     * @private
19381
     */
19382
    _reNewline: /\r?\n/,
19383
 
19384
    /**
19385
     * Retrieves object's fontSize
19386
     * @method getFontSize
19387
     * @memberOf fabric.Text.prototype
19388
     * @return {String} Font size (in pixels)
19389
     */
19390
 
19391
    /**
19392
     * Sets object's fontSize
19393
     * @method setFontSize
19394
     * @memberOf fabric.Text.prototype
19395
     * @param {Number} fontSize Font size (in pixels)
19396
     * @return {fabric.Text}
19397
     * @chainable
19398
     */
19399
 
19400
    /**
19401
     * Retrieves object's fontWeight
19402
     * @method getFontWeight
19403
     * @memberOf fabric.Text.prototype
19404
     * @return {(String|Number)} Font weight
19405
     */
19406
 
19407
    /**
19408
     * Sets object's fontWeight
19409
     * @method setFontWeight
19410
     * @memberOf fabric.Text.prototype
19411
     * @param {(Number|String)} fontWeight Font weight
19412
     * @return {fabric.Text}
19413
     * @chainable
19414
     */
19415
 
19416
    /**
19417
     * Retrieves object's fontFamily
19418
     * @method getFontFamily
19419
     * @memberOf fabric.Text.prototype
19420
     * @return {String} Font family
19421
     */
19422
 
19423
    /**
19424
     * Sets object's fontFamily
19425
     * @method setFontFamily
19426
     * @memberOf fabric.Text.prototype
19427
     * @param {String} fontFamily Font family
19428
     * @return {fabric.Text}
19429
     * @chainable
19430
     */
19431
 
19432
    /**
19433
     * Retrieves object's text
19434
     * @method getText
19435
     * @memberOf fabric.Text.prototype
19436
     * @return {String} text
19437
     */
19438
 
19439
    /**
19440
     * Sets object's text
19441
     * @method setText
19442
     * @memberOf fabric.Text.prototype
19443
     * @param {String} text Text
19444
     * @return {fabric.Text}
19445
     * @chainable
19446
     */
19447
 
19448
    /**
19449
     * Retrieves object's textDecoration
19450
     * @method getTextDecoration
19451
     * @memberOf fabric.Text.prototype
19452
     * @return {String} Text decoration
19453
     */
19454
 
19455
    /**
19456
     * Sets object's textDecoration
19457
     * @method setTextDecoration
19458
     * @memberOf fabric.Text.prototype
19459
     * @param {String} textDecoration Text decoration
19460
     * @return {fabric.Text}
19461
     * @chainable
19462
     */
19463
 
19464
    /**
19465
     * Retrieves object's fontStyle
19466
     * @method getFontStyle
19467
     * @memberOf fabric.Text.prototype
19468
     * @return {String} Font style
19469
     */
19470
 
19471
    /**
19472
     * Sets object's fontStyle
19473
     * @method setFontStyle
19474
     * @memberOf fabric.Text.prototype
19475
     * @param {String} fontStyle Font style
19476
     * @return {fabric.Text}
19477
     * @chainable
19478
     */
19479
 
19480
    /**
19481
     * Retrieves object's lineHeight
19482
     * @method getLineHeight
19483
     * @memberOf fabric.Text.prototype
19484
     * @return {Number} Line height
19485
     */
19486
 
19487
    /**
19488
     * Sets object's lineHeight
19489
     * @method setLineHeight
19490
     * @memberOf fabric.Text.prototype
19491
     * @param {Number} lineHeight Line height
19492
     * @return {fabric.Text}
19493
     * @chainable
19494
     */
19495
 
19496
    /**
19497
     * Retrieves object's textAlign
19498
     * @method getTextAlign
19499
     * @memberOf fabric.Text.prototype
19500
     * @return {String} Text alignment
19501
     */
19502
 
19503
    /**
19504
     * Sets object's textAlign
19505
     * @method setTextAlign
19506
     * @memberOf fabric.Text.prototype
19507
     * @param {String} textAlign Text alignment
19508
     * @return {fabric.Text}
19509
     * @chainable
19510
     */
19511
 
19512
    /**
19513
     * Retrieves object's textBackgroundColor
19514
     * @method getTextBackgroundColor
19515
     * @memberOf fabric.Text.prototype
19516
     * @return {String} Text background color
19517
     */
19518
 
19519
    /**
19520
     * Sets object's textBackgroundColor
19521
     * @method setTextBackgroundColor
19522
     * @memberOf fabric.Text.prototype
19523
     * @param {String} textBackgroundColor Text background color
19524
     * @return {fabric.Text}
19525
     * @chainable
19526
     */
19527
 
19528
    /**
19529
     * Type of an object
19530
     * @type String
19531
     * @default
19532
     */
19533
    type:                 'text',
19534
 
19535
    /**
19536
     * Font size (in pixels)
19537
     * @type Number
19538
     * @default
19539
     */
19540
    fontSize:             40,
19541
 
19542
    /**
19543
     * Font weight (e.g. bold, normal, 400, 600, 800)
19544
     * @type {(Number|String)}
19545
     * @default
19546
     */
19547
    fontWeight:           'normal',
19548
 
19549
    /**
19550
     * Font family
19551
     * @type String
19552
     * @default
19553
     */
19554
    fontFamily:           'Times New Roman',
19555
 
19556
    /**
19557
     * Text decoration Possible values: "", "underline", "overline" or "line-through".
19558
     * @type String
19559
     * @default
19560
     */
19561
    textDecoration:       '',
19562
 
19563
    /**
19564
     * Text alignment. Possible values: "left", "center", or "right".
19565
     * @type String
19566
     * @default
19567
     */
19568
    textAlign:            'left',
19569
 
19570
    /**
19571
     * Font style . Possible values: "", "normal", "italic" or "oblique".
19572
     * @type String
19573
     * @default
19574
     */
19575
    fontStyle:            '',
19576
 
19577
    /**
19578
     * Line height
19579
     * @type Number
19580
     * @default
19581
     */
19582
    lineHeight:           1.16,
19583
 
19584
    /**
19585
     * Background color of text lines
19586
     * @type String
19587
     * @default
19588
     */
19589
    textBackgroundColor:  '',
19590
 
19591
    /**
19592
     * List of properties to consider when checking if
19593
     * state of an object is changed ({@link fabric.Object#hasStateChanged})
19594
     * as well as for history (undo/redo) purposes
19595
     * @type Array
19596
     */
19597
    stateProperties:      stateProperties,
19598
 
19599
    /**
19600
     * When defined, an object is rendered via stroke and this property specifies its color.
19601
     * <b>Backwards incompatibility note:</b> This property was named "strokeStyle" until v1.1.6
19602
     * @type String
19603
     * @default
19604
     */
19605
    stroke:               null,
19606
 
19607
    /**
19608
     * Shadow object representing shadow of this shape.
19609
     * <b>Backwards incompatibility note:</b> This property was named "textShadow" (String) until v1.2.11
19610
     * @type fabric.Shadow
19611
     * @default
19612
     */
19613
    shadow:               null,
19614
 
19615
    /**
19616
     * @private
19617
     */
19618
    _fontSizeFraction: 0.25,
19619
 
19620
    /**
19621
     * Text Line proportion to font Size (in pixels)
19622
     * @type Number
19623
     * @default
19624
     */
19625
    _fontSizeMult:             1.13,
19626
 
19627
    /**
19628
     * Constructor
19629
     * @param {String} text Text string
19630
     * @param {Object} [options] Options object
19631
     * @return {fabric.Text} thisArg
19632
     */
19633
    initialize: function(text, options) {
19634
      options = options || { };
19635
      this.text = text;
19636
      this.__skipDimension = true;
19637
      this.setOptions(options);
19638
      this.__skipDimension = false;
19639
      this._initDimensions();
19640
    },
19641
 
19642
    /**
19643
     * Renders text object on offscreen canvas, so that it would get dimensions
19644
     * @private
19645
     */
19646
    _initDimensions: function(ctx) {
19647
      if (this.__skipDimension) {
19648
        return;
19649
      }
19650
      if (!ctx) {
19651
        ctx = fabric.util.createCanvasElement().getContext('2d');
19652
        this._setTextStyles(ctx);
19653
      }
19654
      this._textLines = this.text.split(this._reNewline);
19655
      this._clearCache();
19656
      var currentTextAlign = this.textAlign;
19657
      this.textAlign = 'left';
19658
      this.width = this._getTextWidth(ctx);
19659
      this.textAlign = currentTextAlign;
19660
      this.height = this._getTextHeight(ctx);
19661
    },
19662
 
19663
    /**
19664
     * Returns string representation of an instance
19665
     * @return {String} String representation of text object
19666
     */
19667
    toString: function() {
19668
      return '#<fabric.Text (' + this.complexity() +
19669
        '): { "text": "' + this.text + '", "fontFamily": "' + this.fontFamily + '" }>';
19670
    },
19671
 
19672
    /**
19673
     * @private
19674
     * @param {CanvasRenderingContext2D} ctx Context to render on
19675
     */
19676
    _render: function(ctx) {
19677
 
19678
      this.clipTo && fabric.util.clipContext(this, ctx);
19679
 
19680
      this._renderTextBackground(ctx);
19681
      this._renderText(ctx);
19682
 
19683
      this._renderTextDecoration(ctx);
19684
      this.clipTo && ctx.restore();
19685
    },
19686
 
19687
    /**
19688
     * @private
19689
     * @param {CanvasRenderingContext2D} ctx Context to render on
19690
     */
19691
    _renderText: function(ctx) {
19692
      ctx.save();
19693
      this._translateForTextAlign(ctx);
19694
      this._setOpacity(ctx);
19695
      this._setShadow(ctx);
19696
      this._setupCompositeOperation(ctx);
19697
      this._renderTextFill(ctx);
19698
      this._renderTextStroke(ctx);
19699
      this._restoreCompositeOperation(ctx);
19700
      this._removeShadow(ctx);
19701
      ctx.restore();
19702
    },
19703
 
19704
    /**
19705
     * @private
19706
     * @param {CanvasRenderingContext2D} ctx Context to render on
19707
     */
19708
    _translateForTextAlign: function(ctx) {
19709
      if (this.textAlign !== 'left' && this.textAlign !== 'justify') {
19710
        ctx.translate(this.textAlign === 'center' ? (this.width / 2) : this.width, 0);
19711
      }
19712
    },
19713
 
19714
    /**
19715
     * @private
19716
     * @param {CanvasRenderingContext2D} ctx Context to render on
19717
     */
19718
    _setTextStyles: function(ctx) {
19719
      ctx.textBaseline = 'alphabetic';
19720
      if (!this.skipTextAlign) {
19721
        ctx.textAlign = this.textAlign;
19722
      }
19723
      ctx.font = this._getFontDeclaration();
19724
    },
19725
 
19726
    /**
19727
     * @private
19728
     * @param {CanvasRenderingContext2D} ctx Context to render on
19729
     * @return {Number} Height of fabric.Text object
19730
     */
19731
    _getTextHeight: function() {
19732
      return this._textLines.length * this._getHeightOfLine();
19733
    },
19734
 
19735
    /**
19736
     * @private
19737
     * @param {CanvasRenderingContext2D} ctx Context to render on
19738
     * @return {Number} Maximum width of fabric.Text object
19739
     */
19740
    _getTextWidth: function(ctx) {
19741
      var maxWidth = this._getLineWidth(ctx, 0);
19742
 
19743
      for (var i = 1, len = this._textLines.length; i < len; i++) {
19744
        var currentLineWidth = this._getLineWidth(ctx, i);
19745
        if (currentLineWidth > maxWidth) {
19746
          maxWidth = currentLineWidth;
19747
        }
19748
      }
19749
      return maxWidth;
19750
    },
19751
 
19752
    /**
19753
     * @private
19754
     * @param {String} method Method name ("fillText" or "strokeText")
19755
     * @param {CanvasRenderingContext2D} ctx Context to render on
19756
     * @param {String} chars Chars to render
19757
     * @param {Number} left Left position of text
19758
     * @param {Number} top Top position of text
19759
     */
19760
    _renderChars: function(method, ctx, chars, left, top) {
19761
      ctx[method](chars, left, top);
19762
    },
19763
 
19764
    /**
19765
     * @private
19766
     * @param {String} method Method name ("fillText" or "strokeText")
19767
     * @param {CanvasRenderingContext2D} ctx Context to render on
19768
     * @param {String} line Text to render
19769
     * @param {Number} left Left position of text
19770
     * @param {Number} top Top position of text
19771
     * @param {Number} lineIndex Index of a line in a text
19772
     */
19773
    _renderTextLine: function(method, ctx, line, left, top, lineIndex) {
19774
      // lift the line by quarter of fontSize
19775
      top -= this.fontSize * this._fontSizeFraction;
19776
 
19777
      // short-circuit
19778
      if (this.textAlign !== 'justify') {
19779
        this._renderChars(method, ctx, line, left, top, lineIndex);
19780
        return;
19781
      }
19782
 
19783
      var lineWidth = this._getLineWidth(ctx, lineIndex),
19784
          totalWidth = this.width;
19785
      if (totalWidth >= lineWidth) {
19786
        // stretch the line
19787
        var words = line.split(/\s+/),
19788
            wordsWidth = this._getWidthOfWords(ctx, line, lineIndex),
19789
            widthDiff = totalWidth - wordsWidth,
19790
            numSpaces = words.length - 1,
19791
            spaceWidth = widthDiff / numSpaces,
19792
            leftOffset = 0;
19793
 
19794
        for (var i = 0, len = words.length; i < len; i++) {
19795
          this._renderChars(method, ctx, words[i], left + leftOffset, top, lineIndex);
19796
          leftOffset += ctx.measureText(words[i]).width + spaceWidth;
19797
        }
19798
      }
19799
      else {
19800
        this._renderChars(method, ctx, line, left, top, lineIndex);
19801
      }
19802
    },
19803
 
19804
    /**
19805
     * @private
19806
     * @param {CanvasRenderingContext2D} ctx Context to render on
19807
     * @param {Number} line
19808
     */
19809
    _getWidthOfWords: function (ctx, line) {
19810
      return ctx.measureText(line.replace(/\s+/g, '')).width;
19811
    },
19812
 
19813
    /**
19814
     * @private
19815
     * @return {Number} Left offset
19816
     */
19817
    _getLeftOffset: function() {
19818
      return -this.width / 2;
19819
    },
19820
 
19821
    /**
19822
     * @private
19823
     * @return {Number} Top offset
19824
     */
19825
    _getTopOffset: function() {
19826
      return -this.height / 2;
19827
    },
19828
 
19829
    /**
19830
     * @private
19831
     * @param {CanvasRenderingContext2D} ctx Context to render on
19832
     */
19833
    _renderTextFill: function(ctx) {
19834
      if (!this.fill && !this._skipFillStrokeCheck) {
19835
        return;
19836
      }
19837
 
19838
      var lineHeights = 0;
19839
 
19840
      for (var i = 0, len = this._textLines.length; i < len; i++) {
19841
        var heightOfLine = this._getHeightOfLine(ctx, i),
19842
            maxHeight = heightOfLine / this.lineHeight;
19843
 
19844
        this._renderTextLine(
19845
          'fillText',
19846
          ctx,
19847
          this._textLines[i],
19848
          this._getLeftOffset(),
19849
          this._getTopOffset() + lineHeights + maxHeight,
19850
          i
19851
        );
19852
        lineHeights += heightOfLine;
19853
      }
19854
      if (this.shadow && !this.shadow.affectStroke) {
19855
        this._removeShadow(ctx);
19856
      }
19857
    },
19858
 
19859
    /**
19860
     * @private
19861
     * @param {CanvasRenderingContext2D} ctx Context to render on
19862
     */
19863
    _renderTextStroke: function(ctx) {
19864
      if ((!this.stroke || this.strokeWidth === 0) && !this._skipFillStrokeCheck) {
19865
        return;
19866
      }
19867
 
19868
      var lineHeights = 0;
19869
 
19870
      ctx.save();
19871
 
19872
      if (this.strokeDashArray) {
19873
        // Spec requires the concatenation of two copies the dash list when the number of elements is odd
19874
        if (1 & this.strokeDashArray.length) {
19875
          this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray);
19876
        }
19877
        supportsLineDash && ctx.setLineDash(this.strokeDashArray);
19878
      }
19879
 
19880
      ctx.beginPath();
19881
      for (var i = 0, len = this._textLines.length; i < len; i++) {
19882
        var heightOfLine = this._getHeightOfLine(ctx, i),
19883
            maxHeight = heightOfLine / this.lineHeight;
19884
 
19885
        this._renderTextLine(
19886
          'strokeText',
19887
          ctx,
19888
          this._textLines[i],
19889
          this._getLeftOffset(),
19890
          this._getTopOffset() + lineHeights + maxHeight,
19891
          i
19892
        );
19893
        lineHeights += heightOfLine;
19894
      }
19895
      ctx.closePath();
19896
      ctx.restore();
19897
    },
19898
 
19899
    _getHeightOfLine: function() {
19900
      return this.fontSize * this._fontSizeMult * this.lineHeight;
19901
    },
19902
 
19903
    /**
19904
     * @private
19905
     * @param {CanvasRenderingContext2D} ctx Context to render on
19906
     * @param {Array} textLines Array of all text lines
19907
     */
19908
    _renderTextBackground: function(ctx) {
19909
      this._renderTextBoxBackground(ctx);
19910
      this._renderTextLinesBackground(ctx);
19911
    },
19912
 
19913
    /**
19914
     * @private
19915
     * @param {CanvasRenderingContext2D} ctx Context to render on
19916
     */
19917
    _renderTextBoxBackground: function(ctx) {
19918
      if (!this.backgroundColor) {
19919
        return;
19920
      }
19921
 
19922
      ctx.save();
19923
      ctx.fillStyle = this.backgroundColor;
19924
 
19925
      ctx.fillRect(
19926
        this._getLeftOffset(),
19927
        this._getTopOffset(),
19928
        this.width,
19929
        this.height
19930
      );
19931
 
19932
      ctx.restore();
19933
    },
19934
 
19935
    /**
19936
     * @private
19937
     * @param {CanvasRenderingContext2D} ctx Context to render on
19938
     */
19939
    _renderTextLinesBackground: function(ctx) {
19940
      var lineTopOffset = 0, heightOfLine = this._getHeightOfLine();
19941
      if (!this.textBackgroundColor) {
19942
        return;
19943
      }
19944
 
19945
      ctx.save();
19946
      ctx.fillStyle = this.textBackgroundColor;
19947
 
19948
      for (var i = 0, len = this._textLines.length; i < len; i++) {
19949
 
19950
        if (this._textLines[i] !== '') {
19951
 
19952
          var lineWidth = this._getLineWidth(ctx, i),
19953
              lineLeftOffset = this._getLineLeftOffset(lineWidth);
19954
 
19955
          ctx.fillRect(
19956
            this._getLeftOffset() + lineLeftOffset,
19957
            this._getTopOffset() + lineTopOffset,
19958
            lineWidth,
19959
            this.fontSize * this._fontSizeMult
19960
          );
19961
        }
19962
        lineTopOffset += heightOfLine;
19963
      }
19964
      ctx.restore();
19965
    },
19966
 
19967
    /**
19968
     * @private
19969
     * @param {Number} lineWidth Width of text line
19970
     * @return {Number} Line left offset
19971
     */
19972
    _getLineLeftOffset: function(lineWidth) {
19973
      if (this.textAlign === 'center') {
19974
        return (this.width - lineWidth) / 2;
19975
      }
19976
      if (this.textAlign === 'right') {
19977
        return this.width - lineWidth;
19978
      }
19979
      return 0;
19980
    },
19981
 
19982
    /**
19983
     * @private
19984
     */
19985
    _clearCache: function() {
19986
      this.__lineWidths = [ ];
19987
      this.__lineHeights = [ ];
19988
      this.__lineOffsets = [ ];
19989
    },
19990
 
19991
    /**
19992
     * @private
19993
     */
19994
    _shouldClearCache: function() {
19995
      var shouldClear = false;
19996
      for (var prop in this._dimensionAffectingProps) {
19997
        if (this['__' + prop] !== this[prop]) {
19998
          this['__' + prop] = this[prop];
19999
          shouldClear = true;
20000
        }
20001
      }
20002
      return shouldClear;
20003
    },
20004
 
20005
    /**
20006
     * @private
20007
     * @param {CanvasRenderingContext2D} ctx Context to render on
20008
     * @return {Number} Line width
20009
     */
20010
    _getLineWidth: function(ctx, lineIndex) {
20011
      if (this.__lineWidths[lineIndex]) {
20012
        return this.__lineWidths[lineIndex];
20013
      }
20014
      this.__lineWidths[lineIndex] = ctx.measureText(this._textLines[lineIndex]).width;
20015
      return this.__lineWidths[lineIndex];
20016
    },
20017
 
20018
    /**
20019
     * @private
20020
     * @param {CanvasRenderingContext2D} ctx Context to render on
20021
     */
20022
    _renderTextDecoration: function(ctx) {
20023
      if (!this.textDecoration) {
20024
        return;
20025
      }
20026
 
20027
      var halfOfVerticalBox = this.height / 2,
20028
          _this = this, offsets = [];
20029
 
20030
      /** @ignore */
20031
      function renderLinesAtOffset(offsets) {
20032
        var i, lineHeight = 0, len, j, oLen;
20033
        for (i = 0, len = _this._textLines.length; i < len; i++) {
20034
 
20035
          var lineWidth = _this._getLineWidth(ctx, i),
20036
              lineLeftOffset = _this._getLineLeftOffset(lineWidth),
20037
              heightOfLine = _this._getHeightOfLine(ctx, i);
20038
 
20039
          for (j = 0, oLen = offsets.length; j < oLen; j++) {
20040
            ctx.fillRect(
20041
              _this._getLeftOffset() + lineLeftOffset,
20042
              lineHeight + (_this._fontSizeMult - 1 + offsets[j] ) * _this.fontSize - halfOfVerticalBox,
20043
              lineWidth,
20044
              _this.fontSize / 15);
20045
          }
20046
          lineHeight += heightOfLine;
20047
        }
20048
      }
20049
 
20050
      if (this.textDecoration.indexOf('underline') > -1) {
20051
        offsets.push(0.85); // 1 - 3/16
20052
      }
20053
      if (this.textDecoration.indexOf('line-through') > -1) {
20054
        offsets.push(0.43);
20055
      }
20056
      if (this.textDecoration.indexOf('overline') > -1) {
20057
        offsets.push(-0.12);
20058
      }
20059
 
20060
      if (offsets.length > 0) {
20061
        renderLinesAtOffset(offsets);
20062
      }
20063
    },
20064
 
20065
    /**
20066
     * @private
20067
     */
20068
    _getFontDeclaration: function() {
20069
      return [
20070
        // node-canvas needs "weight style", while browsers need "style weight"
20071
        (fabric.isLikelyNode ? this.fontWeight : this.fontStyle),
20072
        (fabric.isLikelyNode ? this.fontStyle : this.fontWeight),
20073
        this.fontSize + 'px',
20074
        (fabric.isLikelyNode ? ('"' + this.fontFamily + '"') : this.fontFamily)
20075
      ].join(' ');
20076
    },
20077
 
20078
    /**
20079
     * Renders text instance on a specified context
20080
     * @param {CanvasRenderingContext2D} ctx Context to render on
20081
     */
20082
    render: function(ctx, noTransform) {
20083
      // do not render if object is not visible
20084
      if (!this.visible) {
20085
        return;
20086
      }
20087
 
20088
      ctx.save();
20089
      this._setTextStyles(ctx);
20090
 
20091
      if (this._shouldClearCache()) {
20092
        this._initDimensions(ctx);
20093
      }
20094
      if (!noTransform) {
20095
        this.transform(ctx);
20096
      }
20097
      this._setStrokeStyles(ctx);
20098
      this._setFillStyles(ctx);
20099
      if (this.transformMatrix) {
20100
        ctx.transform.apply(ctx, this.transformMatrix);
20101
      }
20102
      if (this.group && this.group.type === 'path-group') {
20103
        ctx.translate(this.left, this.top);
20104
      }
20105
      this._render(ctx);
20106
      ctx.restore();
20107
    },
20108
 
20109
    /**
20110
     * Returns object representation of an instance
20111
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
20112
     * @return {Object} Object representation of an instance
20113
     */
20114
    toObject: function(propertiesToInclude) {
20115
      var object = extend(this.callSuper('toObject', propertiesToInclude), {
20116
        text:                 this.text,
20117
        fontSize:             this.fontSize,
20118
        fontWeight:           this.fontWeight,
20119
        fontFamily:           this.fontFamily,
20120
        fontStyle:            this.fontStyle,
20121
        lineHeight:           this.lineHeight,
20122
        textDecoration:       this.textDecoration,
20123
        textAlign:            this.textAlign,
20124
        textBackgroundColor:  this.textBackgroundColor
20125
      });
20126
      if (!this.includeDefaultValues) {
20127
        this._removeDefaultValues(object);
20128
      }
20129
      return object;
20130
    },
20131
 
20132
    /* _TO_SVG_START_ */
20133
    /**
20134
     * Returns SVG representation of an instance
20135
     * @param {Function} [reviver] Method for further parsing of svg representation.
20136
     * @return {String} svg representation of an instance
20137
     */
20138
    toSVG: function(reviver) {
20139
      var markup = this._createBaseSVGMarkup(),
20140
          offsets = this._getSVGLeftTopOffsets(this.ctx),
20141
          textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft);
20142
      this._wrapSVGTextAndBg(markup, textAndBg);
20143
 
20144
      return reviver ? reviver(markup.join('')) : markup.join('');
20145
    },
20146
 
20147
    /**
20148
     * @private
20149
     */
20150
    _getSVGLeftTopOffsets: function(ctx) {
20151
      var lineTop = this._getHeightOfLine(ctx, 0),
20152
          textLeft = -this.width / 2,
20153
          textTop = 0;
20154
 
20155
      return {
20156
        textLeft: textLeft + (this.group && this.group.type === 'path-group' ? this.left : 0),
20157
        textTop: textTop + (this.group && this.group.type === 'path-group' ? -this.top : 0),
20158
        lineTop: lineTop
20159
      };
20160
    },
20161
 
20162
    /**
20163
     * @private
20164
     */
20165
    _wrapSVGTextAndBg: function(markup, textAndBg) {
20166
      markup.push(
20167
        '\t<g transform="', this.getSvgTransform(), this.getSvgTransformMatrix(), '">\n',
20168
          textAndBg.textBgRects.join(''),
20169
          '\t\t<text ',
20170
            (this.fontFamily ? 'font-family="' + this.fontFamily.replace(/"/g, '\'') + '" ': ''),
20171
            (this.fontSize ? 'font-size="' + this.fontSize + '" ': ''),
20172
            (this.fontStyle ? 'font-style="' + this.fontStyle + '" ': ''),
20173
            (this.fontWeight ? 'font-weight="' + this.fontWeight + '" ': ''),
20174
            (this.textDecoration ? 'text-decoration="' + this.textDecoration + '" ': ''),
20175
            'style="', this.getSvgStyles(), '" >',
20176
            textAndBg.textSpans.join(''),
20177
          '</text>\n',
20178
        '\t</g>\n'
20179
      );
20180
    },
20181
 
20182
    /**
20183
     * @private
20184
     * @param {Number} textTopOffset Text top offset
20185
     * @param {Number} textLeftOffset Text left offset
20186
     * @return {Object}
20187
     */
20188
    _getSVGTextAndBg: function(textTopOffset, textLeftOffset) {
20189
      var textSpans = [ ],
20190
          textBgRects = [ ],
20191
          height = 0;
20192
      // bounding-box background
20193
      this._setSVGBg(textBgRects);
20194
 
20195
      // text and text-background
20196
      for (var i = 0, len = this._textLines.length; i < len; i++) {
20197
        if (this.textBackgroundColor) {
20198
          this._setSVGTextLineBg(textBgRects, i, textLeftOffset, textTopOffset, height);
20199
        }
20200
        this._setSVGTextLineText(i, textSpans, height, textLeftOffset, textTopOffset, textBgRects);
20201
        height += this._getHeightOfLine(this.ctx, i);
20202
      }
20203
 
20204
      return {
20205
        textSpans: textSpans,
20206
        textBgRects: textBgRects
20207
      };
20208
    },
20209
 
20210
    _setSVGTextLineText: function(i, textSpans, height, textLeftOffset, textTopOffset) {
20211
      var yPos = this.fontSize * (this._fontSizeMult - this._fontSizeFraction)
20212
        - textTopOffset + height - this.height / 2;
20213
      textSpans.push(
20214
        '<tspan x="',
20215
          toFixed(textLeftOffset + this._getLineLeftOffset(this.__lineWidths[i]), 4), '" ',
20216
          'y="',
20217
          toFixed(yPos, 4),
20218
          '" ',
20219
          // doing this on <tspan> elements since setting opacity
20220
          // on containing <text> one doesn't work in Illustrator
20221
          this._getFillAttributes(this.fill), '>',
20222
          fabric.util.string.escapeXml(this._textLines[i]),
20223
        '</tspan>'
20224
      );
20225
    },
20226
 
20227
    _setSVGTextLineBg: function(textBgRects, i, textLeftOffset, textTopOffset, height) {
20228
      textBgRects.push(
20229
        '\t\t<rect ',
20230
          this._getFillAttributes(this.textBackgroundColor),
20231
          ' x="',
20232
          toFixed(textLeftOffset + this._getLineLeftOffset(this.__lineWidths[i]), 4),
20233
          '" y="',
20234
          toFixed(height - this.height / 2, 4),
20235
          '" width="',
20236
          toFixed(this.__lineWidths[i], 4),
20237
          '" height="',
20238
          toFixed(this._getHeightOfLine(this.ctx, i) / this.lineHeight, 4),
20239
        '"></rect>\n');
20240
    },
20241
 
20242
    _setSVGBg: function(textBgRects) {
20243
      if (this.backgroundColor) {
20244
        textBgRects.push(
20245
          '\t\t<rect ',
20246
            this._getFillAttributes(this.backgroundColor),
20247
            ' x="',
20248
            toFixed(-this.width / 2, 4),
20249
            '" y="',
20250
            toFixed(-this.height / 2, 4),
20251
            '" width="',
20252
            toFixed(this.width, 4),
20253
            '" height="',
20254
            toFixed(this.height, 4),
20255
          '"></rect>\n');
20256
      }
20257
    },
20258
 
20259
    /**
20260
     * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values
20261
     * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1
20262
     *
20263
     * @private
20264
     * @param {Any} value
20265
     * @return {String}
20266
     */
20267
    _getFillAttributes: function(value) {
20268
      var fillColor = (value && typeof value === 'string') ? new fabric.Color(value) : '';
20269
      if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) {
20270
        return 'fill="' + value + '"';
20271
      }
20272
      return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"';
20273
    },
20274
    /* _TO_SVG_END_ */
20275
 
20276
    /**
20277
     * Sets specified property to a specified value
20278
     * @param {String} key
20279
     * @param {Any} value
20280
     * @return {fabric.Text} thisArg
20281
     * @chainable
20282
     */
20283
    _set: function(key, value) {
20284
      this.callSuper('_set', key, value);
20285
 
20286
      if (key in this._dimensionAffectingProps) {
20287
        this._initDimensions();
20288
        this.setCoords();
20289
      }
20290
    },
20291
 
20292
    /**
20293
     * Returns complexity of an instance
20294
     * @return {Number} complexity
20295
     */
20296
    complexity: function() {
20297
      return 1;
20298
    }
20299
  });
20300
 
20301
  /* _FROM_SVG_START_ */
20302
  /**
20303
   * List of attribute names to account for when parsing SVG element (used by {@link fabric.Text.fromElement})
20304
   * @static
20305
   * @memberOf fabric.Text
20306
   * @see: http://www.w3.org/TR/SVG/text.html#TextElement
20307
   */
20308
  fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(
20309
    'x y dx dy font-family font-style font-weight font-size text-decoration text-anchor'.split(' '));
20310
 
20311
  /**
20312
   * Default SVG font size
20313
   * @static
20314
   * @memberOf fabric.Text
20315
   */
20316
  fabric.Text.DEFAULT_SVG_FONT_SIZE = 16;
20317
 
20318
  /**
20319
   * Returns fabric.Text instance from an SVG element (<b>not yet implemented</b>)
20320
   * @static
20321
   * @memberOf fabric.Text
20322
   * @param {SVGElement} element Element to parse
20323
   * @param {Object} [options] Options object
20324
   * @return {fabric.Text} Instance of fabric.Text
20325
   */
20326
  fabric.Text.fromElement = function(element, options) {
20327
    if (!element) {
20328
      return null;
20329
    }
20330
 
20331
    var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES);
20332
    options = fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes);
20333
 
20334
    options.top = options.top || 0;
20335
    options.left = options.left || 0;
20336
    if ('dx' in parsedAttributes) {
20337
      options.left += parsedAttributes.dx;
20338
    }
20339
    if ('dy' in parsedAttributes) {
20340
      options.top += parsedAttributes.dy;
20341
    }
20342
    if (!('fontSize' in options)) {
20343
      options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE;
20344
    }
20345
 
20346
    if (!options.originX) {
20347
      options.originX = 'left';
20348
    }
20349
    var textContent = element.textContent.replace(/^\s+|\s+$|\n+/g, '').replace(/\s+/g, ' '),
20350
        text = new fabric.Text(textContent, options),
20351
        /*
20352
          Adjust positioning:
20353
            x/y attributes in SVG correspond to the bottom-left corner of text bounding box
20354
            top/left properties in Fabric correspond to center point of text bounding box
20355
        */
20356
        offX = 0;
20357
 
20358
    if (text.originX === 'left') {
20359
      offX = text.getWidth() / 2;
20360
    }
20361
    if (text.originX === 'right') {
20362
      offX = -text.getWidth() / 2;
20363
    }
20364
    text.set({
20365
      left: text.getLeft() + offX,
20366
      top: text.getTop() - text.getHeight() / 2 + text.fontSize * (0.18 + text._fontSizeFraction) /* 0.3 is the old lineHeight */
20367
    });
20368
 
20369
    return text;
20370
  };
20371
  /* _FROM_SVG_END_ */
20372
 
20373
  /**
20374
   * Returns fabric.Text instance from an object representation
20375
   * @static
20376
   * @memberOf fabric.Text
20377
   * @param {Object} object Object to create an instance from
20378
   * @return {fabric.Text} Instance of fabric.Text
20379
   */
20380
  fabric.Text.fromObject = function(object) {
20381
    return new fabric.Text(object.text, clone(object));
20382
  };
20383
 
20384
  fabric.util.createAccessors(fabric.Text);
20385
 
20386
})(typeof exports !== 'undefined' ? exports : this);
20387
 
20388
 
20389
(function() {
20390
 
20391
  var clone = fabric.util.object.clone;
20392
 
20393
  /**
20394
   * IText class (introduced in <b>v1.4</b>) Events are also fired with "text:"
20395
   * prefix when observing canvas.
20396
   * @class fabric.IText
20397
   * @extends fabric.Text
20398
   * @mixes fabric.Observable
20399
   *
20400
   * @fires changed
20401
   * @fires selection:changed
20402
   * @fires editing:entered
20403
   * @fires editing:exited
20404
   *
20405
   * @return {fabric.IText} thisArg
20406
   * @see {@link fabric.IText#initialize} for constructor definition
20407
   *
20408
   * <p>Supported key combinations:</p>
20409
   * <pre>
20410
   *   Move cursor:                    left, right, up, down
20411
   *   Select character:               shift + left, shift + right
20412
   *   Select text vertically:         shift + up, shift + down
20413
   *   Move cursor by word:            alt + left, alt + right
20414
   *   Select words:                   shift + alt + left, shift + alt + right
20415
   *   Move cursor to line start/end:  cmd + left, cmd + right or home, end
20416
   *   Select till start/end of line:  cmd + shift + left, cmd + shift + right or shift + home, shift + end
20417
   *   Jump to start/end of text:      cmd + up, cmd + down
20418
   *   Select till start/end of text:  cmd + shift + up, cmd + shift + down or shift + pgUp, shift + pgDown
20419
   *   Delete character:               backspace
20420
   *   Delete word:                    alt + backspace
20421
   *   Delete line:                    cmd + backspace
20422
   *   Forward delete:                 delete
20423
   *   Copy text:                      ctrl/cmd + c
20424
   *   Paste text:                     ctrl/cmd + v
20425
   *   Cut text:                       ctrl/cmd + x
20426
   *   Select entire text:             ctrl/cmd + a
20427
   *   Quit editing                    tab or esc
20428
   * </pre>
20429
   *
20430
   * <p>Supported mouse/touch combination</p>
20431
   * <pre>
20432
   *   Position cursor:                click/touch
20433
   *   Create selection:               click/touch & drag
20434
   *   Create selection:               click & shift + click
20435
   *   Select word:                    double click
20436
   *   Select line:                    triple click
20437
   * </pre>
20438
   */
20439
  fabric.IText = fabric.util.createClass(fabric.Text, fabric.Observable, /** @lends fabric.IText.prototype */ {
20440
 
20441
    /**
20442
     * Type of an object
20443
     * @type String
20444
     * @default
20445
     */
20446
    type: 'i-text',
20447
 
20448
    /**
20449
     * Index where text selection starts (or where cursor is when there is no selection)
20450
     * @type Nubmer
20451
     * @default
20452
     */
20453
    selectionStart: 0,
20454
 
20455
    /**
20456
     * Index where text selection ends
20457
     * @type Nubmer
20458
     * @default
20459
     */
20460
    selectionEnd: 0,
20461
 
20462
    /**
20463
     * Color of text selection
20464
     * @type String
20465
     * @default
20466
     */
20467
    selectionColor: 'rgba(17,119,255,0.3)',
20468
 
20469
    /**
20470
     * Indicates whether text is in editing mode
20471
     * @type Boolean
20472
     * @default
20473
     */
20474
    isEditing: false,
20475
 
20476
    /**
20477
     * Indicates whether a text can be edited
20478
     * @type Boolean
20479
     * @default
20480
     */
20481
    editable: true,
20482
 
20483
    /**
20484
     * Border color of text object while it's in editing mode
20485
     * @type String
20486
     * @default
20487
     */
20488
    editingBorderColor: 'rgba(102,153,255,0.25)',
20489
 
20490
    /**
20491
     * Width of cursor (in px)
20492
     * @type Number
20493
     * @default
20494
     */
20495
    cursorWidth: 2,
20496
 
20497
    /**
20498
     * Color of default cursor (when not overwritten by character style)
20499
     * @type String
20500
     * @default
20501
     */
20502
    cursorColor: '#333',
20503
 
20504
    /**
20505
     * Delay between cursor blink (in ms)
20506
     * @type Number
20507
     * @default
20508
     */
20509
    cursorDelay: 1000,
20510
 
20511
    /**
20512
     * Duration of cursor fadein (in ms)
20513
     * @type Number
20514
     * @default
20515
     */
20516
    cursorDuration: 600,
20517
 
20518
    /**
20519
     * Object containing character styles
20520
     * (where top-level properties corresponds to line number and 2nd-level properties -- to char number in a line)
20521
     * @type Object
20522
     * @default
20523
     */
20524
    styles: null,
20525
 
20526
    /**
20527
     * Indicates whether internal text char widths can be cached
20528
     * @type Boolean
20529
     * @default
20530
     */
20531
    caching: true,
20532
 
20533
    /**
20534
     * @private
20535
     * @type Boolean
20536
     * @default
20537
     */
20538
    _skipFillStrokeCheck: false,
20539
 
20540
    /**
20541
     * @private
20542
     */
20543
    _reSpace: /\s|\n/,
20544
 
20545
    /**
20546
     * @private
20547
     */
20548
    _currentCursorOpacity: 0,
20549
 
20550
    /**
20551
     * @private
20552
     */
20553
    _selectionDirection: null,
20554
 
20555
    /**
20556
     * @private
20557
     */
20558
    _abortCursorAnimation: false,
20559
 
20560
    /**
20561
     * @private
20562
     */
20563
    _charWidthsCache: { },
20564
 
20565
    /**
20566
     * Constructor
20567
     * @param {String} text Text string
20568
     * @param {Object} [options] Options object
20569
     * @return {fabric.IText} thisArg
20570
     */
20571
    initialize: function(text, options) {
20572
      this.styles = options ? (options.styles || { }) : { };
20573
      this.callSuper('initialize', text, options);
20574
      this.initBehavior();
20575
    },
20576
 
20577
    /**
20578
     * @private
20579
     */
20580
    _clearCache: function() {
20581
      this.callSuper('_clearCache');
20582
      this.__maxFontHeights = [ ];
20583
      this.__widthOfSpace = [ ];
20584
    },
20585
 
20586
    /**
20587
     * Returns true if object has no styling
20588
     */
20589
    isEmptyStyles: function() {
20590
      if (!this.styles) {
20591
        return true;
20592
      }
20593
      var obj = this.styles;
20594
 
20595
      for (var p1 in obj) {
20596
        for (var p2 in obj[p1]) {
20597
          /*jshint unused:false */
20598
          for (var p3 in obj[p1][p2]) {
20599
            return false;
20600
          }
20601
        }
20602
      }
20603
      return true;
20604
    },
20605
 
20606
    /**
20607
     * Sets selection start (left boundary of a selection)
20608
     * @param {Number} index Index to set selection start to
20609
     */
20610
    setSelectionStart: function(index) {
20611
      index = Math.max(index, 0);
20612
      if (this.selectionStart !== index) {
20613
        this.fire('selection:changed');
20614
        this.canvas && this.canvas.fire('text:selection:changed', { target: this });
20615
        this.selectionStart = index;
20616
      }
20617
      this._updateTextarea();
20618
    },
20619
 
20620
    /**
20621
     * Sets selection end (right boundary of a selection)
20622
     * @param {Number} index Index to set selection end to
20623
     */
20624
    setSelectionEnd: function(index) {
20625
      index = Math.min(index, this.text.length);
20626
      if (this.selectionEnd !== index) {
20627
        this.fire('selection:changed');
20628
        this.canvas && this.canvas.fire('text:selection:changed', { target: this });
20629
        this.selectionEnd = index;
20630
      }
20631
      this._updateTextarea();
20632
    },
20633
 
20634
    /**
20635
     * Gets style of a current selection/cursor (at the start position)
20636
     * @param {Number} [startIndex] Start index to get styles at
20637
     * @param {Number} [endIndex] End index to get styles at
20638
     * @return {Object} styles Style object at a specified (or current) index
20639
     */
20640
    getSelectionStyles: function(startIndex, endIndex) {
20641
 
20642
      if (arguments.length === 2) {
20643
        var styles = [ ];
20644
        for (var i = startIndex; i < endIndex; i++) {
20645
          styles.push(this.getSelectionStyles(i));
20646
        }
20647
        return styles;
20648
      }
20649
 
20650
      var loc = this.get2DCursorLocation(startIndex);
20651
      if (this.styles[loc.lineIndex]) {
20652
        return this.styles[loc.lineIndex][loc.charIndex] || { };
20653
      }
20654
 
20655
      return { };
20656
    },
20657
 
20658
    /**
20659
     * Sets style of a current selection
20660
     * @param {Object} [styles] Styles object
20661
     * @return {fabric.IText} thisArg
20662
     * @chainable
20663
     */
20664
    setSelectionStyles: function(styles) {
20665
      if (this.selectionStart === this.selectionEnd) {
20666
        this._extendStyles(this.selectionStart, styles);
20667
      }
20668
      else {
20669
        for (var i = this.selectionStart; i < this.selectionEnd; i++) {
20670
          this._extendStyles(i, styles);
20671
        }
20672
      }
20673
      /* not included in _extendStyles to avoid clearing cache more than once */
20674
      this._clearCache();
20675
      return this;
20676
    },
20677
 
20678
    /**
20679
     * @private
20680
     */
20681
    _extendStyles: function(index, styles) {
20682
      var loc = this.get2DCursorLocation(index);
20683
 
20684
      if (!this.styles[loc.lineIndex]) {
20685
        this.styles[loc.lineIndex] = { };
20686
      }
20687
      if (!this.styles[loc.lineIndex][loc.charIndex]) {
20688
        this.styles[loc.lineIndex][loc.charIndex] = { };
20689
      }
20690
      fabric.util.object.extend(this.styles[loc.lineIndex][loc.charIndex], styles);
20691
    },
20692
 
20693
    /**
20694
    * @private
20695
    * @param {CanvasRenderingContext2D} ctx Context to render on
20696
    */
20697
    _render: function(ctx) {
20698
      this.callSuper('_render', ctx);
20699
      this.ctx = ctx;
20700
      this.isEditing && this.renderCursorOrSelection();
20701
    },
20702
 
20703
    /**
20704
     * Renders cursor or selection (depending on what exists)
20705
     */
20706
    renderCursorOrSelection: function() {
20707
      if (!this.active) {
20708
        return;
20709
      }
20710
 
20711
      var chars = this.text.split(''),
20712
          boundaries, ctx;
20713
 
20714
      if (this.canvas.contextTop) {
20715
        ctx = this.canvas.contextTop;
20716
        ctx.save();
20717
        ctx.transform.apply(ctx, this.canvas.viewportTransform);
20718
        this.transform(ctx);
20719
      }
20720
      else {
20721
        ctx = this.ctx;
20722
        ctx.save();
20723
      }
20724
 
20725
      if (this.selectionStart === this.selectionEnd) {
20726
        boundaries = this._getCursorBoundaries(chars, 'cursor');
20727
        this.renderCursor(boundaries, ctx);
20728
      }
20729
      else {
20730
        boundaries = this._getCursorBoundaries(chars, 'selection');
20731
        this.renderSelection(chars, boundaries, ctx);
20732
      }
20733
 
20734
      ctx.restore();
20735
    },
20736
 
20737
    /**
20738
     * Returns 2d representation (lineIndex and charIndex) of cursor (or selection start)
20739
     * @param {Number} [selectionStart] Optional index. When not given, current selectionStart is used.
20740
     */
20741
    get2DCursorLocation: function(selectionStart) {
20742
      if (typeof selectionStart === 'undefined') {
20743
        selectionStart = this.selectionStart;
20744
      }
20745
      var textBeforeCursor = this.text.slice(0, selectionStart),
20746
          linesBeforeCursor = textBeforeCursor.split(this._reNewline);
20747
 
20748
      return {
20749
        lineIndex: linesBeforeCursor.length - 1,
20750
        charIndex: linesBeforeCursor[linesBeforeCursor.length - 1].length
20751
      };
20752
    },
20753
 
20754
    /**
20755
     * Returns complete style of char at the current cursor
20756
     * @param {Number} lineIndex Line index
20757
     * @param {Number} charIndex Char index
20758
    * @return {Object} Character style
20759
     */
20760
    getCurrentCharStyle: function(lineIndex, charIndex) {
20761
      var style = this.styles[lineIndex] && this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)];
20762
 
20763
      return {
20764
        fontSize: style && style.fontSize || this.fontSize,
20765
        fill: style && style.fill || this.fill,
20766
        textBackgroundColor: style && style.textBackgroundColor || this.textBackgroundColor,
20767
        textDecoration: style && style.textDecoration || this.textDecoration,
20768
        fontFamily: style && style.fontFamily || this.fontFamily,
20769
        fontWeight: style && style.fontWeight || this.fontWeight,
20770
        fontStyle: style && style.fontStyle || this.fontStyle,
20771
        stroke: style && style.stroke || this.stroke,
20772
        strokeWidth: style && style.strokeWidth || this.strokeWidth
20773
      };
20774
    },
20775
 
20776
    /**
20777
     * Returns fontSize of char at the current cursor
20778
     * @param {Number} lineIndex Line index
20779
     * @param {Number} charIndex Char index
20780
     * @return {Number} Character font size
20781
     */
20782
    getCurrentCharFontSize: function(lineIndex, charIndex) {
20783
      return (
20784
        this.styles[lineIndex] &&
20785
        this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)] &&
20786
        this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)].fontSize) || this.fontSize;
20787
    },
20788
 
20789
    /**
20790
     * Returns color (fill) of char at the current cursor
20791
     * @param {Number} lineIndex Line index
20792
     * @param {Number} charIndex Char index
20793
     * @return {String} Character color (fill)
20794
     */
20795
    getCurrentCharColor: function(lineIndex, charIndex) {
20796
      return (
20797
        this.styles[lineIndex] &&
20798
        this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)] &&
20799
        this.styles[lineIndex][charIndex === 0 ? 0 : (charIndex - 1)].fill) || this.cursorColor;
20800
    },
20801
 
20802
    /**
20803
     * Returns cursor boundaries (left, top, leftOffset, topOffset)
20804
     * @private
20805
     * @param {Array} chars Array of characters
20806
     * @param {String} typeOfBoundaries
20807
     */
20808
    _getCursorBoundaries: function(chars, typeOfBoundaries) {
20809
 
20810
      // left/top are left/top of entire text box
20811
      // leftOffset/topOffset are offset from that left/top point of a text box
20812
 
20813
      var left = Math.round(this._getLeftOffset()),
20814
          top = this._getTopOffset(),
20815
 
20816
          offsets = this._getCursorBoundariesOffsets(
20817
                      chars, typeOfBoundaries);
20818
 
20819
      return {
20820
        left: left,
20821
        top: top,
20822
        leftOffset: offsets.left + offsets.lineLeft,
20823
        topOffset: offsets.top
20824
      };
20825
    },
20826
 
20827
    /**
20828
     * @private
20829
     */
20830
    _getCursorBoundariesOffsets: function(chars, typeOfBoundaries) {
20831
 
20832
      var lineLeftOffset = 0,
20833
 
20834
          lineIndex = 0,
20835
          charIndex = 0,
20836
          topOffset = 0,
20837
          leftOffset = 0;
20838
 
20839
      for (var i = 0; i < this.selectionStart; i++) {
20840
        if (chars[i] === '\n') {
20841
          leftOffset = 0;
20842
          topOffset += this._getHeightOfLine(this.ctx, lineIndex);
20843
 
20844
          lineIndex++;
20845
          charIndex = 0;
20846
        }
20847
        else {
20848
          leftOffset += this._getWidthOfChar(this.ctx, chars[i], lineIndex, charIndex);
20849
          charIndex++;
20850
        }
20851
 
20852
        lineLeftOffset = this._getCachedLineOffset(lineIndex);
20853
      }
20854
      if (typeOfBoundaries === 'cursor') {
20855
        topOffset += (1 - this._fontSizeFraction) * this._getHeightOfLine(this.ctx, lineIndex) / this.lineHeight
20856
          - this.getCurrentCharFontSize(lineIndex, charIndex) * (1 - this._fontSizeFraction);
20857
      }
20858
 
20859
      return {
20860
        top: topOffset,
20861
        left: leftOffset,
20862
        lineLeft: lineLeftOffset
20863
      };
20864
    },
20865
 
20866
    /**
20867
     * @private
20868
     */
20869
    _getCachedLineOffset: function(lineIndex) {
20870
      var widthOfLine = this._getLineWidth(this.ctx, lineIndex);
20871
 
20872
      return this.__lineOffsets[lineIndex] ||
20873
        (this.__lineOffsets[lineIndex] = this._getLineLeftOffset(widthOfLine));
20874
    },
20875
 
20876
    /**
20877
     * Renders cursor
20878
     * @param {Object} boundaries
20879
     * @param {CanvasRenderingContext2D} ctx transformed context to draw on
20880
     */
20881
    renderCursor: function(boundaries, ctx) {
20882
 
20883
      var cursorLocation = this.get2DCursorLocation(),
20884
          lineIndex = cursorLocation.lineIndex,
20885
          charIndex = cursorLocation.charIndex,
20886
          charHeight = this.getCurrentCharFontSize(lineIndex, charIndex),
20887
          leftOffset = (lineIndex === 0 && charIndex === 0)
20888
                    ? this._getCachedLineOffset(lineIndex)
20889
                    : boundaries.leftOffset;
20890
 
20891
      ctx.fillStyle = this.getCurrentCharColor(lineIndex, charIndex);
20892
      ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity;
20893
 
20894
      ctx.fillRect(
20895
        boundaries.left + leftOffset,
20896
        boundaries.top + boundaries.topOffset,
20897
        this.cursorWidth / this.scaleX,
20898
        charHeight);
20899
 
20900
    },
20901
 
20902
    /**
20903
     * Renders text selection
20904
     * @param {Array} chars Array of characters
20905
     * @param {Object} boundaries Object with left/top/leftOffset/topOffset
20906
     * @param {CanvasRenderingContext2D} ctx transformed context to draw on
20907
     */
20908
    renderSelection: function(chars, boundaries, ctx) {
20909
 
20910
      ctx.fillStyle = this.selectionColor;
20911
 
20912
      var start = this.get2DCursorLocation(this.selectionStart),
20913
          end = this.get2DCursorLocation(this.selectionEnd),
20914
          startLine = start.lineIndex,
20915
          endLine = end.lineIndex;
20916
 
20917
      for (var i = startLine; i <= endLine; i++) {
20918
        var lineOffset = this._getCachedLineOffset(i) || 0,
20919
            lineHeight = this._getHeightOfLine(this.ctx, i),
20920
            boxWidth = 0, line = this._textLines[i];
20921
 
20922
        if (i === startLine) {
20923
          for (var j = 0, len = line.length; j < len; j++) {
20924
            if (j >= start.charIndex && (i !== endLine || j < end.charIndex)) {
20925
              boxWidth += this._getWidthOfChar(ctx, line[j], i, j);
20926
            }
20927
            if (j < start.charIndex) {
20928
              lineOffset += this._getWidthOfChar(ctx, line[j], i, j);
20929
            }
20930
          }
20931
        }
20932
        else if (i > startLine && i < endLine) {
20933
          boxWidth += this._getLineWidth(ctx, i) || 5;
20934
        }
20935
        else if (i === endLine) {
20936
          for (var j2 = 0, j2len = end.charIndex; j2 < j2len; j2++) {
20937
            boxWidth += this._getWidthOfChar(ctx, line[j2], i, j2);
20938
          }
20939
        }
20940
 
20941
        ctx.fillRect(
20942
          boundaries.left + lineOffset,
20943
          boundaries.top + boundaries.topOffset,
20944
          boxWidth,
20945
          lineHeight);
20946
 
20947
        boundaries.topOffset += lineHeight;
20948
      }
20949
    },
20950
 
20951
    /**
20952
     * @private
20953
     * @param {String} method
20954
     * @param {CanvasRenderingContext2D} ctx Context to render on
20955
     */
20956
    _renderChars: function(method, ctx, line, left, top, lineIndex) {
20957
 
20958
      if (this.isEmptyStyles()) {
20959
        return this._renderCharsFast(method, ctx, line, left, top);
20960
      }
20961
 
20962
      this.skipTextAlign = true;
20963
 
20964
      // set proper box offset
20965
      left -= this.textAlign === 'center'
20966
        ? (this.width / 2)
20967
        : (this.textAlign === 'right')
20968
          ? this.width
20969
          : 0;
20970
 
20971
      // set proper line offset
20972
      var lineHeight = this._getHeightOfLine(ctx, lineIndex),
20973
          lineLeftOffset = this._getCachedLineOffset(lineIndex),
20974
          chars = line.split(''),
20975
          prevStyle,
20976
          charsToRender = '';
20977
 
20978
      left += lineLeftOffset || 0;
20979
 
20980
      ctx.save();
20981
      top -= lineHeight / this.lineHeight * this._fontSizeFraction;
20982
      for (var i = 0, len = chars.length; i <= len; i++) {
20983
        prevStyle = prevStyle || this.getCurrentCharStyle(lineIndex, i);
20984
        var thisStyle = this.getCurrentCharStyle(lineIndex, i + 1);
20985
 
20986
        if (this._hasStyleChanged(prevStyle, thisStyle) || i === len) {
20987
          this._renderChar(method, ctx, lineIndex, i - 1, charsToRender, left, top, lineHeight);
20988
          charsToRender = '';
20989
          prevStyle = thisStyle;
20990
        }
20991
        charsToRender += chars[i];
20992
      }
20993
 
20994
      ctx.restore();
20995
    },
20996
 
20997
    /**
20998
     * @private
20999
     * @param {String} method
21000
     * @param {CanvasRenderingContext2D} ctx Context to render on
21001
     * @param {String} line Content of the line
21002
     * @param {Number} left Left coordinate
21003
     * @param {Number} top Top coordinate
21004
     */
21005
    _renderCharsFast: function(method, ctx, line, left, top) {
21006
      this.skipTextAlign = false;
21007
 
21008
      if (method === 'fillText' && this.fill) {
21009
        this.callSuper('_renderChars', method, ctx, line, left, top);
21010
      }
21011
      if (method === 'strokeText' && ((this.stroke && this.strokeWidth > 0) || this.skipFillStrokeCheck)) {
21012
        this.callSuper('_renderChars', method, ctx, line, left, top);
21013
      }
21014
    },
21015
 
21016
    /**
21017
     * @private
21018
     * @param {String} method
21019
     * @param {CanvasRenderingContext2D} ctx Context to render on
21020
     * @param {Number} lineIndex
21021
     * @param {Number} i
21022
     * @param {String} _char
21023
     * @param {Number} left Left coordinate
21024
     * @param {Number} top Top coordinate
21025
     * @param {Number} lineHeight Height of the line
21026
     */
21027
    _renderChar: function(method, ctx, lineIndex, i, _char, left, top, lineHeight) {
21028
      var decl, charWidth, charHeight,
21029
          offset = this._fontSizeFraction * lineHeight / this.lineHeight;
21030
 
21031
      if (this.styles && this.styles[lineIndex] && (decl = this.styles[lineIndex][i])) {
21032
 
21033
        var shouldStroke = decl.stroke || this.stroke,
21034
            shouldFill = decl.fill || this.fill;
21035
 
21036
        ctx.save();
21037
        charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i, decl);
21038
        charHeight = this._getHeightOfChar(ctx, _char, lineIndex, i);
21039
 
21040
        if (shouldFill) {
21041
          ctx.fillText(_char, left, top);
21042
        }
21043
        if (shouldStroke) {
21044
          ctx.strokeText(_char, left, top);
21045
        }
21046
 
21047
        this._renderCharDecoration(ctx, decl, left, top, offset, charWidth, charHeight);
21048
        ctx.restore();
21049
 
21050
        ctx.translate(charWidth, 0);
21051
      }
21052
      else {
21053
        if (method === 'strokeText' && this.stroke) {
21054
          ctx[method](_char, left, top);
21055
        }
21056
        if (method === 'fillText' && this.fill) {
21057
          ctx[method](_char, left, top);
21058
        }
21059
        charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i);
21060
        this._renderCharDecoration(ctx, null, left, top, offset, charWidth, this.fontSize);
21061
 
21062
        ctx.translate(ctx.measureText(_char).width, 0);
21063
      }
21064
    },
21065
 
21066
    /**
21067
     * @private
21068
     * @param {Object} prevStyle
21069
     * @param {Object} thisStyle
21070
     */
21071
    _hasStyleChanged: function(prevStyle, thisStyle) {
21072
      return (prevStyle.fill !== thisStyle.fill ||
21073
              prevStyle.fontSize !== thisStyle.fontSize ||
21074
              prevStyle.textBackgroundColor !== thisStyle.textBackgroundColor ||
21075
              prevStyle.textDecoration !== thisStyle.textDecoration ||
21076
              prevStyle.fontFamily !== thisStyle.fontFamily ||
21077
              prevStyle.fontWeight !== thisStyle.fontWeight ||
21078
              prevStyle.fontStyle !== thisStyle.fontStyle ||
21079
              prevStyle.stroke !== thisStyle.stroke ||
21080
              prevStyle.strokeWidth !== thisStyle.strokeWidth
21081
      );
21082
    },
21083
 
21084
    /**
21085
     * @private
21086
     * @param {CanvasRenderingContext2D} ctx Context to render on
21087
     */
21088
    _renderCharDecoration: function(ctx, styleDeclaration, left, top, offset, charWidth, charHeight) {
21089
 
21090
      var textDecoration = styleDeclaration
21091
            ? (styleDeclaration.textDecoration || this.textDecoration)
21092
            : this.textDecoration;
21093
 
21094
      if (!textDecoration) {
21095
        return;
21096
      }
21097
 
21098
      if (textDecoration.indexOf('underline') > -1) {
21099
        ctx.fillRect(
21100
          left,
21101
          top + charHeight / 10,
21102
          charWidth ,
21103
          charHeight / 15
21104
        );
21105
      }
21106
      if (textDecoration.indexOf('line-through') > -1) {
21107
        ctx.fillRect(
21108
          left,
21109
          top - charHeight * (this._fontSizeFraction + this._fontSizeMult - 1) + charHeight / 15,
21110
          charWidth,
21111
          charHeight / 15
21112
        );
21113
      }
21114
      if (textDecoration.indexOf('overline') > -1) {
21115
        ctx.fillRect(
21116
          left,
21117
          top - (this._fontSizeMult - this._fontSizeFraction) * charHeight,
21118
          charWidth,
21119
          charHeight / 15
21120
        );
21121
      }
21122
    },
21123
 
21124
    /**
21125
     * @private
21126
     * @param {String} method
21127
     * @param {CanvasRenderingContext2D} ctx Context to render on
21128
     * @param {String} line
21129
     */
21130
    _renderTextLine: function(method, ctx, line, left, top, lineIndex) {
21131
      // to "cancel" this.fontSize subtraction in fabric.Text#_renderTextLine
21132
      // the adding 0.03 is just to align text with itext by overlap test
21133
      if (!this.isEmptyStyles()) {
21134
        top += this.fontSize * (this._fontSizeFraction + 0.03);
21135
      }
21136
      this.callSuper('_renderTextLine', method, ctx, line, left, top, lineIndex);
21137
    },
21138
 
21139
    /**
21140
     * @private
21141
     * @param {CanvasRenderingContext2D} ctx Context to render on
21142
     */
21143
    _renderTextDecoration: function(ctx) {
21144
      if (this.isEmptyStyles()) {
21145
        return this.callSuper('_renderTextDecoration', ctx);
21146
      }
21147
    },
21148
 
21149
    /**
21150
     * @private
21151
     * @param {CanvasRenderingContext2D} ctx Context to render on
21152
     */
21153
    _renderTextLinesBackground: function(ctx) {
21154
      if (!this.textBackgroundColor && !this.styles) {
21155
        return;
21156
      }
21157
 
21158
      ctx.save();
21159
 
21160
      if (this.textBackgroundColor) {
21161
        ctx.fillStyle = this.textBackgroundColor;
21162
      }
21163
 
21164
      var lineHeights = 0;
21165
 
21166
      for (var i = 0, len = this._textLines.length; i < len; i++) {
21167
 
21168
        var heightOfLine = this._getHeightOfLine(ctx, i);
21169
        if (this._textLines[i] === '') {
21170
          lineHeights += heightOfLine;
21171
          continue;
21172
        }
21173
 
21174
        var lineWidth = this._getLineWidth(ctx, i),
21175
            lineLeftOffset = this._getCachedLineOffset(i);
21176
 
21177
        if (this.textBackgroundColor) {
21178
          ctx.fillStyle = this.textBackgroundColor;
21179
 
21180
          ctx.fillRect(
21181
            this._getLeftOffset() + lineLeftOffset,
21182
            this._getTopOffset() + lineHeights,
21183
            lineWidth,
21184
            heightOfLine / this.lineHeight
21185
          );
21186
        }
21187
        if (this.styles[i]) {
21188
          for (var j = 0, jlen = this._textLines[i].length; j < jlen; j++) {
21189
            if (this.styles[i] && this.styles[i][j] && this.styles[i][j].textBackgroundColor) {
21190
 
21191
              var _char = this._textLines[i][j];
21192
 
21193
              ctx.fillStyle = this.styles[i][j].textBackgroundColor;
21194
 
21195
              ctx.fillRect(
21196
                this._getLeftOffset() + lineLeftOffset + this._getWidthOfCharsAt(ctx, i, j),
21197
                this._getTopOffset() + lineHeights,
21198
                this._getWidthOfChar(ctx, _char, i, j) + 1,
21199
                heightOfLine / this.lineHeight
21200
              );
21201
            }
21202
          }
21203
        }
21204
        lineHeights += heightOfLine;
21205
      }
21206
      ctx.restore();
21207
    },
21208
 
21209
    /**
21210
     * @private
21211
     */
21212
    _getCacheProp: function(_char, styleDeclaration) {
21213
      return _char +
21214
             styleDeclaration.fontFamily +
21215
             styleDeclaration.fontSize +
21216
             styleDeclaration.fontWeight +
21217
             styleDeclaration.fontStyle +
21218
             styleDeclaration.shadow;
21219
    },
21220
 
21221
    /**
21222
     * @private
21223
     * @param {CanvasRenderingContext2D} ctx Context to render on
21224
     * @param {String} _char
21225
     * @param {Number} lineIndex
21226
     * @param {Number} charIndex
21227
     * @param {Object} [decl]
21228
     */
21229
    _applyCharStylesGetWidth: function(ctx, _char, lineIndex, charIndex, decl) {
21230
      var styleDeclaration = decl ||
21231
                            (this.styles[lineIndex] &&
21232
                             this.styles[lineIndex][charIndex]);
21233
 
21234
      if (styleDeclaration) {
21235
        // cloning so that original style object is not polluted with following font declarations
21236
        styleDeclaration = clone(styleDeclaration);
21237
      }
21238
      else {
21239
        styleDeclaration = { };
21240
      }
21241
 
21242
      this._applyFontStyles(styleDeclaration);
21243
 
21244
      var cacheProp = this._getCacheProp(_char, styleDeclaration);
21245
 
21246
      // short-circuit if no styles
21247
      if (this.isEmptyStyles() && this._charWidthsCache[cacheProp] && this.caching) {
21248
        return this._charWidthsCache[cacheProp];
21249
      }
21250
 
21251
      if (typeof styleDeclaration.shadow === 'string') {
21252
        styleDeclaration.shadow = new fabric.Shadow(styleDeclaration.shadow);
21253
      }
21254
 
21255
      var fill = styleDeclaration.fill || this.fill;
21256
      ctx.fillStyle = fill.toLive
21257
        ? fill.toLive(ctx, this)
21258
        : fill;
21259
 
21260
      if (styleDeclaration.stroke) {
21261
        ctx.strokeStyle = (styleDeclaration.stroke && styleDeclaration.stroke.toLive)
21262
          ? styleDeclaration.stroke.toLive(ctx, this)
21263
          : styleDeclaration.stroke;
21264
      }
21265
 
21266
      ctx.lineWidth = styleDeclaration.strokeWidth || this.strokeWidth;
21267
      ctx.font = this._getFontDeclaration.call(styleDeclaration);
21268
      this._setShadow.call(styleDeclaration, ctx);
21269
 
21270
      if (!this.caching) {
21271
        return ctx.measureText(_char).width;
21272
      }
21273
 
21274
      if (!this._charWidthsCache[cacheProp]) {
21275
        this._charWidthsCache[cacheProp] = ctx.measureText(_char).width;
21276
      }
21277
 
21278
      return this._charWidthsCache[cacheProp];
21279
    },
21280
 
21281
    /**
21282
     * @private
21283
     * @param {Object} styleDeclaration
21284
     */
21285
    _applyFontStyles: function(styleDeclaration) {
21286
      if (!styleDeclaration.fontFamily) {
21287
        styleDeclaration.fontFamily = this.fontFamily;
21288
      }
21289
      if (!styleDeclaration.fontSize) {
21290
        styleDeclaration.fontSize = this.fontSize;
21291
      }
21292
      if (!styleDeclaration.fontWeight) {
21293
        styleDeclaration.fontWeight = this.fontWeight;
21294
      }
21295
      if (!styleDeclaration.fontStyle) {
21296
        styleDeclaration.fontStyle = this.fontStyle;
21297
      }
21298
    },
21299
 
21300
    /**
21301
     * @private
21302
     * @param {Number} lineIndex
21303
     * @param {Number} charIndex
21304
     */
21305
    _getStyleDeclaration: function(lineIndex, charIndex) {
21306
      return (this.styles[lineIndex] && this.styles[lineIndex][charIndex])
21307
        ? clone(this.styles[lineIndex][charIndex])
21308
        : { };
21309
    },
21310
 
21311
    /**
21312
     * @private
21313
     * @param {CanvasRenderingContext2D} ctx Context to render on
21314
     */
21315
    _getWidthOfChar: function(ctx, _char, lineIndex, charIndex) {
21316
      if (this.textAlign === 'justify' && /\s/.test(_char)) {
21317
        return this._getWidthOfSpace(ctx, lineIndex);
21318
      }
21319
 
21320
      var styleDeclaration = this._getStyleDeclaration(lineIndex, charIndex);
21321
      this._applyFontStyles(styleDeclaration);
21322
      var cacheProp = this._getCacheProp(_char, styleDeclaration);
21323
 
21324
      if (this._charWidthsCache[cacheProp] && this.caching) {
21325
        return this._charWidthsCache[cacheProp];
21326
      }
21327
      else if (ctx) {
21328
        ctx.save();
21329
        var width = this._applyCharStylesGetWidth(ctx, _char, lineIndex, charIndex);
21330
        ctx.restore();
21331
        return width;
21332
      }
21333
    },
21334
 
21335
    /**
21336
     * @private
21337
     * @param {CanvasRenderingContext2D} ctx Context to render on
21338
     */
21339
    _getHeightOfChar: function(ctx, _char, lineIndex, charIndex) {
21340
      if (this.styles[lineIndex] && this.styles[lineIndex][charIndex]) {
21341
        return this.styles[lineIndex][charIndex].fontSize || this.fontSize;
21342
      }
21343
      return this.fontSize;
21344
    },
21345
 
21346
    /**
21347
     * @private
21348
     * @param {CanvasRenderingContext2D} ctx Context to render on
21349
     */
21350
    _getHeightOfCharAt: function(ctx, lineIndex, charIndex) {
21351
      var _char = this._textLines[lineIndex][charIndex];
21352
      return this._getHeightOfChar(ctx, _char, lineIndex, charIndex);
21353
    },
21354
 
21355
    /**
21356
     * @private
21357
     * @param {CanvasRenderingContext2D} ctx Context to render on
21358
     */
21359
    _getWidthOfCharsAt: function(ctx, lineIndex, charIndex) {
21360
      var width = 0, i, _char;
21361
      for (i = 0; i < charIndex; i++) {
21362
        _char = this._textLines[lineIndex][i];
21363
        width += this._getWidthOfChar(ctx, _char, lineIndex, i);
21364
      }
21365
      return width;
21366
    },
21367
 
21368
    /**
21369
     * @private
21370
     * @param {CanvasRenderingContext2D} ctx Context to render on
21371
     */
21372
    _getLineWidth: function(ctx, lineIndex) {
21373
      if (this.__lineWidths[lineIndex]) {
21374
        return this.__lineWidths[lineIndex];
21375
      }
21376
      this.__lineWidths[lineIndex] = this._getWidthOfCharsAt(ctx, lineIndex, this._textLines[lineIndex].length);
21377
      return this.__lineWidths[lineIndex];
21378
    },
21379
 
21380
    /**
21381
     * @private
21382
     * @param {CanvasRenderingContext2D} ctx Context to render on
21383
     * @param {Number} lineIndex
21384
     */
21385
    _getWidthOfSpace: function (ctx, lineIndex) {
21386
      if (this.__widthOfSpace[lineIndex]) {
21387
        return this.__widthOfSpace[lineIndex];
21388
      }
21389
      var line = this._textLines[lineIndex],
21390
          wordsWidth = this._getWidthOfWords(ctx, line, lineIndex),
21391
          widthDiff = this.width - wordsWidth,
21392
          numSpaces = line.length - line.replace(/\s+/g, '').length,
21393
          width = widthDiff / numSpaces;
21394
      this.__widthOfSpace[lineIndex] = width;
21395
      return width;
21396
    },
21397
 
21398
    /**
21399
     * @private
21400
     * @param {CanvasRenderingContext2D} ctx Context to render on
21401
     * @param {Number} line
21402
     * @param {Number} lineIndex
21403
     */
21404
    _getWidthOfWords: function (ctx, line, lineIndex) {
21405
      var width = 0;
21406
 
21407
      for (var charIndex = 0; charIndex < line.length; charIndex++) {
21408
        var _char = line[charIndex];
21409
 
21410
        if (!_char.match(/\s/)) {
21411
          width += this._getWidthOfChar(ctx, _char, lineIndex, charIndex);
21412
        }
21413
      }
21414
 
21415
      return width;
21416
    },
21417
 
21418
    /**
21419
     * @private
21420
     * @param {CanvasRenderingContext2D} ctx Context to render on
21421
     */
21422
    _getHeightOfLine: function(ctx, lineIndex) {
21423
      if (this.__lineHeights[lineIndex]) {
21424
        return this.__lineHeights[lineIndex];
21425
      }
21426
 
21427
      var line = this._textLines[lineIndex],
21428
          maxHeight = this._getHeightOfChar(ctx, line[0], lineIndex, 0);
21429
 
21430
      for (var i = 1, len = line.length; i < len; i++) {
21431
        var currentCharHeight = this._getHeightOfChar(ctx, line[i], lineIndex, i);
21432
        if (currentCharHeight > maxHeight) {
21433
          maxHeight = currentCharHeight;
21434
        }
21435
      }
21436
      this.__maxFontHeights[lineIndex] = maxHeight;
21437
      this.__lineHeights[lineIndex] = maxHeight * this.lineHeight * this._fontSizeMult;
21438
      return this.__lineHeights[lineIndex];
21439
    },
21440
 
21441
    /**
21442
     * @private
21443
     * @param {CanvasRenderingContext2D} ctx Context to render on
21444
     */
21445
    _getTextHeight: function(ctx) {
21446
      var height = 0;
21447
      for (var i = 0, len = this._textLines.length; i < len; i++) {
21448
        height += this._getHeightOfLine(ctx, i);
21449
      }
21450
      return height;
21451
    },
21452
 
21453
    /**
21454
     * This method is overwritten to account for different top offset
21455
     * @private
21456
     */
21457
    _renderTextBoxBackground: function(ctx) {
21458
      if (!this.backgroundColor) {
21459
        return;
21460
      }
21461
 
21462
      ctx.save();
21463
      ctx.fillStyle = this.backgroundColor;
21464
 
21465
      ctx.fillRect(
21466
        this._getLeftOffset(),
21467
        this._getTopOffset(),
21468
        this.width,
21469
        this.height
21470
      );
21471
 
21472
      ctx.restore();
21473
    },
21474
 
21475
    /**
21476
     * Returns object representation of an instance
21477
     * @method toObject
21478
     * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
21479
     * @return {Object} object representation of an instance
21480
     */
21481
    toObject: function(propertiesToInclude) {
21482
      return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), {
21483
        styles: clone(this.styles)
21484
      });
21485
    }
21486
  });
21487
 
21488
  /**
21489
   * Returns fabric.IText instance from an object representation
21490
   * @static
21491
   * @memberOf fabric.IText
21492
   * @param {Object} object Object to create an instance from
21493
   * @return {fabric.IText} instance of fabric.IText
21494
   */
21495
  fabric.IText.fromObject = function(object) {
21496
    return new fabric.IText(object.text, clone(object));
21497
  };
21498
})();
21499
 
21500
 
21501
(function() {
21502
 
21503
  var clone = fabric.util.object.clone;
21504
 
21505
  fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ {
21506
 
21507
    /**
21508
     * Initializes all the interactive behavior of IText
21509
     */
21510
    initBehavior: function() {
21511
      this.initAddedHandler();
21512
      this.initRemovedHandler();
21513
      this.initCursorSelectionHandlers();
21514
      this.initDoubleClickSimulation();
21515
    },
21516
 
21517
    /**
21518
     * Initializes "selected" event handler
21519
     */
21520
    initSelectedHandler: function() {
21521
      this.on('selected', function() {
21522
 
21523
        var _this = this;
21524
        setTimeout(function() {
21525
          _this.selected = true;
21526
        }, 100);
21527
      });
21528
    },
21529
 
21530
    /**
21531
     * Initializes "added" event handler
21532
     */
21533
    initAddedHandler: function() {
21534
      var _this = this;
21535
      this.on('added', function() {
21536
        if (this.canvas && !this.canvas._hasITextHandlers) {
21537
          this.canvas._hasITextHandlers = true;
21538
          this._initCanvasHandlers();
21539
        }
21540
 
21541
        // Track IText instances per-canvas. Only register in this array once added
21542
        // to a canvas; we don't want to leak a reference to the instance forever
21543
        // simply because it existed at some point.
21544
        //
21545
        // (Might be added to a collection, but not on a canvas.)
21546
        if (_this.canvas) {
21547
          _this.canvas._iTextInstances = _this.canvas._iTextInstances || [];
21548
          _this.canvas._iTextInstances.push(_this);
21549
        }
21550
      });
21551
    },
21552
 
21553
    initRemovedHandler: function() {
21554
      var _this = this;
21555
      this.on('removed', function() {
21556
        // (Might be removed from a collection, but not on a canvas.)
21557
        if (_this.canvas) {
21558
          _this.canvas._iTextInstances = _this.canvas._iTextInstances || [];
21559
          fabric.util.removeFromArray(_this.canvas._iTextInstances, _this);
21560
        }
21561
      });
21562
    },
21563
 
21564
    /**
21565
     * @private
21566
     */
21567
    _initCanvasHandlers: function() {
21568
      var _this = this;
21569
 
21570
      this.canvas.on('selection:cleared', function() {
21571
        fabric.IText.prototype.exitEditingOnOthers(_this.canvas);
21572
      });
21573
 
21574
      this.canvas.on('mouse:up', function() {
21575
        if (_this.canvas._iTextInstances) {
21576
          _this.canvas._iTextInstances.forEach(function(obj) {
21577
            obj.__isMousedown = false;
21578
          });
21579
        }
21580
      });
21581
 
21582
      this.canvas.on('object:selected', function() {
21583
        fabric.IText.prototype.exitEditingOnOthers(_this.canvas);
21584
      });
21585
    },
21586
 
21587
    /**
21588
     * @private
21589
     */
21590
    _tick: function() {
21591
      this._currentTickState = this._animateCursor(this, 1, this.cursorDuration, '_onTickComplete');
21592
    },
21593
 
21594
    /**
21595
     * @private
21596
     */
21597
    _animateCursor: function(obj, targetOpacity, duration, completeMethod) {
21598
 
21599
      var tickState;
21600
 
21601
      tickState = {
21602
        isAborted: false,
21603
        abort: function() {
21604
          this.isAborted = true;
21605
        },
21606
      };
21607
 
21608
      obj.animate('_currentCursorOpacity', targetOpacity, {
21609
        duration: duration,
21610
        onComplete: function() {
21611
          if (!tickState.isAborted) {
21612
            obj[completeMethod]();
21613
          }
21614
        },
21615
        onChange: function() {
21616
          if (obj.canvas) {
21617
            obj.canvas.clearContext(obj.canvas.contextTop || obj.ctx);
21618
            obj.renderCursorOrSelection();
21619
          }
21620
        },
21621
        abort: function() {
21622
          return tickState.isAborted;
21623
        }
21624
      });
21625
      return tickState;
21626
    },
21627
 
21628
    /**
21629
     * @private
21630
     */
21631
    _onTickComplete: function() {
21632
 
21633
      var _this = this;
21634
 
21635
      if (this._cursorTimeout1) {
21636
        clearTimeout(this._cursorTimeout1);
21637
      }
21638
      this._cursorTimeout1 = setTimeout(function() {
21639
        _this._currentTickCompleteState = _this._animateCursor(_this, 0, this.cursorDuration / 2, '_tick');
21640
      }, 100);
21641
    },
21642
 
21643
    /**
21644
     * Initializes delayed cursor
21645
     */
21646
    initDelayedCursor: function(restart) {
21647
      var _this = this,
21648
          delay = restart ? 0 : this.cursorDelay;
21649
 
21650
      this._currentTickState && this._currentTickState.abort();
21651
      this._currentTickCompleteState && this._currentTickCompleteState.abort();
21652
      clearTimeout(this._cursorTimeout1);
21653
      this._currentCursorOpacity = 1;
21654
      if (this.canvas) {
21655
        this.canvas.clearContext(this.canvas.contextTop || this.ctx);
21656
        this.renderCursorOrSelection();
21657
      }
21658
      if (this._cursorTimeout2) {
21659
        clearTimeout(this._cursorTimeout2);
21660
      }
21661
      this._cursorTimeout2 = setTimeout(function() {
21662
        _this._tick();
21663
      }, delay);
21664
    },
21665
 
21666
    /**
21667
     * Aborts cursor animation and clears all timeouts
21668
     */
21669
    abortCursorAnimation: function() {
21670
      this._currentTickState && this._currentTickState.abort();
21671
      this._currentTickCompleteState && this._currentTickCompleteState.abort();
21672
 
21673
      clearTimeout(this._cursorTimeout1);
21674
      clearTimeout(this._cursorTimeout2);
21675
 
21676
      this._currentCursorOpacity = 0;
21677
      this.canvas && this.canvas.clearContext(this.canvas.contextTop || this.ctx);
21678
    },
21679
 
21680
    /**
21681
     * Selects entire text
21682
     */
21683
    selectAll: function() {
21684
      this.setSelectionStart(0);
21685
      this.setSelectionEnd(this.text.length);
21686
    },
21687
 
21688
    /**
21689
     * Returns selected text
21690
     * @return {String}
21691
     */
21692
    getSelectedText: function() {
21693
      return this.text.slice(this.selectionStart, this.selectionEnd);
21694
    },
21695
 
21696
    /**
21697
     * Find new selection index representing start of current word according to current selection index
21698
     * @param {Number} startFrom Surrent selection index
21699
     * @return {Number} New selection index
21700
     */
21701
    findWordBoundaryLeft: function(startFrom) {
21702
      var offset = 0, index = startFrom - 1;
21703
 
21704
      // remove space before cursor first
21705
      if (this._reSpace.test(this.text.charAt(index))) {
21706
        while (this._reSpace.test(this.text.charAt(index))) {
21707
          offset++;
21708
          index--;
21709
        }
21710
      }
21711
      while (/\S/.test(this.text.charAt(index)) && index > -1) {
21712
        offset++;
21713
        index--;
21714
      }
21715
 
21716
      return startFrom - offset;
21717
    },
21718
 
21719
    /**
21720
     * Find new selection index representing end of current word according to current selection index
21721
     * @param {Number} startFrom Current selection index
21722
     * @return {Number} New selection index
21723
     */
21724
    findWordBoundaryRight: function(startFrom) {
21725
      var offset = 0, index = startFrom;
21726
 
21727
      // remove space after cursor first
21728
      if (this._reSpace.test(this.text.charAt(index))) {
21729
        while (this._reSpace.test(this.text.charAt(index))) {
21730
          offset++;
21731
          index++;
21732
        }
21733
      }
21734
      while (/\S/.test(this.text.charAt(index)) && index < this.text.length) {
21735
        offset++;
21736
        index++;
21737
      }
21738
 
21739
      return startFrom + offset;
21740
    },
21741
 
21742
    /**
21743
     * Find new selection index representing start of current line according to current selection index
21744
     * @param {Number} startFrom Current selection index
21745
     * @return {Number} New selection index
21746
     */
21747
    findLineBoundaryLeft: function(startFrom) {
21748
      var offset = 0, index = startFrom - 1;
21749
 
21750
      while (!/\n/.test(this.text.charAt(index)) && index > -1) {
21751
        offset++;
21752
        index--;
21753
      }
21754
 
21755
      return startFrom - offset;
21756
    },
21757
 
21758
    /**
21759
     * Find new selection index representing end of current line according to current selection index
21760
     * @param {Number} startFrom Current selection index
21761
     * @return {Number} New selection index
21762
     */
21763
    findLineBoundaryRight: function(startFrom) {
21764
      var offset = 0, index = startFrom;
21765
 
21766
      while (!/\n/.test(this.text.charAt(index)) && index < this.text.length) {
21767
        offset++;
21768
        index++;
21769
      }
21770
 
21771
      return startFrom + offset;
21772
    },
21773
 
21774
    /**
21775
     * Returns number of newlines in selected text
21776
     * @return {Number} Number of newlines in selected text
21777
     */
21778
    getNumNewLinesInSelectedText: function() {
21779
      var selectedText = this.getSelectedText(),
21780
          numNewLines = 0;
21781
 
21782
      for (var i = 0, chars = selectedText.split(''), len = chars.length; i < len; i++) {
21783
        if (chars[i] === '\n') {
21784
          numNewLines++;
21785
        }
21786
      }
21787
      return numNewLines;
21788
    },
21789
 
21790
    /**
21791
     * Finds index corresponding to beginning or end of a word
21792
     * @param {Number} selectionStart Index of a character
21793
     * @param {Number} direction: 1 or -1
21794
     * @return {Number} Index of the beginning or end of a word
21795
     */
21796
    searchWordBoundary: function(selectionStart, direction) {
21797
      var index = this._reSpace.test(this.text.charAt(selectionStart)) ? selectionStart - 1 : selectionStart,
21798
          _char = this.text.charAt(index),
21799
          reNonWord = /[ \n\.,;!\?\-]/;
21800
 
21801
      while (!reNonWord.test(_char) && index > 0 && index < this.text.length) {
21802
        index += direction;
21803
        _char = this.text.charAt(index);
21804
      }
21805
      if (reNonWord.test(_char) && _char !== '\n') {
21806
        index += direction === 1 ? 0 : 1;
21807
      }
21808
      return index;
21809
    },
21810
 
21811
    /**
21812
     * Selects a word based on the index
21813
     * @param {Number} selectionStart Index of a character
21814
     */
21815
    selectWord: function(selectionStart) {
21816
      var newSelectionStart = this.searchWordBoundary(selectionStart, -1), /* search backwards */
21817
          newSelectionEnd = this.searchWordBoundary(selectionStart, 1); /* search forward */
21818
 
21819
      this.setSelectionStart(newSelectionStart);
21820
      this.setSelectionEnd(newSelectionEnd);
21821
    },
21822
 
21823
    /**
21824
     * Selects a line based on the index
21825
     * @param {Number} selectionStart Index of a character
21826
     */
21827
    selectLine: function(selectionStart) {
21828
      var newSelectionStart = this.findLineBoundaryLeft(selectionStart),
21829
          newSelectionEnd = this.findLineBoundaryRight(selectionStart);
21830
 
21831
      this.setSelectionStart(newSelectionStart);
21832
      this.setSelectionEnd(newSelectionEnd);
21833
    },
21834
 
21835
    /**
21836
     * Enters editing state
21837
     * @return {fabric.IText} thisArg
21838
     * @chainable
21839
     */
21840
    enterEditing: function() {
21841
      if (this.isEditing || !this.editable) {
21842
        return;
21843
      }
21844
 
21845
      if (this.canvas) {
21846
        this.exitEditingOnOthers(this.canvas);
21847
      }
21848
 
21849
      this.isEditing = true;
21850
 
21851
      this.initHiddenTextarea();
21852
      this.hiddenTextarea.focus();
21853
      this._updateTextarea();
21854
      this._saveEditingProps();
21855
      this._setEditingProps();
21856
 
21857
      this._tick();
21858
      this.fire('editing:entered');
21859
 
21860
      if (!this.canvas) {
21861
        return this;
21862
      }
21863
 
21864
      this.canvas.renderAll();
21865
      this.canvas.fire('text:editing:entered', { target: this });
21866
      this.initMouseMoveHandler();
21867
      return this;
21868
    },
21869
 
21870
    exitEditingOnOthers: function(canvas) {
21871
      if (canvas._iTextInstances) {
21872
        canvas._iTextInstances.forEach(function(obj) {
21873
          obj.selected = false;
21874
          if (obj.isEditing) {
21875
            obj.exitEditing();
21876
          }
21877
        });
21878
      }
21879
    },
21880
 
21881
    /**
21882
    * Initializes "mousemove" event handler
21883
    */
21884
    initMouseMoveHandler: function() {
21885
      var _this = this;
21886
      this.canvas.on('mouse:move',  function(options) {
21887
        if (!_this.__isMousedown || !_this.isEditing) {
21888
          return;
21889
        }
21890
 
21891
        var newSelectionStart = _this.getSelectionStartFromPointer(options.e);
21892
        if (newSelectionStart >= _this.__selectionStartOnMouseDown) {
21893
          _this.setSelectionStart(_this.__selectionStartOnMouseDown);
21894
          _this.setSelectionEnd(newSelectionStart);
21895
        }
21896
        else {
21897
          _this.setSelectionStart(newSelectionStart);
21898
          _this.setSelectionEnd(_this.__selectionStartOnMouseDown);
21899
        }
21900
      });
21901
    },
21902
 
21903
    /**
21904
     * @private
21905
     */
21906
    _setEditingProps: function() {
21907
      this.hoverCursor = 'text';
21908
 
21909
      if (this.canvas) {
21910
        this.canvas.defaultCursor = this.canvas.moveCursor = 'text';
21911
      }
21912
 
21913
      this.borderColor = this.editingBorderColor;
21914
 
21915
      this.hasControls = this.selectable = false;
21916
      this.lockMovementX = this.lockMovementY = true;
21917
    },
21918
 
21919
    /**
21920
     * @private
21921
     */
21922
    _updateTextarea: function() {
21923
      if (!this.hiddenTextarea) {
21924
        return;
21925
      }
21926
 
21927
      this.hiddenTextarea.value = this.text;
21928
      this.hiddenTextarea.selectionStart = this.selectionStart;
21929
      this.hiddenTextarea.selectionEnd = this.selectionEnd;
21930
    },
21931
 
21932
    /**
21933
     * @private
21934
     */
21935
    _saveEditingProps: function() {
21936
      this._savedProps = {
21937
        hasControls: this.hasControls,
21938
        borderColor: this.borderColor,
21939
        lockMovementX: this.lockMovementX,
21940
        lockMovementY: this.lockMovementY,
21941
        hoverCursor: this.hoverCursor,
21942
        defaultCursor: this.canvas && this.canvas.defaultCursor,
21943
        moveCursor: this.canvas && this.canvas.moveCursor
21944
      };
21945
    },
21946
 
21947
    /**
21948
     * @private
21949
     */
21950
    _restoreEditingProps: function() {
21951
      if (!this._savedProps) {
21952
        return;
21953
      }
21954
 
21955
      this.hoverCursor = this._savedProps.overCursor;
21956
      this.hasControls = this._savedProps.hasControls;
21957
      this.borderColor = this._savedProps.borderColor;
21958
      this.lockMovementX = this._savedProps.lockMovementX;
21959
      this.lockMovementY = this._savedProps.lockMovementY;
21960
 
21961
      if (this.canvas) {
21962
        this.canvas.defaultCursor = this._savedProps.defaultCursor;
21963
        this.canvas.moveCursor = this._savedProps.moveCursor;
21964
      }
21965
    },
21966
 
21967
    /**
21968
     * Exits from editing state
21969
     * @return {fabric.IText} thisArg
21970
     * @chainable
21971
     */
21972
    exitEditing: function() {
21973
 
21974
      this.selected = false;
21975
      this.isEditing = false;
21976
      this.selectable = true;
21977
 
21978
      this.selectionEnd = this.selectionStart;
21979
      this.hiddenTextarea && this.canvas && this.hiddenTextarea.parentNode.removeChild(this.hiddenTextarea);
21980
      this.hiddenTextarea = null;
21981
 
21982
      this.abortCursorAnimation();
21983
      this._restoreEditingProps();
21984
      this._currentCursorOpacity = 0;
21985
 
21986
      this.fire('editing:exited');
21987
      this.canvas && this.canvas.fire('text:editing:exited', { target: this });
21988
 
21989
      return this;
21990
    },
21991
 
21992
    /**
21993
     * @private
21994
     */
21995
    _removeExtraneousStyles: function() {
21996
      for (var prop in this.styles) {
21997
        if (!this._textLines[prop]) {
21998
          delete this.styles[prop];
21999
        }
22000
      }
22001
    },
22002
 
22003
    /**
22004
     * @private
22005
     */
22006
    _removeCharsFromTo: function(start, end) {
22007
 
22008
      var i = end;
22009
      while (i !== start) {
22010
 
22011
        var prevIndex = this.get2DCursorLocation(i).charIndex;
22012
        i--;
22013
 
22014
        var index = this.get2DCursorLocation(i).charIndex,
22015
            isNewline = index > prevIndex;
22016
 
22017
        if (isNewline) {
22018
          this.removeStyleObject(isNewline, i + 1);
22019
        }
22020
        else {
22021
          this.removeStyleObject(this.get2DCursorLocation(i).charIndex === 0, i);
22022
        }
22023
 
22024
      }
22025
 
22026
      this.text = this.text.slice(0, start) +
22027
                  this.text.slice(end);
22028
      this._clearCache();
22029
    },
22030
 
22031
    /**
22032
     * Inserts a character where cursor is (replacing selection if one exists)
22033
     * @param {String} _chars Characters to insert
22034
     */
22035
    insertChars: function(_chars, useCopiedStyle) {
22036
      var isEndOfLine = this.text.slice(this.selectionStart, this.selectionStart + 1) === '\n';
22037
 
22038
      this.text = this.text.slice(0, this.selectionStart) +
22039
                    _chars +
22040
                  this.text.slice(this.selectionEnd);
22041
 
22042
      if (this.selectionStart === this.selectionEnd) {
22043
        this.insertStyleObjects(_chars, isEndOfLine, useCopiedStyle);
22044
      }
22045
      // else if (this.selectionEnd - this.selectionStart > 1) {
22046
      // TODO: replace styles properly
22047
      // console.log('replacing MORE than 1 char');
22048
      // }
22049
      this.setSelectionStart(this.selectionStart + _chars.length);
22050
      this.setSelectionEnd(this.selectionStart);
22051
      this._clearCache();
22052
      this.canvas && this.canvas.renderAll();
22053
 
22054
      this.setCoords();
22055
      this.fire('changed');
22056
      this.canvas && this.canvas.fire('text:changed', { target: this });
22057
    },
22058
 
22059
    /**
22060
     * Inserts new style object
22061
     * @param {Number} lineIndex Index of a line
22062
     * @param {Number} charIndex Index of a char
22063
     * @param {Boolean} isEndOfLine True if it's end of line
22064
     */
22065
    insertNewlineStyleObject: function(lineIndex, charIndex, isEndOfLine) {
22066
 
22067
      this.shiftLineStyles(lineIndex, +1);
22068
 
22069
      if (!this.styles[lineIndex + 1]) {
22070
        this.styles[lineIndex + 1] = { };
22071
      }
22072
 
22073
      var currentCharStyle = this.styles[lineIndex][charIndex - 1],
22074
          newLineStyles = { };
22075
 
22076
      // if there's nothing after cursor,
22077
      // we clone current char style onto the next (otherwise empty) line
22078
      if (isEndOfLine) {
22079
        newLineStyles[0] = clone(currentCharStyle);
22080
        this.styles[lineIndex + 1] = newLineStyles;
22081
      }
22082
      // otherwise we clone styles of all chars
22083
      // after cursor onto the next line, from the beginning
22084
      else {
22085
        for (var index in this.styles[lineIndex]) {
22086
          if (parseInt(index, 10) >= charIndex) {
22087
            newLineStyles[parseInt(index, 10) - charIndex] = this.styles[lineIndex][index];
22088
            // remove lines from the previous line since they're on a new line now
22089
            delete this.styles[lineIndex][index];
22090
          }
22091
        }
22092
        this.styles[lineIndex + 1] = newLineStyles;
22093
      }
22094
      this._clearCache();
22095
    },
22096
 
22097
    /**
22098
     * Inserts style object for a given line/char index
22099
     * @param {Number} lineIndex Index of a line
22100
     * @param {Number} charIndex Index of a char
22101
     * @param {Object} [style] Style object to insert, if given
22102
     */
22103
    insertCharStyleObject: function(lineIndex, charIndex, style) {
22104
 
22105
      var currentLineStyles = this.styles[lineIndex],
22106
          currentLineStylesCloned = clone(currentLineStyles);
22107
 
22108
      if (charIndex === 0 && !style) {
22109
        charIndex = 1;
22110
      }
22111
 
22112
      // shift all char styles by 1 forward
22113
      // 0,1,2,3 -> (charIndex=2) -> 0,1,3,4 -> (insert 2) -> 0,1,2,3,4
22114
      for (var index in currentLineStylesCloned) {
22115
        var numericIndex = parseInt(index, 10);
22116
        if (numericIndex >= charIndex) {
22117
          currentLineStyles[numericIndex + 1] = currentLineStylesCloned[numericIndex];
22118
          //delete currentLineStyles[index];
22119
        }
22120
      }
22121
 
22122
      this.styles[lineIndex][charIndex] =
22123
        style || clone(currentLineStyles[charIndex - 1]);
22124
      this._clearCache();
22125
    },
22126
 
22127
    /**
22128
     * Inserts style object(s)
22129
     * @param {String} _chars Characters at the location where style is inserted
22130
     * @param {Boolean} isEndOfLine True if it's end of line
22131
     * @param {Boolean} [useCopiedStyle] Style to insert
22132
     */
22133
    insertStyleObjects: function(_chars, isEndOfLine, useCopiedStyle) {
22134
      // removed shortcircuit over isEmptyStyles
22135
 
22136
      var cursorLocation = this.get2DCursorLocation(),
22137
          lineIndex = cursorLocation.lineIndex,
22138
          charIndex = cursorLocation.charIndex;
22139
 
22140
      if (!this.styles[lineIndex]) {
22141
        this.styles[lineIndex] = { };
22142
      }
22143
 
22144
      if (_chars === '\n') {
22145
        this.insertNewlineStyleObject(lineIndex, charIndex, isEndOfLine);
22146
      }
22147
      else {
22148
        if (useCopiedStyle) {
22149
          this._insertStyles(this.copiedStyles);
22150
        }
22151
        else {
22152
          // TODO: support multiple style insertion if _chars.length > 1
22153
          this.insertCharStyleObject(lineIndex, charIndex);
22154
        }
22155
      }
22156
    },
22157
 
22158
    /**
22159
     * @private
22160
     */
22161
    _insertStyles: function(styles) {
22162
      for (var i = 0, len = styles.length; i < len; i++) {
22163
 
22164
        var cursorLocation = this.get2DCursorLocation(this.selectionStart + i),
22165
            lineIndex = cursorLocation.lineIndex,
22166
            charIndex = cursorLocation.charIndex;
22167
 
22168
        this.insertCharStyleObject(lineIndex, charIndex, styles[i]);
22169
      }
22170
    },
22171
 
22172
    /**
22173
     * Shifts line styles up or down
22174
     * @param {Number} lineIndex Index of a line
22175
     * @param {Number} offset Can be -1 or +1
22176
     */
22177
    shiftLineStyles: function(lineIndex, offset) {
22178
      // shift all line styles by 1 upward
22179
      var clonedStyles = clone(this.styles);
22180
      for (var line in this.styles) {
22181
        var numericLine = parseInt(line, 10);
22182
        if (numericLine > lineIndex) {
22183
          this.styles[numericLine + offset] = clonedStyles[numericLine];
22184
        }
22185
      }
22186
    },
22187
 
22188
    /**
22189
     * Removes style object
22190
     * @param {Boolean} isBeginningOfLine True if cursor is at the beginning of line
22191
     * @param {Number} [index] Optional index. When not given, current selectionStart is used.
22192
     */
22193
    removeStyleObject: function(isBeginningOfLine, index) {
22194
 
22195
      var cursorLocation = this.get2DCursorLocation(index),
22196
          lineIndex = cursorLocation.lineIndex,
22197
          charIndex = cursorLocation.charIndex;
22198
 
22199
      if (isBeginningOfLine) {
22200
 
22201
        var textOnPreviousLine = this._textLines[lineIndex - 1],
22202
            newCharIndexOnPrevLine = textOnPreviousLine
22203
              ? textOnPreviousLine.length
22204
              : 0;
22205
 
22206
        if (!this.styles[lineIndex - 1]) {
22207
          this.styles[lineIndex - 1] = { };
22208
        }
22209
 
22210
        for (charIndex in this.styles[lineIndex]) {
22211
          this.styles[lineIndex - 1][parseInt(charIndex, 10) + newCharIndexOnPrevLine]
22212
            = this.styles[lineIndex][charIndex];
22213
        }
22214
 
22215
        this.shiftLineStyles(lineIndex, -1);
22216
      }
22217
      else {
22218
        var currentLineStyles = this.styles[lineIndex];
22219
 
22220
        if (currentLineStyles) {
22221
          var offset = this.selectionStart === this.selectionEnd ? -1 : 0;
22222
          delete currentLineStyles[charIndex + offset];
22223
          // console.log('deleting', lineIndex, charIndex + offset);
22224
        }
22225
 
22226
        var currentLineStylesCloned = clone(currentLineStyles);
22227
 
22228
        // shift all styles by 1 backwards
22229
        for (var i in currentLineStylesCloned) {
22230
          var numericIndex = parseInt(i, 10);
22231
          if (numericIndex >= charIndex && numericIndex !== 0) {
22232
            currentLineStyles[numericIndex - 1] = currentLineStylesCloned[numericIndex];
22233
            delete currentLineStyles[numericIndex];
22234
          }
22235
        }
22236
      }
22237
    },
22238
 
22239
    /**
22240
     * Inserts new line
22241
     */
22242
    insertNewline: function() {
22243
      this.insertChars('\n');
22244
    }
22245
  });
22246
})();
22247
 
22248
 
22249
fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ {
22250
  /**
22251
   * Initializes "dbclick" event handler
22252
   */
22253
  initDoubleClickSimulation: function() {
22254
 
22255
    // for double click
22256
    this.__lastClickTime = +new Date();
22257
 
22258
    // for triple click
22259
    this.__lastLastClickTime = +new Date();
22260
 
22261
    this.__lastPointer = { };
22262
 
22263
    this.on('mousedown', this.onMouseDown.bind(this));
22264
  },
22265
 
22266
  onMouseDown: function(options) {
22267
 
22268
    this.__newClickTime = +new Date();
22269
    var newPointer = this.canvas.getPointer(options.e);
22270
 
22271
    if (this.isTripleClick(newPointer)) {
22272
      this.fire('tripleclick', options);
22273
      this._stopEvent(options.e);
22274
    }
22275
    else if (this.isDoubleClick(newPointer)) {
22276
      this.fire('dblclick', options);
22277
      this._stopEvent(options.e);
22278
    }
22279
 
22280
    this.__lastLastClickTime = this.__lastClickTime;
22281
    this.__lastClickTime = this.__newClickTime;
22282
    this.__lastPointer = newPointer;
22283
    this.__lastIsEditing = this.isEditing;
22284
    this.__lastSelected = this.selected;
22285
  },
22286
 
22287
  isDoubleClick: function(newPointer) {
22288
    return this.__newClickTime - this.__lastClickTime < 500 &&
22289
        this.__lastPointer.x === newPointer.x &&
22290
        this.__lastPointer.y === newPointer.y && this.__lastIsEditing;
22291
  },
22292
 
22293
  isTripleClick: function(newPointer) {
22294
    return this.__newClickTime - this.__lastClickTime < 500 &&
22295
        this.__lastClickTime - this.__lastLastClickTime < 500 &&
22296
        this.__lastPointer.x === newPointer.x &&
22297
        this.__lastPointer.y === newPointer.y;
22298
  },
22299
 
22300
  /**
22301
   * @private
22302
   */
22303
  _stopEvent: function(e) {
22304
    e.preventDefault && e.preventDefault();
22305
    e.stopPropagation && e.stopPropagation();
22306
  },
22307
 
22308
  /**
22309
   * Initializes event handlers related to cursor or selection
22310
   */
22311
  initCursorSelectionHandlers: function() {
22312
    this.initSelectedHandler();
22313
    this.initMousedownHandler();
22314
    this.initMouseupHandler();
22315
    this.initClicks();
22316
  },
22317
 
22318
  /**
22319
   * Initializes double and triple click event handlers
22320
   */
22321
  initClicks: function() {
22322
    this.on('dblclick', function(options) {
22323
      this.selectWord(this.getSelectionStartFromPointer(options.e));
22324
    });
22325
    this.on('tripleclick', function(options) {
22326
      this.selectLine(this.getSelectionStartFromPointer(options.e));
22327
    });
22328
  },
22329
 
22330
  /**
22331
   * Initializes "mousedown" event handler
22332
   */
22333
  initMousedownHandler: function() {
22334
    this.on('mousedown', function(options) {
22335
 
22336
      var pointer = this.canvas.getPointer(options.e);
22337
 
22338
      this.__mousedownX = pointer.x;
22339
      this.__mousedownY = pointer.y;
22340
      this.__isMousedown = true;
22341
 
22342
      if (this.hiddenTextarea && this.canvas) {
22343
        this.canvas.wrapperEl.appendChild(this.hiddenTextarea);
22344
      }
22345
 
22346
      if (this.selected) {
22347
        this.setCursorByClick(options.e);
22348
      }
22349
 
22350
      if (this.isEditing) {
22351
        this.__selectionStartOnMouseDown = this.selectionStart;
22352
        this.initDelayedCursor(true);
22353
      }
22354
    });
22355
  },
22356
 
22357
  /**
22358
   * @private
22359
   */
22360
  _isObjectMoved: function(e) {
22361
    var pointer = this.canvas.getPointer(e);
22362
 
22363
    return this.__mousedownX !== pointer.x ||
22364
           this.__mousedownY !== pointer.y;
22365
  },
22366
 
22367
  /**
22368
   * Initializes "mouseup" event handler
22369
   */
22370
  initMouseupHandler: function() {
22371
    this.on('mouseup', function(options) {
22372
      this.__isMousedown = false;
22373
      if (this._isObjectMoved(options.e)) {
22374
        return;
22375
      }
22376
 
22377
      if (this.__lastSelected) {
22378
        this.enterEditing();
22379
        this.initDelayedCursor(true);
22380
      }
22381
      this.selected = true;
22382
    });
22383
  },
22384
 
22385
  /**
22386
   * Changes cursor location in a text depending on passed pointer (x/y) object
22387
   * @param {Event} e Event object
22388
   */
22389
  setCursorByClick: function(e) {
22390
    var newSelectionStart = this.getSelectionStartFromPointer(e);
22391
 
22392
    if (e.shiftKey) {
22393
      if (newSelectionStart < this.selectionStart) {
22394
        this.setSelectionEnd(this.selectionStart);
22395
        this.setSelectionStart(newSelectionStart);
22396
      }
22397
      else {
22398
        this.setSelectionEnd(newSelectionStart);
22399
      }
22400
    }
22401
    else {
22402
      this.setSelectionStart(newSelectionStart);
22403
      this.setSelectionEnd(newSelectionStart);
22404
    }
22405
  },
22406
 
22407
  /**
22408
   * @private
22409
   * @param {Event} e Event object
22410
   * @return {Object} Coordinates of a pointer (x, y)
22411
   */
22412
  _getLocalRotatedPointer: function(e) {
22413
    var pointer = this.canvas.getPointer(e),
22414
 
22415
        pClicked = new fabric.Point(pointer.x, pointer.y),
22416
        pLeftTop = new fabric.Point(this.left, this.top),
22417
 
22418
        rotated = fabric.util.rotatePoint(
22419
          pClicked, pLeftTop, fabric.util.degreesToRadians(-this.angle));
22420
 
22421
    return this.getLocalPointer(e, rotated);
22422
  },
22423
 
22424
  /**
22425
   * Returns index of a character corresponding to where an object was clicked
22426
   * @param {Event} e Event object
22427
   * @return {Number} Index of a character
22428
   */
22429
  getSelectionStartFromPointer: function(e) {
22430
    var mouseOffset = this._getLocalRotatedPointer(e),
22431
        prevWidth = 0,
22432
        width = 0,
22433
        height = 0,
22434
        charIndex = 0,
22435
        newSelectionStart,
22436
        line;
22437
 
22438
    for (var i = 0, len = this._textLines.length; i < len; i++) {
22439
      line = this._textLines[i].split('');
22440
      height += this._getHeightOfLine(this.ctx, i) * this.scaleY;
22441
 
22442
      var widthOfLine = this._getLineWidth(this.ctx, i),
22443
          lineLeftOffset = this._getLineLeftOffset(widthOfLine);
22444
 
22445
      width = lineLeftOffset * this.scaleX;
22446
 
22447
      if (this.flipX) {
22448
        // when oject is horizontally flipped we reverse chars
22449
        this._textLines[i] = line.reverse().join('');
22450
      }
22451
 
22452
      for (var j = 0, jlen = line.length; j < jlen; j++) {
22453
 
22454
        var _char = line[j];
22455
        prevWidth = width;
22456
 
22457
        width += this._getWidthOfChar(this.ctx, _char, i, this.flipX ? jlen - j : j) *
22458
                 this.scaleX;
22459
 
22460
        if (height <= mouseOffset.y || width <= mouseOffset.x) {
22461
          charIndex++;
22462
          continue;
22463
        }
22464
 
22465
        return this._getNewSelectionStartFromOffset(
22466
          mouseOffset, prevWidth, width, charIndex + i, jlen);
22467
      }
22468
 
22469
      if (mouseOffset.y < height) {
22470
        return this._getNewSelectionStartFromOffset(
22471
          mouseOffset, prevWidth, width, charIndex + i, jlen);
22472
      }
22473
    }
22474
 
22475
    // clicked somewhere after all chars, so set at the end
22476
    if (typeof newSelectionStart === 'undefined') {
22477
      return this.text.length;
22478
    }
22479
  },
22480
 
22481
  /**
22482
   * @private
22483
   */
22484
  _getNewSelectionStartFromOffset: function(mouseOffset, prevWidth, width, index, jlen) {
22485
 
22486
    var distanceBtwLastCharAndCursor = mouseOffset.x - prevWidth,
22487
        distanceBtwNextCharAndCursor = width - mouseOffset.x,
22488
        offset = distanceBtwNextCharAndCursor > distanceBtwLastCharAndCursor ? 0 : 1,
22489
        newSelectionStart = index + offset;
22490
 
22491
    // if object is horizontally flipped, mirror cursor location from the end
22492
    if (this.flipX) {
22493
      newSelectionStart = jlen - newSelectionStart;
22494
    }
22495
 
22496
    if (newSelectionStart > this.text.length) {
22497
      newSelectionStart = this.text.length;
22498
    }
22499
 
22500
    return newSelectionStart;
22501
  }
22502
});
22503
 
22504
 
22505
fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ {
22506
 
22507
  /**
22508
   * Initializes hidden textarea (needed to bring up keyboard in iOS)
22509
   */
22510
  initHiddenTextarea: function() {
22511
    this.hiddenTextarea = fabric.document.createElement('textarea');
22512
 
22513
    this.hiddenTextarea.setAttribute('autocapitalize', 'off');
22514
    this.hiddenTextarea.style.cssText = 'position: fixed; bottom: 20px; left: 0px; opacity: 0;'
22515
                                        + ' width: 0px; height: 0px; z-index: -999;';
22516
    fabric.document.body.appendChild(this.hiddenTextarea);
22517
 
22518
    fabric.util.addListener(this.hiddenTextarea, 'keydown', this.onKeyDown.bind(this));
22519
    fabric.util.addListener(this.hiddenTextarea, 'keypress', this.onKeyPress.bind(this));
22520
    fabric.util.addListener(this.hiddenTextarea, 'copy', this.copy.bind(this));
22521
    fabric.util.addListener(this.hiddenTextarea, 'paste', this.paste.bind(this));
22522
 
22523
    if (!this._clickHandlerInitialized && this.canvas) {
22524
      fabric.util.addListener(this.canvas.upperCanvasEl, 'click', this.onClick.bind(this));
22525
      this._clickHandlerInitialized = true;
22526
    }
22527
  },
22528
 
22529
  /**
22530
   * @private
22531
   */
22532
  _keysMap: {
22533
    8:  'removeChars',
22534
    9:  'exitEditing',
22535
    27: 'exitEditing',
22536
    13: 'insertNewline',
22537
    33: 'moveCursorUp',
22538
    34: 'moveCursorDown',
22539
    35: 'moveCursorRight',
22540
    36: 'moveCursorLeft',
22541
    37: 'moveCursorLeft',
22542
    38: 'moveCursorUp',
22543
    39: 'moveCursorRight',
22544
    40: 'moveCursorDown',
22545
    46: 'forwardDelete'
22546
  },
22547
 
22548
  /**
22549
   * @private
22550
   */
22551
  _ctrlKeysMap: {
22552
    65: 'selectAll',
22553
    88: 'cut'
22554
  },
22555
 
22556
  onClick: function() {
22557
    // No need to trigger click event here, focus is enough to have the keyboard appear on Android
22558
    this.hiddenTextarea && this.hiddenTextarea.focus();
22559
  },
22560
 
22561
  /**
22562
   * Handles keyup event
22563
   * @param {Event} e Event object
22564
   */
22565
  onKeyDown: function(e) {
22566
    if (!this.isEditing) {
22567
      return;
22568
    }
22569
    if (e.keyCode in this._keysMap) {
22570
      this[this._keysMap[e.keyCode]](e);
22571
    }
22572
    else if ((e.keyCode in this._ctrlKeysMap) && (e.ctrlKey || e.metaKey)) {
22573
      this[this._ctrlKeysMap[e.keyCode]](e);
22574
    }
22575
    else {
22576
      return;
22577
    }
22578
    e.stopImmediatePropagation();
22579
    e.preventDefault();
22580
    this.canvas && this.canvas.renderAll();
22581
  },
22582
 
22583
  /**
22584
   * Forward delete
22585
   */
22586
  forwardDelete: function(e) {
22587
    if (this.selectionStart === this.selectionEnd) {
22588
      this.moveCursorRight(e);
22589
    }
22590
    this.removeChars(e);
22591
  },
22592
 
22593
  /**
22594
   * Copies selected text
22595
   * @param {Event} e Event object
22596
   */
22597
  copy: function(e) {
22598
    var selectedText = this.getSelectedText(),
22599
        clipboardData = this._getClipboardData(e);
22600
 
22601
    // Check for backward compatibility with old browsers
22602
    if (clipboardData) {
22603
      clipboardData.setData('text', selectedText);
22604
    }
22605
 
22606
    this.copiedText = selectedText;
22607
    this.copiedStyles = this.getSelectionStyles(
22608
                          this.selectionStart,
22609
                          this.selectionEnd);
22610
  },
22611
 
22612
  /**
22613
   * Pastes text
22614
   * @param {Event} e Event object
22615
   */
22616
  paste: function(e) {
22617
    var copiedText = null,
22618
        clipboardData = this._getClipboardData(e);
22619
 
22620
    // Check for backward compatibility with old browsers
22621
    if (clipboardData) {
22622
      copiedText = clipboardData.getData('text');
22623
    }
22624
    else {
22625
      copiedText = this.copiedText;
22626
    }
22627
 
22628
    if (copiedText) {
22629
      this.insertChars(copiedText, true);
22630
    }
22631
  },
22632
 
22633
  /**
22634
   * Cuts text
22635
   * @param {Event} e Event object
22636
   */
22637
  cut: function(e) {
22638
    if (this.selectionStart === this.selectionEnd) {
22639
      return;
22640
    }
22641
 
22642
    this.copy();
22643
    this.removeChars(e);
22644
  },
22645
 
22646
  /**
22647
   * @private
22648
   * @param {Event} e Event object
22649
   * @return {Object} Clipboard data object
22650
   */
22651
  _getClipboardData: function(e) {
22652
    return e && (e.clipboardData || fabric.window.clipboardData);
22653
  },
22654
 
22655
  /**
22656
   * Handles keypress event
22657
   * @param {Event} e Event object
22658
   */
22659
  onKeyPress: function(e) {
22660
    if (!this.isEditing || e.metaKey || e.ctrlKey) {
22661
      return;
22662
    }
22663
    if (e.which !== 0) {
22664
      this.insertChars(String.fromCharCode(e.which));
22665
    }
22666
    e.stopPropagation();
22667
  },
22668
 
22669
  /**
22670
   * Gets start offset of a selection
22671
   * @param {Event} e Event object
22672
   * @param {Boolean} isRight
22673
   * @return {Number}
22674
   */
22675
  getDownCursorOffset: function(e, isRight) {
22676
    var selectionProp = isRight ? this.selectionEnd : this.selectionStart,
22677
        _char, lineLeftOffset,
22678
        textBeforeCursor = this.text.slice(0, selectionProp),
22679
        textAfterCursor = this.text.slice(selectionProp),
22680
 
22681
        textOnSameLineBeforeCursor = textBeforeCursor.slice(textBeforeCursor.lastIndexOf('\n') + 1),
22682
        textOnSameLineAfterCursor = textAfterCursor.match(/(.*)\n?/)[1],
22683
        textOnNextLine = (textAfterCursor.match(/.*\n(.*)\n?/) || { })[1] || '',
22684
 
22685
        cursorLocation = this.get2DCursorLocation(selectionProp);
22686
 
22687
    // if on last line, down cursor goes to end of line
22688
    if (cursorLocation.lineIndex === this._textLines.length - 1 || e.metaKey || e.keyCode === 34) {
22689
 
22690
      // move to the end of a text
22691
      return this.text.length - selectionProp;
22692
    }
22693
 
22694
    var widthOfSameLineBeforeCursor = this._getLineWidth(this.ctx, cursorLocation.lineIndex);
22695
    lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor);
22696
 
22697
    var widthOfCharsOnSameLineBeforeCursor = lineLeftOffset,
22698
        lineIndex = cursorLocation.lineIndex;
22699
 
22700
    for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) {
22701
      _char = textOnSameLineBeforeCursor[i];
22702
      widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i);
22703
    }
22704
 
22705
    var indexOnNextLine = this._getIndexOnNextLine(
22706
      cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor);
22707
 
22708
    return textOnSameLineAfterCursor.length + 1 + indexOnNextLine;
22709
  },
22710
 
22711
  /**
22712
   * @private
22713
   */
22714
  _getIndexOnNextLine: function(cursorLocation, textOnNextLine, widthOfCharsOnSameLineBeforeCursor) {
22715
    var lineIndex = cursorLocation.lineIndex + 1,
22716
        widthOfNextLine = this._getLineWidth(this.ctx, lineIndex),
22717
        lineLeftOffset = this._getLineLeftOffset(widthOfNextLine),
22718
        widthOfCharsOnNextLine = lineLeftOffset,
22719
        indexOnNextLine = 0,
22720
        foundMatch;
22721
 
22722
    for (var j = 0, jlen = textOnNextLine.length; j < jlen; j++) {
22723
 
22724
      var _char = textOnNextLine[j],
22725
          widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j);
22726
 
22727
      widthOfCharsOnNextLine += widthOfChar;
22728
 
22729
      if (widthOfCharsOnNextLine > widthOfCharsOnSameLineBeforeCursor) {
22730
 
22731
        foundMatch = true;
22732
 
22733
        var leftEdge = widthOfCharsOnNextLine - widthOfChar,
22734
            rightEdge = widthOfCharsOnNextLine,
22735
            offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor),
22736
            offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor);
22737
 
22738
        indexOnNextLine = offsetFromRightEdge < offsetFromLeftEdge ? j + 1 : j;
22739
 
22740
        break;
22741
      }
22742
    }
22743
 
22744
    // reached end
22745
    if (!foundMatch) {
22746
      indexOnNextLine = textOnNextLine.length;
22747
    }
22748
 
22749
    return indexOnNextLine;
22750
  },
22751
 
22752
  /**
22753
   * Moves cursor down
22754
   * @param {Event} e Event object
22755
   */
22756
  moveCursorDown: function(e) {
22757
    this.abortCursorAnimation();
22758
    this._currentCursorOpacity = 1;
22759
 
22760
    var offset = this.getDownCursorOffset(e, this._selectionDirection === 'right');
22761
 
22762
    if (e.shiftKey) {
22763
      this.moveCursorDownWithShift(offset);
22764
    }
22765
    else {
22766
      this.moveCursorDownWithoutShift(offset);
22767
    }
22768
 
22769
    this.initDelayedCursor();
22770
  },
22771
 
22772
  /**
22773
   * Moves cursor down without keeping selection
22774
   * @param {Number} offset
22775
   */
22776
  moveCursorDownWithoutShift: function(offset) {
22777
    this._selectionDirection = 'right';
22778
    this.setSelectionStart(this.selectionStart + offset);
22779
    this.setSelectionEnd(this.selectionStart);
22780
  },
22781
 
22782
  /**
22783
   * private
22784
   */
22785
  swapSelectionPoints: function() {
22786
    var swapSel = this.selectionEnd;
22787
    this.setSelectionEnd(this.selectionStart);
22788
    this.setSelectionStart(swapSel);
22789
  },
22790
 
22791
  /**
22792
   * Moves cursor down while keeping selection
22793
   * @param {Number} offset
22794
   */
22795
  moveCursorDownWithShift: function(offset) {
22796
    if (this.selectionEnd === this.selectionStart) {
22797
      this._selectionDirection = 'right';
22798
    }
22799
    if (this._selectionDirection === 'right') {
22800
      this.setSelectionEnd(this.selectionEnd + offset);
22801
    }
22802
    else {
22803
      this.setSelectionStart(this.selectionStart + offset);
22804
    }
22805
    if (this.selectionEnd < this.selectionStart  && this._selectionDirection === 'left') {
22806
      this.swapSelectionPoints();
22807
      this._selectionDirection = 'right';
22808
    }
22809
    if (this.selectionEnd > this.text.length) {
22810
      this.setSelectionEnd(this.text.length);
22811
    }
22812
  },
22813
 
22814
  /**
22815
   * @param {Event} e Event object
22816
   * @param {Boolean} isRight
22817
   * @return {Number}
22818
   */
22819
  getUpCursorOffset: function(e, isRight) {
22820
    var selectionProp = isRight ? this.selectionEnd : this.selectionStart,
22821
        cursorLocation = this.get2DCursorLocation(selectionProp);
22822
    // if on first line, up cursor goes to start of line
22823
    if (cursorLocation.lineIndex === 0 || e.metaKey || e.keyCode === 33) {
22824
      return selectionProp;
22825
    }
22826
 
22827
    var textBeforeCursor = this.text.slice(0, selectionProp),
22828
        textOnSameLineBeforeCursor = textBeforeCursor.slice(textBeforeCursor.lastIndexOf('\n') + 1),
22829
        textOnPreviousLine = (textBeforeCursor.match(/\n?(.*)\n.*$/) || {})[1] || '',
22830
        _char,
22831
        widthOfSameLineBeforeCursor = this._getLineWidth(this.ctx, cursorLocation.lineIndex),
22832
        lineLeftOffset = this._getLineLeftOffset(widthOfSameLineBeforeCursor),
22833
        widthOfCharsOnSameLineBeforeCursor = lineLeftOffset,
22834
        lineIndex = cursorLocation.lineIndex;
22835
 
22836
    for (var i = 0, len = textOnSameLineBeforeCursor.length; i < len; i++) {
22837
      _char = textOnSameLineBeforeCursor[i];
22838
      widthOfCharsOnSameLineBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i);
22839
    }
22840
 
22841
    var indexOnPrevLine = this._getIndexOnPrevLine(
22842
      cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor);
22843
 
22844
    return textOnPreviousLine.length - indexOnPrevLine + textOnSameLineBeforeCursor.length;
22845
  },
22846
 
22847
  /**
22848
   * @private
22849
   */
22850
  _getIndexOnPrevLine: function(cursorLocation, textOnPreviousLine, widthOfCharsOnSameLineBeforeCursor) {
22851
 
22852
    var lineIndex = cursorLocation.lineIndex - 1,
22853
        widthOfPreviousLine = this._getLineWidth(this.ctx, lineIndex),
22854
        lineLeftOffset = this._getLineLeftOffset(widthOfPreviousLine),
22855
        widthOfCharsOnPreviousLine = lineLeftOffset,
22856
        indexOnPrevLine = 0,
22857
        foundMatch;
22858
 
22859
    for (var j = 0, jlen = textOnPreviousLine.length; j < jlen; j++) {
22860
 
22861
      var _char = textOnPreviousLine[j],
22862
          widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j);
22863
 
22864
      widthOfCharsOnPreviousLine += widthOfChar;
22865
 
22866
      if (widthOfCharsOnPreviousLine > widthOfCharsOnSameLineBeforeCursor) {
22867
 
22868
        foundMatch = true;
22869
 
22870
        var leftEdge = widthOfCharsOnPreviousLine - widthOfChar,
22871
            rightEdge = widthOfCharsOnPreviousLine,
22872
            offsetFromLeftEdge = Math.abs(leftEdge - widthOfCharsOnSameLineBeforeCursor),
22873
            offsetFromRightEdge = Math.abs(rightEdge - widthOfCharsOnSameLineBeforeCursor);
22874
 
22875
        indexOnPrevLine = offsetFromRightEdge < offsetFromLeftEdge ? j : (j - 1);
22876
 
22877
        break;
22878
      }
22879
    }
22880
 
22881
    // reached end
22882
    if (!foundMatch) {
22883
      indexOnPrevLine = textOnPreviousLine.length - 1;
22884
    }
22885
 
22886
    return indexOnPrevLine;
22887
  },
22888
 
22889
  /**
22890
   * Moves cursor up
22891
   * @param {Event} e Event object
22892
   */
22893
  moveCursorUp: function(e) {
22894
 
22895
    this.abortCursorAnimation();
22896
    this._currentCursorOpacity = 1;
22897
 
22898
    var offset = this.getUpCursorOffset(e, this._selectionDirection === 'right');
22899
    if (e.shiftKey) {
22900
      this.moveCursorUpWithShift(offset);
22901
    }
22902
    else {
22903
      this.moveCursorUpWithoutShift(offset);
22904
    }
22905
 
22906
    this.initDelayedCursor();
22907
  },
22908
 
22909
  /**
22910
   * Moves cursor up with shift
22911
   * @param {Number} offset
22912
   */
22913
  moveCursorUpWithShift: function(offset) {
22914
    if (this.selectionEnd === this.selectionStart) {
22915
      this._selectionDirection = 'left';
22916
    }
22917
    if (this._selectionDirection === 'right') {
22918
      this.setSelectionEnd(this.selectionEnd - offset);
22919
    }
22920
    else {
22921
      this.setSelectionStart(this.selectionStart - offset);
22922
    }
22923
    if (this.selectionEnd < this.selectionStart && this._selectionDirection === 'right') {
22924
      this.swapSelectionPoints();
22925
      this._selectionDirection = 'left';
22926
    }
22927
  },
22928
 
22929
  /**
22930
   * Moves cursor up without shift
22931
   * @param {Number} offset
22932
   */
22933
  moveCursorUpWithoutShift: function(offset) {
22934
    if (this.selectionStart === this.selectionEnd) {
22935
      this.setSelectionStart(this.selectionStart - offset);
22936
    }
22937
    this.setSelectionEnd(this.selectionStart);
22938
 
22939
    this._selectionDirection = 'left';
22940
  },
22941
 
22942
  /**
22943
   * Moves cursor left
22944
   * @param {Event} e Event object
22945
   */
22946
  moveCursorLeft: function(e) {
22947
    if (this.selectionStart === 0 && this.selectionEnd === 0) {
22948
      return;
22949
    }
22950
 
22951
    this.abortCursorAnimation();
22952
    this._currentCursorOpacity = 1;
22953
 
22954
    if (e.shiftKey) {
22955
      this.moveCursorLeftWithShift(e);
22956
    }
22957
    else {
22958
      this.moveCursorLeftWithoutShift(e);
22959
    }
22960
 
22961
    this.initDelayedCursor();
22962
  },
22963
 
22964
  /**
22965
   * @private
22966
   */
22967
  _move: function(e, prop, direction) {
22968
    var propMethod = (prop === 'selectionStart' ? 'setSelectionStart' : 'setSelectionEnd');
22969
    if (e.altKey) {
22970
      this[propMethod](this['findWordBoundary' + direction](this[prop]));
22971
    }
22972
    else if (e.metaKey || e.keyCode === 35 ||  e.keyCode === 36 ) {
22973
      this[propMethod](this['findLineBoundary' + direction](this[prop]));
22974
    }
22975
    else {
22976
      this[propMethod](this[prop] + (direction === 'Left' ? -1 : 1));
22977
    }
22978
  },
22979
 
22980
  /**
22981
   * @private
22982
   */
22983
  _moveLeft: function(e, prop) {
22984
    this._move(e, prop, 'Left');
22985
  },
22986
 
22987
  /**
22988
   * @private
22989
   */
22990
  _moveRight: function(e, prop) {
22991
    this._move(e, prop, 'Right');
22992
  },
22993
 
22994
  /**
22995
   * Moves cursor left without keeping selection
22996
   * @param {Event} e
22997
   */
22998
  moveCursorLeftWithoutShift: function(e) {
22999
    this._selectionDirection = 'left';
23000
 
23001
    // only move cursor when there is no selection,
23002
    // otherwise we discard it, and leave cursor on same place
23003
    if (this.selectionEnd === this.selectionStart) {
23004
      this._moveLeft(e, 'selectionStart');
23005
    }
23006
    this.setSelectionEnd(this.selectionStart);
23007
  },
23008
 
23009
  /**
23010
   * Moves cursor left while keeping selection
23011
   * @param {Event} e
23012
   */
23013
  moveCursorLeftWithShift: function(e) {
23014
    if (this._selectionDirection === 'right' && this.selectionStart !== this.selectionEnd) {
23015
      this._moveLeft(e, 'selectionEnd');
23016
    }
23017
    else {
23018
      this._selectionDirection = 'left';
23019
      this._moveLeft(e, 'selectionStart');
23020
 
23021
      // increase selection by one if it's a newline
23022
      if (this.text.charAt(this.selectionStart) === '\n') {
23023
        this.setSelectionStart(this.selectionStart - 1);
23024
      }
23025
    }
23026
  },
23027
 
23028
  /**
23029
   * Moves cursor right
23030
   * @param {Event} e Event object
23031
   */
23032
  moveCursorRight: function(e) {
23033
    if (this.selectionStart >= this.text.length && this.selectionEnd >= this.text.length) {
23034
      return;
23035
    }
23036
 
23037
    this.abortCursorAnimation();
23038
    this._currentCursorOpacity = 1;
23039
 
23040
    if (e.shiftKey) {
23041
      this.moveCursorRightWithShift(e);
23042
    }
23043
    else {
23044
      this.moveCursorRightWithoutShift(e);
23045
    }
23046
 
23047
    this.initDelayedCursor();
23048
  },
23049
 
23050
  /**
23051
   * Moves cursor right while keeping selection
23052
   * @param {Event} e
23053
   */
23054
  moveCursorRightWithShift: function(e) {
23055
    if (this._selectionDirection === 'left' && this.selectionStart !== this.selectionEnd) {
23056
      this._moveRight(e, 'selectionStart');
23057
    }
23058
    else {
23059
      this._selectionDirection = 'right';
23060
      this._moveRight(e, 'selectionEnd');
23061
 
23062
      // increase selection by one if it's a newline
23063
      if (this.text.charAt(this.selectionEnd - 1) === '\n') {
23064
        this.setSelectionEnd(this.selectionEnd + 1);
23065
      }
23066
    }
23067
  },
23068
 
23069
  /**
23070
   * Moves cursor right without keeping selection
23071
   * @param {Event} e Event object
23072
   */
23073
  moveCursorRightWithoutShift: function(e) {
23074
    this._selectionDirection = 'right';
23075
 
23076
    if (this.selectionStart === this.selectionEnd) {
23077
      this._moveRight(e, 'selectionStart');
23078
      this.setSelectionEnd(this.selectionStart);
23079
    }
23080
    else {
23081
      this.setSelectionEnd(this.selectionEnd + this.getNumNewLinesInSelectedText());
23082
      this.setSelectionStart(this.selectionEnd);
23083
    }
23084
  },
23085
 
23086
  /**
23087
   * Removes characters selected by selection
23088
   * @param {Event} e Event object
23089
   */
23090
  removeChars: function(e) {
23091
    if (this.selectionStart === this.selectionEnd) {
23092
      this._removeCharsNearCursor(e);
23093
    }
23094
    else {
23095
      this._removeCharsFromTo(this.selectionStart, this.selectionEnd);
23096
    }
23097
 
23098
    this.setSelectionEnd(this.selectionStart);
23099
 
23100
    this._removeExtraneousStyles();
23101
 
23102
    this._clearCache();
23103
    this.canvas && this.canvas.renderAll();
23104
 
23105
    this.setCoords();
23106
    this.fire('changed');
23107
    this.canvas && this.canvas.fire('text:changed', { target: this });
23108
  },
23109
 
23110
  /**
23111
   * @private
23112
   * @param {Event} e Event object
23113
   */
23114
  _removeCharsNearCursor: function(e) {
23115
    if (this.selectionStart !== 0) {
23116
 
23117
      if (e.metaKey) {
23118
        // remove all till the start of current line
23119
        var leftLineBoundary = this.findLineBoundaryLeft(this.selectionStart);
23120
 
23121
        this._removeCharsFromTo(leftLineBoundary, this.selectionStart);
23122
        this.setSelectionStart(leftLineBoundary);
23123
      }
23124
      else if (e.altKey) {
23125
        // remove all till the start of current word
23126
        var leftWordBoundary = this.findWordBoundaryLeft(this.selectionStart);
23127
 
23128
        this._removeCharsFromTo(leftWordBoundary, this.selectionStart);
23129
        this.setSelectionStart(leftWordBoundary);
23130
      }
23131
      else {
23132
        var isBeginningOfLine = this.text.slice(this.selectionStart - 1, this.selectionStart) === '\n';
23133
        this.removeStyleObject(isBeginningOfLine);
23134
        this.setSelectionStart(this.selectionStart - 1);
23135
        this.text = this.text.slice(0, this.selectionStart) +
23136
                    this.text.slice(this.selectionStart + 1);
23137
      }
23138
    }
23139
  }
23140
});
23141
 
23142
 
23143
/* _TO_SVG_START_ */
23144
fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ {
23145
 
23146
  /**
23147
   * @private
23148
   */
23149
  _setSVGTextLineText: function(lineIndex, textSpans, height, textLeftOffset, textTopOffset, textBgRects) {
23150
    if (!this.styles[lineIndex]) {
23151
      this.callSuper('_setSVGTextLineText',
23152
        lineIndex, textSpans, height, textLeftOffset, textTopOffset);
23153
    }
23154
    else {
23155
      this._setSVGTextLineChars(
23156
        lineIndex, textSpans, height, textLeftOffset, textBgRects);
23157
    }
23158
  },
23159
 
23160
  /**
23161
   * @private
23162
   */
23163
  _setSVGTextLineChars: function(lineIndex, textSpans, height, textLeftOffset, textBgRects) {
23164
 
23165
    var chars = this._textLines[lineIndex].split(''),
23166
        charOffset = 0,
23167
        lineLeftOffset = this._getSVGLineLeftOffset(lineIndex) - this.width / 2,
23168
        lineOffset = this._getSVGLineTopOffset(lineIndex),
23169
        heightOfLine = this._getHeightOfLine(this.ctx, lineIndex);
23170
 
23171
    for (var i = 0, len = chars.length; i < len; i++) {
23172
      var styleDecl = this.styles[lineIndex][i] || { };
23173
 
23174
      textSpans.push(
23175
        this._createTextCharSpan(
23176
          chars[i], styleDecl, lineLeftOffset, lineOffset.lineTop + lineOffset.offset, charOffset));
23177
 
23178
      var charWidth = this._getWidthOfChar(this.ctx, chars[i], lineIndex, i);
23179
 
23180
      if (styleDecl.textBackgroundColor) {
23181
        textBgRects.push(
23182
          this._createTextCharBg(
23183
            styleDecl, lineLeftOffset, lineOffset.lineTop, heightOfLine, charWidth, charOffset));
23184
      }
23185
 
23186
      charOffset += charWidth;
23187
    }
23188
  },
23189
 
23190
  /**
23191
   * @private
23192
   */
23193
  _getSVGLineLeftOffset: function(lineIndex) {
23194
    return fabric.util.toFixed(this._getLineLeftOffset(this.__lineWidths[lineIndex]), 2);
23195
  },
23196
 
23197
  /**
23198
   * @private
23199
   */
23200
  _getSVGLineTopOffset: function(lineIndex) {
23201
    var lineTopOffset = 0, lastHeight = 0;
23202
    for (var j = 0; j < lineIndex; j++) {
23203
      lineTopOffset += this._getHeightOfLine(this.ctx, j);
23204
    }
23205
    lastHeight = this._getHeightOfLine(this.ctx, j);
23206
    return {
23207
      lineTop: lineTopOffset,
23208
      offset: (this._fontSizeMult - this._fontSizeFraction) * lastHeight / (this.lineHeight * this._fontSizeMult)
23209
    };
23210
  },
23211
 
23212
  /**
23213
   * @private
23214
   */
23215
  _createTextCharBg: function(styleDecl, lineLeftOffset, lineTopOffset, heightOfLine, charWidth, charOffset) {
23216
    return [
23217
      //jscs:disable validateIndentation
23218
      '<rect fill="', styleDecl.textBackgroundColor,
23219
      '" x="', lineLeftOffset + charOffset,
23220
      '" y="', lineTopOffset - this.height/2,
23221
      '" width="', charWidth,
23222
      '" height="', heightOfLine / this.lineHeight,
23223
      '"></rect>'
23224
      //jscs:enable validateIndentation
23225
    ].join('');
23226
  },
23227
 
23228
  /**
23229
   * @private
23230
   */
23231
  _createTextCharSpan: function(_char, styleDecl, lineLeftOffset, lineTopOffset, charOffset) {
23232
 
23233
    var fillStyles = this.getSvgStyles.call(fabric.util.object.extend({
23234
      visible: true,
23235
      fill: this.fill,
23236
      stroke: this.stroke,
23237
      type: 'text'
23238
    }, styleDecl));
23239
 
23240
    return [
23241
      //jscs:disable validateIndentation
23242
      '<tspan x="', lineLeftOffset + charOffset, '" y="',
23243
        lineTopOffset - this.height/2, '" ',
23244
        (styleDecl.fontFamily ? 'font-family="' + styleDecl.fontFamily.replace(/"/g, '\'') + '" ': ''),
23245
        (styleDecl.fontSize ? 'font-size="' + styleDecl.fontSize + '" ': ''),
23246
        (styleDecl.fontStyle ? 'font-style="' + styleDecl.fontStyle + '" ': ''),
23247
        (styleDecl.fontWeight ? 'font-weight="' + styleDecl.fontWeight + '" ': ''),
23248
        (styleDecl.textDecoration ? 'text-decoration="' + styleDecl.textDecoration + '" ': ''),
23249
        'style="', fillStyles, '">',
23250
        fabric.util.string.escapeXml(_char),
23251
      '</tspan>'
23252
      //jscs:enable validateIndentation
23253
    ].join('');
23254
  }
23255
});
23256
/* _TO_SVG_END_ */
23257
 
23258
 
23259
(function() {
23260
 
23261
  if (typeof document !== 'undefined' && typeof window !== 'undefined') {
23262
    return;
23263
  }
23264
 
23265
  var DOMParser = require('xmldom').DOMParser,
23266
      URL = require('url'),
23267
      HTTP = require('http'),
23268
      HTTPS = require('https'),
23269
 
23270
      Canvas = require('canvas'),
23271
      Image = require('canvas').Image;
23272
 
23273
  /** @private */
23274
  function request(url, encoding, callback) {
23275
    var oURL = URL.parse(url);
23276
 
23277
    // detect if http or https is used
23278
    if ( !oURL.port ) {
23279
      oURL.port = ( oURL.protocol.indexOf('https:') === 0 ) ? 443 : 80;
23280
    }
23281
 
23282
    // assign request handler based on protocol
23283
    var reqHandler = (oURL.protocol.indexOf('https:') === 0 ) ? HTTPS : HTTP,
23284
        req = reqHandler.request({
23285
          hostname: oURL.hostname,
23286
          port: oURL.port,
23287
          path: oURL.path,
23288
          method: 'GET'
23289
        }, function(response) {
23290
          var body = '';
23291
          if (encoding) {
23292
            response.setEncoding(encoding);
23293
          }
23294
          response.on('end', function () {
23295
            callback(body);
23296
          });
23297
          response.on('data', function (chunk) {
23298
            if (response.statusCode === 200) {
23299
              body += chunk;
23300
            }
23301
          });
23302
        });
23303
 
23304
    req.on('error', function(err) {
23305
      if (err.errno === process.ECONNREFUSED) {
23306
        fabric.log('ECONNREFUSED: connection refused to ' + oURL.hostname + ':' + oURL.port);
23307
      }
23308
      else {
23309
        fabric.log(err.message);
23310
      }
23311
    });
23312
 
23313
    req.end();
23314
  }
23315
 
23316
  /** @private */
23317
  function requestFs(path, callback) {
23318
    var fs = require('fs');
23319
    fs.readFile(path, function (err, data) {
23320
      if (err) {
23321
        fabric.log(err);
23322
        throw err;
23323
      }
23324
      else {
23325
        callback(data);
23326
      }
23327
    });
23328
  }
23329
 
23330
  fabric.util.loadImage = function(url, callback, context) {
23331
    function createImageAndCallBack(data) {
23332
      img.src = new Buffer(data, 'binary');
23333
      // preserving original url, which seems to be lost in node-canvas
23334
      img._src = url;
23335
      callback && callback.call(context, img);
23336
    }
23337
    var img = new Image();
23338
    if (url && (url instanceof Buffer || url.indexOf('data') === 0)) {
23339
      img.src = img._src = url;
23340
      callback && callback.call(context, img);
23341
    }
23342
    else if (url && url.indexOf('http') !== 0) {
23343
      requestFs(url, createImageAndCallBack);
23344
    }
23345
    else if (url) {
23346
      request(url, 'binary', createImageAndCallBack);
23347
    }
23348
    else {
23349
      callback && callback.call(context, url);
23350
    }
23351
  };
23352
 
23353
  fabric.loadSVGFromURL = function(url, callback, reviver) {
23354
    url = url.replace(/^\n\s*/, '').replace(/\?.*$/, '').trim();
23355
    if (url.indexOf('http') !== 0) {
23356
      requestFs(url, function(body) {
23357
        fabric.loadSVGFromString(body.toString(), callback, reviver);
23358
      });
23359
    }
23360
    else {
23361
      request(url, '', function(body) {
23362
        fabric.loadSVGFromString(body, callback, reviver);
23363
      });
23364
    }
23365
  };
23366
 
23367
  fabric.loadSVGFromString = function(string, callback, reviver) {
23368
    var doc = new DOMParser().parseFromString(string);
23369
    fabric.parseSVGDocument(doc.documentElement, function(results, options) {
23370
      callback && callback(results, options);
23371
    }, reviver);
23372
  };
23373
 
23374
  fabric.util.getScript = function(url, callback) {
23375
    request(url, '', function(body) {
23376
      eval(body);
23377
      callback && callback();
23378
    });
23379
  };
23380
 
23381
  fabric.Image.fromObject = function(object, callback) {
23382
    fabric.util.loadImage(object.src, function(img) {
23383
      var oImg = new fabric.Image(img);
23384
 
23385
      oImg._initConfig(object);
23386
      oImg._initFilters(object, function(filters) {
23387
        oImg.filters = filters || [ ];
23388
        callback && callback(oImg);
23389
      });
23390
    });
23391
  };
23392
 
23393
  /**
23394
   * Only available when running fabric on node.js
23395
   * @param {Number} width Canvas width
23396
   * @param {Number} height Canvas height
23397
   * @param {Object} [options] Options to pass to FabricCanvas.
23398
   * @param {Object} [nodeCanvasOptions] Options to pass to NodeCanvas.
23399
   * @return {Object} wrapped canvas instance
23400
   */
23401
  fabric.createCanvasForNode = function(width, height, options, nodeCanvasOptions) {
23402
    nodeCanvasOptions = nodeCanvasOptions || options;
23403
 
23404
    var canvasEl = fabric.document.createElement('canvas'),
23405
        nodeCanvas = new Canvas(width || 600, height || 600, nodeCanvasOptions);
23406
 
23407
    // jsdom doesn't create style on canvas element, so here be temp. workaround
23408
    canvasEl.style = { };
23409
 
23410
    canvasEl.width = nodeCanvas.width;
23411
    canvasEl.height = nodeCanvas.height;
23412
 
23413
    var FabricCanvas = fabric.Canvas || fabric.StaticCanvas,
23414
        fabricCanvas = new FabricCanvas(canvasEl, options);
23415
 
23416
    fabricCanvas.contextContainer = nodeCanvas.getContext('2d');
23417
    fabricCanvas.nodeCanvas = nodeCanvas;
23418
    fabricCanvas.Font = Canvas.Font;
23419
 
23420
    return fabricCanvas;
23421
  };
23422
 
23423
  /** @ignore */
23424
  fabric.StaticCanvas.prototype.createPNGStream = function() {
23425
    return this.nodeCanvas.createPNGStream();
23426
  };
23427
 
23428
  fabric.StaticCanvas.prototype.createJPEGStream = function(opts) {
23429
    return this.nodeCanvas.createJPEGStream(opts);
23430
  };
23431
 
23432
  var origSetWidth = fabric.StaticCanvas.prototype.setWidth;
23433
  fabric.StaticCanvas.prototype.setWidth = function(width, options) {
23434
    origSetWidth.call(this, width, options);
23435
    this.nodeCanvas.width = width;
23436
    return this;
23437
  };
23438
  if (fabric.Canvas) {
23439
    fabric.Canvas.prototype.setWidth = fabric.StaticCanvas.prototype.setWidth;
23440
  }
23441
 
23442
  var origSetHeight = fabric.StaticCanvas.prototype.setHeight;
23443
  fabric.StaticCanvas.prototype.setHeight = function(height, options) {
23444
    origSetHeight.call(this, height, options);
23445
    this.nodeCanvas.height = height;
23446
    return this;
23447
  };
23448
  if (fabric.Canvas) {
23449
    fabric.Canvas.prototype.setHeight = fabric.StaticCanvas.prototype.setHeight;
23450
  }
23451
 
23452
})();