mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-05-09 14:24:44 +00:00
247 lines
6.8 KiB
JavaScript
247 lines
6.8 KiB
JavaScript
(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.FreeDrawing is already defined');
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Free drawing class
|
|
* Free Drawer handles scribbling on a fabric canvas
|
|
* It converts the hand writting to a SVG Path and adds this path to the canvas
|
|
*
|
|
* @class FreeDrawing
|
|
* @memberOf fabric
|
|
*/
|
|
fabric.FreeDrawing = fabric.util.createClass( /** @scope fabric.FreeDrawing.prototype */ {
|
|
|
|
/**
|
|
* Constructor
|
|
* @metod initialize
|
|
* @param fabricCanvas {fabric.Canvas}
|
|
* @return {fabric.FreeDrawing}
|
|
*/
|
|
initialize: function(fabricCanvas) {
|
|
this.canvas = fabricCanvas;
|
|
this._points = [];
|
|
},
|
|
|
|
/**
|
|
* @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.canvas.freeDrawingColor;
|
|
ctx.lineWidth = this.canvas.freeDrawingLineWidth;
|
|
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 pointerPoint = new fabric.Point(pointer.x, pointer.y);
|
|
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];
|
|
|
|
ctx.moveTo(p1.x, p1.y);
|
|
|
|
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);
|
|
|
|
p1 = this._points[i];
|
|
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);
|
|
},
|
|
|
|
/**
|
|
* Returns bounding box of a path based on given points
|
|
* @method getPathBoundingBox
|
|
* @param {Array} points
|
|
* @return {Object} object with minx, miny, maxx, maxy
|
|
*/
|
|
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)
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Converts points to SVG path
|
|
* @method convertPointsToSVGPath
|
|
* @param {Array} points Array of points
|
|
* @return {String} SVG path
|
|
*/
|
|
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() {
|
|
this.canvas._isCurrentlyDrawing = false;
|
|
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
|
|
this.canvas.renderAll();
|
|
return;
|
|
}
|
|
|
|
var p = new fabric.Path(path);
|
|
p.fill = null;
|
|
p.stroke = this.canvas.freeDrawingColor;
|
|
p.strokeWidth = this.canvas.freeDrawingLineWidth;
|
|
this.canvas.add(p);
|
|
|
|
// set path origin coordinates based on our bounding 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, 0, Math.PI * 2, false);
|
|
|
|
p.set({ left: originLeft, 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);
|