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;
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);
21 Shape.prototype.className = name;
22 Shape.fromJSON = props.fromJSON;
24 legacyDrawFunc = props.draw;
25 legacyDrawLatestFunc = props.draw || function(ctx, bufferCtx, retryCallback) {
26 return this.draw(ctx, bufferCtx, retryCallback);
28 drawFunc = function(ctx, shape, retryCallback) {
29 return legacyDrawFunc.call(shape, ctx, retryCallback);
31 drawLatestFunc = function(ctx, bufferCtx, shape, retryCallback) {
32 return legacyDrawLatestFunc.call(shape, ctx, bufferCtx, retryCallback);
35 if (props.drawLatest) {
36 delete props.drawLatest;
38 defineCanvasRenderer(name, drawFunc, drawLatestFunc);
41 legacySVGFunc = props.toSVG;
42 svgFunc = function(shape) {
43 return legacySVGFunc.call(shape);
46 defineSVGRenderer(name, svgFunc);
48 Shape.prototype.draw = function(ctx, retryCallback) {
49 return renderShapeToContext(ctx, this, {
50 retryCallback: retryCallback
53 Shape.prototype.drawLatest = function(ctx, bufferCtx, retryCallback) {
54 return renderShapeToContext(ctx, this, {
55 retryCallback: retryCallback,
57 shouldOnlyDrawLatest: true
60 Shape.prototype.toSVG = function() {
61 return renderShapeToSVG(this);
64 if (k !== 'fromJSON') {
65 Shape.prototype[k] = props[k];
72 createShape = function(name, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) {
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();
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);
90 console.log('Unreadable shape:', className, data);
94 console.log("Unknown shape:", className, data);
99 shapeToJSON = function(shape) {
101 className: shape.className,
102 data: shape.toJSON(),
107 bspline = function(points, order) {
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));
119 for (q = 0, len = points.length; q < len; q++) {
121 refined[index * 2] = point;
122 if (points[index + 1]) {
123 refined[index * 2 + 1] = _mid(point, points[index + 1]);
130 _dual = function(points) {
131 var dualed, index, len, point, q;
134 for (q = 0, len = points.length; q < len; q++) {
136 if (points[index + 1]) {
137 dualed[index] = _mid(point, points[index + 1]);
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),
153 defineShape('Image', {
154 constructor: function(args) {
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;
163 getBoundingRect: function() {
167 width: this.image.width * this.scale,
168 height: this.image.height * this.scale
175 imageSrc: this.image.src,
176 imageObject: this.image,
180 fromJSON: function(data) {
183 if ((ref2 = data.imageObject) != null ? ref2.width : void 0) {
184 img = data.imageObject;
187 img.src = data.imageSrc;
189 return createShape('Image', {
196 move: function(moveInfo) {
197 if (moveInfo == null) {
200 this.x = this.x - moveInfo.xDiff;
201 return this.y = this.y - moveInfo.yDiff;
203 setUpperLeft: function(upperLeft) {
204 if (upperLeft == null) {
207 this.x = upperLeft.x;
208 return this.y = upperLeft.y;
212 defineShape('Rectangle', {
213 constructor: function(args) {
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';
225 getBoundingRect: function() {
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
239 strokeWidth: this.strokeWidth,
240 strokeColor: this.strokeColor,
241 fillColor: this.fillColor
244 fromJSON: function(data) {
245 return createShape('Rectangle', data);
247 move: function(moveInfo) {
248 if (moveInfo == null) {
251 this.x = this.x - moveInfo.xDiff;
252 return this.y = this.y - moveInfo.yDiff;
254 setUpperLeft: function(upperLeft) {
255 if (upperLeft == null) {
258 this.x = upperLeft.x;
259 return this.y = upperLeft.y;
263 defineShape('Ellipse', {
264 constructor: function(args) {
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';
276 getBoundingRect: function() {
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
290 strokeWidth: this.strokeWidth,
291 strokeColor: this.strokeColor,
292 fillColor: this.fillColor
295 fromJSON: function(data) {
296 return createShape('Ellipse', data);
298 move: function(moveInfo) {
299 if (moveInfo == null) {
302 this.x = this.x - moveInfo.xDiff;
303 return this.y = this.y - moveInfo.yDiff;
305 setUpperLeft: function(upperLeft) {
306 if (upperLeft == null) {
309 this.x = upperLeft.x;
310 return this.y = upperLeft.y;
314 defineShape('Line', {
315 constructor: function(args) {
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;
329 getBoundingRect: function() {
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
343 strokeWidth: this.strokeWidth,
345 capStyle: this.capStyle,
347 endCapShapes: this.endCapShapes
350 fromJSON: function(data) {
351 return createShape('Line', data);
353 move: function(moveInfo) {
354 if (moveInfo == null) {
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;
362 setUpperLeft: function(upperLeft) {
363 var br, xDiff, yDiff;
364 if (upperLeft == null) {
367 br = this.getBoundingRect();
368 xDiff = br.x - upperLeft.x;
369 yDiff = br.y - upperLeft.y;
377 _doAllPointsShareStyle = function(points) {
378 var color, len, point, q, size;
379 if (!points.length) {
382 size = points[0].size;
383 color = points[0].color;
384 for (q = 0, len = points.length; q < len; q++) {
386 if (!(point.size === size && point.color === color)) {
387 console.log(size, color, point.size, point.color);
389 if (!(point.size === size && point.color === color)) {
396 _createLinePathFromData = function(shapeName, data) {
397 var pointData, points, smoothedPoints, x, y;
400 points = (function() {
401 var len, q, ref2, results;
404 for (q = 0, len = ref2.length; q < len; q++) {
406 results.push(JSONToShape(pointData));
410 } else if (data.pointCoordinatePairs) {
411 points = (function() {
412 var len, q, ref2, ref3, results;
413 ref2 = data.pointCoordinatePairs;
415 for (q = 0, len = ref2.length; q < len; q++) {
416 ref3 = ref2[q], x = ref3[0], y = ref3[1];
417 results.push(JSONToShape({
422 size: data.pointSize,
423 color: data.pointColor,
431 smoothedPoints = null;
432 if (data.smoothedPointCoordinatePairs) {
433 smoothedPoints = (function() {
434 var len, q, ref2, ref3, results;
435 ref2 = data.smoothedPointCoordinatePairs;
437 for (q = 0, len = ref2.length; q < len; q++) {
438 ref3 = ref2[q], x = ref3[0], y = ref3[1];
439 results.push(JSONToShape({
444 size: data.pointSize,
445 color: data.pointColor,
456 return createShape(shapeName, {
458 smoothedPoints: smoothedPoints,
460 tailSize: data.tailSize,
466 constructor: function(args) {
467 var len, point, points, q, results;
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;
483 for (q = 0, len = points.length; q < len; q++) {
485 results.push(this.addPoint(point));
490 getBoundingRect: function() {
491 return util.getBoundingRect(this.points.map(function(p) {
502 if (_doAllPointsShareStyle(this.points)) {
505 tailSize: this.tailSize,
507 pointCoordinatePairs: (function() {
508 var len, q, ref2, results;
511 for (q = 0, len = ref2.length; q < len; q++) {
513 results.push([point.x, point.y]);
517 smoothedPointCoordinatePairs: (function() {
518 var len, q, ref2, results;
519 ref2 = this.smoothedPoints;
521 for (q = 0, len = ref2.length; q < len; q++) {
523 results.push([point.x, point.y]);
527 pointSize: this.points[0].size,
528 pointColor: this.points[0].color
533 tailSize: this.tailSize,
535 points: (function() {
536 var len, q, ref2, results;
539 for (q = 0, len = ref2.length; q < len; q++) {
541 results.push(shapeToJSON(p));
548 fromJSON: function(data) {
549 return _createLinePathFromData('LinePath', data);
551 addPoint: function(point) {
552 this.points.push(point);
554 this.smoothedPoints = this.points;
557 if (!this.smoothedPoints || this.points.length < this.sampleSize) {
558 return this.smoothedPoints = bspline(this.points, this.order);
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);
564 move: function(moveInfo) {
566 if (moveInfo == null) {
572 pts = this.smoothedPoints;
574 for (q = 0, len = pts.length; q < len; q++) {
578 return this.points = this.smoothedPoints;
580 setUpperLeft: function(upperLeft) {
581 var br, xDiff, yDiff;
582 if (upperLeft == null) {
585 br = this.getBoundingRect();
586 xDiff = br.x - upperLeft.x;
587 yDiff = br.y - upperLeft.y;
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);
607 defineShape('Point', {
608 constructor: function(args) {
612 this.x = args.x || 0;
613 this.y = args.y || 0;
614 this.size = args.size || 0;
615 return this.color = args.color || '';
617 getBoundingRect: function() {
619 x: this.x - this.size / 2,
620 y: this.y - this.size / 2,
633 fromJSON: function(data) {
634 return createShape('Point', data);
636 move: function(moveInfo) {
637 if (moveInfo == null) {
640 this.x = this.x - moveInfo.xDiff;
641 return this.y = this.y - moveInfo.yDiff;
643 setUpperLeft: function(upperLeft) {
644 if (upperLeft == null) {
647 this.x = upperLeft.x;
648 return this.y = upperLeft.y;
652 defineShape('Polygon', {
653 constructor: function(args) {
654 var len, point, q, ref2, results;
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;
666 this.isClosed = args.isClosed;
669 for (q = 0, len = ref2.length; q < len; q++) {
671 point.color = this.strokeColor;
672 results.push(point.size = this.strokeWidth);
676 addPoint: function(x, y) {
677 return this.points.push(LC.createShape('Point', {
682 getBoundingRect: function() {
683 return util.getBoundingRect(this.points.map(function(p) {
684 return p.getBoundingRect();
689 strokeWidth: this.strokeWidth,
690 fillColor: this.fillColor,
691 strokeColor: this.strokeColor,
693 isClosed: this.isClosed,
694 pointCoordinatePairs: this.points.map(function(p) {
699 fromJSON: function(data) {
700 data.points = data.pointCoordinatePairs.map(function(arg) {
702 x = arg[0], y = arg[1];
703 return createShape('Point', {
706 size: data.strokeWidth,
707 color: data.strokeColor
710 return createShape('Polygon', data);
712 move: function(moveInfo) {
713 var len, pt, q, ref2, results;
714 if (moveInfo == null) {
719 for (q = 0, len = ref2.length; q < len; q++) {
721 results.push(pt.move(moveInfo));
725 setUpperLeft: function(upperLeft) {
726 var br, xDiff, yDiff;
727 if (upperLeft == null) {
730 br = this.getBoundingRect();
731 xDiff = br.x - upperLeft.x;
732 yDiff = br.y - upperLeft.y;
740 defineShape('Text', {
741 constructor: function(args) {
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;
754 _makeRenderer: function(ctx) {
755 ctx.lineHeight = 1.2;
756 this.renderer = new TextRenderer(ctx, this.text, this.font, this.forcedWidth, this.forcedHeight);
758 console.log('repairing baseline');
760 this.x -= this.renderer.metrics.bounds.minx;
761 return this.y -= this.renderer.metrics.leading - this.renderer.metrics.descent;
764 setText: function(text) {
766 return this.renderer = null;
768 setFont: function(font) {
770 return this.renderer = null;
772 setPosition: function(x, y) {
776 setSize: function(forcedWidth, forcedHeight) {
777 this.forcedWidth = Math.max(forcedWidth, 0);
778 this.forcedHeight = Math.max(forcedHeight, 0);
779 return this.renderer = null;
781 enforceMaxBoundingRect: function(lc) {
782 var br, dx, lcBoundingRect;
783 br = this.getBoundingRect(lc.ctx);
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
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;
796 getBoundingRect: function(ctx, isEditing) {
797 if (isEditing == null) {
800 if (!this.renderer) {
802 this._makeRenderer(ctx);
804 throw "Must pass ctx if text hasn't been rendered yet";
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())
821 forcedWidth: this.forcedWidth,
822 forcedHeight: this.forcedHeight,
826 fromJSON: function(data) {
827 return createShape('Text', data);
829 move: function(moveInfo) {
830 if (moveInfo == null) {
833 this.x = this.x - moveInfo.xDiff;
834 return this.y = this.y - moveInfo.yDiff;
836 setUpperLeft: function(upperLeft) {
837 if (upperLeft == null) {
840 this.x = upperLeft.x;
841 return this.y = upperLeft.y;
845 defineShape('SelectionBox', {
846 constructor: function(args) {
850 this.shape = args.shape;
851 if (args.handleSize != null) {
852 this.handleSize = args.handleSize;
854 this.handleSize = 10;
857 this.backgroundColor = args.backgroundColor || null;
858 return this._br = this.shape.getBoundingRect(args.ctx);
862 shape: shapeToJSON(this.shape),
863 backgroundColor: this.backgroundColor
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
874 getTopLeftHandleRect: function() {
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
882 getBottomLeftHandleRect: function() {
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
890 getTopRightHandleRect: function() {
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
898 getBottomRightHandleRect: function() {
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
906 getBoundingRect: function() {
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
917 defineShape: defineShape,
918 createShape: createShape,
919 JSONToShape: JSONToShape,
920 shapeToJSON: shapeToJSON