diff --git a/crates/typst-layout/src/shapes.rs b/crates/typst-layout/src/shapes.rs index a35021721..db9acece3 100644 --- a/crates/typst-layout/src/shapes.rs +++ b/crates/typst-layout/src/shapes.rs @@ -348,29 +348,48 @@ fn layout_shape( pod.size = crate::pad::shrink(region.size, &inset); } - // Layout the child. - frame = crate::layout_frame(engine, child, locator.relayout(), styles, pod)?; - - // If the child is a square or circle, relayout with full expansion into - // square region to make sure the result is really quadratic. + // If the shape is quadratic, we first measure it to determine its size + // and then layout with full expansion to force the aspect ratio and + // make sure it's really quadratic. if kind.is_quadratic() { - let length = frame.size().max_by_side().min(pod.size.min_by_side()); - let quad_pod = Region::new(Size::splat(length), Axes::splat(true)); - frame = crate::layout_frame(engine, child, locator, styles, quad_pod)?; + let length = match quadratic_size(pod) { + Some(length) => length, + None => { + // Take as much as the child wants, but without overflowing. + crate::layout_frame(engine, child, locator.relayout(), styles, pod)? + .size() + .max_by_side() + .min(pod.size.min_by_side()) + } + }; + + pod = Region::new(Size::splat(length), Axes::splat(true)); } + // Layout the child. + frame = crate::layout_frame(engine, child, locator, styles, pod)?; + // Apply the inset. if has_inset { crate::pad::grow(&mut frame, &inset); } } else { - // The default size that a shape takes on if it has no child and - // enough space. - let default = Size::new(Abs::pt(45.0), Abs::pt(30.0)); - let mut size = region.expand.select(region.size, default.min(region.size)); - if kind.is_quadratic() { - size = Size::splat(size.min_by_side()); - } + // The default size that a shape takes on if it has no child and no + // forced sizes. + let default = Size::new(Abs::pt(45.0), Abs::pt(30.0)).min(region.size); + + let size = if kind.is_quadratic() { + Size::splat(match quadratic_size(region) { + Some(length) => length, + None => default.min_by_side(), + }) + } else { + // For each dimension, pick the region size if forced, otherwise + // use the default size (or the region size if the default + // is too large for the region). + region.expand.select(region.size, default) + }; + frame = Frame::soft(size); } @@ -411,6 +430,24 @@ fn layout_shape( Ok(frame) } +/// Determines the forced size of a quadratic shape based on the region, if any. +/// +/// The size is forced if at least one axis is expanded because `expand` is +/// `true` for axes whose size was manually specified by the user. +fn quadratic_size(region: Region) -> Option { + if region.expand.x && region.expand.y { + // If both `width` and `height` are specified, we choose the + // smaller one. + Some(region.size.x.min(region.size.y)) + } else if region.expand.x { + Some(region.size.x) + } else if region.expand.y { + Some(region.size.y) + } else { + None + } +} + /// Creates a new rectangle as a path. pub fn clip_rect( size: Size, diff --git a/tests/ref/circle-beyond-page-width-overflows.png b/tests/ref/circle-beyond-page-width-overflows.png new file mode 100644 index 000000000..941cb0092 Binary files /dev/null and b/tests/ref/circle-beyond-page-width-overflows.png differ diff --git a/tests/ref/circle-size-beyond-default.png b/tests/ref/circle-size-beyond-default.png new file mode 100644 index 000000000..f25aad070 Binary files /dev/null and b/tests/ref/circle-size-beyond-default.png differ diff --git a/tests/ref/rect-size-beyond-default.png b/tests/ref/rect-size-beyond-default.png new file mode 100644 index 000000000..1f4d80fe1 Binary files /dev/null and b/tests/ref/rect-size-beyond-default.png differ diff --git a/tests/ref/square-overflow.png b/tests/ref/square-no-overflow.png similarity index 100% rename from tests/ref/square-overflow.png rename to tests/ref/square-no-overflow.png diff --git a/tests/ref/square-overflow-forced-height.png b/tests/ref/square-overflow-forced-height.png new file mode 100644 index 000000000..f7cb0ee35 Binary files /dev/null and b/tests/ref/square-overflow-forced-height.png differ diff --git a/tests/ref/square-overflow-forced-width.png b/tests/ref/square-overflow-forced-width.png new file mode 100644 index 000000000..466718168 Binary files /dev/null and b/tests/ref/square-overflow-forced-width.png differ diff --git a/tests/ref/square-size-beyond-default.png b/tests/ref/square-size-beyond-default.png new file mode 100644 index 000000000..a513a7db1 Binary files /dev/null and b/tests/ref/square-size-beyond-default.png differ diff --git a/tests/suite/visualize/circle.typ b/tests/suite/visualize/circle.typ index 43459eb5e..0687e0682 100644 --- a/tests/suite/visualize/circle.typ +++ b/tests/suite/visualize/circle.typ @@ -67,3 +67,15 @@ Expanded by height. circle(width: 10%), circle(height: 50%), ) + +--- circle-size-beyond-default --- +// Test that setting a circle's height beyond its default sizes it correctly. +#circle() +#circle(height: 60pt) +#circle(width: 60pt) +#circle(radius: 30pt) + +--- circle-beyond-page-width-overflows --- +// Test that sizing a circle beyond the page width correctly overflows the page. +#set page(height: 100pt) +#circle(width: 150%) diff --git a/tests/suite/visualize/rect.typ b/tests/suite/visualize/rect.typ index f84fafcb1..5dfe29f3f 100644 --- a/tests/suite/visualize/rect.typ +++ b/tests/suite/visualize/rect.typ @@ -105,3 +105,9 @@ #align(right, rect(width: -1cm, fill: gradient.linear(red, blue))[Reverse right]) #align(right, rect(width: 1cm, fill: gradient.linear(red, blue))[Right]) + +--- rect-size-beyond-default --- +// Test that setting a rectangle's height beyond its default sizes it correctly. +#rect() +#rect(height: 60pt) +#rect(width: 60pt) diff --git a/tests/suite/visualize/square.typ b/tests/suite/visualize/square.typ index b346561df..bb7bbd9d6 100644 --- a/tests/suite/visualize/square.typ +++ b/tests/suite/visualize/square.typ @@ -77,11 +77,25 @@ #set page(width: 20pt, height: 10pt, margin: 0pt) #stack(dir: ltr, square(fill: forest), square(fill: conifer)) ---- square-overflow --- +--- square-no-overflow --- // Test that square doesn't overflow due to its aspect ratio. #set page(width: 40pt, height: 25pt, margin: 5pt) -#square(width: 100%) -#square(width: 100%)[Hello there] +#square() +#square[Hello there] + +--- square-overflow-forced-width --- +// Test that a width-overflowing square is laid out regardless of the +// presence of inner content. +#set page(width: 60pt, height: 100pt) +#square(width: 150%) +#square(width: 150%)[Hello there] + +--- square-overflow-forced-height --- +// Test that a height-overflowing square is laid out regardless of the +// presence of inner content. +#set page(width: 120pt, height: 60pt) +#square(height: 150%) +#square(height: 150%)[Hello there] --- square-size-relative-invalid --- // Size cannot be relative because we wouldn't know @@ -144,3 +158,10 @@ // Test that square sets correct base for its content. #set page(height: 80pt) #square(width: 40%, rect(width: 60%, height: 80%)) + +--- square-size-beyond-default --- +// Test that setting a square's height beyond its default sizes it correctly. +#square() +#square(height: 60pt) +#square(width: 60pt) +#square(size: 60pt)