Added literallycanvas and react libraries.
[openemr.git] / library / js / literallycanvas / js / core / shapes.js
blobd1051e516075064288880dae5c38d5ce6fb6ad5d
1 var JSONToShape, LinePath, TextRenderer, _createLinePathFromData, _doAllPointsShareStyle, _dual, _mid, _refine, bspline, createShape, defineCanvasRenderer, defineSVGRenderer, defineShape, lineEndCapShapes, linePathFuncs, ref, ref1, renderShapeToContext, renderShapeToSVG, shapeToJSON, shapes, util;
3 util = require('./util');
5 TextRenderer = require('./TextRenderer');
7 lineEndCapShapes = require('./lineEndCapShapes');
9 ref = require('./canvasRenderer'), defineCanvasRenderer = ref.defineCanvasRenderer, renderShapeToContext = ref.renderShapeToContext;
11 ref1 = require('./svgRenderer'), defineSVGRenderer = ref1.defineSVGRenderer, renderShapeToSVG = ref1.renderShapeToSVG;
13 shapes = {};
15 defineShape = function(name, props) {
16   var Shape, drawFunc, drawLatestFunc, k, legacyDrawFunc, legacyDrawLatestFunc, legacySVGFunc, svgFunc;
17   Shape = function(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) {
18     props.constructor.call(this, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p);
19     return this;
20   };
21   Shape.prototype.className = name;
22   Shape.fromJSON = props.fromJSON;
23   if (props.draw) {
24     legacyDrawFunc = props.draw;
25     legacyDrawLatestFunc = props.draw || function(ctx, bufferCtx, retryCallback) {
26       return this.draw(ctx, bufferCtx, retryCallback);
27     };
28     drawFunc = function(ctx, shape, retryCallback) {
29       return legacyDrawFunc.call(shape, ctx, retryCallback);
30     };
31     drawLatestFunc = function(ctx, bufferCtx, shape, retryCallback) {
32       return legacyDrawLatestFunc.call(shape, ctx, bufferCtx, retryCallback);
33     };
34     delete props.draw;
35     if (props.drawLatest) {
36       delete props.drawLatest;
37     }
38     defineCanvasRenderer(name, drawFunc, drawLatestFunc);
39   }
40   if (props.toSVG) {
41     legacySVGFunc = props.toSVG;
42     svgFunc = function(shape) {
43       return legacySVGFunc.call(shape);
44     };
45     delete props.toSVG;
46     defineSVGRenderer(name, svgFunc);
47   }
48   Shape.prototype.draw = function(ctx, retryCallback) {
49     return renderShapeToContext(ctx, this, {
50       retryCallback: retryCallback
51     });
52   };
53   Shape.prototype.drawLatest = function(ctx, bufferCtx, retryCallback) {
54     return renderShapeToContext(ctx, this, {
55       retryCallback: retryCallback,
56       bufferCtx: bufferCtx,
57       shouldOnlyDrawLatest: true
58     });
59   };
60   Shape.prototype.toSVG = function() {
61     return renderShapeToSVG(this);
62   };
63   for (k in props) {
64     if (k !== 'fromJSON') {
65       Shape.prototype[k] = props[k];
66     }
67   }
68   shapes[name] = Shape;
69   return Shape;
72 createShape = function(name, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) {
73   var s;
74   s = new shapes[name](a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p);
75   s.id = util.getGUID();
76   return s;
79 JSONToShape = function(arg) {
80   var className, data, id, shape;
81   className = arg.className, data = arg.data, id = arg.id;
82   if (className in shapes) {
83     shape = shapes[className].fromJSON(data);
84     if (shape) {
85       if (id) {
86         shape.id = id;
87       }
88       return shape;
89     } else {
90       console.log('Unreadable shape:', className, data);
91       return null;
92     }
93   } else {
94     console.log("Unknown shape:", className, data);
95     return null;
96   }
99 shapeToJSON = function(shape) {
100   return {
101     className: shape.className,
102     data: shape.toJSON(),
103     id: shape.id
104   };
107 bspline = function(points, order) {
108   if (!order) {
109     return points;
110   }
111   return bspline(_dual(_dual(_refine(points))), order - 1);
114 _refine = function(points) {
115   var index, len, point, q, refined;
116   points = [points[0]].concat(points).concat(util.last(points));
117   refined = [];
118   index = 0;
119   for (q = 0, len = points.length; q < len; q++) {
120     point = points[q];
121     refined[index * 2] = point;
122     if (points[index + 1]) {
123       refined[index * 2 + 1] = _mid(point, points[index + 1]);
124     }
125     index += 1;
126   }
127   return refined;
130 _dual = function(points) {
131   var dualed, index, len, point, q;
132   dualed = [];
133   index = 0;
134   for (q = 0, len = points.length; q < len; q++) {
135     point = points[q];
136     if (points[index + 1]) {
137       dualed[index] = _mid(point, points[index + 1]);
138     }
139     index += 1;
140   }
141   return dualed;
144 _mid = function(a, b) {
145   return createShape('Point', {
146     x: a.x + ((b.x - a.x) / 2),
147     y: a.y + ((b.y - a.y) / 2),
148     size: a.size + ((b.size - a.size) / 2),
149     color: a.color
150   });
153 defineShape('Image', {
154   constructor: function(args) {
155     if (args == null) {
156       args = {};
157     }
158     this.x = args.x || 0;
159     this.y = args.y || 0;
160     this.scale = args.scale || 1;
161     return this.image = args.image || null;
162   },
163   getBoundingRect: function() {
164     return {
165       x: this.x,
166       y: this.y,
167       width: this.image.width * this.scale,
168       height: this.image.height * this.scale
169     };
170   },
171   toJSON: function() {
172     return {
173       x: this.x,
174       y: this.y,
175       imageSrc: this.image.src,
176       imageObject: this.image,
177       scale: this.scale
178     };
179   },
180   fromJSON: function(data) {
181     var img, ref2;
182     img = null;
183     if ((ref2 = data.imageObject) != null ? ref2.width : void 0) {
184       img = data.imageObject;
185     } else {
186       img = new Image();
187       img.src = data.imageSrc;
188     }
189     return createShape('Image', {
190       x: data.x,
191       y: data.y,
192       image: img,
193       scale: data.scale
194     });
195   },
196   move: function(moveInfo) {
197     if (moveInfo == null) {
198       moveInfo = {};
199     }
200     this.x = this.x - moveInfo.xDiff;
201     return this.y = this.y - moveInfo.yDiff;
202   },
203   setUpperLeft: function(upperLeft) {
204     if (upperLeft == null) {
205       upperLeft = {};
206     }
207     this.x = upperLeft.x;
208     return this.y = upperLeft.y;
209   }
212 defineShape('Rectangle', {
213   constructor: function(args) {
214     if (args == null) {
215       args = {};
216     }
217     this.x = args.x || 0;
218     this.y = args.y || 0;
219     this.width = args.width || 0;
220     this.height = args.height || 0;
221     this.strokeWidth = args.strokeWidth || 1;
222     this.strokeColor = args.strokeColor || 'black';
223     return this.fillColor = args.fillColor || 'transparent';
224   },
225   getBoundingRect: function() {
226     return {
227       x: this.x - this.strokeWidth / 2,
228       y: this.y - this.strokeWidth / 2,
229       width: this.width + this.strokeWidth,
230       height: this.height + this.strokeWidth
231     };
232   },
233   toJSON: function() {
234     return {
235       x: this.x,
236       y: this.y,
237       width: this.width,
238       height: this.height,
239       strokeWidth: this.strokeWidth,
240       strokeColor: this.strokeColor,
241       fillColor: this.fillColor
242     };
243   },
244   fromJSON: function(data) {
245     return createShape('Rectangle', data);
246   },
247   move: function(moveInfo) {
248     if (moveInfo == null) {
249       moveInfo = {};
250     }
251     this.x = this.x - moveInfo.xDiff;
252     return this.y = this.y - moveInfo.yDiff;
253   },
254   setUpperLeft: function(upperLeft) {
255     if (upperLeft == null) {
256       upperLeft = {};
257     }
258     this.x = upperLeft.x;
259     return this.y = upperLeft.y;
260   }
263 defineShape('Ellipse', {
264   constructor: function(args) {
265     if (args == null) {
266       args = {};
267     }
268     this.x = args.x || 0;
269     this.y = args.y || 0;
270     this.width = args.width || 0;
271     this.height = args.height || 0;
272     this.strokeWidth = args.strokeWidth || 1;
273     this.strokeColor = args.strokeColor || 'black';
274     return this.fillColor = args.fillColor || 'transparent';
275   },
276   getBoundingRect: function() {
277     return {
278       x: this.x - this.strokeWidth / 2,
279       y: this.y - this.strokeWidth / 2,
280       width: this.width + this.strokeWidth,
281       height: this.height + this.strokeWidth
282     };
283   },
284   toJSON: function() {
285     return {
286       x: this.x,
287       y: this.y,
288       width: this.width,
289       height: this.height,
290       strokeWidth: this.strokeWidth,
291       strokeColor: this.strokeColor,
292       fillColor: this.fillColor
293     };
294   },
295   fromJSON: function(data) {
296     return createShape('Ellipse', data);
297   },
298   move: function(moveInfo) {
299     if (moveInfo == null) {
300       moveInfo = {};
301     }
302     this.x = this.x - moveInfo.xDiff;
303     return this.y = this.y - moveInfo.yDiff;
304   },
305   setUpperLeft: function(upperLeft) {
306     if (upperLeft == null) {
307       upperLeft = {};
308     }
309     this.x = upperLeft.x;
310     return this.y = upperLeft.y;
311   }
314 defineShape('Line', {
315   constructor: function(args) {
316     if (args == null) {
317       args = {};
318     }
319     this.x1 = args.x1 || 0;
320     this.y1 = args.y1 || 0;
321     this.x2 = args.x2 || 0;
322     this.y2 = args.y2 || 0;
323     this.strokeWidth = args.strokeWidth || 1;
324     this.color = args.color || 'black';
325     this.capStyle = args.capStyle || 'round';
326     this.endCapShapes = args.endCapShapes || [null, null];
327     return this.dash = args.dash || null;
328   },
329   getBoundingRect: function() {
330     return {
331       x: Math.min(this.x1, this.x2) - this.strokeWidth / 2,
332       y: Math.min(this.y1, this.y2) - this.strokeWidth / 2,
333       width: Math.abs(this.x2 - this.x1) + this.strokeWidth / 2,
334       height: Math.abs(this.y2 - this.y1) + this.strokeWidth / 2
335     };
336   },
337   toJSON: function() {
338     return {
339       x1: this.x1,
340       y1: this.y1,
341       x2: this.x2,
342       y2: this.y2,
343       strokeWidth: this.strokeWidth,
344       color: this.color,
345       capStyle: this.capStyle,
346       dash: this.dash,
347       endCapShapes: this.endCapShapes
348     };
349   },
350   fromJSON: function(data) {
351     return createShape('Line', data);
352   },
353   move: function(moveInfo) {
354     if (moveInfo == null) {
355       moveInfo = {};
356     }
357     this.x1 = this.x1 - moveInfo.xDiff;
358     this.y1 = this.y1 - moveInfo.yDiff;
359     this.x2 = this.x2 - moveInfo.xDiff;
360     return this.y2 = this.y2 - moveInfo.yDiff;
361   },
362   setUpperLeft: function(upperLeft) {
363     var br, xDiff, yDiff;
364     if (upperLeft == null) {
365       upperLeft = {};
366     }
367     br = this.getBoundingRect();
368     xDiff = br.x - upperLeft.x;
369     yDiff = br.y - upperLeft.y;
370     return this.move({
371       xDiff: xDiff,
372       yDiff: yDiff
373     });
374   }
377 _doAllPointsShareStyle = function(points) {
378   var color, len, point, q, size;
379   if (!points.length) {
380     return false;
381   }
382   size = points[0].size;
383   color = points[0].color;
384   for (q = 0, len = points.length; q < len; q++) {
385     point = points[q];
386     if (!(point.size === size && point.color === color)) {
387       console.log(size, color, point.size, point.color);
388     }
389     if (!(point.size === size && point.color === color)) {
390       return false;
391     }
392   }
393   return true;
396 _createLinePathFromData = function(shapeName, data) {
397   var pointData, points, smoothedPoints, x, y;
398   points = null;
399   if (data.points) {
400     points = (function() {
401       var len, q, ref2, results;
402       ref2 = data.points;
403       results = [];
404       for (q = 0, len = ref2.length; q < len; q++) {
405         pointData = ref2[q];
406         results.push(JSONToShape(pointData));
407       }
408       return results;
409     })();
410   } else if (data.pointCoordinatePairs) {
411     points = (function() {
412       var len, q, ref2, ref3, results;
413       ref2 = data.pointCoordinatePairs;
414       results = [];
415       for (q = 0, len = ref2.length; q < len; q++) {
416         ref3 = ref2[q], x = ref3[0], y = ref3[1];
417         results.push(JSONToShape({
418           className: 'Point',
419           data: {
420             x: x,
421             y: y,
422             size: data.pointSize,
423             color: data.pointColor,
424             smooth: data.smooth
425           }
426         }));
427       }
428       return results;
429     })();
430   }
431   smoothedPoints = null;
432   if (data.smoothedPointCoordinatePairs) {
433     smoothedPoints = (function() {
434       var len, q, ref2, ref3, results;
435       ref2 = data.smoothedPointCoordinatePairs;
436       results = [];
437       for (q = 0, len = ref2.length; q < len; q++) {
438         ref3 = ref2[q], x = ref3[0], y = ref3[1];
439         results.push(JSONToShape({
440           className: 'Point',
441           data: {
442             x: x,
443             y: y,
444             size: data.pointSize,
445             color: data.pointColor,
446             smooth: data.smooth
447           }
448         }));
449       }
450       return results;
451     })();
452   }
453   if (!points[0]) {
454     return null;
455   }
456   return createShape(shapeName, {
457     points: points,
458     smoothedPoints: smoothedPoints,
459     order: data.order,
460     tailSize: data.tailSize,
461     smooth: data.smooth
462   });
465 linePathFuncs = {
466   constructor: function(args) {
467     var len, point, points, q, results;
468     if (args == null) {
469       args = {};
470     }
471     points = args.points || [];
472     this.order = args.order || 3;
473     this.tailSize = args.tailSize || 3;
474     this.smooth = 'smooth' in args ? args.smooth : true;
475     this.segmentSize = Math.pow(2, this.order);
476     this.sampleSize = this.tailSize + 1;
477     if (args.smoothedPoints) {
478       this.points = args.points;
479       return this.smoothedPoints = args.smoothedPoints;
480     } else {
481       this.points = [];
482       results = [];
483       for (q = 0, len = points.length; q < len; q++) {
484         point = points[q];
485         results.push(this.addPoint(point));
486       }
487       return results;
488     }
489   },
490   getBoundingRect: function() {
491     return util.getBoundingRect(this.points.map(function(p) {
492       return {
493         x: p.x - p.size / 2,
494         y: p.y - p.size / 2,
495         width: p.size,
496         height: p.size
497       };
498     }));
499   },
500   toJSON: function() {
501     var p, point;
502     if (_doAllPointsShareStyle(this.points)) {
503       return {
504         order: this.order,
505         tailSize: this.tailSize,
506         smooth: this.smooth,
507         pointCoordinatePairs: (function() {
508           var len, q, ref2, results;
509           ref2 = this.points;
510           results = [];
511           for (q = 0, len = ref2.length; q < len; q++) {
512             point = ref2[q];
513             results.push([point.x, point.y]);
514           }
515           return results;
516         }).call(this),
517         smoothedPointCoordinatePairs: (function() {
518           var len, q, ref2, results;
519           ref2 = this.smoothedPoints;
520           results = [];
521           for (q = 0, len = ref2.length; q < len; q++) {
522             point = ref2[q];
523             results.push([point.x, point.y]);
524           }
525           return results;
526         }).call(this),
527         pointSize: this.points[0].size,
528         pointColor: this.points[0].color
529       };
530     } else {
531       return {
532         order: this.order,
533         tailSize: this.tailSize,
534         smooth: this.smooth,
535         points: (function() {
536           var len, q, ref2, results;
537           ref2 = this.points;
538           results = [];
539           for (q = 0, len = ref2.length; q < len; q++) {
540             p = ref2[q];
541             results.push(shapeToJSON(p));
542           }
543           return results;
544         }).call(this)
545       };
546     }
547   },
548   fromJSON: function(data) {
549     return _createLinePathFromData('LinePath', data);
550   },
551   addPoint: function(point) {
552     this.points.push(point);
553     if (!this.smooth) {
554       this.smoothedPoints = this.points;
555       return;
556     }
557     if (!this.smoothedPoints || this.points.length < this.sampleSize) {
558       return this.smoothedPoints = bspline(this.points, this.order);
559     } else {
560       this.tail = util.last(bspline(util.last(this.points, this.sampleSize), this.order), this.segmentSize * this.tailSize);
561       return this.smoothedPoints = this.smoothedPoints.slice(0, this.smoothedPoints.length - this.segmentSize * (this.tailSize - 1)).concat(this.tail);
562     }
563   },
564   move: function(moveInfo) {
565     var len, pt, pts, q;
566     if (moveInfo == null) {
567       moveInfo = {};
568     }
569     if (!this.smooth) {
570       pts = this.points;
571     } else {
572       pts = this.smoothedPoints;
573     }
574     for (q = 0, len = pts.length; q < len; q++) {
575       pt = pts[q];
576       pt.move(moveInfo);
577     }
578     return this.points = this.smoothedPoints;
579   },
580   setUpperLeft: function(upperLeft) {
581     var br, xDiff, yDiff;
582     if (upperLeft == null) {
583       upperLeft = {};
584     }
585     br = this.getBoundingRect();
586     xDiff = br.x - upperLeft.x;
587     yDiff = br.y - upperLeft.y;
588     return this.move({
589       xDiff: xDiff,
590       yDiff: yDiff
591     });
592   }
595 LinePath = defineShape('LinePath', linePathFuncs);
597 defineShape('ErasedLinePath', {
598   constructor: linePathFuncs.constructor,
599   toJSON: linePathFuncs.toJSON,
600   addPoint: linePathFuncs.addPoint,
601   getBoundingRect: linePathFuncs.getBoundingRect,
602   fromJSON: function(data) {
603     return _createLinePathFromData('ErasedLinePath', data);
604   }
607 defineShape('Point', {
608   constructor: function(args) {
609     if (args == null) {
610       args = {};
611     }
612     this.x = args.x || 0;
613     this.y = args.y || 0;
614     this.size = args.size || 0;
615     return this.color = args.color || '';
616   },
617   getBoundingRect: function() {
618     return {
619       x: this.x - this.size / 2,
620       y: this.y - this.size / 2,
621       width: this.size,
622       height: this.size
623     };
624   },
625   toJSON: function() {
626     return {
627       x: this.x,
628       y: this.y,
629       size: this.size,
630       color: this.color
631     };
632   },
633   fromJSON: function(data) {
634     return createShape('Point', data);
635   },
636   move: function(moveInfo) {
637     if (moveInfo == null) {
638       moveInfo = {};
639     }
640     this.x = this.x - moveInfo.xDiff;
641     return this.y = this.y - moveInfo.yDiff;
642   },
643   setUpperLeft: function(upperLeft) {
644     if (upperLeft == null) {
645       upperLeft = {};
646     }
647     this.x = upperLeft.x;
648     return this.y = upperLeft.y;
649   }
652 defineShape('Polygon', {
653   constructor: function(args) {
654     var len, point, q, ref2, results;
655     if (args == null) {
656       args = {};
657     }
658     this.points = args.points;
659     this.fillColor = args.fillColor || 'white';
660     this.strokeColor = args.strokeColor || 'black';
661     this.strokeWidth = args.strokeWidth;
662     this.dash = args.dash || null;
663     if (args.isClosed == null) {
664       args.isClosed = true;
665     }
666     this.isClosed = args.isClosed;
667     ref2 = this.points;
668     results = [];
669     for (q = 0, len = ref2.length; q < len; q++) {
670       point = ref2[q];
671       point.color = this.strokeColor;
672       results.push(point.size = this.strokeWidth);
673     }
674     return results;
675   },
676   addPoint: function(x, y) {
677     return this.points.push(LC.createShape('Point', {
678       x: x,
679       y: y
680     }));
681   },
682   getBoundingRect: function() {
683     return util.getBoundingRect(this.points.map(function(p) {
684       return p.getBoundingRect();
685     }));
686   },
687   toJSON: function() {
688     return {
689       strokeWidth: this.strokeWidth,
690       fillColor: this.fillColor,
691       strokeColor: this.strokeColor,
692       dash: this.dash,
693       isClosed: this.isClosed,
694       pointCoordinatePairs: this.points.map(function(p) {
695         return [p.x, p.y];
696       })
697     };
698   },
699   fromJSON: function(data) {
700     data.points = data.pointCoordinatePairs.map(function(arg) {
701       var x, y;
702       x = arg[0], y = arg[1];
703       return createShape('Point', {
704         x: x,
705         y: y,
706         size: data.strokeWidth,
707         color: data.strokeColor
708       });
709     });
710     return createShape('Polygon', data);
711   },
712   move: function(moveInfo) {
713     var len, pt, q, ref2, results;
714     if (moveInfo == null) {
715       moveInfo = {};
716     }
717     ref2 = this.points;
718     results = [];
719     for (q = 0, len = ref2.length; q < len; q++) {
720       pt = ref2[q];
721       results.push(pt.move(moveInfo));
722     }
723     return results;
724   },
725   setUpperLeft: function(upperLeft) {
726     var br, xDiff, yDiff;
727     if (upperLeft == null) {
728       upperLeft = {};
729     }
730     br = this.getBoundingRect();
731     xDiff = br.x - upperLeft.x;
732     yDiff = br.y - upperLeft.y;
733     return this.move({
734       xDiff: xDiff,
735       yDiff: yDiff
736     });
737   }
740 defineShape('Text', {
741   constructor: function(args) {
742     if (args == null) {
743       args = {};
744     }
745     this.x = args.x || 0;
746     this.y = args.y || 0;
747     this.v = args.v || 0;
748     this.text = args.text || '';
749     this.color = args.color || 'black';
750     this.font = args.font || '18px sans-serif';
751     this.forcedWidth = args.forcedWidth || null;
752     return this.forcedHeight = args.forcedHeight || null;
753   },
754   _makeRenderer: function(ctx) {
755     ctx.lineHeight = 1.2;
756     this.renderer = new TextRenderer(ctx, this.text, this.font, this.forcedWidth, this.forcedHeight);
757     if (this.v < 1) {
758       console.log('repairing baseline');
759       this.v = 1;
760       this.x -= this.renderer.metrics.bounds.minx;
761       return this.y -= this.renderer.metrics.leading - this.renderer.metrics.descent;
762     }
763   },
764   setText: function(text) {
765     this.text = text;
766     return this.renderer = null;
767   },
768   setFont: function(font) {
769     this.font = font;
770     return this.renderer = null;
771   },
772   setPosition: function(x, y) {
773     this.x = x;
774     return this.y = y;
775   },
776   setSize: function(forcedWidth, forcedHeight) {
777     this.forcedWidth = Math.max(forcedWidth, 0);
778     this.forcedHeight = Math.max(forcedHeight, 0);
779     return this.renderer = null;
780   },
781   enforceMaxBoundingRect: function(lc) {
782     var br, dx, lcBoundingRect;
783     br = this.getBoundingRect(lc.ctx);
784     lcBoundingRect = {
785       x: -lc.position.x / lc.scale,
786       y: -lc.position.y / lc.scale,
787       width: lc.canvas.width / lc.scale,
788       height: lc.canvas.height / lc.scale
789     };
790     if (br.x + br.width > lcBoundingRect.x + lcBoundingRect.width) {
791       dx = br.x - lcBoundingRect.x;
792       this.forcedWidth = lcBoundingRect.width - dx - 10;
793       return this.renderer = null;
794     }
795   },
796   getBoundingRect: function(ctx, isEditing) {
797     if (isEditing == null) {
798       isEditing = false;
799     }
800     if (!this.renderer) {
801       if (ctx) {
802         this._makeRenderer(ctx);
803       } else {
804         throw "Must pass ctx if text hasn't been rendered yet";
805       }
806     }
807     return {
808       x: Math.floor(this.x),
809       y: Math.floor(this.y),
810       width: Math.ceil(this.renderer.getWidth(true)),
811       height: Math.ceil(this.renderer.getHeight())
812     };
813   },
814   toJSON: function() {
815     return {
816       x: this.x,
817       y: this.y,
818       text: this.text,
819       color: this.color,
820       font: this.font,
821       forcedWidth: this.forcedWidth,
822       forcedHeight: this.forcedHeight,
823       v: this.v
824     };
825   },
826   fromJSON: function(data) {
827     return createShape('Text', data);
828   },
829   move: function(moveInfo) {
830     if (moveInfo == null) {
831       moveInfo = {};
832     }
833     this.x = this.x - moveInfo.xDiff;
834     return this.y = this.y - moveInfo.yDiff;
835   },
836   setUpperLeft: function(upperLeft) {
837     if (upperLeft == null) {
838       upperLeft = {};
839     }
840     this.x = upperLeft.x;
841     return this.y = upperLeft.y;
842   }
845 defineShape('SelectionBox', {
846   constructor: function(args) {
847     if (args == null) {
848       args = {};
849     }
850     this.shape = args.shape;
851     if (args.handleSize != null) {
852       this.handleSize = args.handleSize;
853     } else {
854       this.handleSize = 10;
855     }
856     this.margin = 4;
857     this.backgroundColor = args.backgroundColor || null;
858     return this._br = this.shape.getBoundingRect(args.ctx);
859   },
860   toJSON: function() {
861     return {
862       shape: shapeToJSON(this.shape),
863       backgroundColor: this.backgroundColor
864     };
865   },
866   fromJSON: function(arg) {
867     var backgroundColor, handleSize, margin, shape;
868     shape = arg.shape, handleSize = arg.handleSize, margin = arg.margin, backgroundColor = arg.backgroundColor;
869     return createShape('SelectionBox', {
870       shape: JSONToShape(shape),
871       backgroundColor: backgroundColor
872     });
873   },
874   getTopLeftHandleRect: function() {
875     return {
876       x: this._br.x - this.handleSize - this.margin,
877       y: this._br.y - this.handleSize - this.margin,
878       width: this.handleSize,
879       height: this.handleSize
880     };
881   },
882   getBottomLeftHandleRect: function() {
883     return {
884       x: this._br.x - this.handleSize - this.margin,
885       y: this._br.y + this._br.height + this.margin,
886       width: this.handleSize,
887       height: this.handleSize
888     };
889   },
890   getTopRightHandleRect: function() {
891     return {
892       x: this._br.x + this._br.width + this.margin,
893       y: this._br.y - this.handleSize - this.margin,
894       width: this.handleSize,
895       height: this.handleSize
896     };
897   },
898   getBottomRightHandleRect: function() {
899     return {
900       x: this._br.x + this._br.width + this.margin,
901       y: this._br.y + this._br.height + this.margin,
902       width: this.handleSize,
903       height: this.handleSize
904     };
905   },
906   getBoundingRect: function() {
907     return {
908       x: this._br.x - this.margin,
909       y: this._br.y - this.margin,
910       width: this._br.width + this.margin * 2,
911       height: this._br.height + this.margin * 2
912     };
913   }
916 module.exports = {
917   defineShape: defineShape,
918   createShape: createShape,
919   JSONToShape: JSONToShape,
920   shapeToJSON: shapeToJSON