Skip to content

Commit

Permalink
Add Canvas isPointInPath()
Browse files Browse the repository at this point in the history
  • Loading branch information
TooTallNate committed Feb 19, 2024
1 parent 0d9f7a5 commit 8ea15f5
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 56 deletions.
5 changes: 5 additions & 0 deletions .changeset/six-years-tickle.md
@@ -0,0 +1,5 @@
---
'nxjs-runtime': patch
---

Add Canvas `isPointInPath()`
7 changes: 7 additions & 0 deletions apps/tests/src/canvas.ts
Expand Up @@ -19,4 +19,11 @@ test('`CanvasRenderingContext2D#getImageData()`', () => {
assert.equal(data.data[3], 255);
});

test('`CanvasRenderingContext2D#isPointInPath()`', () => {
ctx.beginPath();
ctx.rect(10, 10, 100, 100);
assert.equal(ctx.isPointInPath(30, 70), true);
assert.equal(ctx.isPointInPath(8, 8), false);
});

test.run();
80 changes: 58 additions & 22 deletions packages/runtime/src/canvas/canvas-rendering-context-2d.ts
Expand Up @@ -54,28 +54,6 @@ export class CanvasRenderingContext2D {
// TODO: implement
declare direction: string;
declare fontKerning: string;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/clip) */
clip(fillRule?: CanvasFillRule): void;
clip(path: Path2D, fillRule?: CanvasFillRule): void;
clip(path: unknown, fillRule?: unknown) {
stub();
}
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/isPointInPath) */
isPointInPath(x: number, y: number, fillRule?: CanvasFillRule): boolean;
isPointInPath(
path: Path2D,
x: number,
y: number,
fillRule?: CanvasFillRule
): boolean;
isPointInPath(
path: unknown,
x: unknown,
y: unknown,
fillRule?: unknown
): boolean {
stub();
}
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/isPointInStroke) */
isPointInStroke(x: number, y: number): boolean;
isPointInStroke(path: Path2D, x: number, y: number): boolean;
Expand Down Expand Up @@ -322,6 +300,64 @@ export class CanvasRenderingContext2D {
stub();
}

/**
* Turns the current or given path into the current clipping region. The
* previous clipping region, if any, is intersected with the current or
* given path to create the new clipping region.
*
* @example
*
* ```typescript
* // Create circular clipping region
* ctx.beginPath();
* ctx.arc(100, 75, 50, 0, Math.PI * 2);
* ctx.clip();
*
* // Draw stuff that gets clipped
* ctx.fillStyle = "blue";
* ctx.fillRect(0, 0, canvas.width, canvas.height);
* ctx.fillStyle = "orange";
* ctx.fillRect(0, 0, 100, 100);
* ```
*
* @see https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/clip
*/
clip(fillRule?: CanvasFillRule): void;
clip(path: Path2D, fillRule?: CanvasFillRule): void;
clip(path: unknown, fillRule?: unknown) {
stub();
}

/**
* Reports whether or not the specified point is contained in the current path.
*
* @example
*
* ```typescript
* ctx.beginPath();
* ctx.rect(10, 10, 100, 100);
* console.log(ctx.isPointInPath(30, 70));
* // true
* ```
*
* @see https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/isPointInPath
*/
isPointInPath(x: number, y: number, fillRule?: CanvasFillRule): boolean;
isPointInPath(
path: Path2D,
x: number,
y: number,
fillRule?: CanvasFillRule
): boolean;
isPointInPath(
path: unknown,
x: unknown,
y: unknown,
fillRule?: unknown
): boolean {
stub();
}

/**
* Attempts to add a straight line from the current point to the start of
* the current sub-path. If the shape has already been closed or has only
Expand Down
Expand Up @@ -54,28 +54,6 @@ export class OffscreenCanvasRenderingContext2D {
// TODO: implement
declare direction: string;
declare fontKerning: string;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/clip) */
clip(fillRule?: CanvasFillRule): void;
clip(path: Path2D, fillRule?: CanvasFillRule): void;
clip(path: unknown, fillRule?: unknown) {
stub();
}
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/isPointInPath) */
isPointInPath(x: number, y: number, fillRule?: CanvasFillRule): boolean;
isPointInPath(
path: Path2D,
x: number,
y: number,
fillRule?: CanvasFillRule
): boolean;
isPointInPath(
path: unknown,
x: unknown,
y: unknown,
fillRule?: unknown
): boolean {
stub();
}
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/isPointInStroke) */
isPointInStroke(x: number, y: number): boolean;
isPointInStroke(path: Path2D, x: number, y: number): boolean;
Expand Down Expand Up @@ -320,6 +298,64 @@ export class OffscreenCanvasRenderingContext2D {
stub();
}

/**
* Turns the current or given path into the current clipping region. The
* previous clipping region, if any, is intersected with the current or
* given path to create the new clipping region.
*
* @example
*
* ```typescript
* // Create circular clipping region
* ctx.beginPath();
* ctx.arc(100, 75, 50, 0, Math.PI * 2);
* ctx.clip();
*
* // Draw stuff that gets clipped
* ctx.fillStyle = "blue";
* ctx.fillRect(0, 0, canvas.width, canvas.height);
* ctx.fillStyle = "orange";
* ctx.fillRect(0, 0, 100, 100);
* ```
*
* @see https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/clip
*/
clip(fillRule?: CanvasFillRule): void;
clip(path: Path2D, fillRule?: CanvasFillRule): void;
clip(path: unknown, fillRule?: unknown) {
stub();
}

/**
* Reports whether or not the specified point is contained in the current path.
*
* @example
*
* ```typescript
* ctx.beginPath();
* ctx.rect(10, 10, 100, 100);
* console.log(ctx.isPointInPath(30, 70));
* // true
* ```
*
* @see https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/isPointInPath
*/
isPointInPath(x: number, y: number, fillRule?: CanvasFillRule): boolean;
isPointInPath(
path: Path2D,
x: number,
y: number,
fillRule?: CanvasFillRule
): boolean;
isPointInPath(
path: unknown,
x: unknown,
y: unknown,
fillRule?: unknown
): boolean {
stub();
}

/**
* Attempts to add a straight line from the current point to the start of
* the current sub-path. If the shape has already been closed or has only
Expand Down
56 changes: 44 additions & 12 deletions source/canvas.c
Expand Up @@ -98,6 +98,14 @@ static void set_fill_rule(JSContext *ctx, JSValueConst fill_rule, cairo_t *cr)
cairo_set_fill_rule(cr, rule);
}

static void apply_path(JSContext *ctx, JSValue this_val, JSValue path) {
nx_context_t *nx_ctx = JS_GetContextOpaque(ctx);
JSValue apply_path_func = JS_GetPropertyStr(ctx, nx_ctx->init_obj, "applyPath");
JSValue apply_path_argv[] = {this_val, path};
JS_Call(ctx, apply_path_func, JS_NULL, 2, apply_path_argv);
JS_FreeValue(ctx, apply_path_func);
}

static void save_path(nx_canvas_context_2d_t *context)
{
context->path = cairo_copy_path_flat(context->ctx);
Expand Down Expand Up @@ -166,6 +174,37 @@ static JSValue nx_canvas_context_2d_move_to(JSContext *ctx, JSValueConst this_va
return JS_UNDEFINED;
}

static JSValue nx_canvas_context_2d_is_point_in_path(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
CANVAS_CONTEXT_THIS;
JSValue path = JS_NULL;
if (argc > 0 && JS_IsObject(argv[0])) {
path = argv[0];
argc--;
argv++;
}
bool is_in = false;
if (argc >= 2) {
double args[2];
if (js_validate_doubles_args(ctx, argv, args, 2, 0))
return JS_EXCEPTION;
if (argc == 3 && JS_IsString(argv[2])) {
set_fill_rule(ctx, argv[2], cr);
}
bool needs_restore = false;
if (!JS_IsNull(path)) {
needs_restore = true;
save_path(context);
apply_path(ctx, this_val, path);
}
is_in = cairo_in_fill(cr, args[0], args[1]);
if (needs_restore) {
restore_path(context);
}
}
return JS_NewBool(ctx, is_in);
}

static JSValue nx_canvas_context_2d_line_to(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
CANVAS_CONTEXT_THIS;
Expand Down Expand Up @@ -1674,7 +1713,7 @@ static JSValue nx_canvas_context_2d_fill(JSContext *ctx, JSValueConst this_val,
{
fill_rule = argv[0];
}
else
else if (!JS_IsUndefined(argv[0]))
{
return JS_ThrowTypeError(ctx, "Expected Path2D or string at index 0");
}
Expand All @@ -1693,7 +1732,7 @@ static JSValue nx_canvas_context_2d_fill(JSContext *ctx, JSValueConst this_val,
{
fill_rule = argv[1];
}
else
else if (!JS_IsUndefined(argv[1]))
{
return JS_ThrowTypeError(ctx, "Expected string at index 1");
}
Expand All @@ -1709,11 +1748,7 @@ static JSValue nx_canvas_context_2d_fill(JSContext *ctx, JSValueConst this_val,
else
{
save_path(context);
nx_context_t *nx_ctx = JS_GetContextOpaque(ctx);
JSValue apply_path_func = JS_GetPropertyStr(ctx, nx_ctx->init_obj, "applyPath");
JSValue apply_path_argv[] = {this_val, argv[0]};
JS_Call(ctx, apply_path_func, JS_NULL, 2, apply_path_argv);
JS_FreeValue(ctx, apply_path_func);
apply_path(ctx, this_val, path);
fill(context, false);
restore_path(context);
}
Expand Down Expand Up @@ -1742,11 +1777,7 @@ static JSValue nx_canvas_context_2d_stroke(JSContext *ctx, JSValueConst this_val
else
{
save_path(context);
nx_context_t *nx_ctx = JS_GetContextOpaque(ctx);
JSValue apply_path_func = JS_GetPropertyStr(ctx, nx_ctx->init_obj, "applyPath");
JSValue apply_path_argv[] = {this_val, argv[0]};
JS_Call(ctx, apply_path_func, JS_NULL, 2, apply_path_argv);
JS_FreeValue(ctx, apply_path_func);
apply_path(ctx, this_val, path);
stroke(context, false);
restore_path(context);
}
Expand Down Expand Up @@ -2673,6 +2704,7 @@ static JSValue nx_canvas_context_2d_init_class(JSContext *ctx, JSValueConst this
NX_DEF_FUNC(proto, "fillRect", nx_canvas_context_2d_fill_rect, 4);
NX_DEF_FUNC(proto, "fillText", nx_canvas_context_2d_fill_text, 3);
NX_DEF_FUNC(proto, "getLineDash", nx_canvas_context_2d_get_line_dash, 0);
NX_DEF_FUNC(proto, "isPointInPath", nx_canvas_context_2d_is_point_in_path, 2);
NX_DEF_FUNC(proto, "lineTo", nx_canvas_context_2d_line_to, 2);
NX_DEF_FUNC(proto, "measureText", nx_canvas_context_2d_measure_text, 1);
NX_DEF_FUNC(proto, "moveTo", nx_canvas_context_2d_move_to, 2);
Expand Down

0 comments on commit 8ea15f5

Please sign in to comment.