From 2897aa62351747b14b7e77bd651d7e93555cedf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Fri, 10 Jul 2020 21:01:19 +0200 Subject: [PATCH] pathFixed rebased on two --- src/index.js | 2 +- src/path.js | 64 +++++++++++++++++++++++++++++---------- test/pathFixed-test.js | 68 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 17 deletions(-) create mode 100644 test/pathFixed-test.js diff --git a/src/index.js b/src/index.js index 968e6e5..5b4ff94 100644 --- a/src/index.js +++ b/src/index.js @@ -1 +1 @@ -export {default as path} from "./path.js"; +export {default as path, pathFixed} from "./path.js"; diff --git a/src/path.js b/src/path.js index 28f1f66..f098c69 100644 --- a/src/path.js +++ b/src/path.js @@ -15,8 +15,13 @@ function path() { Path.prototype = path.prototype = { constructor: Path, + _number: function(x) { + return +x; + }, moveTo: function(x, y) { - this._ += "M" + (this._x0 = this._x1 = +x) + "," + (this._y0 = this._y1 = +y); + x = this._number(x); + y = this._number(y); + this._ += "M" + (this._x0 = this._x1 = x) + "," + (this._y0 = this._y1 = y); }, closePath: function() { if (this._x1 !== null) { @@ -25,26 +30,41 @@ Path.prototype = path.prototype = { } }, lineTo: function(x, y) { - this._ += "L" + (this._x1 = +x) + "," + (this._y1 = +y); + x = this._number(x); + y = this._number(y); + this._ += "L" + (this._x1 = x) + "," + (this._y1 = y); }, quadraticCurveTo: function(x1, y1, x, y) { - this._ += "Q" + (+x1) + "," + (+y1) + "," + (this._x1 = +x) + "," + (this._y1 = +y); + x1 = this._number(x1); + y1 = this._number(y1); + x = this._number(x); + y = this._number(y); + this._ += "Q" + x1 + "," + y1 + "," + (this._x1 = x) + "," + (this._y1 = y); }, bezierCurveTo: function(x1, y1, x2, y2, x, y) { - this._ += "C" + (+x1) + "," + (+y1) + "," + (+x2) + "," + (+y2) + "," + (this._x1 = +x) + "," + (this._y1 = +y); + x1 = this._number(x1); + y1 = this._number(y1); + x2 = this._number(x2); + y2 = this._number(y2); + x = this._number(x); + y = this._number(y); + this._ += "C" + x1 + "," + y1 + "," + x2 + "," + y2 + "," + (this._x1 = x) + "," + (this._y1 = y); }, arcTo: function(x1, y1, x2, y2, r) { - x1 = +x1, y1 = +y1, x2 = +x2, y2 = +y2, r = +r; var x0 = this._x1, y0 = this._y1, x21 = x2 - x1, y21 = y2 - y1, x01 = x0 - x1, y01 = y0 - y1, - l01_2 = x01 * x01 + y01 * y01; + l01_2 = x01 * x01 + y01 * y01, + r_ = this._number(r); // Is the radius negative? Error. - if (r < 0) throw new Error("negative radius: " + r); + if (r_ < 0) throw new Error("negative radius: " + r_); + + x1 = this._number(x1); + y1 = this._number(y1); // Is this path empty? Move to (x1,y1). if (this._x1 === null) { @@ -57,7 +77,7 @@ Path.prototype = path.prototype = { // Or, are (x0,y0), (x1,y1) and (x2,y2) collinear? // Equivalently, is (x1,y1) coincident with (x2,y2)? // Or, is the radius zero? Line to (x1,y1). - else if (!(Math.abs(y01 * x21 - y21 * x01) > epsilon) || !r) { + else if (!(Math.abs(y01 * x21 - y21 * x01) > epsilon) || !r_) { this._ += "L" + (this._x1 = x1) + "," + (this._y1 = y1); } @@ -75,18 +95,18 @@ Path.prototype = path.prototype = { // If the start tangent is not coincident with (x0,y0), line to. if (Math.abs(t01 - 1) > epsilon) { - this._ += "L" + (x1 + t01 * x01) + "," + (y1 + t01 * y01); + this._ += "L" + this._number(x1 + t01 * x01) + "," + this._number(y1 + t01 * y01); } - this._ += "A" + r + "," + r + ",0,0," + (+(y01 * x20 > x01 * y20)) + "," + (this._x1 = x1 + t21 * x21) + "," + (this._y1 = y1 + t21 * y21); + this._ += "A" + r_ + "," + r_ + ",0,0," + this._number(y01 * x20 > x01 * y20) + "," + (this._x1 = this._number(x1 + t21 * x21)) + "," + (this._y1 = this._number(y1 + t21 * y21)); } }, arc: function(x, y, r, a0, a1, ccw) { x = +x, y = +y, r = +r, ccw = !!ccw; var dx = r * Math.cos(a0), dy = r * Math.sin(a0), - x0 = x + dx, - y0 = y + dy, + x0 = this._number(x + dx), + y0 = this._number(y + dy), cw = 1 ^ ccw, da = ccw ? a0 - a1 : a1 - a0; @@ -104,23 +124,28 @@ Path.prototype = path.prototype = { } // Is this arc empty? We’re done. - if (!r) return; + var r_ = this._number(r); + if (!(r_)) return; // Does the angle go the wrong way? Flip the direction. if (da < 0) da = da % tau + tau; // Is this a complete circle? Draw two arcs to complete the circle. if (da > tauEpsilon) { - this._ += "A" + r + "," + r + ",0,1," + cw + "," + (x - dx) + "," + (y - dy) + "A" + r + "," + r + ",0,1," + cw + "," + (this._x1 = x0) + "," + (this._y1 = y0); + this._ += "A" + r_ + "," + r_ + ",0,1," + cw + "," + this._number(x - dx) + "," + this._number(y - dy) + "A" + r + "," + r + ",0,1," + cw + "," + (this._x1 = x0) + "," + (this._y1 = y0); } // Is this arc non-empty? Draw an arc! else if (da > epsilon) { - this._ += "A" + r + "," + r + ",0," + (+(da >= pi)) + "," + cw + "," + (this._x1 = x + r * Math.cos(a1)) + "," + (this._y1 = y + r * Math.sin(a1)); + this._ += "A" + r_ + "," + r_ + ",0," + (+(da >= pi)) + "," + cw + "," + (this._x1 = this._number(x + r * Math.cos(a1))) + "," + (this._y1 = this._number(y + r * Math.sin(a1))); } }, rect: function(x, y, w, h) { - this._ += "M" + (this._x0 = this._x1 = +x) + "," + (this._y0 = this._y1 = +y) + "h" + (+w) + "v" + (+h) + "h" + (-w) + "Z"; + x = this._number(x); + y = this._number(y); + w = this._number(w); + h = this._number(h); + this._ += "M" + (this._x0 = this._x1 = x) + "," + (this._y0 = this._y1 = y) + "h" + w + "v" + h + "h" + (-w) + "Z"; }, toString: function() { return this._; @@ -128,3 +153,10 @@ Path.prototype = path.prototype = { }; export default path; + +export function pathFixed(digits) { + var path = new Path; + (digits = +digits).toFixed(digits); // Validate digits. + path._number = function(x) { return +x.toFixed(digits); }; + return path; +} diff --git a/test/pathFixed-test.js b/test/pathFixed-test.js new file mode 100644 index 0000000..74f599a --- /dev/null +++ b/test/pathFixed-test.js @@ -0,0 +1,68 @@ +var tape = require("tape"), + path = require("../"); + +tape("pathFixed.moveTo(x, y) limits the precision", function(test) { + var p = path.pathFixed(1); + p.moveTo(123.456, 789.012); + test.strictEqual(p + "", "M123.5,789"); + test.end(); +}); + +tape("pathFixed.lineTo(x, y) limits the precision", function(test) { + var p = path.pathFixed(1); + p.moveTo(0, 0); + p.lineTo(123.456, 789.012); + test.strictEqual(p + "", "M0,0L123.5,789"); + test.end(); +}); + +tape("pathFixed.arc(x, y, r, a0, a1, ccw) limits the precision", function(test) { + var p0 = path.path(), p = path.pathFixed(1); + p0.arc(10.0001, 10.0001, 123.456, 0, Math.PI+0.0001); + p.arc(10.0001, 10.0001, 123.456, 0, Math.PI+0.0001); + test.strictEqual(p + "", precision(p0 + "", 1)); + p0.arc(10.0001, 10.0001, 123.456, 0, Math.PI-0.0001); + p.arc(10.0001, 10.0001, 123.456, 0, Math.PI-0.0001); + test.strictEqual(p + "", precision(p0 + "", 1)); + p0.arc(10.0001, 10.0001, 123.456, 0, Math.PI / 2, true); + p.arc(10.0001, 10.0001, 123.456, 0, Math.PI / 2, true); + test.strictEqual(p + "", precision(p0 + "", 1)); + test.end(); +}); + +tape("pathFixed.arcTo(x1, y1, x2, y2, r) limits the precision", function(test) { + var p0 = path.path(), p = path.pathFixed(1); + p0.arcTo(10.0001, 10.0001, 123.456, 456.789, 12345.6789); + p.arcTo(10.0001, 10.0001, 123.456, 456.789, 12345.6789); + test.strictEqual(p + "", precision(p0 + "", 1)); + test.end(); +}); + +tape("pathFixed.quadraticCurveTo(x1, y1, x, y) limits the precision", function(test) { + var p0 = path.path(), p = path.pathFixed(1); + p0.quadraticCurveTo(10.0001, 10.0001, 123.456, 456.789); + p.quadraticCurveTo(10.0001, 10.0001, 123.456, 456.789); + test.strictEqual(p + "", precision(p0 + "", 1)); + test.end(); +}); + +tape("pathFixed.bezierCurveTo(x1, y1, x2, y2, x, y) limits the precision", function(test) { + var p0 = path.path(), p = path.pathFixed(1); + p0.bezierCurveTo(10.0001, 10.0001, 123.456, 456.789, 0.007, 0.006); + p.bezierCurveTo(10.0001, 10.0001, 123.456, 456.789, 0.007, 0.006); + test.strictEqual(p + "", precision(p0 + "", 1)); + test.end(); +}); + +tape("pathFixed.rect(x, y, w, h) limits the precision", function(test) { + var p0 = path.path(), p = path.pathFixed(1); + p0.rect(10.0001, 10.0001, 123.456, 456.789); + p.rect(10.0001, 10.0001, 123.456, 456.789); + test.strictEqual(p + "", precision(p0 + "", 1)); + test.end(); +}); + + +function precision(str, precision) { + return str.replace(/\d+\.\d+/g, s => +parseFloat(s).toFixed(precision)); +}