diff --git a/src/brushes/pencil_brush.class.js b/src/brushes/pencil_brush.class.js
index e9c3830b19c..68c7c7f052a 100644
--- a/src/brushes/pencil_brush.class.js
+++ b/src/brushes/pencil_brush.class.js
@@ -179,43 +179,26 @@
/**
* Converts points to SVG path
* @param {Array} points Array of points
- * @return {String} SVG path
+ * @return {(string|number)[][]} SVG path commands
*/
- convertPointsToSVGPath: function(points) {
- var path = [], i, width = this.width / 1000,
- p1 = new fabric.Point(points[0].x, points[0].y),
- p2 = new fabric.Point(points[1].x, points[1].y),
- len = points.length, multSignX = 1, multSignY = 0, manyPoints = len > 2;
+ convertPointsToSVGPath: function (points) {
+ var correction = this.width / 1000;
+ return fabric.util.getSmoothPathFromPoints(points, correction);
+ },
- if (manyPoints) {
- multSignX = points[2].x < p2.x ? -1 : points[2].x === p2.x ? 0 : 1;
- multSignY = points[2].y < p2.y ? -1 : points[2].y === p2.y ? 0 : 1;
- }
- path.push('M ', p1.x - multSignX * width, ' ', p1.y - multSignY * width, ' ');
- for (i = 1; i < len; i++) {
- if (!p1.eq(p2)) {
- 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 = points[i];
- if ((i + 1) < points.length) {
- p2 = points[i + 1];
- }
- }
- if (manyPoints) {
- multSignX = p1.x > points[i - 2].x ? 1 : p1.x === points[i - 2].x ? 0 : -1;
- multSignY = p1.y > points[i - 2].y ? 1 : p1.y === points[i - 2].y ? 0 : -1;
- }
- path.push('L ', p1.x + multSignX * width, ' ', p1.y + multSignY * width);
- return path;
+ /**
+ * @private
+ * @param {(string|number)[][]} pathData SVG path commands
+ * @returns {boolean}
+ */
+ _isEmptySVGPath: function (pathData) {
+ var pathString = pathData.map(function (segment) { return segment.join(' '); }).join(' ');
+ return pathString === 'M 0 0 Q 0 0 0 0 L 0 0';
},
/**
* Creates fabric.Path object to add on canvas
- * @param {String} pathData Path data
+ * @param {(string|number)[][]} pathData Path data
* @return {fabric.Path} Path to add on canvas
*/
createPath: function(pathData) {
@@ -272,8 +255,8 @@
if (this.decimate) {
this._points = this.decimatePoints(this._points, this.decimate);
}
- var pathData = this.convertPointsToSVGPath(this._points).join('');
- if (pathData === 'M 0 0 Q 0 0 0 0 L 0 0') {
+ var pathData = this.convertPointsToSVGPath(this._points);
+ if (this._isEmptySVGPath(pathData)) {
// do not create 0 width/height paths, as they are
// rendered inconsistently across browsers
// Firefox 4, for example, renders a dot,
diff --git a/src/mixins/eraser_brush.mixin.js b/src/mixins/eraser_brush.mixin.js
index 7e4568571e7..52a977e03f8 100644
--- a/src/mixins/eraser_brush.mixin.js
+++ b/src/mixins/eraser_brush.mixin.js
@@ -674,9 +674,9 @@
this._isErasing = false;
var pathData = this._points && this._points.length > 1 ?
- this.convertPointsToSVGPath(this._points).join('') :
- 'M 0 0 Q 0 0 0 0 L 0 0';
- if (pathData === 'M 0 0 Q 0 0 0 0 L 0 0') {
+ this.convertPointsToSVGPath(this._points) :
+ null;
+ if (!pathData || this._isEmptySVGPath(pathData)) {
canvas.fire('erasing:end');
// do not create 0 width/height paths, as they are
// rendered inconsistently across browsers
diff --git a/src/util/path.js b/src/util/path.js
index b72378ea05f..b6479352b4c 100644
--- a/src/util/path.js
+++ b/src/util/path.js
@@ -661,6 +661,18 @@
}
}
+ /**
+ *
+ * @param {string} pathString
+ * @return {(string|number)[][]} An array of SVG path commands
+ * @example
Usage
+ * parsePath('M 3 4 Q 3 5 2 1 4 0 Q 9 12 2 1 4 0') === [
+ * ['M', 3, 4],
+ * ['Q', 3, 5, 2, 1, 4, 0],
+ * ['Q', 9, 12, 2, 1, 4, 0],
+ * ];
+ *
+ */
function parsePath(pathString) {
var result = [],
coords = [],
@@ -729,6 +741,46 @@
return result;
};
+ /**
+ *
+ * Converts points to a smooth SVG path
+ * @param {{ x: number,y: number }[]} points Array of points
+ * @param {number} [correction] Apply a correction to the path (usually we use `width / 1000`). If value is undefined 0 is used as the correction value.
+ * @return {(string|number)[][]} An array of SVG path commands
+ */
+ function getSmoothPathFromPoints(points, correction) {
+ var path = [], i,
+ p1 = new fabric.Point(points[0].x, points[0].y),
+ p2 = new fabric.Point(points[1].x, points[1].y),
+ len = points.length, multSignX = 1, multSignY = 0, manyPoints = len > 2;
+ correction = correction || 0;
+
+ if (manyPoints) {
+ multSignX = points[2].x < p2.x ? -1 : points[2].x === p2.x ? 0 : 1;
+ multSignY = points[2].y < p2.y ? -1 : points[2].y === p2.y ? 0 : 1;
+ }
+ path.push(['M', p1.x - multSignX * correction, p1.y - multSignY * correction]);
+ for (i = 1; i < len; i++) {
+ if (!p1.eq(p2)) {
+ 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 = points[i];
+ if ((i + 1) < points.length) {
+ p2 = points[i + 1];
+ }
+ }
+ if (manyPoints) {
+ multSignX = p1.x > points[i - 2].x ? 1 : p1.x === points[i - 2].x ? 0 : -1;
+ multSignY = p1.y > points[i - 2].y ? 1 : p1.y === points[i - 2].y ? 0 : -1;
+ }
+ path.push(['L', p1.x + multSignX * correction, p1.y + multSignY * correction]);
+ return path;
+ }
+
/**
* Calculate bounding box of a elliptic-arc
* @deprecated
@@ -775,6 +827,7 @@
fabric.util.parsePath = parsePath;
fabric.util.makePathSimpler = makePathSimpler;
+ fabric.util.getSmoothPathFromPoints = getSmoothPathFromPoints;
fabric.util.getPathSegmentsInfo = getPathSegmentsInfo;
fabric.util.fromArcToBeziers = fromArcToBeziers;
/**
diff --git a/test/unit/brushes.js b/test/unit/brushes.js
index f8cd02199f2..a018089ad69 100644
--- a/test/unit/brushes.js
+++ b/test/unit/brushes.js
@@ -1,5 +1,6 @@
(function() {
var canvas = new fabric.Canvas();
+ var parsePath = fabric.util.parsePath;
QUnit.module('fabric.BaseBrush', function(hooks) {
hooks.afterEach(function() {
canvas.cancelRequestedRender();
@@ -40,8 +41,8 @@
var brush = new fabric.PencilBrush(canvas);
var pointer = canvas.getPointer({ clientX: 10, clientY: 10});
brush.onMouseDown(pointer, { e: {} });
- var pathData = brush.convertPointsToSVGPath(brush._points).join('');
- assert.equal(pathData, 'M 9.999 10 L 10.001 10', 'path data create a small line that looks like a point');
+ var pathData = brush.convertPointsToSVGPath(brush._points);
+ assert.deepEqual(pathData, parsePath('M 9.999 10 L 10.001 10'), 'path data create a small line that looks like a point');
});
QUnit.test('fabric pencil brush multiple points', function(assert) {
var brush = new fabric.PencilBrush(canvas);
@@ -51,8 +52,8 @@
brush.onMouseMove(pointer, { e: {} });
brush.onMouseMove(pointer, { e: {} });
brush.onMouseMove(pointer, { e: {} });
- var pathData = brush.convertPointsToSVGPath(brush._points).join('');
- assert.equal(pathData, 'M 9.999 10 L 10.001 10', 'path data create a small line that looks like a point');
+ var pathData = brush.convertPointsToSVGPath(brush._points);
+ assert.deepEqual(pathData, parsePath('M 9.999 10 L 10.001 10'), 'path data create a small line that looks like a point');
assert.equal(brush._points.length, 2, 'concident points are discarded');
});
QUnit.test('fabric pencil brush multiple points not discarded', function(assert) {
@@ -65,8 +66,12 @@
brush.onMouseMove(pointer3, { e: {} });
brush.onMouseMove(pointer2, { e: {} });
brush.onMouseMove(pointer3, { e: {} });
- var pathData = brush.convertPointsToSVGPath(brush._points).join('');
- assert.equal(pathData, 'M 9.999 9.999 Q 10 10 12.5 12.5 Q 15 15 17.5 17.5 Q 20 20 17.5 17.5 Q 15 15 17.5 17.5 L 20.001 20.001', 'path data create a complex path');
+ var pathData = brush.convertPointsToSVGPath(brush._points);
+ assert.deepEqual(
+ pathData,
+ parsePath('M 9.999 9.999 Q 10 10 12.5 12.5 Q 15 15 17.5 17.5 Q 20 20 17.5 17.5 Q 15 15 17.5 17.5 L 20.001 20.001'),
+ 'path data create a complex path'
+ );
assert.equal(brush._points.length, 6, 'concident points are discarded');
});
QUnit.test('fabric pencil brush multiple points outside canvas', function(assert) {
@@ -81,8 +86,12 @@
brush.onMouseMove(pointer3, { e: {} });
brush.onMouseMove(pointer4, { e: {} });
brush.onMouseMove(pointer5, { e: {} });
- var pathData = brush.convertPointsToSVGPath(brush._points).join('');
- assert.equal(pathData, 'M 9.999 9.999 Q 10 10 12.5 55 Q 15 100 17.5 130 Q 20 160 170 130 Q 320 100 210 100 L 99.999 100', 'path data create a path that goes beyond canvas');
+ var pathData = brush.convertPointsToSVGPath(brush._points);
+ assert.deepEqual(
+ pathData,
+ parsePath('M 9.999 9.999 Q 10 10 12.5 55 Q 15 100 17.5 130 Q 20 160 170 130 Q 320 100 210 100 L 99.999 100'),
+ 'path data create a path that goes beyond canvas'
+ );
assert.equal(brush._points.length, 6, 'all points are available');
});
QUnit.test('fabric pencil brush multiple points outside canvas, limitedToCanvasSize true', function(assert) {
@@ -98,8 +107,12 @@
brush.onMouseMove(pointer3, { e: {} });
brush.onMouseMove(pointer4, { e: {} });
brush.onMouseMove(pointer5, { e: {} });
- var pathData = brush.convertPointsToSVGPath(brush._points).join('');
- assert.equal(pathData, 'M 9.999 9.999 Q 10 10 12.5 55 Q 15 100 57.5 100 L 100.001 100', 'path data create a path that does not go beyond canvas');
+ var pathData = brush.convertPointsToSVGPath(brush._points);
+ assert.deepEqual(
+ pathData,
+ parsePath('M 9.999 9.999 Q 10 10 12.5 55 Q 15 100 57.5 100 L 100.001 100'),
+ 'path data create a path that does not go beyond canvas'
+ );
assert.equal(brush._points.length, 4, '2 points have been discarded');
});
QUnit.test('fabric pencil brush multiple points not discarded', function(assert) {