diff --git a/__test__/draw.spec.ts b/__test__/draw.spec.ts index a0d3a206..d4508bb4 100644 --- a/__test__/draw.spec.ts +++ b/__test__/draw.spec.ts @@ -295,7 +295,29 @@ test('getImageData', async (t) => { await snapshotImage(t) }) -test.todo('isPointInPath') +test('isPointInPath', (t) => { + const { ctx } = t.context + + ctx.rect(0, 0, 100, 100) + t.is(ctx.isPointInPath(50, -1), false) // Outside the rect + t.is(ctx.isPointInPath(50, 0), true) // On the edge of the rect + t.is(ctx.isPointInPath(50, 1), true) // Inside the rect + + ctx.rect(40, 40, 20, 20) // Overlap the area center + t.is(ctx.isPointInPath(50, 50), true) + t.is(ctx.isPointInPath(50, 50, 'nonzero'), true) + t.is(ctx.isPointInPath(50, 50, 'evenodd'), false) + + const path = new Path2D() + path.rect(0, 0, 100, 100) + t.is(ctx.isPointInPath(path, 50, -1), false) + t.is(ctx.isPointInPath(path, 50, 1), true) + + path.rect(40, 40, 20, 20) + t.is(ctx.isPointInPath(50, 50), true) + t.is(ctx.isPointInPath(path, 50, 50, 'nonzero'), true) + t.is(ctx.isPointInPath(path, 50, 50, 'evenodd'), false) +}) test('isPointInStroke', (t) => { const { ctx } = t.context diff --git a/skia-c/skia_c.cpp b/skia-c/skia_c.cpp index d2975d65..a187e9d5 100644 --- a/skia-c/skia_c.cpp +++ b/skia-c/skia_c.cpp @@ -550,6 +550,15 @@ extern "C" return PATH_CAST->isEmpty(); } + bool skiac_path_hit_test(skiac_path *c_path, float x, float y, int type) + { + auto prev_fill = PATH_CAST->getFillType(); + PATH_CAST->setFillType((SkPathFillType)type); + auto result = PATH_CAST->contains(x, y); + PATH_CAST->setFillType(prev_fill); + return result; + } + bool skiac_path_stroke_hit_test(skiac_path *c_path, float x, float y, float stroke_w) { auto path = PATH_CAST; diff --git a/skia-c/skia_c.hpp b/skia-c/skia_c.hpp index 8b907d07..1acd0afa 100644 --- a/skia-c/skia_c.hpp +++ b/skia-c/skia_c.hpp @@ -160,6 +160,7 @@ extern "C" void skiac_path_transform(skiac_path *c_path, skiac_transform c_transform); void skiac_path_transform_matrix(skiac_path *c_path, skiac_matrix *c_matrix); bool skiac_path_is_empty(skiac_path *c_path); + bool skiac_path_hit_test(skiac_path *c_path, float x, float y, int type); bool skiac_path_stroke_hit_test(skiac_path *c_path, float x, float y, float stroke_w); // PathEffect diff --git a/src/ctx.rs b/src/ctx.rs index 079e16fc..785168da 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -91,6 +91,7 @@ impl Context { Property::new(&env, "createLinearGradient")?.with_method(create_linear_gradient), Property::new(&env, "createRadialGradient")?.with_method(create_radial_gradient), Property::new(&env, "drawImage")?.with_method(draw_image), + Property::new(&env, "isPointInPath")?.with_method(is_point_in_path), Property::new(&env, "isPointInStroke")?.with_method(is_point_in_stroke), Property::new(&env, "ellipse")?.with_method(ellipse), Property::new(&env, "lineTo")?.with_method(line_to), @@ -684,6 +685,67 @@ fn draw_image(ctx: CallContext) -> Result { ctx.env.get_undefined() } +#[js_function(4)] +fn is_point_in_path(ctx: CallContext) -> Result { + let this = ctx.this_unchecked::(); + let context_2d = ctx.env.unwrap::(&this)?; + let result; + + if ctx.length == 2 { + let x: f64 = ctx.get::(0)?.try_into()?; + let y: f64 = ctx.get::(1)?.try_into()?; + result = context_2d + .path + .hit_test(y as f32, x as f32, FillType::Winding); + return ctx.env.get_boolean(result); + } else if ctx.length == 3 { + let input = ctx.get::(0)?; + match input.get_type()? { + ValueType::Number => { + let x: f64 = ctx.get::(0)?.try_into()?; + let y: f64 = ctx.get::(1)?.try_into()?; + let fill_rule_js = ctx.get::(2)?.into_utf8()?; + result = context_2d.path.hit_test( + y as f32, + x as f32, + FillType::from_str(fill_rule_js.as_str()?)?, + ); + } + ValueType::Object => { + let x: f64 = ctx.get::(1)?.try_into()?; + let y: f64 = ctx.get::(2)?.try_into()?; + let path_js = ctx.get::(0)?; + let path = ctx.env.unwrap::(&path_js)?; + result = path.hit_test(x as f32, y as f32, FillType::Winding); + } + _ => { + return Err(Error::new( + Status::InvalidArg, + format!("Invalid isPointInPath argument"), + )) + } + } + return ctx.env.get_boolean(result); + } else if ctx.length == 4 { + let path_js = ctx.get::(0)?; + let path = ctx.env.unwrap::(&path_js)?; + let x: f64 = ctx.get::(1)?.try_into()?; + let y: f64 = ctx.get::(2)?.try_into()?; + let fill_rule_js = ctx.get::(3)?.into_utf8()?; + result = path.hit_test( + y as f32, + x as f32, + FillType::from_str(fill_rule_js.as_str()?)?, + ); + return ctx.env.get_boolean(result); + } else { + return Err(Error::new( + Status::InvalidArg, + format!("Invalid isPointInPath arguments length"), + )); + } +} + #[js_function(3)] fn is_point_in_stroke(ctx: CallContext) -> Result { let this = ctx.this_unchecked::(); diff --git a/src/sk.rs b/src/sk.rs index fc9c6d85..0d6430da 100644 --- a/src/sk.rs +++ b/src/sk.rs @@ -359,6 +359,8 @@ mod ffi { pub fn skiac_path_is_empty(path: *mut skiac_path) -> bool; + pub fn skiac_path_hit_test(path: *mut skiac_path, x: f32, y: f32, kind: i32) -> bool; + pub fn skiac_path_stroke_hit_test(path: *mut skiac_path, x: f32, y: f32, stroke_w: f32) -> bool; @@ -1720,6 +1722,11 @@ impl Path { unsafe { ffi::skiac_path_is_empty(self.0) } } + #[inline] + pub fn hit_test(&self, x: f32, y: f32, kind: FillType) -> bool { + unsafe { ffi::skiac_path_hit_test(self.0, x, y, kind as i32) } + } + #[inline] pub fn stroke_hit_test(&self, x: f32, y: f32, stroke_w: f32) -> bool { unsafe { ffi::skiac_path_stroke_hit_test(self.0, x, y, stroke_w) }