diff --git a/src/frame.rs b/src/frame.rs index f889601eb..2cf584d84 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -478,7 +478,7 @@ pub fn rect_paths( ]; for (side, radius) in sides.into_iter().zip(radius.windows(2)) { - let stroke_continuity = strokes.get(side) == strokes.get(side.clockwise()); + let stroke_continuity = strokes.get(side) == strokes.get(side.next_cw()); connection = connection.advance(stroke_continuity && side != Side::Left); always_continuous &= stroke_continuity; diff --git a/src/geom/sides.rs b/src/geom/sides.rs index 255c21eef..f214a1bf3 100644 --- a/src/geom/sides.rs +++ b/src/geom/sides.rs @@ -128,7 +128,7 @@ impl Side { } /// The next side, clockwise. - pub fn clockwise(self) -> Self { + pub fn next_cw(self) -> Self { match self { Self::Left => Self::Top, Self::Top => Self::Right, @@ -138,7 +138,7 @@ impl Side { } /// The next side, counter-clockwise. - pub fn counter_clockwise(self) -> Self { + pub fn next_ccw(self) -> Self { match self { Self::Left => Self::Bottom, Self::Top => Self::Left, diff --git a/src/library/graphics/shape.rs b/src/library/graphics/shape.rs index 7a1bfb1f3..640c879b9 100644 --- a/src/library/graphics/shape.rs +++ b/src/library/graphics/shape.rs @@ -102,6 +102,7 @@ castable! { } }, Value::Length(l) => Sides::splat(Some(l.into())), + Value::Ratio(r) => Sides::splat(Some(r.into())), Value::Relative(r) => Sides::splat(Some(r)), } @@ -117,40 +118,44 @@ impl Layout for AngularNode { let inset = styles.get(Self::INSET); // Pad the child. - let child = child - .clone() - .padded(inset.map(|side| side.map(|abs| RawLength::from(abs)))); + let child = child.clone().padded(inset.map(|side| side.map(RawLength::from))); let mut pod = Regions::one(regions.first, regions.base, regions.expand); frames = child.layout(ctx, &pod, styles)?; // Relayout with full expansion into square region to make sure // the result is really a square or circle. - let length = if regions.expand.x || regions.expand.y { - let target = regions.expand.select(regions.first, Size::zero()); - target.x.max(target.y) - } else { - let size = frames[0].size; - let desired = size.x.max(size.y); - desired.min(regions.first.x).min(regions.first.y) - }; + if is_quadratic(S) { + let length = if regions.expand.x || regions.expand.y { + let target = regions.expand.select(regions.first, Size::zero()); + target.x.max(target.y) + } else { + let size = frames[0].size; + let desired = size.x.max(size.y); + desired.min(regions.first.x).min(regions.first.y) + }; - pod.first = Size::splat(length); - pod.expand = Spec::splat(true); - frames = child.layout(ctx, &pod, styles)?; + pod.first = Size::splat(length); + pod.expand = Spec::splat(true); + frames = child.layout(ctx, &pod, styles)?; + } } else { // The default size that a shape takes on if it has no child and // enough space. let mut size = Size::new(Length::pt(45.0), Length::pt(30.0)).min(regions.first); - let length = if regions.expand.x || regions.expand.y { - let target = regions.expand.select(regions.first, Size::zero()); - target.x.max(target.y) + if is_quadratic(S) { + let length = if regions.expand.x || regions.expand.y { + let target = regions.expand.select(regions.first, Size::zero()); + target.x.max(target.y) + } else { + size.x.min(size.y) + }; + size = Size::splat(length); } else { - size.x.min(size.y) - }; - size = Size::splat(length); + size = regions.expand.select(regions.first, size); + } frames = vec![Arc::new(Frame::new(size))]; } @@ -162,27 +167,38 @@ impl Layout for AngularNode { let stroke = match styles.get(Self::STROKE) { Smart::Auto if fill.is_none() => Sides::splat(Some(Stroke::default())), Smart::Auto => Sides::splat(None), - Smart::Custom(strokes) => strokes.map(|s| Some(s.unwrap_or_default())), + Smart::Custom(strokes) => strokes.map(|s| s.map(|s| s.unwrap_or_default())), }; - let radius = { - let radius = styles.get(Self::RADIUS); - - Sides { - left: radius.left.relative_to(frame.size.x / 2.0), - top: radius.top.relative_to(frame.size.y / 2.0), - right: radius.right.relative_to(frame.size.x / 2.0), - bottom: radius.bottom.relative_to(frame.size.y / 2.0), - } + let outset = styles.get(Self::OUTSET); + let outset = Sides { + left: outset.left.relative_to(frame.size.x), + top: outset.top.relative_to(frame.size.y), + right: outset.right.relative_to(frame.size.x), + bottom: outset.bottom.relative_to(frame.size.y), }; + let size = Spec::new( + frame.size.x + outset.left + outset.right, + frame.size.y + outset.top + outset.bottom, + ); + + let radius = styles.get(Self::RADIUS); + let radius = Sides { + left: radius.left.relative_to(size.x / 2.0), + top: radius.top.relative_to(size.y / 2.0), + right: radius.right.relative_to(size.x / 2.0), + bottom: radius.bottom.relative_to(size.y / 2.0), + }; + + if fill.is_some() || stroke.iter().any(Option::is_some) { let shape = Shape { - geometry: Geometry::Rect(frame.size, radius), + geometry: Geometry::Rect(size, radius), fill, stroke, }; - frame.prepend(Point::zero(), Element::Shape(shape)); + frame.prepend(Point::new(-outset.left, -outset.top), Element::Shape(shape)); } // Apply link if it exists. @@ -208,3 +224,13 @@ const CIRCLE: ShapeKind = 2; /// A curve around two focal points. const ELLIPSE: ShapeKind = 3; + +/// Whether a shape kind is curvy. +fn is_round(kind: ShapeKind) -> bool { + matches!(kind, CIRCLE | ELLIPSE) +} + +/// Whether a shape kind has equal side length. +fn is_quadratic(kind: ShapeKind) -> bool { + matches!(kind, SQUARE | CIRCLE) +} diff --git a/src/model/styles.rs b/src/model/styles.rs index 2e752625b..00d1df0fa 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use super::{Content, Show, ShowNode}; use crate::diag::{At, TypResult}; use crate::eval::{Args, Func, Node, Smart, Value}; -use crate::geom::{Numeric, Relative, Sides, Spec}; +use crate::geom::{Length, Numeric, Relative, Sides, Spec}; use crate::library::layout::PageNode; use crate::library::structure::{EnumNode, ListNode}; use crate::library::text::{FontFamily, ParNode, TextNode}; @@ -459,17 +459,30 @@ where } } -impl Fold for Sides> +impl Fold for Sides where - T: Default, + T: Fold, { - type Output = Sides; + type Output = Sides; + + fn fold(self, outer: Self::Output) -> Self::Output { + Sides { + left: self.left.fold(outer.left), + top: self.top.fold(outer.top), + right: self.right.fold(outer.right), + bottom: self.bottom.fold(outer.bottom), + } + } +} + +impl Fold for Sides>> { + type Output = Sides>; fn fold(self, outer: Self::Output) -> Self::Output { Sides { left: self.left.unwrap_or(outer.left), - right: self.right.unwrap_or(outer.right), top: self.top.unwrap_or(outer.top), + right: self.right.unwrap_or(outer.right), bottom: self.bottom.unwrap_or(outer.bottom), } } diff --git a/tests/typ/code/let.typ b/tests/typ/code/let.typ index a95d651aa..c3be64a5d 100644 --- a/tests/typ/code/let.typ +++ b/tests/typ/code/let.typ @@ -11,7 +11,7 @@ // Syntax sugar for function definitions. #let fill = conifer -#let rect(body) = rect(width: 2cm, fill: fill, padding: 5pt, body) +#let rect(body) = rect(width: 2cm, fill: fill, inset: 5pt, body) #rect[Hi!] --- diff --git a/tests/typ/code/target.typ b/tests/typ/code/target.typ index 6c3215920..b0a3fbf34 100644 --- a/tests/typ/code/target.typ +++ b/tests/typ/code/target.typ @@ -7,6 +7,6 @@ #let d = 3 #let value = [hi] #let item(a, b) = a + b -#let fn = rect.with(fill: conifer, padding: 5pt) +#let fn = rect.with(fill: conifer, inset: 5pt) Some _includable_ text. diff --git a/tests/typ/graphics/shape-aspect.typ b/tests/typ/graphics/shape-aspect.typ index 970857b66..70d689f7d 100644 --- a/tests/typ/graphics/shape-aspect.typ +++ b/tests/typ/graphics/shape-aspect.typ @@ -11,7 +11,7 @@ --- // Test alignment in automatically sized square and circle. #set text(8pt) -#square(padding: 4pt)[ +#square(inset: 4pt)[ Hey there, #align(center + bottom, rotate(180deg, [you!])) ] #circle(align(center + horizon, [Hey.])) diff --git a/tests/typ/graphics/shape-circle.typ b/tests/typ/graphics/shape-circle.typ index dc1e3f242..13ff67de1 100644 --- a/tests/typ/graphics/shape-circle.typ +++ b/tests/typ/graphics/shape-circle.typ @@ -16,13 +16,13 @@ Auto-sized circle. \ Center-aligned rect in auto-sized circle. #circle(fill: forest, stroke: conifer, align(center + horizon, - rect(fill: conifer, padding: 5pt)[But, soft!] + rect(fill: conifer, inset: 5pt)[But, soft!] ) ) Rect in auto-sized circle. \ #circle(fill: forest, - rect(fill: conifer, stroke: white, padding: 4pt)[ + rect(fill: conifer, stroke: white, inset: 4pt)[ #set text(8pt) But, soft! what light through yonder window breaks? ] diff --git a/tests/typ/graphics/shape-rect.typ b/tests/typ/graphics/shape-rect.typ index e035fc91d..52fe03eac 100644 --- a/tests/typ/graphics/shape-rect.typ +++ b/tests/typ/graphics/shape-rect.typ @@ -8,7 +8,7 @@ #set page(width: 150pt) // Fit to text. -#rect(fill: conifer, padding: 3pt)[Textbox] +#rect(fill: conifer, inset: 3pt)[Textbox] // Empty with fixed width and height. #block(rect( @@ -18,7 +18,7 @@ )) // Fixed width, text height. -#rect(width: 2cm, fill: rgb("9650d6"), padding: 5pt)[Fixed and padded] +#rect(width: 2cm, fill: rgb("9650d6"), inset: 5pt)[Fixed and padded] // Page width, fixed height. #rect(height: 1cm, width: 100%, fill: rgb("734ced"))[Topleft] diff --git a/tests/typ/graphics/shape-square.typ b/tests/typ/graphics/shape-square.typ index c4ece7786..622fa9c82 100644 --- a/tests/typ/graphics/shape-square.typ +++ b/tests/typ/graphics/shape-square.typ @@ -7,7 +7,7 @@ --- // Test auto-sized square. -#square(fill: eastern, padding: 5pt)[ +#square(fill: eastern, inset: 5pt)[ #set text(fill: white, weight: "bold") Typst ] diff --git a/tests/typ/layout/columns.typ b/tests/typ/layout/columns.typ index ce291fb2f..1e77e6bc7 100644 --- a/tests/typ/layout/columns.typ +++ b/tests/typ/layout/columns.typ @@ -16,7 +16,7 @@ // Test the `columns` function. #set page(width: auto) -#rect(width: 180pt, height: 100pt, padding: 8pt, columns(2, [ +#rect(width: 180pt, height: 100pt, inset: 8pt, columns(2, [ A special plight has befallen our document. Columns in text boxes reigned down unto the soil to waste a year's crop of rich layouts. @@ -40,7 +40,7 @@ a page for a test but it does get the job done. // Test the expansion behavior. #set page(height: 2.5cm, width: 7.05cm) -#rect(padding: 6pt, columns(2, [ +#rect(inset: 6pt, columns(2, [ ABC \ BCD #colbreak() @@ -73,7 +73,7 @@ D // Test an empty second column. #set page(width: 7.05cm, columns: 2) -#rect(width: 100%, padding: 3pt)[So there isn't anything in the second column?] +#rect(width: 100%, inset: 3pt)[So there isn't anything in the second column?] --- // Test columns when one of them is empty. diff --git a/tests/typ/style/construct.typ b/tests/typ/style/construct.typ index f01b534b4..890c4b94f 100644 --- a/tests/typ/style/construct.typ +++ b/tests/typ/style/construct.typ @@ -16,17 +16,17 @@ // but the B should be center-aligned. #set par(align: center) #par(align: right)[ - A #rect(width: 2cm, fill: conifer, padding: 4pt)[B] + A #rect(width: 2cm, fill: conifer, inset: 4pt)[B] ] --- // The inner rectangle should also be yellow here. // (and therefore invisible) -[#set rect(fill: yellow);#text(1em, rect(padding: 5pt, rect()))] +[#set rect(fill: yellow);#text(1em, rect(inset: 5pt, rect()))] --- // The inner rectangle should not be yellow here. -A #rect(fill: yellow, padding: 5pt, rect()) B +A #rect(fill: yellow, inset: 5pt, rect()) B --- // The inner list should not be indented extra.