diff --git a/crates/typst-layout/src/flow/block.rs b/crates/typst-layout/src/flow/block.rs index 1dd988120..48ca1fba7 100644 --- a/crates/typst-layout/src/flow/block.rs +++ b/crates/typst-layout/src/flow/block.rs @@ -84,8 +84,7 @@ pub fn layout_single_block( // Clip the contents, if requested. if elem.clip(styles) { - let size = frame.size() + outset.relative_to(frame.size()).sum_by_axis(); - frame.clip(clip_rect(size, &radius, &stroke)); + frame.clip(clip_rect(frame.size(), &radius, &stroke, &outset)); } // Add fill and/or stroke. @@ -231,8 +230,7 @@ pub fn layout_multi_block( // Clip the contents, if requested. if clip { - let size = frame.size() + outset.relative_to(frame.size()).sum_by_axis(); - frame.clip(clip_rect(size, &radius, &stroke)); + frame.clip(clip_rect(frame.size(), &radius, &stroke, &outset)); } // Add fill and/or stroke. diff --git a/crates/typst-layout/src/inline/box.rs b/crates/typst-layout/src/inline/box.rs index 30572e4e6..62054bead 100644 --- a/crates/typst-layout/src/inline/box.rs +++ b/crates/typst-layout/src/inline/box.rs @@ -61,8 +61,7 @@ pub fn layout_box( // Clip the contents, if requested. if elem.clip(styles) { - let size = frame.size() + outset.relative_to(frame.size()).sum_by_axis(); - frame.clip(clip_rect(size, &radius, &stroke)); + frame.clip(clip_rect(frame.size(), &radius, &stroke, &outset)); } // Add fill and/or stroke. diff --git a/crates/typst-layout/src/shapes.rs b/crates/typst-layout/src/shapes.rs index 31f8c42be..81be12190 100644 --- a/crates/typst-layout/src/shapes.rs +++ b/crates/typst-layout/src/shapes.rs @@ -412,7 +412,11 @@ pub fn clip_rect( size: Size, radius: &Corners>, stroke: &Sides>, + outset: &Sides>, ) -> Path { + let outset = outset.relative_to(size); + let size = size + outset.sum_by_axis(); + let stroke_widths = stroke .as_ref() .map(|s| s.as_ref().map_or(Abs::zero(), |s| s.thickness / 2.0)); @@ -441,6 +445,7 @@ pub fn clip_rect( } } path.close_path(); + path.translate(Point::new(-outset.left, -outset.top)); path } diff --git a/crates/typst-library/src/visualize/path.rs b/crates/typst-library/src/visualize/path.rs index 76fd0df0f..f592a7124 100644 --- a/crates/typst-library/src/visualize/path.rs +++ b/crates/typst-library/src/visualize/path.rs @@ -1,4 +1,5 @@ use kurbo::ParamCurveExtrema; +use typst_utils::Numeric; use self::PathVertex::{AllControlPoints, MirroredControlPoint, Vertex}; use crate::diag::{bail, SourceResult}; @@ -228,6 +229,25 @@ impl Path { self.0.push(PathItem::ClosePath); } + /// Translate all points in this path by the given offset. + pub fn translate(&mut self, offset: Point) { + if offset.is_zero() { + return; + } + for item in self.0.iter_mut() { + match item { + PathItem::MoveTo(p) => *p += offset, + PathItem::LineTo(p) => *p += offset, + PathItem::CubicTo(p1, p2, p3) => { + *p1 += offset; + *p2 += offset; + *p3 += offset; + } + PathItem::ClosePath => (), + } + } + } + /// Computes the size of bounding box of this path. pub fn bbox_size(&self) -> Size { let mut min_x = Abs::inf(); diff --git a/tests/ref/box-clip-outset.png b/tests/ref/box-clip-outset.png new file mode 100644 index 000000000..21538e85f Binary files /dev/null and b/tests/ref/box-clip-outset.png differ diff --git a/tests/suite/layout/container.typ b/tests/suite/layout/container.typ index e13679675..799300f0d 100644 --- a/tests/suite/layout/container.typ +++ b/tests/suite/layout/container.typ @@ -251,6 +251,19 @@ First! image("/assets/images/rhino.png", width: 30pt) ) +--- box-clip-outset --- +// Test clipping with `outset`. +#set page(height: 60pt) + +#box( + outset: 5pt, + stroke: 2pt + black, + width: 20pt, + height: 20pt, + clip: true, + image("/assets/images/rhino.png", width: 30pt) +) + --- container-layoutable-child --- // Test box/block sizing with directly layoutable child. //