diff options
Diffstat (limited to 'library/src/layout/container.rs')
| -rw-r--r-- | library/src/layout/container.rs | 154 |
1 files changed, 124 insertions, 30 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); } } |
