diff --git a/crates/typst-library/src/layout/container.rs b/crates/typst-library/src/layout/container.rs index 36b628643..2e6ccdd2f 100644 --- a/crates/typst-library/src/layout/container.rs +++ b/crates/typst-library/src/layout/container.rs @@ -155,7 +155,7 @@ impl Layout for BoxElem { let outset = self.outset(styles).relative_to(frame.size()); let size = frame.size() + outset.sum_by_axis(); let radius = self.radius(styles); - frame.clip(path_rect(size, radius, &stroke)); + frame.clip(clip_rect(size, radius, &stroke)); } // Add fill and/or stroke. @@ -421,7 +421,7 @@ impl Layout for BlockElem { let outset = self.outset(styles).relative_to(frame.size()); let size = frame.size() + outset.sum_by_axis(); let radius = self.radius(styles); - frame.clip(path_rect(size, radius, &stroke)); + frame.clip(clip_rect(size, radius, &stroke)); } } diff --git a/crates/typst/src/geom/mod.rs b/crates/typst/src/geom/mod.rs index 8ad6cea09..1b2a79bce 100644 --- a/crates/typst/src/geom/mod.rs +++ b/crates/typst/src/geom/mod.rs @@ -46,7 +46,7 @@ pub use self::paint::Paint; pub use self::path::{Path, PathItem}; pub use self::point::Point; pub use self::ratio::Ratio; -pub use self::rect::{path_rect, styled_rect}; +pub use self::rect::{clip_rect, styled_rect}; pub use self::rel::Rel; pub use self::scalar::Scalar; pub use self::shape::{Geometry, Shape}; diff --git a/crates/typst/src/geom/rect.rs b/crates/typst/src/geom/rect.rs index 37b94527f..230f8e8c5 100644 --- a/crates/typst/src/geom/rect.rs +++ b/crates/typst/src/geom/rect.rs @@ -43,16 +43,41 @@ impl PathExtension for Path { } /// Creates a new rectangle as a path. -pub fn path_rect( +pub fn clip_rect( size: Size, radius: Corners>, stroke: &Sides>, ) -> Path { - if stroke.is_uniform() && radius.iter().cloned().all(Rel::is_zero) { - Path::rect(size) + let stroke_widths = stroke + .as_ref() + .map(|s| s.as_ref().map_or(Abs::zero(), |s| s.thickness / 2.0)); + + let max_radius = (size.x.min(size.y)) / 2.0 + + stroke_widths.iter().cloned().min().unwrap_or(Abs::zero()); + + let radius = radius.map(|side| side.relative_to(max_radius * 2.0).min(max_radius)); + + let corners = corners_control_points(size, radius, stroke, stroke_widths); + + let mut path = Path::new(); + if corners.top_left.arc_inner() { + path.arc_move( + corners.top_left.start_inner(), + corners.top_left.center_inner(), + corners.top_left.end_inner(), + ); } else { - segmented_path_rect(size, radius, stroke) + path.move_to(corners.top_left.center_inner()); } + for corner in [&corners.top_right, &corners.bottom_right, &corners.bottom_left] { + if corner.arc_inner() { + path.arc_line(corner.start_inner(), corner.center_inner(), corner.end_inner()) + } else { + path.line_to(corner.center_inner()); + } + } + path.close_path(); + path } /// Create a styled rectangle with shapes. @@ -110,46 +135,6 @@ fn corners_control_points( }) } -fn segmented_path_rect( - size: Size, - radius: Corners>, - strokes: &Sides>, -) -> Path { - let stroke_widths = strokes - .as_ref() - .map(|s| s.as_ref().map_or(Abs::zero(), |s| s.thickness / 2.0)); - - let max_radius = (size.x.min(size.y)) / 2.0 - + stroke_widths.iter().cloned().min().unwrap_or(Abs::zero()); - - let radius = radius.map(|side| side.relative_to(max_radius * 2.0).min(max_radius)); - - // insert stroked sides below filled sides - let mut path = Path::new(); - let corners = corners_control_points(size, radius, strokes, stroke_widths); - let current = corners.iter().find(|c| !c.same).map(|c| c.corner); - if let Some(mut current) = current { - // multiple segments - // start at a corner with a change between sides and iterate clockwise all other corners - let mut last = current; - for _ in 0..4 { - current = current.next_cw(); - if corners.get_ref(current).same { - continue; - } - // create segment - let start = last; - let end = current; - last = current; - path_segment(start, end, &corners, &mut path); - } - } else if strokes.top.is_some() { - // single segment - path_segment(Corner::TopLeft, Corner::TopLeft, &corners, &mut path); - } - path -} - /// Use stroke and fill for the rectangle fn segmented_rect( size: Size, diff --git a/tests/ref/layout/clip.png b/tests/ref/layout/clip.png index c847fc63d..f37bf9ad1 100644 Binary files a/tests/ref/layout/clip.png and b/tests/ref/layout/clip.png differ diff --git a/tests/typ/layout/clip.typ b/tests/typ/layout/clip.typ index d05fdb74d..d20609d82 100644 --- a/tests/typ/layout/clip.typ +++ b/tests/typ/layout/clip.typ @@ -54,3 +54,15 @@ First! clip: true, image("/files/rhino.png", width: 30pt) ) +--- +// Test clipping with `radius`, but without `stroke`. + +#set page(height: 60pt) + +#box( + radius: 5pt, + width: 20pt, + height: 20pt, + clip: true, + image("/files/rhino.png", width: 30pt) +)