fabric-rails 1.0.12.1 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/CHANGELOG.md +4 -0
  2. data/README.md +1 -1
  3. data/lib/fabric/rails/version.rb +2 -2
  4. data/vendor/assets/javascripts/cufon.js +1226 -0
  5. data/vendor/assets/javascripts/event.js +20 -20
  6. data/vendor/assets/javascripts/excanvas.js +1464 -0
  7. data/vendor/assets/javascripts/fabric.js +56 -33
  8. data/vendor/assets/javascripts/fabric/HEADER.js +2 -4
  9. data/vendor/assets/javascripts/fabric/brushes/base_brush.class.js +96 -0
  10. data/vendor/assets/javascripts/fabric/brushes/circle_brush.class.js +99 -0
  11. data/vendor/assets/javascripts/fabric/brushes/pattern_brush.class.js +55 -0
  12. data/vendor/assets/javascripts/fabric/{freedrawing.class.js → brushes/pencil_brush.class.js} +73 -65
  13. data/vendor/assets/javascripts/fabric/brushes/spray_brush.class.js +157 -0
  14. data/vendor/assets/javascripts/fabric/canvas.class.js +154 -135
  15. data/vendor/assets/javascripts/fabric/color.class.js +195 -29
  16. data/vendor/assets/javascripts/fabric/filters/brightness_filter.class.js +70 -0
  17. data/vendor/assets/javascripts/fabric/filters/convolute_filter.class.js +122 -0
  18. data/vendor/assets/javascripts/fabric/filters/gradienttransparency_filter.class.js +69 -0
  19. data/vendor/assets/javascripts/fabric/filters/grayscale_filter.class.js +61 -0
  20. data/vendor/assets/javascripts/fabric/filters/invert_filter.class.js +57 -0
  21. data/vendor/assets/javascripts/fabric/filters/noise_filter.class.js +73 -0
  22. data/vendor/assets/javascripts/fabric/filters/pixelate_filter.class.js +98 -0
  23. data/vendor/assets/javascripts/fabric/filters/removewhite_filter.class.js +86 -0
  24. data/vendor/assets/javascripts/fabric/filters/sepia2_filter.class.js +61 -0
  25. data/vendor/assets/javascripts/fabric/filters/sepia_filter.class.js +58 -0
  26. data/vendor/assets/javascripts/fabric/filters/tint_filter.class.js +80 -0
  27. data/vendor/assets/javascripts/fabric/gradient.class.js +232 -80
  28. data/vendor/assets/javascripts/fabric/intersection.class.js +10 -28
  29. data/vendor/assets/javascripts/fabric/log.js +0 -2
  30. data/vendor/assets/javascripts/fabric/{canvas_animation.mixin.js → mixins/canvas_animation.mixin.js} +3 -6
  31. data/vendor/assets/javascripts/fabric/mixins/canvas_dataurl_exporter.mixin.js +137 -0
  32. data/vendor/assets/javascripts/fabric/{canvas_events.mixin.js → mixins/canvas_events.mixin.js} +97 -144
  33. data/vendor/assets/javascripts/fabric/{canvas_gestures.mixin.js → mixins/canvas_gestures.mixin.js} +4 -8
  34. data/vendor/assets/javascripts/fabric/{canvas_serialization.mixin.js → mixins/canvas_serialization.mixin.js} +19 -14
  35. data/vendor/assets/javascripts/fabric/mixins/collection.mixin.js +137 -0
  36. data/vendor/assets/javascripts/fabric/{object_geometry.mixin.js → mixins/object_geometry.mixin.js} +130 -47
  37. data/vendor/assets/javascripts/fabric/{object_interactivity.mixin.js → mixins/object_interactivity.mixin.js} +21 -102
  38. data/vendor/assets/javascripts/fabric/{object_origin.mixin.js → mixins/object_origin.mixin.js} +36 -26
  39. data/vendor/assets/javascripts/fabric/{object_straightening.mixin.js → mixins/object_straightening.mixin.js} +4 -9
  40. data/vendor/assets/javascripts/fabric/{observable.mixin.js → mixins/observable.mixin.js} +27 -35
  41. data/vendor/assets/javascripts/fabric/mixins/stateful.mixin.js +45 -0
  42. data/vendor/assets/javascripts/fabric/node.js +62 -26
  43. data/vendor/assets/javascripts/fabric/parser.js +181 -58
  44. data/vendor/assets/javascripts/fabric/pattern.class.js +43 -14
  45. data/vendor/assets/javascripts/fabric/point.class.js +4 -43
  46. data/vendor/assets/javascripts/fabric/shadow.class.js +19 -19
  47. data/vendor/assets/javascripts/fabric/{circle.class.js → shapes/circle.class.js} +32 -29
  48. data/vendor/assets/javascripts/fabric/{ellipse.class.js → shapes/ellipse.class.js} +45 -27
  49. data/vendor/assets/javascripts/fabric/{group.class.js → shapes/group.class.js} +67 -169
  50. data/vendor/assets/javascripts/fabric/{image.class.js → shapes/image.class.js} +134 -72
  51. data/vendor/assets/javascripts/fabric/{line.class.js → shapes/line.class.js} +67 -36
  52. data/vendor/assets/javascripts/fabric/{object.class.js → shapes/object.class.js} +394 -252
  53. data/vendor/assets/javascripts/fabric/{path.class.js → shapes/path.class.js} +89 -174
  54. data/vendor/assets/javascripts/fabric/{path_group.class.js → shapes/path_group.class.js} +12 -18
  55. data/vendor/assets/javascripts/fabric/{polygon.class.js → shapes/polygon.class.js} +64 -38
  56. data/vendor/assets/javascripts/fabric/{polyline.class.js → shapes/polyline.class.js} +64 -39
  57. data/vendor/assets/javascripts/fabric/{rect.class.js → shapes/rect.class.js} +78 -112
  58. data/vendor/assets/javascripts/fabric/{text.class.js → shapes/text.class.js} +264 -270
  59. data/vendor/assets/javascripts/fabric/shapes/text.cufon.js +79 -0
  60. data/vendor/assets/javascripts/fabric/{triangle.class.js → shapes/triangle.class.js} +46 -26
  61. data/vendor/assets/javascripts/fabric/static_canvas.class.js +134 -358
  62. data/vendor/assets/javascripts/fabric/util/anim_ease.js +2 -31
  63. data/vendor/assets/javascripts/fabric/util/dom_event.js +21 -7
  64. data/vendor/assets/javascripts/fabric/util/dom_misc.js +49 -39
  65. data/vendor/assets/javascripts/fabric/util/dom_request.js +1 -2
  66. data/vendor/assets/javascripts/fabric/util/dom_style.js +1 -2
  67. data/vendor/assets/javascripts/fabric/util/lang_array.js +19 -13
  68. data/vendor/assets/javascripts/fabric/util/lang_class.js +1 -2
  69. data/vendor/assets/javascripts/fabric/util/lang_function.js +3 -1
  70. data/vendor/assets/javascripts/fabric/util/lang_object.js +5 -5
  71. data/vendor/assets/javascripts/fabric/util/lang_string.js +7 -5
  72. data/vendor/assets/javascripts/fabric/util/misc.js +207 -42
  73. metadata +47 -29
  74. data/vendor/assets/javascripts/fabric/image_filters.js +0 -809
  75. data/vendor/assets/javascripts/fabric/scout.js +0 -45
  76. data/vendor/assets/javascripts/fabric/stateful.js +0 -88
@@ -0,0 +1,157 @@
1
+ /**
2
+ * SprayBrush class
3
+ * @class fabric.SprayBrush
4
+ */
5
+ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric.SprayBrush.prototype */ {
6
+
7
+ /**
8
+ * Width of a spray
9
+ * @type Number
10
+ */
11
+ width: 10,
12
+
13
+ /**
14
+ * Density of a spray (number of dots per chunk)
15
+ * @type Number
16
+ */
17
+ density: 20,
18
+
19
+ /**
20
+ * Width of spray dots
21
+ * @type Number
22
+ */
23
+ dotWidth: 1,
24
+
25
+ /**
26
+ * Width variance of spray dots
27
+ * @type Number
28
+ */
29
+ dotWidthVariance: 1,
30
+
31
+ /**
32
+ * Whether opacity of a dot should be random
33
+ * @type Boolean
34
+ */
35
+ randomOpacity: false,
36
+
37
+ /**
38
+ * Constructor
39
+ * @param {fabric.Canvas} canvas
40
+ * @return {fabric.SprayBrush} Instance of a spray brush
41
+ */
42
+ initialize: function(canvas) {
43
+ this.canvas = canvas;
44
+ this.sprayChunks = [ ];
45
+ },
46
+
47
+ /**
48
+ * Invoked on mouse down
49
+ * @param {Object} pointer
50
+ */
51
+ onMouseDown: function(pointer) {
52
+ this.sprayChunks.length = 0;
53
+ this.canvas.clearContext(this.canvas.contextTop);
54
+ this.setShadowStyles();
55
+
56
+ this.addSprayChunk(pointer);
57
+ this.render();
58
+ },
59
+
60
+ /**
61
+ * Invoked on mouse move
62
+ * @param {Object} pointer
63
+ */
64
+ onMouseMove: function(pointer) {
65
+ this.addSprayChunk(pointer);
66
+ this.render();
67
+ },
68
+
69
+ /**
70
+ * Invoked on mouse up
71
+ */
72
+ onMouseUp: function() {
73
+ var originalRenderOnAddition = this.canvas.renderOnAddition;
74
+ this.canvas.renderOnAddition = false;
75
+
76
+ for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) {
77
+ var sprayChunk = this.sprayChunks[i];
78
+
79
+ for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) {
80
+
81
+ var rect = new fabric.Rect({
82
+ width: sprayChunk[j].width,
83
+ height: sprayChunk[j].width,
84
+ left: sprayChunk[j].x + 1,
85
+ top: sprayChunk[j].y + 1,
86
+ fill: this.color,
87
+ shadow: {
88
+ color: this.shadowColor || this.color,
89
+ blur: this.shadowBlur,
90
+ offsetX: this.shadowOffsetX,
91
+ offsetY: this.shadowOffsetY
92
+ }
93
+ });
94
+
95
+ this.canvas.add(rect);
96
+ }
97
+ }
98
+
99
+ this.canvas.clearContext(this.canvas.contextTop);
100
+ this.removeShadowStyles();
101
+ this.canvas.renderOnAddition = originalRenderOnAddition;
102
+ this.canvas.renderAll();
103
+ },
104
+
105
+ /**
106
+ * Renders brush
107
+ */
108
+ render: function() {
109
+ var ctx = this.canvas.contextTop;
110
+ ctx.fillStyle = this.color;
111
+ ctx.save();
112
+
113
+ for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) {
114
+ var point = this.sprayChunkPoints[i];
115
+ if (typeof point.opacity !== 'undefined') {
116
+ ctx.globalAlpha = point.opacity;
117
+ }
118
+ ctx.fillRect(point.x, point.y, point.width, point.width);
119
+ }
120
+ ctx.restore();
121
+ },
122
+
123
+ /**
124
+ * @param {Object} pointer
125
+ */
126
+ addSprayChunk: function(pointer) {
127
+ this.sprayChunkPoints = [ ];
128
+
129
+ var x, y, width, radius = this.width / 2;
130
+
131
+ for (var i = 0; i < this.density; i++) {
132
+
133
+ x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius);
134
+ y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius);
135
+
136
+ if (this.dotWidthVariance) {
137
+ width = fabric.util.getRandomInt(
138
+ // bottom clamp width to 1
139
+ Math.max(1, this.dotWidth - this.dotWidthVariance),
140
+ this.dotWidth + this.dotWidthVariance);
141
+ }
142
+ else {
143
+ width = this.dotWidth;
144
+ }
145
+
146
+ var point = { x: x, y: y, width: width };
147
+
148
+ if (this.randomOpacity) {
149
+ point.opacity = fabric.util.getRandomInt(0, 100) / 100;
150
+ }
151
+
152
+ this.sprayChunkPoints.push(point);
153
+ }
154
+
155
+ this.sprayChunks.push(this.sprayChunkPoints);
156
+ }
157
+ });
@@ -33,39 +33,34 @@
33
33
  ProtoProxy.prototype = fabric.StaticCanvas.prototype;
34
34
  fabric.Canvas.prototype = new ProtoProxy();
35
35
 
36
- var InteractiveMethods = /** @scope fabric.Canvas.prototype */ {
36
+ var InteractiveMethods = /** @lends fabric.Canvas.prototype */ {
37
37
 
38
38
  /**
39
39
  * When true, objects can be transformed by one side (unproportionally)
40
- * @property
41
40
  * @type Boolean
42
41
  */
43
42
  uniScaleTransform: false,
44
43
 
45
44
  /**
46
45
  * When true, objects use center point as the origin of transformation
47
- * @property
48
46
  * @type Boolean
49
47
  */
50
48
  centerTransform: false,
51
49
 
52
50
  /**
53
51
  * Indicates that canvas is interactive. This property should not be changed.
54
- * @property
55
52
  * @type Boolean
56
53
  */
57
54
  interactive: true,
58
55
 
59
56
  /**
60
57
  * Indicates whether group selection should be enabled
61
- * @property
62
58
  * @type Boolean
63
59
  */
64
60
  selection: true,
65
61
 
66
62
  /**
67
63
  * Color of selection
68
- * @property
69
64
  * @type String
70
65
  */
71
66
  selectionColor: 'rgba(100, 100, 255, 0.3)', // blue
@@ -73,112 +68,88 @@
73
68
  /**
74
69
  * Default dash array pattern
75
70
  * If not empty the selection border is dashed
76
- * @property
77
71
  * @type Array
78
72
  */
79
73
  selectionDashArray: [ ],
80
74
 
81
75
  /**
82
76
  * Color of the border of selection (usually slightly darker than color of selection itself)
83
- * @property
84
77
  * @type String
85
78
  */
86
79
  selectionBorderColor: 'rgba(255, 255, 255, 0.3)',
87
80
 
88
81
  /**
89
82
  * Width of a line used in object/group selection
90
- * @property
91
83
  * @type Number
92
84
  */
93
85
  selectionLineWidth: 1,
94
86
 
95
- /**
96
- * Color of the line used in free drawing mode
97
- * @property
98
- * @type String
99
- */
100
- freeDrawingColor: 'rgb(0, 0, 0)',
101
-
102
- /**
103
- * Width of a line used in free drawing mode
104
- * @property
105
- * @type Number
106
- */
107
- freeDrawingLineWidth: 1,
108
-
109
87
  /**
110
88
  * Default cursor value used when hovering over an object on canvas
111
- * @property
112
89
  * @type String
113
90
  */
114
91
  hoverCursor: 'move',
115
92
 
116
93
  /**
117
94
  * Default cursor value used when moving an object on canvas
118
- * @property
119
95
  * @type String
120
96
  */
121
97
  moveCursor: 'move',
122
98
 
123
99
  /**
124
100
  * Default cursor value used for the entire canvas
125
- * @property
126
101
  * @type String
127
102
  */
128
103
  defaultCursor: 'default',
129
104
 
130
105
  /**
131
106
  * Cursor value used during free drawing
132
- * @property
133
107
  * @type String
134
108
  */
135
109
  freeDrawingCursor: 'crosshair',
136
110
 
137
111
  /**
138
112
  * Cursor value used for rotation point
139
- * @property
140
113
  * @type String
141
114
  */
142
115
  rotationCursor: 'crosshair',
143
116
 
144
117
  /**
145
118
  * Default element class that's given to wrapper (div) element of canvas
146
- * @property
147
119
  * @type String
148
120
  */
149
121
  containerClass: 'canvas-container',
150
122
 
151
123
  /**
152
124
  * When true, object detection happens on per-pixel basis rather than on per-bounding-box
153
- * @property
154
125
  * @type Boolean
155
126
  */
156
127
  perPixelTargetFind: false,
157
128
 
158
129
  /**
159
130
  * Number of pixels around target pixel to tolerate (consider active) during object detection
160
- * @property
161
131
  * @type Number
162
132
  */
163
133
  targetFindTolerance: 0,
164
134
 
165
135
  /**
166
- * @method _initInteractive
167
136
  * @private
168
137
  */
169
138
  _initInteractive: function() {
170
139
  this._currentTransform = null;
171
140
  this._groupSelector = null;
172
- this.freeDrawing = fabric.FreeDrawing && new fabric.FreeDrawing(this);
173
141
  this._initWrapperElement();
174
142
  this._createUpperCanvas();
175
143
  this._initEvents();
144
+
145
+ this.freeDrawingBrush = fabric.PencilBrush && new fabric.PencilBrush(this);
146
+
176
147
  this.calcOffset();
177
148
  },
178
149
 
179
150
  /**
180
151
  * Resets the current transform to its original values and chooses the type of resizing based on the event
181
- * @method _resetCurrentTransform
152
+ * @private
182
153
  * @param e {Event} Event object fired on mousemove
183
154
  */
184
155
  _resetCurrentTransform: function(e) {
@@ -189,7 +160,7 @@
189
160
  t.target.set('left', t.original.left);
190
161
  t.target.set('top', t.original.top);
191
162
 
192
- if (e.altKey || this.centerTransform) {
163
+ if (e.altKey || this.centerTransform || t.target.centerTransform) {
193
164
  if (t.originX !== 'center') {
194
165
  if (t.originX === 'right') {
195
166
  t.mouseXSign = -1;
@@ -217,36 +188,22 @@
217
188
  },
218
189
 
219
190
  /**
220
- * Applies one implementation of 'point inside polygon' algorithm
221
- * @method containsPoint
222
- * @param e { Event } event object
223
- * @param target { fabric.Object } object to test against
224
- * @return {Boolean} true if point contains within area of given object
191
+ * Checks if point is contained within an area of given object
192
+ * @param {Event} e Event object
193
+ * @param {fabric.Object} target Object to test against
194
+ * @return {Boolean} true if point is contained within an area of given object
225
195
  */
226
196
  containsPoint: function (e, target) {
227
197
  var pointer = this.getPointer(e),
228
- xy = this._normalizePointer(target, pointer),
229
- x = xy.x,
230
- y = xy.y;
198
+ xy = this._normalizePointer(target, pointer);
231
199
 
232
200
  // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html
233
201
  // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html
234
-
235
- // we iterate through each object. If target found, return it.
236
- var iLines = target._getImageLines(target.oCoords),
237
- xpoints = target._findCrossPoints(x, y, iLines);
238
-
239
- // if xcount is odd then we clicked inside the object
240
- // For the specific case of square images xcount === 1 in all true cases
241
- if ((xpoints && xpoints % 2 === 1) || target._findTargetCorner(e, this._offset)) {
242
- return true;
243
- }
244
- return false;
202
+ return (target.containsPoint(xy) || target._findTargetCorner(e, this._offset));
245
203
  },
246
204
 
247
205
  /**
248
206
  * @private
249
- * @method _normalizePointer
250
207
  */
251
208
  _normalizePointer: function (object, pointer) {
252
209
 
@@ -268,13 +225,18 @@
268
225
  },
269
226
 
270
227
  /**
271
- * @private
272
- * @method _isTargetTransparent
228
+ * Returns true if object is transparent at a certain location
229
+ * @param {fabric.Object} target Object to check
230
+ * @param {Number} x Left coordinate
231
+ * @param {Number} y Top coordinate
232
+ * @return {Boolean}
273
233
  */
274
- _isTargetTransparent: function (target, x, y) {
234
+ isTargetTransparent: function (target, x, y) {
275
235
  var cacheContext = this.contextCache;
276
236
 
277
- var hasBorders = target.hasBorders, transparentCorners = target.transparentCorners;
237
+ var hasBorders = target.hasBorders,
238
+ transparentCorners = target.transparentCorners;
239
+
278
240
  target.hasBorders = target.transparentCorners = false;
279
241
 
280
242
  this._draw(cacheContext, target);
@@ -303,40 +265,42 @@
303
265
  x, y, (this.targetFindTolerance * 2) || 1, (this.targetFindTolerance * 2) || 1);
304
266
 
305
267
  // Split image data - for tolerance > 1, pixelDataSize = 4;
306
- for (var i = 3; i < imageData.data.length; i += 4) {
307
- var temp = imageData.data[i];
308
- isTransparent = temp <= 0;
309
- if (isTransparent === false) break; //Stop if colour found
268
+ for (var i = 3, l = imageData.data.length; i < l; i += 4) {
269
+ var temp = imageData.data[i];
270
+ isTransparent = temp <= 0;
271
+ if (isTransparent === false) break; //Stop if colour found
310
272
  }
311
273
 
312
274
  imageData = null;
313
275
  this.clearContext(cacheContext);
276
+
314
277
  return isTransparent;
315
278
  },
316
279
 
317
280
  /**
318
281
  * @private
319
- * @method _shouldClearSelection
320
282
  */
321
- _shouldClearSelection: function (e) {
322
- var target = this.findTarget(e),
323
- activeGroup = this.getActiveGroup();
283
+ _shouldClearSelection: function (e, target) {
284
+ var activeGroup = this.getActiveGroup();
285
+
324
286
  return (
325
287
  !target || (
326
- target &&
327
- activeGroup &&
328
- !activeGroup.contains(target) &&
329
- activeGroup !== target &&
330
- !e.shiftKey
331
- )
288
+ target &&
289
+ activeGroup &&
290
+ !activeGroup.contains(target) &&
291
+ activeGroup !== target &&
292
+ !e.shiftKey) || (
293
+ target &&
294
+ !target.selectable)
332
295
  );
333
296
  },
334
297
 
335
298
  /**
336
299
  * @private
337
- * @method _setupCurrentTransform
338
300
  */
339
301
  _setupCurrentTransform: function (e, target) {
302
+ if (!target) return;
303
+
340
304
  var action = 'drag',
341
305
  corner,
342
306
  pointer = getPointer(e, target.canvas.upperCanvasEl);
@@ -406,7 +370,7 @@
406
370
  },
407
371
 
408
372
  /**
409
- * @method _shouldHandleGroupLogic
373
+ * @private
410
374
  * @param e {Event}
411
375
  * @param target {fabric.Object}
412
376
  * @return {Boolean}
@@ -420,7 +384,6 @@
420
384
 
421
385
  /**
422
386
  * @private
423
- * @method _handleGroupLogic
424
387
  */
425
388
  _handleGroupLogic: function (e, target) {
426
389
  if (target === this.getActiveGroup()) {
@@ -436,7 +399,7 @@
436
399
  if (activeGroup.contains(target)) {
437
400
  activeGroup.removeWithUpdate(target);
438
401
  this._resetObjectTransform(activeGroup);
439
- target.setActive(false);
402
+ target.set('active', false);
440
403
  if (activeGroup.size() === 1) {
441
404
  // remove group alltogether if after removal it only contains 1 object
442
405
  this.discardActiveGroup();
@@ -447,7 +410,7 @@
447
410
  this._resetObjectTransform(activeGroup);
448
411
  }
449
412
  this.fire('selection:created', { target: activeGroup, e: e });
450
- activeGroup.setActive(true);
413
+ activeGroup.set('active', true);
451
414
  }
452
415
  else {
453
416
  // group does not exist
@@ -455,13 +418,18 @@
455
418
  // only if there's an active object
456
419
  if (target !== this._activeObject) {
457
420
  // and that object is not the actual target
458
- var group = new fabric.Group([ this._activeObject, target ]);
421
+ var objects = this.getObjects();
422
+ var isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target);
423
+ var group = new fabric.Group(
424
+ isActiveLower ? [ target, this._activeObject ] : [ this._activeObject, target ]);
425
+
459
426
  this.setActiveGroup(group);
460
427
  activeGroup = this.getActiveGroup();
428
+ this.fire('selection:created', { target: activeGroup, e: e });
461
429
  }
462
430
  }
463
431
  // activate target object in any case
464
- target.setActive(true);
432
+ target.set('active', true);
465
433
  }
466
434
 
467
435
  if (activeGroup) {
@@ -471,7 +439,7 @@
471
439
 
472
440
  /**
473
441
  * Translates object by "setting" its left/top
474
- * @method _translateObject
442
+ * @private
475
443
  * @param x {Number} pointer's x coordinate
476
444
  * @param y {Number} pointer's y coordinate
477
445
  */
@@ -488,7 +456,7 @@
488
456
 
489
457
  /**
490
458
  * Scales object by invoking its scaleX/scaleY methods
491
- * @method _scaleObject
459
+ * @private
492
460
  * @param x {Number} pointer's x coordinate
493
461
  * @param y {Number} pointer's y coordinate
494
462
  * @param by {String} Either 'x' or 'y' - specifies dimension constraint by which to scale an object.
@@ -530,14 +498,33 @@
530
498
  }
531
499
  }
532
500
 
501
+ // adjust the mouse coordinates when dealing with padding
502
+ if (abs(localMouse.x) > target.padding) {
503
+ if (localMouse.x < 0 ) {
504
+ localMouse.x += target.padding;
505
+ } else {
506
+ localMouse.x -= target.padding;
507
+ }
508
+ } else { // mouse is within the padding, set to 0
509
+ localMouse.x = 0;
510
+ }
511
+
512
+ if (abs(localMouse.y) > target.padding) {
513
+ if (localMouse.y < 0 ) {
514
+ localMouse.y += target.padding;
515
+ } else {
516
+ localMouse.y -= target.padding;
517
+ }
518
+ } else {
519
+ localMouse.y = 0;
520
+ }
521
+
533
522
  // Actually scale the object
534
523
  var newScaleX = target.scaleX, newScaleY = target.scaleY;
535
524
  if (by === 'equally' && !lockScalingX && !lockScalingY) {
536
525
  var dist = localMouse.y + localMouse.x;
537
- var lastDist = (target.height) * t.original.scaleY +
538
- (target.width) * t.original.scaleX +
539
- (target.padding * 2) -
540
- (target.strokeWidth * 2) + 1 /* additional offset needed probably due to subpixel rendering, and avoids jerk when scaling an object */;
526
+ var lastDist = (target.height + (target.strokeWidth)) * t.original.scaleY +
527
+ (target.width + (target.strokeWidth)) * t.original.scaleX;
541
528
 
542
529
  // We use t.scaleX/Y instead of target.scaleX/Y because the object may have a min scale and we'll loose the proportions
543
530
  newScaleX = t.original.scaleX * dist/lastDist;
@@ -547,18 +534,18 @@
547
534
  target.set('scaleY', newScaleY);
548
535
  }
549
536
  else if (!by) {
550
- newScaleX = localMouse.x/(target.width+target.padding);
551
- newScaleY = localMouse.y/(target.height+target.padding);
537
+ newScaleX = localMouse.x/(target.width+target.strokeWidth);
538
+ newScaleY = localMouse.y/(target.height+target.strokeWidth);
552
539
 
553
540
  lockScalingX || target.set('scaleX', newScaleX);
554
541
  lockScalingY || target.set('scaleY', newScaleY);
555
542
  }
556
543
  else if (by === 'x' && !target.get('lockUniScaling')) {
557
- newScaleX = localMouse.x/(target.width+target.padding);
544
+ newScaleX = localMouse.x/(target.width + target.strokeWidth);
558
545
  lockScalingX || target.set('scaleX', newScaleX);
559
546
  }
560
547
  else if (by === 'y' && !target.get('lockUniScaling')) {
561
- newScaleY = localMouse.y/(target.height+target.padding);
548
+ newScaleY = localMouse.y/(target.height + target.strokeWidth);
562
549
  lockScalingY || target.set('scaleY', newScaleY);
563
550
  }
564
551
 
@@ -585,7 +572,7 @@
585
572
 
586
573
  /**
587
574
  * Rotates object by invoking its rotate method
588
- * @method _rotateObject
575
+ * @private
589
576
  * @param x {Number} pointer's x coordinate
590
577
  * @param y {Number} pointer's y coordinate
591
578
  */
@@ -597,22 +584,27 @@
597
584
  if (t.target.get('lockRotation')) return;
598
585
 
599
586
  var lastAngle = atan2(t.ey - t.top - o.top, t.ex - t.left - o.left),
600
- curAngle = atan2(y - t.top - o.top, x - t.left - o.left);
587
+ curAngle = atan2(y - t.top - o.top, x - t.left - o.left),
588
+ angle = radiansToDegrees(curAngle - lastAngle + t.theta);
589
+
590
+ // normalize angle to positive value
591
+ if (angle < 0) {
592
+ angle = 360 + angle;
593
+ }
601
594
 
602
- t.target.angle = radiansToDegrees(curAngle - lastAngle + t.theta);
595
+ t.target.angle = angle;
603
596
  },
604
597
 
605
598
  /**
606
- * @method _setCursor
599
+ * @private
607
600
  */
608
601
  _setCursor: function (value) {
609
602
  this.upperCanvasEl.style.cursor = value;
610
603
  },
611
604
 
612
605
  /**
613
- * @private
614
- * @method _resetObjectTransform:
615
- */
606
+ * @private
607
+ */
616
608
  _resetObjectTransform: function (target) {
617
609
  target.scaleX = 1;
618
610
  target.scaleY = 1;
@@ -620,7 +612,6 @@
620
612
  },
621
613
 
622
614
  /**
623
- * @method _drawSelection
624
615
  * @private
625
616
  */
626
617
  _drawSelection: function () {
@@ -671,7 +662,6 @@
671
662
 
672
663
  /**
673
664
  * @private
674
- * @method _findSelectedObjects
675
665
  */
676
666
  _findSelectedObjects: function (e) {
677
667
  var group = [ ],
@@ -681,19 +671,25 @@
681
671
  y2 = y1 + this._groupSelector.top,
682
672
  currentObject,
683
673
  selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)),
684
- selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2));
674
+ selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2)),
675
+ isClick = x1 === x2 && y1 === y2;
685
676
 
686
- for (var i = 0, len = this._objects.length; i < len; ++i) {
677
+ for (var i = this._objects.length; i--; ) {
687
678
  currentObject = this._objects[i];
688
679
 
689
680
  if (!currentObject) continue;
690
681
 
691
682
  if (currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2) ||
692
- currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2)) {
683
+ currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2) ||
684
+ currentObject.containsPoint(selectionX1Y1) ||
685
+ currentObject.containsPoint(selectionX2Y2)) {
693
686
 
694
687
  if (this.selection && currentObject.selectable) {
695
- currentObject.setActive(true);
688
+ currentObject.set('active', true);
696
689
  group.push(currentObject);
690
+
691
+ // only add one object if it's a click
692
+ if (isClick) break;
697
693
  }
698
694
  }
699
695
  }
@@ -703,18 +699,16 @@
703
699
  this.setActiveObject(group[0], e);
704
700
  }
705
701
  else if (group.length > 1) {
706
- group = new fabric.Group(group);
702
+ group = new fabric.Group(group.reverse());
707
703
  this.setActiveGroup(group);
708
704
  group.saveCoords();
709
705
  this.fire('selection:created', { target: group });
706
+ this.renderAll();
710
707
  }
711
-
712
- this.renderAll();
713
708
  },
714
709
 
715
710
  /**
716
711
  * Method that determines what object we are clicking on
717
- * @method findTarget
718
712
  * @param {Event} e mouse event
719
713
  * @param {Boolean} skipGroup when true, group is skipped and only objects are traversed through
720
714
  */
@@ -725,6 +719,7 @@
725
719
 
726
720
  if (this.controlsAboveOverlay &&
727
721
  this.lastRenderedObjectWithControlsAboveOverlay &&
722
+ this.lastRenderedObjectWithControlsAboveOverlay.visible &&
728
723
  this.containsPoint(e, this.lastRenderedObjectWithControlsAboveOverlay) &&
729
724
  this.lastRenderedObjectWithControlsAboveOverlay._findTargetCorner(e, this._offset)) {
730
725
  target = this.lastRenderedObjectWithControlsAboveOverlay;
@@ -742,7 +737,7 @@
742
737
  // Cache all targets where their bounding box contains point.
743
738
  var possibleTargets = [];
744
739
  for (var i = this._objects.length; i--; ) {
745
- if (this._objects[i] && this.containsPoint(e, this._objects[i])) {
740
+ if (this._objects[i] && this._objects[i].visible && this.containsPoint(e, this._objects[i])) {
746
741
  if (this.perPixelTargetFind || this._objects[i].perPixelTargetFind) {
747
742
  possibleTargets[possibleTargets.length] = this._objects[i];
748
743
  }
@@ -755,21 +750,19 @@
755
750
  }
756
751
  for (var j = 0, len = possibleTargets.length; j < len; j++) {
757
752
  pointer = this.getPointer(e);
758
- var isTransparent = this._isTargetTransparent(possibleTargets[j], pointer.x, pointer.y);
753
+ var isTransparent = this.isTargetTransparent(possibleTargets[j], pointer.x, pointer.y);
759
754
  if (!isTransparent) {
760
755
  target = possibleTargets[j];
761
756
  this.relatedTarget = target;
762
757
  break;
763
758
  }
764
759
  }
765
- if (target && target.selectable) {
766
- return target;
767
- }
760
+
761
+ return target;
768
762
  },
769
763
 
770
764
  /**
771
765
  * Returns pointer coordinates relative to canvas.
772
- * @method getPointer
773
766
  * @param {Event} e
774
767
  * @return {Object} object with "x" and "y" number values
775
768
  */
@@ -783,23 +776,24 @@
783
776
 
784
777
  /**
785
778
  * @private
786
- * @method _createUpperCanvas
787
779
  * @param {HTMLElement|String} canvasEl Canvas element
788
780
  * @throws {CANVAS_INIT_ERROR} If canvas can not be initialized
789
781
  */
790
782
  _createUpperCanvas: function () {
783
+ var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, '');
784
+
791
785
  this.upperCanvasEl = this._createCanvasElement();
792
- this.upperCanvasEl.className = 'upper-canvas';
786
+ fabric.util.addClass(this.upperCanvasEl, 'upper-canvas ' + lowerCanvasClass);
793
787
 
794
788
  this.wrapperEl.appendChild(this.upperCanvasEl);
795
789
 
790
+ this._copyCanvasStyle(this.lowerCanvasEl, this.upperCanvasEl);
796
791
  this._applyCanvasStyle(this.upperCanvasEl);
797
792
  this.contextTop = this.upperCanvasEl.getContext('2d');
798
793
  },
799
794
 
800
795
  /**
801
796
  * @private
802
- * @method _createCacheCanvas
803
797
  */
804
798
  _createCacheCanvas: function () {
805
799
  this.cacheCanvasEl = this._createCanvasElement();
@@ -810,7 +804,6 @@
810
804
 
811
805
  /**
812
806
  * @private
813
- * @method _initWrapperElement
814
807
  * @param {Number} width
815
808
  * @param {Number} height
816
809
  */
@@ -828,7 +821,6 @@
828
821
 
829
822
  /**
830
823
  * @private
831
- * @method _applyCanvasStyle
832
824
  * @param {Element} element
833
825
  */
834
826
  _applyCanvasStyle: function (element) {
@@ -847,9 +839,18 @@
847
839
  fabric.util.makeElementUnselectable(element);
848
840
  },
849
841
 
842
+ /**
843
+ * Copys the the entire inline style from one element (fromEl) to another (toEl)
844
+ * @private
845
+ * @param {Element} fromEl Element style is copied from
846
+ * @param {Element} toEl Element copied style is applied to
847
+ */
848
+ _copyCanvasStyle: function (fromEl, toEl) {
849
+ toEl.style.cssText = fromEl.style.cssText;
850
+ },
851
+
850
852
  /**
851
853
  * Returns context of canvas where object selection is drawn
852
- * @method getSelectionContext
853
854
  * @return {CanvasRenderingContext2D}
854
855
  */
855
856
  getSelectionContext: function() {
@@ -858,7 +859,6 @@
858
859
 
859
860
  /**
860
861
  * Returns &lt;canvas> element on which object selection is drawn
861
- * @method getSelectionElement
862
862
  * @return {HTMLCanvasElement}
863
863
  */
864
864
  getSelectionElement: function () {
@@ -866,18 +866,17 @@
866
866
  },
867
867
 
868
868
  /**
869
- * Sets given object as active
870
- * @method setActiveObject
869
+ * Sets given object as the only active object on canvas
871
870
  * @param object {fabric.Object} Object to set as an active one
872
871
  * @return {fabric.Canvas} thisArg
873
872
  * @chainable
874
873
  */
875
874
  setActiveObject: function (object, e) {
876
875
  if (this._activeObject) {
877
- this._activeObject.setActive(false);
876
+ this._activeObject.set('active', false);
878
877
  }
879
878
  this._activeObject = object;
880
- object.setActive(true);
879
+ object.set('active', true);
881
880
 
882
881
  this.renderAll();
883
882
 
@@ -888,7 +887,6 @@
888
887
 
889
888
  /**
890
889
  * Returns currently active object
891
- * @method getActiveObject
892
890
  * @return {fabric.Object} active object
893
891
  */
894
892
  getActiveObject: function () {
@@ -897,13 +895,12 @@
897
895
 
898
896
  /**
899
897
  * Discards currently active object
900
- * @method discardActiveObject
901
898
  * @return {fabric.Canvas} thisArg
902
899
  * @chainable
903
900
  */
904
901
  discardActiveObject: function () {
905
902
  if (this._activeObject) {
906
- this._activeObject.setActive(false);
903
+ this._activeObject.set('active', false);
907
904
  }
908
905
  this._activeObject = null;
909
906
  return this;
@@ -911,7 +908,6 @@
911
908
 
912
909
  /**
913
910
  * Sets active group to a speicified one
914
- * @method setActiveGroup
915
911
  * @param {fabric.Group} group Group to set as a current one
916
912
  * @return {fabric.Canvas} thisArg
917
913
  * @chainable
@@ -920,14 +916,13 @@
920
916
  this._activeGroup = group;
921
917
  if (group) {
922
918
  group.canvas = this;
923
- group.setActive(true);
919
+ group.set('active', true);
924
920
  }
925
921
  return this;
926
922
  },
927
923
 
928
924
  /**
929
925
  * Returns currently active group
930
- * @method getActiveGroup
931
926
  * @return {fabric.Group} Current group
932
927
  */
933
928
  getActiveGroup: function () {
@@ -936,7 +931,6 @@
936
931
 
937
932
  /**
938
933
  * Removes currently active group
939
- * @method discardActiveGroup
940
934
  * @return {fabric.Canvas} thisArg
941
935
  */
942
936
  discardActiveGroup: function () {
@@ -948,8 +942,7 @@
948
942
  },
949
943
 
950
944
  /**
951
- * Deactivates all objects by calling their setActive(false)
952
- * @method deactivateAll
945
+ * Deactivates all objects on canvas, removing any active group or object
953
946
  * @return {fabric.Canvas} thisArg
954
947
  */
955
948
  deactivateAll: function () {
@@ -957,7 +950,7 @@
957
950
  i = 0,
958
951
  len = allObjects.length;
959
952
  for ( ; i < len; i++) {
960
- allObjects[i].setActive(false);
953
+ allObjects[i].set('active', false);
961
954
  }
962
955
  this.discardActiveGroup();
963
956
  this.discardActiveObject();
@@ -966,7 +959,6 @@
966
959
 
967
960
  /**
968
961
  * Deactivates all objects and dispatches appropriate events
969
- * @method deactivateAllWithDispatch
970
962
  * @return {fabric.Canvas} thisArg
971
963
  */
972
964
  deactivateAllWithDispatch: function () {
@@ -979,6 +971,32 @@
979
971
  this.fire('selection:cleared');
980
972
  }
981
973
  return this;
974
+ },
975
+
976
+ /**
977
+ * Draws objects' controls (borders/controls)
978
+ * @param {Object} ctx context to render controls on
979
+ */
980
+ drawControls: function(ctx) {
981
+ var activeGroup = this.getActiveGroup();
982
+ if (activeGroup) {
983
+ ctx.save();
984
+ fabric.Group.prototype.transform.call(activeGroup, ctx);
985
+ activeGroup.drawBorders(ctx).drawControls(ctx);
986
+ ctx.restore();
987
+ }
988
+ else {
989
+ for (var i = 0, len = this._objects.length; i < len; ++i) {
990
+ if (!this._objects[i] || !this._objects[i].active) continue;
991
+
992
+ ctx.save();
993
+ fabric.Object.prototype.transform.call(this._objects[i], ctx);
994
+ this._objects[i].drawBorders(ctx).drawControls(ctx);
995
+ ctx.restore();
996
+
997
+ this.lastRenderedObjectWithControlsAboveOverlay = this._objects[i];
998
+ }
999
+ }
982
1000
  }
983
1001
  };
984
1002
 
@@ -994,6 +1012,7 @@
994
1012
  }
995
1013
 
996
1014
  if (fabric.isTouchSupported) {
1015
+ /** @ignore */
997
1016
  fabric.Canvas.prototype._setCursorFromEvent = function() { };
998
1017
  }
999
1018