Smooth live free drawing on contextTop canvas

This commit is contained in:
coulix 2012-11-24 20:12:33 +01:00 committed by Greg Tappero
parent d0952172a3
commit 61db93d5ec
7 changed files with 604 additions and 188 deletions

View file

@ -112,6 +112,8 @@ var filesToInclude = [
'src/color.class.js',
'src/static_canvas.class.js',
ifSpecifiedInclude('interaction', 'src/freedrawing.class.js'),
ifSpecifiedInclude('interaction', 'src/canvas.class.js'),
'src/canvas.animation.js',
@ -162,4 +164,4 @@ appendFileContents(filesToInclude, function() {
});
});
});
});
});

393
dist/all.js vendored
View file

@ -4536,6 +4536,13 @@ fabric.util.string = {
return Math.sqrt(dx * dx + dy * dy);
},
/**
* Return the point between A (x,y) and B (x,y)
*/
midPointFrom: function (that) {
return new Point(this.x + (that.x - this.x)/2, this.y + (that.y - this.y)/2);
},
min: function (that) {
return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y));
},
@ -4569,6 +4576,7 @@ fabric.util.string = {
};
})(typeof exports !== 'undefined' ? exports : this);
(function(global) {
"use strict";
@ -6191,6 +6199,282 @@ fabric.util.string = {
fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject;
})();
(function(global) {
"use strict";
var fabric = global.fabric || (global.fabric = { });
var utilMin = fabric.util.array.min,
utilMax = fabric.util.array.max;
if (fabric.FreeDrawing) {
fabric.warn('fabric.FreeDrawin is already defined');
return;
}
fabric.FreeDrawing = FreeDrawing;
function FreeDrawing(fabricCanvas) {
this.init(fabricCanvas);
}
FreeDrawing.prototype =  {
constructor: FreeDrawing,
/**
* Free Drawer handles scribbling on a fabricCanvas.
* It converts the hand writting to a SVG Path and adds this
* path to the canvas.
*
* @metod init
* @param fabricCavnas {FabricCanvas}
*
*/
init: function(fabricCanvas) {
this.canvas = fabricCanvas;
this._points = [];
this._color = this.canvas.freeDrawingColor;
this._strokeWidth = this.canvas.freeDrawingLineWidth;
},
/**
* Set path color
* @method setColor
* @param color {String/rgb/rgba}
*
*/
setColor: function(color) {
this._color = color;
},
/**
* Set path thichness (strokeWidth)
* @method setThickness
* @param thickness {int}
*
*/
setThickness: function(thickness) {
this._strokeWidth = thickness;
},
/**
* @private
* @method _addPoint
*
*/
_addPoint: function(point) {
this._points.push(point);
},
/**
* Clear points array and set contextTop canvas
* style.
*
* @private
* @method _reset
*
*/
_reset: function() {
this._points.length = 0;
var ctx = this.canvas.contextTop;
// set freehanddrawing line canvas parameters
ctx.strokeStyle = this._color;
ctx.lineWidth = this._strokeWidth;
ctx.lineCap = ctx.lineJoin = 'round';
},
/**
* @method _prepareForDrawing
*/
_prepareForDrawing: function(pointer) {
this.canvas._isCurrentlyDrawing = true;
this.canvas.discardActiveObject().renderAll();
var p = new fabric.Point(pointer.x, pointer.y);
this._reset();
this._addPoint(p);
this.canvas.contextTop.moveTo(p.x, p.y);
},
/**
* @private
* @method _captureDrawingPath
*
* @param point {pointer} (fabric.util.pointer) actual mouse position
* related to the canvas.
*/
_captureDrawingPath: function(pointer) {
var MIN_POINT_DISTANCE = 2;
var pointerPoint = new fabric.Point(pointer.x, pointer.y);
// Only push point if min_point_distance is reached
var lastPoint = this._points[this._points.length -1];
if (pointerPoint.distanceFrom(lastPoint) > MIN_POINT_DISTANCE)
this._addPoint(pointerPoint);
if (this._points.length < 2)
this._addPoint(pointerPoint);
},
/**
* Draw a smooth path on the topCanvas using
* quadraticCurveTo.
*
* @private
* @method _render
*
*/
_render: function() {
var ctx = this.canvas.contextTop;
ctx.beginPath();
var p1 = this._points[0];
var p2 = this._points[1];
for (var i = 1, len = this._points.length; i < len; i++) {
// we pick the point between pi+1 & pi+2 as the
// end point and p1 as our control point.
var midPoint = p1.midPointFrom(p2);
ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y);
var p1 = this._points[i];
var p2 = this._points[i+1];
}
// Draw last line as a straight line while
// we wait for the next point to be able to calculate
// the bezier control point
ctx.lineTo(p1.x, p1.y);
ctx.stroke();
},
/**
* Return an SVG path based on our
* captured points and their boundinb box.
*
* @private
* @method _getSVGPathData
*
*/
_getSVGPathData: function() {
this.box = this.getPathBoundingBox(this._points);
return this.convertPointsToSVGPath(this._points, this.box.minx, this.box.maxx, this.box.miny, this.box.maxy);
},
/**
* @method getPathBoundingBox
* @param points {Array of points}
*/
getPathBoundingBox: function(points) {
var xBounds = [],
yBounds = [],
p1 = points[0],
p2 = points[1],
startPoint = p1;
for (var i = 1, len = points.length; i < len; i++) {
var midPoint = p1.midPointFrom(p2);
// with startPoint, p1 as control point, midpoint as end point
xBounds.push(startPoint.x);
xBounds.push(midPoint.x);
yBounds.push(startPoint.y);
yBounds.push(midPoint.y);
p1 = points[i];
p2 = points[i+1];
startPoint = midPoint;
} // end for
xBounds.push(p1.x);
yBounds.push(p1.y);
return {
minx: utilMin(xBounds),
miny: utilMin(yBounds),
maxx: utilMax(xBounds),
maxy: utilMax(yBounds)
};
},
/**
* @method convertPointsToSVGPath
* @param points {Array of points}
*/
convertPointsToSVGPath: function(points, minX, maxX, minY, maxY) {
var path = [];
var p1 = new fabric.Point(points[0].x - minX, points[0].y - minY);
var p2 = new fabric.Point(points[1].x - minX, points[1].y - minY);
path.push('M ', points[0].x - minX, ' ', points[0].y - minY, ' ');
for (var i = 1, len = points.length; i < len; i++) {
var midPoint = p1.midPointFrom(p2);
// p1 is our bezier control point
// midpoint is our endpoint
// start point is p(i-1) value.
path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' ');
p1 = new fabric.Point(points[i].x - minX, points[i].y - minY);
if ((i+1) < points.length)
p2 = new fabric.Point(points[i+1].x - minX, points[i+1].y - minY);
}
path.push('L ', p1.x, ' ', p1.y, ' ');
return path;
},
/**
* On mouseup after drawing the path on contextTop canvas
* we use the points captured to create an new fabric path object
* and add it to the fabric canvas.
*
* @method _finalizeAndAddPath
*
*/
_finalizeAndAddPath: function() {
var ctx = this.canvas.contextTop;
ctx.closePath();
var path = this._getSVGPathData();
path = path.join('');
if (path === "M 0 0 Q 0 0 0 0 L 0 0") {
// do not create 0 width/height paths, as they are
// rendered inconsistently across browsers
// Firefox 4, for example, renders a dot,
// whereas Chrome 10 renders nothing
fabricCanvas.renderAll();
return;
}
var p = new fabric.Path(path);
p.fill = null;
p.stroke = this._color;
p.strokeWidth = this._strokeWidth;
this.canvas.add(p);
// set path origin coordinates based on our bouding box
var originLeft = this.box.minx + (this.box.maxx - this.box.minx) /2;
var originTop = this.box.miny + (this.box.maxy - this.box.miny) /2;
this.canvas.contextTop.arc(originLeft, originTop, 3);
p.set("left", originLeft);
p.set("top", originTop)
// does not change position
p.setCoords();
this.canvas.renderAll();
// fire event 'path' created
this.canvas.fire('path:created', { path: p });
}
};
})(typeof exports !== 'undefined' ? exports : this);
(function() {
var extend = fabric.util.object.extend,
@ -6364,8 +6648,7 @@ fabric.util.string = {
_initInteractive: function() {
this._currentTransform = null;
this._groupSelector = null;
this._freeDrawingXPoints = [ ];
this._freeDrawingYPoints = [ ];
this.freeDrawing = new fabric.FreeDrawing(this);
this._initWrapperElement();
this._createUpperCanvas();
this._initEvents();
@ -6442,7 +6725,8 @@ fabric.util.string = {
var target;
if (this.isDrawingMode && this._isCurrentlyDrawing) {
this._finalizeDrawingPath();
this._isCurrentlyDrawing = false;
this.freeDrawing._finalizeAndAddPath();
this.fire('mouse:up', { e: e });
return;
}
@ -6517,10 +6801,13 @@ fabric.util.string = {
if (!isLeftClick && !fabric.isTouchSupported) return;
if (this.isDrawingMode) {
this._prepareForDrawing(e);
var pointer = this.getPointer(e);
this.freeDrawing._prepareForDrawing(pointer);
// capture coordinates immediately;
// this allows to draw dots (when movement never occurs)
this.freeDrawing._captureDrawingPath(pointer);
// capture coordinates immediately; this allows to draw dots (when movement never occurs)
this._captureDrawingPath(e);
this.fire('mouse:down', { e: e });
return;
}
@ -6590,7 +6877,13 @@ fabric.util.string = {
if (this.isDrawingMode) {
if (this._isCurrentlyDrawing) {
this._captureDrawingPath(e);
var pointer = this.getPointer(e);
this.freeDrawing._captureDrawingPath(pointer);
// redraw curve
// clear top canvas
this.clearContext(this.contextTop);
this.freeDrawing._render(this.contextTop);
}
this.fire('mouse:move', { e: e });
return;
@ -6902,91 +7195,6 @@ fabric.util.string = {
}
},
/**
* @private
* @method _prepareForDrawing
*/
_prepareForDrawing: function(e) {
this._isCurrentlyDrawing = true;
this.discardActiveObject().renderAll();
var pointer = this.getPointer(e);
this._freeDrawingXPoints.length = this._freeDrawingYPoints.length = 0;
this._freeDrawingXPoints.push(pointer.x);
this._freeDrawingYPoints.push(pointer.y);
this.contextTop.beginPath();
this.contextTop.moveTo(pointer.x, pointer.y);
this.contextTop.strokeStyle = this.freeDrawingColor;
this.contextTop.lineWidth = this.freeDrawingLineWidth;
this.contextTop.lineCap = this.contextTop.lineJoin = 'round';
},
/**
* @private
* @method _captureDrawingPath
*/
_captureDrawingPath: function(e) {
var pointer = this.getPointer(e);
this._freeDrawingXPoints.push(pointer.x);
this._freeDrawingYPoints.push(pointer.y);
this.contextTop.lineTo(pointer.x, pointer.y);
this.contextTop.stroke();
},
/**
* @private
* @method _finalizeDrawingPath
*/
_finalizeDrawingPath: function() {
this.contextTop.closePath();
this._isCurrentlyDrawing = false;
var minX = utilMin(this._freeDrawingXPoints),
minY = utilMin(this._freeDrawingYPoints),
maxX = utilMax(this._freeDrawingXPoints),
maxY = utilMax(this._freeDrawingYPoints),
path = [ ],
xPoints = this._freeDrawingXPoints,
yPoints = this._freeDrawingYPoints;
path.push('M ', xPoints[0] - minX, ' ', yPoints[0] - minY, ' ');
for (var i = 1, len = xPoints.length; i < len; i++) {
path.push('L ', xPoints[i] - minX, ' ', yPoints[i] - minY, ' ');
}
// TODO (kangax): maybe remove Path creation from here, to decouple fabric.Canvas from fabric.Path,
// and instead fire something like "drawing:completed" event with path string
path = path.join('');
if (path === "M 0 0 L 0 0 ") {
// do not create 0 width/height paths, as they are rendered inconsistently across browsers
// Firefox 4, for example, renders a dot, whereas Chrome 10 renders nothing
this.renderAll();
return;
}
var p = new fabric.Path(path);
p.fill = null;
p.stroke = this.freeDrawingColor;
p.strokeWidth = this.freeDrawingLineWidth;
this.add(p);
p.set("left", minX + (maxX - minX) / 2).set("top", minY + (maxY - minY) / 2).setCoords();
this.renderAll();
this.fire('path:created', { path: p });
},
/**
* Translates object by "setting" its left/top
* @method _translateObject
@ -11550,6 +11758,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, {
};
})(typeof exports !== 'undefined' ? exports : this);
(function(global) {
"use strict";

View file

@ -171,8 +171,7 @@
_initInteractive: function() {
this._currentTransform = null;
this._groupSelector = null;
this._freeDrawingXPoints = [ ];
this._freeDrawingYPoints = [ ];
this.freeDrawing = new fabric.FreeDrawing(this);
this._initWrapperElement();
this._createUpperCanvas();
this._initEvents();
@ -249,7 +248,8 @@
var target;
if (this.isDrawingMode && this._isCurrentlyDrawing) {
this._finalizeDrawingPath();
this._isCurrentlyDrawing = false;
this.freeDrawing._finalizeAndAddPath();
this.fire('mouse:up', { e: e });
return;
}
@ -324,10 +324,13 @@
if (!isLeftClick && !fabric.isTouchSupported) return;
if (this.isDrawingMode) {
this._prepareForDrawing(e);
var pointer = this.getPointer(e);
this.freeDrawing._prepareForDrawing(pointer);
// capture coordinates immediately;
// this allows to draw dots (when movement never occurs)
this.freeDrawing._captureDrawingPath(pointer);
// capture coordinates immediately; this allows to draw dots (when movement never occurs)
this._captureDrawingPath(e);
this.fire('mouse:down', { e: e });
return;
}
@ -397,7 +400,13 @@
if (this.isDrawingMode) {
if (this._isCurrentlyDrawing) {
this._captureDrawingPath(e);
var pointer = this.getPointer(e);
this.freeDrawing._captureDrawingPath(pointer);
// redraw curve
// clear top canvas
this.clearContext(this.contextTop);
this.freeDrawing._render(this.contextTop);
}
this.fire('mouse:move', { e: e });
return;
@ -709,91 +718,6 @@
}
},
/**
* @private
* @method _prepareForDrawing
*/
_prepareForDrawing: function(e) {
this._isCurrentlyDrawing = true;
this.discardActiveObject().renderAll();
var pointer = this.getPointer(e);
this._freeDrawingXPoints.length = this._freeDrawingYPoints.length = 0;
this._freeDrawingXPoints.push(pointer.x);
this._freeDrawingYPoints.push(pointer.y);
this.contextTop.beginPath();
this.contextTop.moveTo(pointer.x, pointer.y);
this.contextTop.strokeStyle = this.freeDrawingColor;
this.contextTop.lineWidth = this.freeDrawingLineWidth;
this.contextTop.lineCap = this.contextTop.lineJoin = 'round';
},
/**
* @private
* @method _captureDrawingPath
*/
_captureDrawingPath: function(e) {
var pointer = this.getPointer(e);
this._freeDrawingXPoints.push(pointer.x);
this._freeDrawingYPoints.push(pointer.y);
this.contextTop.lineTo(pointer.x, pointer.y);
this.contextTop.stroke();
},
/**
* @private
* @method _finalizeDrawingPath
*/
_finalizeDrawingPath: function() {
this.contextTop.closePath();
this._isCurrentlyDrawing = false;
var minX = utilMin(this._freeDrawingXPoints),
minY = utilMin(this._freeDrawingYPoints),
maxX = utilMax(this._freeDrawingXPoints),
maxY = utilMax(this._freeDrawingYPoints),
path = [ ],
xPoints = this._freeDrawingXPoints,
yPoints = this._freeDrawingYPoints;
path.push('M ', xPoints[0] - minX, ' ', yPoints[0] - minY, ' ');
for (var i = 1, len = xPoints.length; i < len; i++) {
path.push('L ', xPoints[i] - minX, ' ', yPoints[i] - minY, ' ');
}
// TODO (kangax): maybe remove Path creation from here, to decouple fabric.Canvas from fabric.Path,
// and instead fire something like "drawing:completed" event with path string
path = path.join('');
if (path === "M 0 0 L 0 0 ") {
// do not create 0 width/height paths, as they are rendered inconsistently across browsers
// Firefox 4, for example, renders a dot, whereas Chrome 10 renders nothing
this.renderAll();
return;
}
var p = new fabric.Path(path);
p.fill = null;
p.stroke = this.freeDrawingColor;
p.strokeWidth = this.freeDrawingLineWidth;
this.add(p);
p.set("left", minX + (maxX - minX) / 2).set("top", minY + (maxY - minY) / 2).setCoords();
this.renderAll();
this.fire('path:created', { path: p });
},
/**
* Translates object by "setting" its left/top
* @method _translateObject

274
src/freedrawing.class.js Normal file
View file

@ -0,0 +1,274 @@
(function(global) {
"use strict";
var fabric = global.fabric || (global.fabric = { });
var utilMin = fabric.util.array.min,
utilMax = fabric.util.array.max;
if (fabric.FreeDrawing) {
fabric.warn('fabric.FreeDrawin is already defined');
return;
}
fabric.FreeDrawing = FreeDrawing;
function FreeDrawing(fabricCanvas) {
this.init(fabricCanvas);
}
FreeDrawing.prototype =  {
constructor: FreeDrawing,
/**
* Free Drawer handles scribbling on a fabricCanvas.
* It converts the hand writting to a SVG Path and adds this
* path to the canvas.
*
* @metod init
* @param fabricCavnas {FabricCanvas}
*
*/
init: function(fabricCanvas) {
this.canvas = fabricCanvas;
this._points = [];
this._color = this.canvas.freeDrawingColor;
this._strokeWidth = this.canvas.freeDrawingLineWidth;
},
/**
* Set path color
* @method setColor
* @param color {String/rgb/rgba}
*
*/
setColor: function(color) {
this._color = color;
},
/**
* Set path thichness (strokeWidth)
* @method setThickness
* @param thickness {int}
*
*/
setThickness: function(thickness) {
this._strokeWidth = thickness;
},
/**
* @private
* @method _addPoint
*
*/
_addPoint: function(point) {
this._points.push(point);
},
/**
* Clear points array and set contextTop canvas
* style.
*
* @private
* @method _reset
*
*/
_reset: function() {
this._points.length = 0;
var ctx = this.canvas.contextTop;
// set freehanddrawing line canvas parameters
ctx.strokeStyle = this._color;
ctx.lineWidth = this._strokeWidth;
ctx.lineCap = ctx.lineJoin = 'round';
},
/**
* @method _prepareForDrawing
*/
_prepareForDrawing: function(pointer) {
this.canvas._isCurrentlyDrawing = true;
this.canvas.discardActiveObject().renderAll();
var p = new fabric.Point(pointer.x, pointer.y);
this._reset();
this._addPoint(p);
this.canvas.contextTop.moveTo(p.x, p.y);
},
/**
* @private
* @method _captureDrawingPath
*
* @param point {pointer} (fabric.util.pointer) actual mouse position
* related to the canvas.
*/
_captureDrawingPath: function(pointer) {
var MIN_POINT_DISTANCE = 2;
var pointerPoint = new fabric.Point(pointer.x, pointer.y);
// Only push point if min_point_distance is reached
var lastPoint = this._points[this._points.length -1];
if (pointerPoint.distanceFrom(lastPoint) > MIN_POINT_DISTANCE)
this._addPoint(pointerPoint);
if (this._points.length < 2)
this._addPoint(pointerPoint);
},
/**
* Draw a smooth path on the topCanvas using
* quadraticCurveTo.
*
* @private
* @method _render
*
*/
_render: function() {
var ctx = this.canvas.contextTop;
ctx.beginPath();
var p1 = this._points[0];
var p2 = this._points[1];
for (var i = 1, len = this._points.length; i < len; i++) {
// we pick the point between pi+1 & pi+2 as the
// end point and p1 as our control point.
var midPoint = p1.midPointFrom(p2);
ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y);
var p1 = this._points[i];
var p2 = this._points[i+1];
}
// Draw last line as a straight line while
// we wait for the next point to be able to calculate
// the bezier control point
ctx.lineTo(p1.x, p1.y);
ctx.stroke();
},
/**
* Return an SVG path based on our
* captured points and their boundinb box.
*
* @private
* @method _getSVGPathData
*
*/
_getSVGPathData: function() {
this.box = this.getPathBoundingBox(this._points);
return this.convertPointsToSVGPath(this._points, this.box.minx, this.box.maxx, this.box.miny, this.box.maxy);
},
/**
* @method getPathBoundingBox
* @param points {Array of points}
*/
getPathBoundingBox: function(points) {
var xBounds = [],
yBounds = [],
p1 = points[0],
p2 = points[1],
startPoint = p1;
for (var i = 1, len = points.length; i < len; i++) {
var midPoint = p1.midPointFrom(p2);
// with startPoint, p1 as control point, midpoint as end point
xBounds.push(startPoint.x);
xBounds.push(midPoint.x);
yBounds.push(startPoint.y);
yBounds.push(midPoint.y);
p1 = points[i];
p2 = points[i+1];
startPoint = midPoint;
} // end for
xBounds.push(p1.x);
yBounds.push(p1.y);
return {
minx: utilMin(xBounds),
miny: utilMin(yBounds),
maxx: utilMax(xBounds),
maxy: utilMax(yBounds)
};
},
/**
* @method convertPointsToSVGPath
* @param points {Array of points}
*/
convertPointsToSVGPath: function(points, minX, maxX, minY, maxY) {
var path = [];
var p1 = new fabric.Point(points[0].x - minX, points[0].y - minY);
var p2 = new fabric.Point(points[1].x - minX, points[1].y - minY);
path.push('M ', points[0].x - minX, ' ', points[0].y - minY, ' ');
for (var i = 1, len = points.length; i < len; i++) {
var midPoint = p1.midPointFrom(p2);
// p1 is our bezier control point
// midpoint is our endpoint
// start point is p(i-1) value.
path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' ');
p1 = new fabric.Point(points[i].x - minX, points[i].y - minY);
if ((i+1) < points.length)
p2 = new fabric.Point(points[i+1].x - minX, points[i+1].y - minY);
}
path.push('L ', p1.x, ' ', p1.y, ' ');
return path;
},
/**
* On mouseup after drawing the path on contextTop canvas
* we use the points captured to create an new fabric path object
* and add it to the fabric canvas.
*
* @method _finalizeAndAddPath
*
*/
_finalizeAndAddPath: function() {
var ctx = this.canvas.contextTop;
ctx.closePath();
var path = this._getSVGPathData();
path = path.join('');
if (path === "M 0 0 Q 0 0 0 0 L 0 0") {
// do not create 0 width/height paths, as they are
// rendered inconsistently across browsers
// Firefox 4, for example, renders a dot,
// whereas Chrome 10 renders nothing
fabricCanvas.renderAll();
return;
}
var p = new fabric.Path(path);
p.fill = null;
p.stroke = this._color;
p.strokeWidth = this._strokeWidth;
this.canvas.add(p);
// set path origin coordinates based on our bouding box
var originLeft = this.box.minx + (this.box.maxx - this.box.minx) /2;
var originTop = this.box.miny + (this.box.maxy - this.box.miny) /2;
this.canvas.contextTop.arc(originLeft, originTop, 3);
p.set("left", originLeft);
p.set("top", originTop)
// does not change position
p.setCoords();
this.canvas.renderAll();
// fire event 'path' created
this.canvas.fire('path:created', { path: p });
}
};
})(typeof exports !== 'undefined' ? exports : this);

View file

@ -760,4 +760,4 @@
return new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options));
};
})(typeof exports !== 'undefined' ? exports : this);
})(typeof exports !== 'undefined' ? exports : this);

View file

@ -161,6 +161,13 @@
return Math.sqrt(dx * dx + dy * dy);
},
/**
* Return the point between A (x,y) and B (x,y)
*/
midPointFrom: function (that) {
return new Point(this.x + (that.x - this.x)/2, this.y + (that.y - this.y)/2);
},
min: function (that) {
return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y));
},
@ -193,4 +200,4 @@
}
};
})(typeof exports !== 'undefined' ? exports : this);
})(typeof exports !== 'undefined' ? exports : this);

View file

@ -1188,4 +1188,4 @@
*/
fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject;
})();
})();