diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-02-13 15:14:25 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-02-13 16:04:25 +0100 |
| commit | b1b4e52af9e2da8f8ae1fc17a81ed6cbcbb8f525 (patch) | |
| tree | e97ad140dac6d0414e9b49f7d01abccdcd717d4f /library/src | |
| parent | 72b60dfde751b4a2ab279aa1fcfa559b4a75eb51 (diff) | |
Block sizing
Diffstat (limited to 'library/src')
| -rw-r--r-- | library/src/layout/container.rs | 154 | ||||
| -rw-r--r-- | library/src/layout/enum.rs | 16 | ||||
| -rw-r--r-- | library/src/layout/flow.rs | 13 | ||||
| -rw-r--r-- | library/src/layout/grid.rs | 2 | ||||
| -rw-r--r-- | library/src/layout/pad.rs | 2 | ||||
| -rw-r--r-- | library/src/layout/page.rs | 6 | ||||
| -rw-r--r-- | library/src/layout/place.rs | 4 | ||||
| -rw-r--r-- | library/src/meta/heading.rs | 7 | ||||
| -rw-r--r-- | library/src/text/raw.rs | 7 | ||||
| -rw-r--r-- | library/src/visualize/shape.rs | 20 |
10 files changed, 176 insertions, 55 deletions
diff --git a/library/src/layout/container.rs b/library/src/layout/container.rs index 43823339..8b10f7a6 100644 --- a/library/src/layout/container.rs +++ b/library/src/layout/container.rs @@ -41,20 +41,13 @@ use crate::prelude::*; /// - height: `Rel<Length>` (named) /// The height of the box. /// -/// - baseline: `Rel<Length>` (named) -/// An amount to shift the box's baseline by. -/// -/// ```example -/// Image: #box(baseline: 40%, image("tiger.jpg", width: 2cm)). -/// ``` -/// /// ## Category /// layout #[func] #[capable(Layout)] #[derive(Debug, Hash)] pub struct BoxNode { - /// The content to be sized. + /// The box's content. pub body: Content, /// The box's width. pub width: Sizing, @@ -64,7 +57,11 @@ pub struct BoxNode { #[node] impl BoxNode { - /// The box's baseline shift. + /// An amount to shift the box's baseline by. + /// + /// ```example + /// Image: #box(baseline: 40%, image("tiger.jpg", width: 2cm)). + /// ``` #[property(resolve)] pub const BASELINE: Rel<Length> = Rel::zero(); @@ -127,11 +124,12 @@ impl Layout for BoxNode { // Resolve the sizing to a concrete size. let sizing = Axes::new(width, self.height); + let expand = sizing.as_ref().map(Smart::is_custom); let size = sizing .resolve(styles) .zip(regions.base()) .map(|(s, b)| s.map(|v| v.relative_to(b))) - .unwrap_or(regions.size); + .unwrap_or(regions.base()); // Apply inset. let mut child = self.body.clone(); @@ -142,8 +140,6 @@ impl Layout for BoxNode { // Select the appropriate base and expansion for the child depending // on whether it is automatically or relatively sized. - let is_auto = sizing.as_ref().map(Smart::is_auto); - let expand = regions.expand | !is_auto; let pod = Regions::one(size, expand); let mut frame = child.layout(vt, styles, pod)?.into_frame(); @@ -181,9 +177,9 @@ impl Layout for BoxNode { /// /// ## Examples /// With a block, you can give a background to content while still allowing it -/// to break across multiple pages. The documentation examples can only have a -/// single page, but the example below demonstrates how this would work. +/// to break across multiple pages. /// ```example +/// #set page(height: 100pt) /// #block( /// fill: luma(230), /// inset: 8pt, @@ -204,27 +200,55 @@ impl Layout for BoxNode { /// More text. /// ``` /// -/// Last but not least, set rules for the block function can be used to -/// configure the spacing around arbitrary block-level elements. -/// ```example -/// #set align(center) -/// #show math.formula: set block(above: 8pt, below: 16pt) -/// -/// This sum of $x$ and $y$: -/// $ x + y = z $ -/// A second paragraph. -/// ``` -/// /// ## Parameters /// - body: `Content` (positional) /// The contents of the block. /// +/// - width: `Smart<Rel<Length>>` (named) +/// The block's width. +/// +/// ```example +/// #set align(center) +/// #block( +/// width: 60%, +/// inset: 8pt, +/// fill: silver, +/// lorem(10), +/// ) +/// ``` +/// +/// - height: `Smart<Rel<Length>>` (named) +/// The block's height. When the height is larger than the remaining space on +/// a page and [`breakable`]($func/block.breakable) is `{true}`, the block +/// will continue on the next page with the remaining height. +/// +/// ```example +/// #set page(height: 80pt) +/// #set align(center) +/// #block( +/// width: 80%, +/// height: 150%, +/// fill: aqua, +/// ) +/// ``` +/// /// - spacing: `Spacing` (named, settable) -/// The spacing around this block. +/// The spacing around this block. This is shorthand to set `above` and +/// `below` to the same value. +/// +/// ```example +/// #set align(center) +/// #show math.formula: set block(above: 8pt, below: 16pt) +/// +/// This sum of $x$ and $y$: +/// $ x + y = z $ +/// A second paragraph. +/// ``` /// /// - above: `Spacing` (named, settable) /// The spacing between this block and its predecessor. Takes precedence over -/// `spacing`. +/// `spacing`. Can be used in combination with a show rule to adjust the +/// spacing around arbitrary block-level elements. /// /// The default value is `{1.2em}`. /// @@ -240,11 +264,30 @@ impl Layout for BoxNode { #[capable(Layout)] #[derive(Debug, Hash)] pub struct BlockNode { + /// The block's content. pub body: Content, + /// The box's width. + pub width: Smart<Rel<Length>>, + /// The box's height. + pub height: Smart<Rel<Length>>, } #[node] impl BlockNode { + /// Whether the block can be broken and continue on the next page. + /// + /// Defaults to `{true}`. + /// ```example + /// #set page(height: 80pt) + /// The following block will + /// jump to its own page. + /// #block( + /// breakable: false, + /// lorem(15), + /// ) + /// ``` + pub const BREAKABLE: bool = true; + /// The block's background color. See the /// [rectangle's documentation]($func/rect.fill) for more details. pub const FILL: Option<Paint> = None; @@ -285,7 +328,9 @@ impl BlockNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { let body = args.eat()?.unwrap_or_default(); - Ok(Self { body }.pack()) + let width = args.named("width")?.unwrap_or_default(); + let height = args.named("height")?.unwrap_or_default(); + Ok(Self { body, width, height }.pack()) } fn set(...) { @@ -315,8 +360,52 @@ impl Layout for BlockNode { child = child.clone().padded(inset.map(|side| side.map(Length::from))); } + // Resolve the sizing to a concrete size. + let sizing = Axes::new(self.width, self.height); + let mut expand = sizing.as_ref().map(Smart::is_custom); + let mut size = sizing + .resolve(styles) + .zip(regions.base()) + .map(|(s, b)| s.map(|v| v.relative_to(b))) + .unwrap_or(regions.base()); + // Layout the child. - let mut frames = child.layout(vt, styles, regions)?.into_frames(); + let mut frames = if styles.get(Self::BREAKABLE) { + // Measure to ensure frames for all regions have the same width. + if self.width == Smart::Auto { + let pod = Regions::one(size, Axes::splat(false)); + let frame = child.layout(vt, styles, pod)?.into_frame(); + size.x = frame.width(); + expand.x = true; + } + + let mut pod = regions; + pod.size.x = size.x; + pod.expand = expand; + + // Generate backlog for fixed height. + let mut heights = vec![]; + if self.height.is_custom() { + let mut remaining = size.y; + for region in regions.iter() { + let limited = region.y.min(remaining); + heights.push(limited); + remaining -= limited; + if Abs::zero().fits(remaining) { + break; + } + } + + pod.size.y = heights[0]; + pod.backlog = &heights[1..]; + pod.last = None; + } + + child.layout(vt, styles, pod)?.into_frames() + } else { + let pod = Regions::one(size, expand); + child.layout(vt, styles, pod)?.into_frames() + }; // Prepare fill and stroke. let fill = styles.get(Self::FILL); @@ -326,9 +415,14 @@ impl Layout for BlockNode { // Add fill and/or stroke. if fill.is_some() || stroke.iter().any(Option::is_some) { + let mut skip = false; + if let [first, rest @ ..] = frames.as_slice() { + skip = first.is_empty() && rest.iter().any(|frame| !frame.is_empty()); + } + let outset = styles.get(Self::OUTSET); let radius = styles.get(Self::RADIUS); - for frame in &mut frames { + for frame in frames.iter_mut().skip(skip as usize) { frame.fill_and_stroke(fill, stroke, outset, radius); } } diff --git a/library/src/layout/enum.rs b/library/src/layout/enum.rs index 9a83420c..585b20e5 100644 --- a/library/src/layout/enum.rs +++ b/library/src/layout/enum.rs @@ -25,12 +25,21 @@ use crate::prelude::*; /// #enum[First][Second] /// ``` /// +/// You can easily switch all your enumerations to a different numbering style +/// with a set rule. +/// ```example +/// #set enum(numbering: "a)") +/// +/// + Starting off ... +/// + Don't forget step two +/// ``` +/// /// ## Syntax /// This functions also has dedicated syntax: /// /// - Starting a line with a plus sign creates an automatically numbered /// enumeration item. -/// - Start a line with a number followed by a dot creates an explicitly +/// - Starting a line with a number followed by a dot creates an explicitly /// numbered enumeration item. /// /// Enumeration items can contain multiple paragraphs and other block-level @@ -98,10 +107,13 @@ impl EnumNode { /// /// ```example /// #set enum(numbering: "(a)") - /// /// + Different /// + Numbering /// + Style + /// + /// #set enum(numbering: n => super[#n]) + /// + Superscript + /// + Numbering! /// ``` #[property(referenced)] pub const NUMBERING: Numbering = diff --git a/library/src/layout/flow.rs b/library/src/layout/flow.rs index db9eed8d..00eeb537 100644 --- a/library/src/layout/flow.rs +++ b/library/src/layout/flow.rs @@ -44,7 +44,10 @@ impl Layout for FlowNode { } else if child.has::<dyn Layout>() { layouter.layout_multiple(vt, child, styles)?; } else if child.is::<ColbreakNode>() { - layouter.finish_region(); + if !layouter.regions.backlog.is_empty() || layouter.regions.last.is_some() + { + layouter.finish_region(); + } } else { panic!("unexpected flow child: {child:?}"); } @@ -207,7 +210,10 @@ impl<'a> FlowLayouter<'a> { // Layout the block itself. let sticky = styles.get(BlockNode::STICKY); let fragment = block.layout(vt, styles, self.regions)?; - for frame in fragment { + for (i, frame) in fragment.into_iter().enumerate() { + if i > 0 { + self.finish_region(); + } self.layout_item(FlowItem::Frame(frame, aligns, sticky)); } @@ -264,8 +270,7 @@ impl<'a> FlowLayouter<'a> { // Determine the size of the flow in this region depending on whether // the region expands. - let mut size = self.expand.select(self.full, used); - size.y.set_min(self.full.y); + let mut size = self.expand.select(self.full, used).min(self.full); // Account for fractional spacing in the size calculation. let remaining = self.full.y - used.y; diff --git a/library/src/layout/grid.rs b/library/src/layout/grid.rs index 5e8b50d1..92bf19d1 100644 --- a/library/src/layout/grid.rs +++ b/library/src/layout/grid.rs @@ -37,7 +37,7 @@ use super::Sizing; /// /// ## Example /// ```example -/// #set text(10pt, weight: "bold") +/// #set text(10pt, style: "italic") /// #let cell = rect.with( /// inset: 8pt, /// fill: rgb("e4e5ea"), diff --git a/library/src/layout/pad.rs b/library/src/layout/pad.rs index e43763e5..4fc2ff29 100644 --- a/library/src/layout/pad.rs +++ b/library/src/layout/pad.rs @@ -13,7 +13,7 @@ use crate::prelude::*; /// /// #pad(x: 16pt, image("typing.jpg")) /// _Typing speeds can be -/// measured in words per minute._ +/// measured in words per minute._ /// ``` /// /// ## Parameters diff --git a/library/src/layout/page.rs b/library/src/layout/page.rs index e38e6d87..ac0e27cb 100644 --- a/library/src/layout/page.rs +++ b/library/src/layout/page.rs @@ -351,14 +351,14 @@ impl Debug for PageNode { /// more details on compound theory. /// #pagebreak() /// -/// // Examples only render the first -/// // page, so this is not visible. /// == Compound Theory +/// In 1984, the first ... /// ``` /// /// ## Parameters /// - weak: `bool` (named) -/// If `{true}`, the page break is skipped if the current page is already empty. +/// If `{true}`, the page break is skipped if the current page is already +/// empty. /// /// ## Category /// layout diff --git a/library/src/layout/place.rs b/library/src/layout/place.rs index c64766cc..05de369b 100644 --- a/library/src/layout/place.rs +++ b/library/src/layout/place.rs @@ -16,8 +16,8 @@ use crate::prelude::*; /// #place( /// top + right, /// square( -/// width: 10pt, -/// stroke: 1pt + blue +/// width: 20pt, +/// stroke: 2pt + blue /// ), /// ) /// ``` diff --git a/library/src/meta/heading.rs b/library/src/meta/heading.rs index bc788243..b5b0fd9e 100644 --- a/library/src/meta/heading.rs +++ b/library/src/meta/heading.rs @@ -147,7 +147,12 @@ impl Show for HeadingNode { if numbers != Value::None { realized = numbers.display() + SpaceNode.pack() + realized; } - Ok(BlockNode { body: realized }.pack()) + Ok(BlockNode { + body: realized, + width: Smart::Auto, + height: Smart::Auto, + } + .pack()) } } diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs index a20ec6f3..aa3a10d7 100644 --- a/library/src/text/raw.rs +++ b/library/src/text/raw.rs @@ -199,7 +199,12 @@ impl Show for RawNode { }; if self.block { - realized = BlockNode { body: realized }.pack(); + realized = BlockNode { + body: realized, + width: Smart::Auto, + height: Smart::Auto, + } + .pack(); } Ok(realized) diff --git a/library/src/visualize/shape.rs b/library/src/visualize/shape.rs index 7eddc6a6..e5259d91 100644 --- a/library/src/visualize/shape.rs +++ b/library/src/visualize/shape.rs @@ -160,6 +160,8 @@ impl Layout for RectNode { ) -> SourceResult<Fragment> { layout( vt, + styles, + regions, ShapeKind::Rect, &self.body, Axes::new(self.width, self.height), @@ -168,8 +170,6 @@ impl Layout for RectNode { styles.get(Self::INSET), styles.get(Self::OUTSET), styles.get(Self::RADIUS), - styles, - regions, ) } } @@ -278,6 +278,8 @@ impl Layout for SquareNode { ) -> SourceResult<Fragment> { layout( vt, + styles, + regions, ShapeKind::Square, &self.body, Axes::new(self.width, self.height), @@ -286,8 +288,6 @@ impl Layout for SquareNode { styles.get(Self::INSET), styles.get(Self::OUTSET), styles.get(Self::RADIUS), - styles, - regions, ) } } @@ -372,6 +372,8 @@ impl Layout for EllipseNode { ) -> SourceResult<Fragment> { layout( vt, + styles, + regions, ShapeKind::Ellipse, &self.body, Axes::new(self.width, self.height), @@ -380,8 +382,6 @@ impl Layout for EllipseNode { styles.get(Self::INSET), styles.get(Self::OUTSET), Corners::splat(Rel::zero()), - styles, - regions, ) } } @@ -485,6 +485,8 @@ impl Layout for CircleNode { ) -> SourceResult<Fragment> { layout( vt, + styles, + regions, ShapeKind::Circle, &self.body, Axes::new(self.width, self.height), @@ -493,8 +495,6 @@ impl Layout for CircleNode { styles.get(Self::INSET), styles.get(Self::OUTSET), Corners::splat(Rel::zero()), - styles, - regions, ) } } @@ -502,6 +502,8 @@ impl Layout for CircleNode { /// Layout a shape. fn layout( vt: &mut Vt, + styles: StyleChain, + regions: Regions, kind: ShapeKind, body: &Option<Content>, sizing: Axes<Smart<Rel<Length>>>, @@ -510,8 +512,6 @@ fn layout( mut inset: Sides<Rel<Abs>>, outset: Sides<Rel<Abs>>, radius: Corners<Rel<Abs>>, - styles: StyleChain, - regions: Regions, ) -> SourceResult<Fragment> { let resolved = sizing .zip(regions.base()) |
