From 080874dd88225318bffa8e38224f017b4e8486e9 Mon Sep 17 00:00:00 2001 From: Jonathan Haas Date: Mon, 8 Apr 2024 08:25:09 +0200 Subject: [PATCH] fix(core): Path.offset(...) no longer fails on very short curves or zero length paths. Fixes #6519 --- packages/core/src/path.mjs | 28 +++++++++++++++++++++++++--- packages/core/tests/path.test.mjs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/packages/core/src/path.mjs b/packages/core/src/path.mjs index b25c27c8c63..a7a938d01d8 100644 --- a/packages/core/src/path.mjs +++ b/packages/core/src/path.mjs @@ -192,6 +192,11 @@ Path.prototype.bbox = function () { if (op.to) current = op.to } + if (bbs.length === 0 && current) { + // Degenerate case: Line is a point + bbs.push(__lineBoundingBox({ from: current, to: current })) + } + return __bbbbox(bbs) } @@ -646,7 +651,7 @@ Path.prototype.noop = function (id = false) { Path.prototype.offset = function (distance) { distance = __asNumber(distance, 'distance', 'Path.offset', this.log) - return __pathOffset(this, distance) + return __pathOffset(this, distance, this.log) } /** @@ -1355,7 +1360,7 @@ function __offsetLine(from, to, distance, log = false) { * @param {float} distance - The distance to offset by * @return {Path} offsetted - The offsetted Path instance */ -function __pathOffset(path, distance) { +function __pathOffset(path, distance, log) { let offset = [] let current let start = false @@ -1392,7 +1397,24 @@ function __pathOffset(path, distance) { if (!start) start = current } - return closed ? __joinPaths(offset).close() : __joinPaths(offset) + let result + + if (offset.length !== 0) { + result = __joinPaths(offset) + } else { + // degenerate case: Original path was likely short, so all the "if (segment)" checks returned false + // retry treating the path as a simple straight line from start to end + // note: do not call __joinPaths in this branch as this could result in "over-optimizing" this short path + let segment = __offsetLine(start, current, distance, path.log) + if (segment) { + result = segment + } else { + result = new Path().move(start).line(current) + log.warn(`Could not properly calculate offset path, the given path is likely too short.`) + } + } + + return closed ? result.close() : result } /** diff --git a/packages/core/tests/path.test.mjs b/packages/core/tests/path.test.mjs index 1cbf31f2065..4ab0b4ae820 100644 --- a/packages/core/tests/path.test.mjs +++ b/packages/core/tests/path.test.mjs @@ -118,6 +118,28 @@ describe('Path', () => { expect(round(bbox.bottomRight.x)).to.equal(119.86) expect(round(bbox.bottomRight.y)).to.equal(43.49) }) + + it('Should offset small curves', () => { + const curve = new Path() + .move(new Point(0, 0)) + .curve(new Point(0.1, 0.1), new Point(0.2, 0.2), new Point(0.1, 1.1)) + const offset = curve.offset(1) + const bbox = offset.bbox() + expect(round(bbox.bottomRight.x)).to.equal(-0.9) + expect(round(bbox.bottomRight.y)).to.equal(1.19) + }) + + it('Should offset zero length path', () => { + let logged = false + const log = { warn: () => (logged = true) } + const curve = new Path().__withLog(log).move(new Point(0, 0)).line(new Point(0, 0)).close() + expect(logged).to.equal(false) + const offset = curve.offset(1) + expect(logged).to.equal(true) + const bbox = offset.bbox() + expect(round(bbox.bottomRight.x)).to.equal(0) + expect(round(bbox.bottomRight.y)).to.equal(0) + }) }) describe('length', () => { @@ -354,6 +376,15 @@ describe('Path', () => { expect(box.bottomRight.y).to.equal(456) }) + it('Should find the bounding box of an empty path', () => { + const path = new Path().move(new Point(123, 456)).close() + const box = path.bbox() + expect(box.topLeft.x).to.equal(123) + expect(box.topLeft.y).to.equal(456) + expect(box.bottomRight.x).to.equal(123) + expect(box.bottomRight.y).to.equal(456) + }) + it('Should reverse a path', () => { const test = new Path() .move(new Point(123, 456))