diff --git a/crates/typst-ide/src/jump.rs b/crates/typst-ide/src/jump.rs index 428335426..c03f83221 100644 --- a/crates/typst-ide/src/jump.rs +++ b/crates/typst-ide/src/jump.rs @@ -53,10 +53,14 @@ pub fn jump_from_click( for (mut pos, item) in frame.items().rev() { match item { FrameItem::Group(group) => { - // TODO: Handle transformation. - if let Some(span) = - jump_from_click(world, document, &group.frame, click - pos) - { + let pos = click - pos; + if let Some(clip) = &group.clip { + if !clip.contains(pos) { + continue; + } + } + let pos = pos.transform_inf(group.transform.invert().unwrap()); + if let Some(span) = jump_from_click(world, document, &group.frame, pos) { return Some(span); } } @@ -146,9 +150,8 @@ pub fn jump_from_cursor( fn find_in_frame(frame: &Frame, span: Span) -> Option { for (mut pos, item) in frame.items() { if let FrameItem::Group(group) = item { - // TODO: Handle transformation. if let Some(point) = find_in_frame(&group.frame, span) { - return Some(point + pos); + return Some(pos + point.transform(group.transform)); } } @@ -269,6 +272,42 @@ mod tests { test_click("$a + b$", point(28.0, 14.0), cursor(5)); } + #[test] + fn test_jump_from_click_transform_clip() { + let margin = point(10.0, 10.0); + test_click( + "#rect(width: 20pt, height: 20pt)", + point(10.0, 10.0) + margin, + cursor(1), + ); + test_click("#rect(width: 60pt, height: 10pt)", point(5.0, 30.0) + margin, None); + test_click( + "#rotate(90deg, origin: bottom + left, rect(width: 60pt, height: 10pt))", + point(5.0, 30.0) + margin, + cursor(38), + ); + test_click( + "#scale(x: 300%, y: 300%, origin: top + left, rect(width: 10pt, height: 10pt))", + point(20.0, 20.0) + margin, + cursor(45), + ); + test_click( + "#box(width: 10pt, height: 10pt, clip: true, scale(x: 300%, y: 300%, origin: top + left, rect(width: 10pt, height: 10pt)))", + point(20.0, 20.0) + margin, + None, + ); + test_click( + "#box(width: 10pt, height: 10pt, clip: false, rect(width: 30pt, height: 30pt))", + point(20.0, 20.0) + margin, + cursor(45), + ); + test_click( + "#box(width: 10pt, height: 10pt, clip: true, rect(width: 30pt, height: 30pt))", + point(20.0, 20.0) + margin, + None, + ); + } + #[test] fn test_jump_from_cursor() { let s = "*Hello* #box[ABC] World"; @@ -281,6 +320,15 @@ mod tests { test_cursor("$a + b$", -3, pos(1, 27.51, 16.83)); } + #[test] + fn test_jump_from_cursor_transform() { + test_cursor( + r#"#rotate(90deg, origin: bottom + left, [hello world])"#, + -5, + pos(1, 10.0, 16.58), + ); + } + #[test] fn test_backlink() { let s = "#footnote[Hi]"; diff --git a/crates/typst-library/src/visualize/curve.rs b/crates/typst-library/src/visualize/curve.rs index fb5151e8f..f797ff852 100644 --- a/crates/typst-library/src/visualize/curve.rs +++ b/crates/typst-library/src/visualize/curve.rs @@ -530,3 +530,30 @@ impl Curve { Size::new(max_x - min_x, max_y - min_y) } } + +fn point_to_kurbo(point: Point) -> kurbo::Point { + kurbo::Point::new(point.x.to_raw(), point.y.to_raw()) +} + +impl Curve { + fn to_kurbo(&self) -> kurbo::BezPath { + use kurbo::{BezPath, PathEl}; + + let path = self.0.iter().map(|item| match *item { + CurveItem::Move(point) => PathEl::MoveTo(point_to_kurbo(point)), + CurveItem::Line(point) => PathEl::LineTo(point_to_kurbo(point)), + CurveItem::Cubic(point, point1, point2) => PathEl::CurveTo( + point_to_kurbo(point), + point_to_kurbo(point1), + point_to_kurbo(point2), + ), + CurveItem::Close => PathEl::ClosePath, + }); + BezPath::from_vec(path.collect()) + } + + /// When this curve is interpreted as a clip mask, would it contain `point`? + pub fn contains(&self, needle: Point) -> bool { + kurbo::Shape::contains(&self.to_kurbo(), point_to_kurbo(needle)) + } +}