diff --git a/CHANGELOG.md b/CHANGELOG.md index 26fb99743..cef0c724e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,9 @@ project adheres to [Semantic Versioning](http://semver.org/). ================== ### Changed ### Added +* Add `ctx.isPointInStroke()` (#1770) ### Fixed +* Normalize ellipse angles according to the Canvas specification * Load images from Node.js object URLs (#2525) 3.2.3 diff --git a/index.d.ts b/index.d.ts index 886ec2dfd..a917735fd 100644 --- a/index.d.ts +++ b/index.d.ts @@ -206,6 +206,7 @@ export class CanvasRenderingContext2D { setTransform(transform?: DOMMatrix): void; setTransform(a: number, b: number, c: number, d: number, e: number, f: number): void; isPointInPath(x: number, y: number, fillRule?: CanvasFillRule): boolean; + isPointInStroke(x: number, y: number): boolean; scale(x: number, y: number): void; clip(fillRule?: CanvasFillRule): void; fill(fillRule?: CanvasFillRule): void; diff --git a/index.test-d.ts b/index.test-d.ts index 35b5afa96..d53e85e8d 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -36,6 +36,8 @@ ctx.currentTransform = ctx.getTransform() ctx.quality = 'best' ctx.textDrawingMode = 'glyph' +expectType(ctx.isPointInStroke(1, 2)) + const grad = ctx.createLinearGradient(0, 1, 2, 3) expectType(grad) grad.addColorStop(0.1, 'red') diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index 6183837c0..25000ebf0 100644 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -103,6 +103,7 @@ Context2d::Initialize(Napi::Env& env, Napi::Object& exports) { InstanceMethod<&Context2d::ResetTransform>("resetTransform", napi_default_method), InstanceMethod<&Context2d::SetTransform>("setTransform", napi_default_method), InstanceMethod<&Context2d::IsPointInPath>("isPointInPath", napi_default_method), + InstanceMethod<&Context2d::IsPointInStroke>("isPointInStroke", napi_default_method), InstanceMethod<&Context2d::Scale>("scale", napi_default_method), InstanceMethod<&Context2d::Clip>("clip", napi_default_method), InstanceMethod<&Context2d::Fill>("fill", napi_default_method), @@ -2081,6 +2082,21 @@ Context2d::IsPointInPath(const Napi::CallbackInfo& info) { return Napi::Boolean::New(env, false); } +/* + * Check if the given point is within the stroke of the current path. + */ + +Napi::Value +Context2d::IsPointInStroke(const Napi::CallbackInfo& info) { + if (info[0].IsNumber() && info[1].IsNumber()) { + cairo_t *ctx = context(); + double x = info[0].As(), y = info[1].As(); + cairo_device_to_user(ctx, &x, &y); + return Napi::Boolean::New(env, cairo_in_stroke(ctx, x, y)); + } + return Napi::Boolean::New(env, false); +} + /* * Set shadow color. */ @@ -3299,6 +3315,9 @@ Context2d::Ellipse(const Napi::CallbackInfo& info) { cairo_t *ctx = context(); + canonicalizeAngle(startAngle, endAngle); + endAngle = adjustEndAngle(startAngle, endAngle, anticlockwise); + // See https://www.cairographics.org/cookbook/ellipses/ double xRatio = radiusX / radiusY; @@ -3308,7 +3327,7 @@ Context2d::Ellipse(const Napi::CallbackInfo& info) { cairo_rotate(ctx, rotation); cairo_scale(ctx, xRatio, 1.0); cairo_translate(ctx, -x, -y); - if (anticlockwise && M_PI * 2 != args[4]) { + if (anticlockwise) { cairo_arc_negative(ctx, x, y, diff --git a/src/CanvasRenderingContext2d.h b/src/CanvasRenderingContext2d.h index 946b1f2bc..fa363f62f 100644 --- a/src/CanvasRenderingContext2d.h +++ b/src/CanvasRenderingContext2d.h @@ -98,6 +98,7 @@ class Context2d : public Napi::ObjectWrap { void ResetTransform(const Napi::CallbackInfo& info); void SetTransform(const Napi::CallbackInfo& info); Napi::Value IsPointInPath(const Napi::CallbackInfo& info); + Napi::Value IsPointInStroke(const Napi::CallbackInfo& info); void BeginPath(const Napi::CallbackInfo& info); void ClosePath(const Napi::CallbackInfo& info); void AddPage(const Napi::CallbackInfo& info); diff --git a/test/canvas.test.js b/test/canvas.test.js index b8c50b979..2cafcb191 100644 --- a/test/canvas.test.js +++ b/test/canvas.test.js @@ -607,6 +607,26 @@ describe('Canvas', function () { assert.ok(!ctx.isPointInPath(50, 120)) }) + it('Context2d#isPointInStroke()', function () { + const canvas = createCanvas(100, 100) + const ctx = canvas.getContext('2d') + + ctx.lineWidth = 10 + ctx.rect(20, 20, 60, 60) + + assert.equal(ctx.isPointInStroke(20, 50), true) + assert.equal(ctx.isPointInStroke(50, 50), false) + assert.equal(ctx.isPointInStroke(5, 5), false) + assert.equal(ctx.isPointInStroke('20', 50), false) + + ctx.scale(2, 2) + ctx.beginPath() + ctx.moveTo(10, 10) + ctx.lineTo(40, 10) + assert.equal(ctx.isPointInStroke(40, 20), true) + assert.equal(ctx.isPointInStroke(40, 40), false) + }) + it('Context2d#textAlign', function () { const canvas = createCanvas(200, 200) const ctx = canvas.getContext('2d')