From fd90736fb6239409210f845a8589ba3d6b849ef3 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sun, 12 Feb 2023 22:04:27 +0100 Subject: [PATCH] Support fractional width for `box` --- library/src/layout/container.rs | 57 ++++++++++++++- library/src/layout/enum.rs | 10 +-- library/src/layout/flow.rs | 4 +- library/src/layout/grid.rs | 98 +++++++++----------------- library/src/layout/list.rs | 10 +-- library/src/layout/mod.rs | 20 +++--- library/src/layout/par.rs | 118 +++++++++++++++++--------------- library/src/layout/repeat.rs | 37 +++++++++- library/src/layout/spacing.rs | 22 +++--- library/src/layout/stack.rs | 4 +- library/src/layout/table.rs | 14 ++-- library/src/layout/terms.rs | 6 +- library/src/math/mod.rs | 2 +- library/src/meta/outline.rs | 16 ++++- src/doc.rs | 2 +- tests/ref/layout/container.png | Bin 7512 -> 8332 bytes tests/ref/layout/repeat.png | Bin 9344 -> 9260 bytes tests/typ/layout/container.typ | 4 ++ tests/typ/layout/repeat.typ | 16 ++--- 19 files changed, 255 insertions(+), 185 deletions(-) diff --git a/library/src/layout/container.rs b/library/src/layout/container.rs index 09cfac8db..930a27e71 100644 --- a/library/src/layout/container.rs +++ b/library/src/layout/container.rs @@ -44,7 +44,7 @@ pub struct BoxNode { /// The content to be sized. pub body: Content, /// The box's width. - pub width: Smart>, + pub width: Sizing, /// The box's height. pub height: Smart>, /// The box's baseline shift. @@ -76,8 +76,14 @@ impl Layout for BoxNode { styles: StyleChain, regions: Regions, ) -> SourceResult { + let width = match self.width { + Sizing::Auto => Smart::Auto, + Sizing::Rel(rel) => Smart::Custom(rel), + Sizing::Fr(_) => Smart::Custom(Ratio::one().into()), + }; + // Resolve the sizing to a concrete size. - let sizing = Axes::new(self.width, self.height); + let sizing = Axes::new(width, self.height); let size = sizing .resolve(styles) .zip(regions.base()) @@ -196,3 +202,50 @@ impl Layout for BlockNode { self.0.layout(vt, styles, regions) } } + +/// Defines how to size a grid cell along an axis. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum Sizing { + /// A track that fits its cell's contents. + Auto, + /// A track size specified in absolute terms and relative to the parent's + /// size. + Rel(Rel), + /// A track size specified as a fraction of the remaining free space in the + /// parent. + Fr(Fr), +} + +impl Sizing { + /// Whether this is fractional sizing. + pub fn is_fractional(self) -> bool { + matches!(self, Self::Fr(_)) + } + + pub fn encode(self) -> Value { + match self { + Self::Auto => Value::Auto, + Self::Rel(rel) => Spacing::Rel(rel).encode(), + Self::Fr(fr) => Spacing::Fr(fr).encode(), + } + } + + pub fn encode_slice(vec: &[Sizing]) -> Value { + Value::Array(vec.iter().copied().map(Self::encode).collect()) + } +} + +impl Default for Sizing { + fn default() -> Self { + Self::Auto + } +} + +impl From for Sizing { + fn from(spacing: Spacing) -> Self { + match spacing { + Spacing::Rel(rel) => Self::Rel(rel), + Spacing::Fr(fr) => Self::Fr(fr), + } + } +} diff --git a/library/src/layout/enum.rs b/library/src/layout/enum.rs index b1b186801..9a83420c6 100644 --- a/library/src/layout/enum.rs +++ b/library/src/layout/enum.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use crate::compute::{Numbering, NumberingPattern}; -use crate::layout::{BlockNode, GridNode, ParNode, Spacing, TrackSizing}; +use crate::layout::{BlockNode, GridNode, ParNode, Sizing, Spacing}; use crate::prelude::*; /// # Numbered List @@ -193,10 +193,10 @@ impl Layout for EnumNode { GridNode { tracks: Axes::with_x(vec![ - TrackSizing::Relative(indent.into()), - TrackSizing::Auto, - TrackSizing::Relative(body_indent.into()), - TrackSizing::Auto, + Sizing::Rel(indent.into()), + Sizing::Auto, + Sizing::Rel(body_indent.into()), + Sizing::Auto, ]), gutter: Axes::with_y(vec![gutter.into()]), cells, diff --git a/library/src/layout/flow.rs b/library/src/layout/flow.rs index 7b721c594..db9eed8d4 100644 --- a/library/src/layout/flow.rs +++ b/library/src/layout/flow.rs @@ -113,11 +113,11 @@ impl<'a> FlowLayouter<'a> { /// Layout vertical spacing. fn layout_spacing(&mut self, node: VNode, styles: StyleChain) { self.layout_item(match node.amount { - Spacing::Relative(v) => FlowItem::Absolute( + Spacing::Rel(v) => FlowItem::Absolute( v.resolve(styles).relative_to(self.full.y), node.weakness > 0, ), - Spacing::Fractional(v) => FlowItem::Fractional(v), + Spacing::Fr(v) => FlowItem::Fractional(v), }); } diff --git a/library/src/layout/grid.rs b/library/src/layout/grid.rs index 95e4ac8fe..6fea5bbc9 100644 --- a/library/src/layout/grid.rs +++ b/library/src/layout/grid.rs @@ -1,7 +1,7 @@ use crate::prelude::*; use crate::text::TextNode; -use super::Spacing; +use super::Sizing; /// # Grid /// Arrange content in a grid. @@ -94,9 +94,9 @@ use super::Spacing; #[derive(Debug, Hash)] pub struct GridNode { /// Defines sizing for content rows and columns. - pub tracks: Axes>, + pub tracks: Axes>, /// Defines sizing of gutter rows and columns between content. - pub gutter: Axes>, + pub gutter: Axes>, /// The content to be arranged in a grid. pub cells: Vec, } @@ -122,10 +122,10 @@ impl GridNode { fn field(&self, name: &str) -> Option { match name { - "columns" => Some(TrackSizing::encode_slice(&self.tracks.x)), - "rows" => Some(TrackSizing::encode_slice(&self.tracks.y)), - "column-gutter" => Some(TrackSizing::encode_slice(&self.gutter.x)), - "row-gutter" => Some(TrackSizing::encode_slice(&self.gutter.y)), + "columns" => Some(Sizing::encode_slice(&self.tracks.x)), + "rows" => Some(Sizing::encode_slice(&self.tracks.y)), + "column-gutter" => Some(Sizing::encode_slice(&self.gutter.x)), + "row-gutter" => Some(Sizing::encode_slice(&self.gutter.y)), "cells" => Some(Value::Array( self.cells.iter().cloned().map(Value::Content).collect(), )), @@ -156,50 +156,14 @@ impl Layout for GridNode { } } -/// Defines how to size a grid cell along an axis. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum TrackSizing { - /// A track that fits its cell's contents. - Auto, - /// A track size specified in absolute terms and relative to the parent's - /// size. - Relative(Rel), - /// A track size specified as a fraction of the remaining free space in the - /// parent. - Fractional(Fr), -} - -impl TrackSizing { - pub fn encode(self) -> Value { - match self { - Self::Auto => Value::Auto, - Self::Relative(rel) => Spacing::Relative(rel).encode(), - Self::Fractional(fr) => Spacing::Fractional(fr).encode(), - } - } - - pub fn encode_slice(vec: &[TrackSizing]) -> Value { - Value::Array(vec.iter().copied().map(Self::encode).collect()) - } -} - -impl From for TrackSizing { - fn from(spacing: Spacing) -> Self { - match spacing { - Spacing::Relative(rel) => Self::Relative(rel), - Spacing::Fractional(fr) => Self::Fractional(fr), - } - } -} - /// Track sizing definitions. #[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] -pub struct TrackSizings(pub Vec); +pub struct TrackSizings(pub Vec); castable! { TrackSizings, - sizing: TrackSizing => Self(vec![sizing]), - count: NonZeroUsize => Self(vec![TrackSizing::Auto; count.get()]), + sizing: Sizing => Self(vec![sizing]), + count: NonZeroUsize => Self(vec![Sizing::Auto; count.get()]), values: Array => Self(values .into_iter() .filter_map(|v| v.cast().ok()) @@ -207,10 +171,10 @@ castable! { } castable! { - TrackSizing, + Sizing, _: AutoValue => Self::Auto, - v: Rel => Self::Relative(v), - v: Fr => Self::Fractional(v), + v: Rel => Self::Rel(v), + v: Fr => Self::Fr(v), } /// Performs grid layout. @@ -224,9 +188,9 @@ struct GridLayouter<'a, 'v> { /// Whether this grid has gutters. has_gutter: bool, /// The column tracks including gutter tracks. - cols: Vec, + cols: Vec, /// The row tracks including gutter tracks. - rows: Vec, + rows: Vec, /// The regions to layout children into. regions: Regions<'a>, /// The inherited styles. @@ -259,8 +223,8 @@ impl<'a, 'v> GridLayouter<'a, 'v> { /// This prepares grid layout by unifying content and gutter tracks. fn new( vt: &'a mut Vt<'v>, - tracks: Axes<&[TrackSizing]>, - gutter: Axes<&[TrackSizing]>, + tracks: Axes<&[Sizing]>, + gutter: Axes<&[Sizing]>, cells: &'a [Content], regions: Regions<'a>, styles: StyleChain<'a>, @@ -281,8 +245,8 @@ impl<'a, 'v> GridLayouter<'a, 'v> { }; let has_gutter = gutter.any(|tracks| !tracks.is_empty()); - let auto = TrackSizing::Auto; - let zero = TrackSizing::Relative(Rel::zero()); + let auto = Sizing::Auto; + let zero = Sizing::Rel(Rel::zero()); let get_or = |tracks: &[_], idx, default| { tracks.get(idx).or(tracks.last()).copied().unwrap_or(default) }; @@ -352,9 +316,9 @@ impl<'a, 'v> GridLayouter<'a, 'v> { } match self.rows[y] { - TrackSizing::Auto => self.layout_auto_row(y)?, - TrackSizing::Relative(v) => self.layout_relative_row(v, y)?, - TrackSizing::Fractional(v) => { + Sizing::Auto => self.layout_auto_row(y)?, + Sizing::Rel(v) => self.layout_relative_row(v, y)?, + Sizing::Fr(v) => { self.lrows.push(Row::Fr(v, y)); self.fr += v; } @@ -377,14 +341,14 @@ impl<'a, 'v> GridLayouter<'a, 'v> { // fractional tracks. for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { match col { - TrackSizing::Auto => {} - TrackSizing::Relative(v) => { + Sizing::Auto => {} + Sizing::Rel(v) => { let resolved = v.resolve(self.styles).relative_to(self.regions.base().x); *rcol = resolved; rel += resolved; } - TrackSizing::Fractional(v) => fr += v, + Sizing::Fr(v) => fr += v, } } @@ -418,7 +382,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> { // Determine size of auto columns by laying out all cells in those // columns, measuring them and finding the largest one. for (x, &col) in self.cols.iter().enumerate() { - if col != TrackSizing::Auto { + if col != Sizing::Auto { continue; } @@ -428,7 +392,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> { // For relative rows, we can already resolve the correct // base and for auto and fr we could only guess anyway. let height = match self.rows[y] { - TrackSizing::Relative(v) => { + Sizing::Rel(v) => { v.resolve(self.styles).relative_to(self.regions.base().y) } _ => self.regions.base().y, @@ -456,7 +420,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> { } for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { - if let TrackSizing::Fractional(v) = col { + if let Sizing::Fr(v) = col { *rcol = v.share(fr, remaining); } } @@ -479,7 +443,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> { for (&col, &rcol) in self.cols.iter().zip(&self.rcols) { // Remove an auto column if it is not overlarge (rcol <= fair), // but also hasn't already been removed (rcol > last). - if col == TrackSizing::Auto && rcol <= fair && rcol > last { + if col == Sizing::Auto && rcol <= fair && rcol > last { redistribute -= rcol; overlarge -= 1; changed = true; @@ -489,7 +453,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> { // Redistribute space fairly among overlarge columns. for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) { - if col == TrackSizing::Auto && *rcol > fair { + if col == Sizing::Auto && *rcol > fair { *rcol = fair; } } @@ -597,7 +561,7 @@ impl<'a, 'v> GridLayouter<'a, 'v> { if let Some(cell) = self.cell(x, y) { let size = Size::new(rcol, height); let mut pod = Regions::one(size, Axes::splat(true)); - if self.rows[y] == TrackSizing::Auto { + if self.rows[y] == Sizing::Auto { pod.full = self.regions.full; } let frame = cell.layout(self.vt, self.styles, pod)?.into_frame(); diff --git a/library/src/layout/list.rs b/library/src/layout/list.rs index 8bdbe7378..eab835cae 100644 --- a/library/src/layout/list.rs +++ b/library/src/layout/list.rs @@ -1,4 +1,4 @@ -use crate::layout::{BlockNode, GridNode, ParNode, Spacing, TrackSizing}; +use crate::layout::{BlockNode, GridNode, ParNode, Sizing, Spacing}; use crate::prelude::*; use crate::text::TextNode; @@ -147,10 +147,10 @@ impl Layout for ListNode { GridNode { tracks: Axes::with_x(vec![ - TrackSizing::Relative(indent.into()), - TrackSizing::Auto, - TrackSizing::Relative(body_indent.into()), - TrackSizing::Auto, + Sizing::Rel(indent.into()), + Sizing::Auto, + Sizing::Rel(body_indent.into()), + Sizing::Auto, ]), gutter: Axes::with_y(vec![gutter.into()]), cells, diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs index 3294a96c2..9ee77a61f 100644 --- a/library/src/layout/mod.rs +++ b/library/src/layout/mod.rs @@ -227,9 +227,16 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> { fn accept( &mut self, - content: &'a Content, + mut content: &'a Content, styles: StyleChain<'a>, ) -> SourceResult<()> { + if content.has::() && !content.is::() { + content = self + .scratch + .content + .alloc(FormulaNode { body: content.clone(), block: false }.pack()); + } + // Prepare only if this is the first application for this node. if let Some(node) = content.with::() { if !content.is_prepared() { @@ -470,22 +477,15 @@ impl<'a> ParBuilder<'a> { if content.is::() || content.is::() || content.is::() - || content.is::() || content.is::() - || content.is::() - || content.is::() + || content.is::() || content.to::().map_or(false, |node| !node.block) + || content.is::() { self.0.push(content.clone(), styles); return true; } - if !content.is::() && content.has::() { - let formula = FormulaNode { body: content.clone(), block: false }.pack(); - self.0.push(formula, styles); - return true; - } - false } diff --git a/library/src/layout/par.rs b/library/src/layout/par.rs index b712d8b17..bd08b8a57 100644 --- a/library/src/layout/par.rs +++ b/library/src/layout/par.rs @@ -4,8 +4,9 @@ use xi_unicode::LineBreakIterator; use typst::model::Key; -use super::{HNode, RepeatNode, Spacing}; +use super::{BoxNode, HNode, Sizing, Spacing}; use crate::layout::AlignNode; +use crate::math::FormulaNode; use crate::prelude::*; use crate::text::{ shape, LinebreakNode, Quoter, Quotes, ShapedText, SmartQuoteNode, SpaceNode, TextNode, @@ -330,8 +331,10 @@ enum Segment<'a> { Text(usize), /// Horizontal spacing between other segments. Spacing(Spacing), - /// Arbitrary inline-level content. - Inline(&'a Content), + /// A math formula. + Formula(&'a FormulaNode), + /// A box with arbitrary content. + Box(&'a BoxNode), } impl Segment<'_> { @@ -340,7 +343,8 @@ impl Segment<'_> { match *self { Self::Text(len) => len, Self::Spacing(_) => SPACING_REPLACE.len_utf8(), - Self::Inline(_) => NODE_REPLACE.len_utf8(), + Self::Box(node) if node.width.is_fractional() => SPACING_REPLACE.len_utf8(), + Self::Formula(_) | Self::Box(_) => NODE_REPLACE.len_utf8(), } } } @@ -353,11 +357,9 @@ enum Item<'a> { /// Absolute spacing between other items. Absolute(Abs), /// Fractional spacing between other items. - Fractional(Fr), + Fractional(Fr, Option<(&'a BoxNode, StyleChain<'a>)>), /// Layouted inline-level content. Frame(Frame), - /// A repeating node that fills the remaining space in a line. - Repeat(&'a RepeatNode, StyleChain<'a>), } impl<'a> Item<'a> { @@ -373,8 +375,8 @@ impl<'a> Item<'a> { fn len(&self) -> usize { match self { Self::Text(shaped) => shaped.text.len(), - Self::Absolute(_) | Self::Fractional(_) => SPACING_REPLACE.len_utf8(), - Self::Frame(_) | Self::Repeat(_, _) => NODE_REPLACE.len_utf8(), + Self::Absolute(_) | Self::Fractional(_, _) => SPACING_REPLACE.len_utf8(), + Self::Frame(_) => NODE_REPLACE.len_utf8(), } } @@ -384,7 +386,7 @@ impl<'a> Item<'a> { Self::Text(shaped) => shaped.width, Self::Absolute(v) => *v, Self::Frame(frame) => frame.width(), - Self::Fractional(_) | Self::Repeat(_, _) => Abs::zero(), + Self::Fractional(_, _) => Abs::zero(), } } } @@ -473,8 +475,7 @@ impl<'a> Line<'a> { fn fr(&self) -> Fr { self.items() .filter_map(|item| match item { - Item::Fractional(fr) => Some(*fr), - Item::Repeat(_, _) => Some(Fr::one()), + Item::Fractional(fr, _) => Some(*fr), _ => None, }) .sum() @@ -530,6 +531,9 @@ fn collect<'a>( full.push_str(&node.0); } Segment::Text(full.len() - prev) + } else if let Some(&node) = child.to::() { + full.push(SPACING_REPLACE); + Segment::Spacing(node.amount) } else if let Some(node) = child.to::() { let c = if node.justify { '\u{2028}' } else { '\n' }; full.push(c); @@ -557,12 +561,18 @@ fn collect<'a>( full.push(if node.double { '"' } else { '\'' }); } Segment::Text(full.len() - prev) - } else if let Some(&node) = child.to::() { - full.push(SPACING_REPLACE); - Segment::Spacing(node.amount) - } else { + } else if let Some(node) = child.to::() { full.push(NODE_REPLACE); - Segment::Inline(child) + Segment::Formula(node) + } else if let Some(node) = child.to::() { + full.push(if node.width.is_fractional() { + SPACING_REPLACE + } else { + NODE_REPLACE + }); + Segment::Box(node) + } else { + panic!("unexpected par child: {child:?}"); }; if let Some(last) = full.chars().last() { @@ -614,20 +624,26 @@ fn prepare<'a>( shape_range(&mut items, vt, &bidi, cursor..end, styles); } Segment::Spacing(spacing) => match spacing { - Spacing::Relative(v) => { + Spacing::Rel(v) => { let resolved = v.resolve(styles).relative_to(region.x); items.push(Item::Absolute(resolved)); } - Spacing::Fractional(v) => { - items.push(Item::Fractional(v)); + Spacing::Fr(v) => { + items.push(Item::Fractional(v, None)); } }, - Segment::Inline(inline) => { - if let Some(repeat) = inline.to::() { - items.push(Item::Repeat(repeat, styles)); + Segment::Formula(formula) => { + let pod = Regions::one(region, Axes::splat(false)); + let mut frame = formula.layout(vt, styles, pod)?.into_frame(); + frame.translate(Point::with_y(styles.get(TextNode::BASELINE))); + items.push(Item::Frame(frame)); + } + Segment::Box(node) => { + if let Sizing::Fr(v) = node.width { + items.push(Item::Fractional(v, Some((node, styles)))); } else { let pod = Regions::one(region, Axes::splat(false)); - let mut frame = inline.layout(vt, styles, pod)?.into_frame(); + let mut frame = node.layout(vt, styles, pod)?.into_frame(); frame.translate(Point::with_y(styles.get(TextNode::BASELINE))); items.push(Item::Frame(frame)); } @@ -1111,20 +1127,23 @@ fn finalize( vt: &mut Vt, p: &Preparation, lines: &[Line], - mut region: Size, + region: Size, expand: bool, ) -> SourceResult { // Determine the paragraph's width: Full width of the region if we // should expand or there's fractional spacing, fit-to-width otherwise. - if !region.x.is_finite() || (!expand && lines.iter().all(|line| line.fr().is_zero())) + let width = if !region.x.is_finite() + || (!expand && lines.iter().all(|line| line.fr().is_zero())) { - region.x = lines.iter().map(|line| line.width).max().unwrap_or_default(); - } + lines.iter().map(|line| line.width).max().unwrap_or_default() + } else { + region.x + }; // Stack the lines into one frame per region. let mut frames: Vec = lines .iter() - .map(|line| commit(vt, p, line, region)) + .map(|line| commit(vt, p, line, width, region.y)) .collect::>()?; // Prevent orphans. @@ -1159,9 +1178,10 @@ fn commit( vt: &mut Vt, p: &Preparation, line: &Line, - region: Size, + width: Abs, + full: Abs, ) -> SourceResult { - let mut remaining = region.x - line.width; + let mut remaining = width - line.width; let mut offset = Abs::zero(); // Reorder the line from logical to visual order. @@ -1223,8 +1243,17 @@ fn commit( Item::Absolute(v) => { offset += *v; } - Item::Fractional(v) => { - offset += v.share(fr, remaining); + Item::Fractional(v, node) => { + let amount = v.share(fr, remaining); + if let Some((node, styles)) = node { + let region = Size::new(amount, full); + let pod = Regions::one(region, Axes::new(true, false)); + let mut frame = node.layout(vt, *styles, pod)?.into_frame(); + frame.translate(Point::with_y(styles.get(TextNode::BASELINE))); + push(&mut offset, frame); + } else { + offset += amount; + } } Item::Text(shaped) => { let frame = shaped.build(vt, justification); @@ -1233,27 +1262,6 @@ fn commit( Item::Frame(frame) => { push(&mut offset, frame.clone()); } - Item::Repeat(repeat, styles) => { - let before = offset; - let fill = Fr::one().share(fr, remaining); - let size = Size::new(fill, region.y); - let pod = Regions::one(size, Axes::new(false, false)); - let frame = repeat.layout(vt, *styles, pod)?.into_frame(); - let width = frame.width(); - let count = (fill / width).floor(); - let remaining = fill % width; - let apart = remaining / (count - 1.0); - if count == 1.0 { - offset += p.align.position(remaining); - } - if width > Abs::zero() { - for _ in 0..(count as usize).min(1000) { - push(&mut offset, frame.clone()); - offset += apart; - } - } - offset = before + fill; - } } } @@ -1262,7 +1270,7 @@ fn commit( remaining = Abs::zero(); } - let size = Size::new(region.x, top + bottom); + let size = Size::new(width, top + bottom); let mut output = Frame::new(size); output.set_baseline(top); diff --git a/library/src/layout/repeat.rs b/library/src/layout/repeat.rs index 10cd1d254..ef630cf28 100644 --- a/library/src/layout/repeat.rs +++ b/library/src/layout/repeat.rs @@ -1,7 +1,9 @@ use crate::prelude::*; +use super::AlignNode; + /// # Repeat -/// Repeats content to fill a line. +/// Repeats content to the available space. /// /// This can be useful when implementing a custom index, reference, or outline. /// @@ -10,7 +12,8 @@ use crate::prelude::*; /// /// ## Example /// ```example -/// Sign on the dotted line: #repeat[.] +/// Sign on the dotted line: +/// #box(width: 1fr, repeat[.]) /// /// #set text(10pt) /// #v(8pt, weak: true) @@ -51,6 +54,34 @@ impl Layout for RepeatNode { styles: StyleChain, regions: Regions, ) -> SourceResult { - self.0.layout(vt, styles, regions) + let pod = Regions::one(regions.size, Axes::new(false, false)); + let piece = self.0.layout(vt, styles, pod)?.into_frame(); + let align = styles.get(AlignNode::ALIGNS).x.resolve(styles); + + let fill = regions.size.x; + let width = piece.width(); + let count = (fill / width).floor(); + let remaining = fill % width; + let apart = remaining / (count - 1.0); + + let size = Size::new(regions.size.x, piece.height()); + let mut frame = Frame::new(size); + if piece.has_baseline() { + frame.set_baseline(piece.baseline()); + } + + let mut offset = Abs::zero(); + if count == 1.0 { + offset += align.position(remaining); + } + + if width > Abs::zero() { + for _ in 0..(count as usize).min(1000) { + frame.push_frame(Point::with_x(offset), piece.clone()); + offset += piece.width() + apart; + } + } + + Ok(Fragment::frame(frame)) } } diff --git a/library/src/layout/spacing.rs b/library/src/layout/spacing.rs index 17f6b5bee..295d7f2fc 100644 --- a/library/src/layout/spacing.rs +++ b/library/src/layout/spacing.rs @@ -222,22 +222,22 @@ impl Behave for VNode { #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum Spacing { /// Spacing specified in absolute terms and relative to the parent's size. - Relative(Rel), + Rel(Rel), /// Spacing specified as a fraction of the remaining free space in the /// parent. - Fractional(Fr), + Fr(Fr), } impl Spacing { /// Whether this is fractional spacing. pub fn is_fractional(self) -> bool { - matches!(self, Self::Fractional(_)) + matches!(self, Self::Fr(_)) } /// Encode into a value. pub fn encode(self) -> Value { match self { - Self::Relative(rel) => { + Self::Rel(rel) => { if rel.rel.is_zero() { Value::Length(rel.abs) } else if rel.abs.is_zero() { @@ -246,28 +246,28 @@ impl Spacing { Value::Relative(rel) } } - Self::Fractional(fr) => Value::Fraction(fr), + Self::Fr(fr) => Value::Fraction(fr), } } } impl From for Spacing { fn from(abs: Abs) -> Self { - Self::Relative(abs.into()) + Self::Rel(abs.into()) } } impl From for Spacing { fn from(em: Em) -> Self { - Self::Relative(Rel::new(Ratio::zero(), em.into())) + Self::Rel(Rel::new(Ratio::zero(), em.into())) } } impl PartialOrd for Spacing { fn partial_cmp(&self, other: &Self) -> Option { match (self, other) { - (Self::Relative(a), Self::Relative(b)) => a.partial_cmp(b), - (Self::Fractional(a), Self::Fractional(b)) => a.partial_cmp(b), + (Self::Rel(a), Self::Rel(b)) => a.partial_cmp(b), + (Self::Fr(a), Self::Fr(b)) => a.partial_cmp(b), _ => None, } } @@ -275,6 +275,6 @@ impl PartialOrd for Spacing { castable! { Spacing, - v: Rel => Self::Relative(v), - v: Fr => Self::Fractional(v), + v: Rel => Self::Rel(v), + v: Fr => Self::Fr(v), } diff --git a/library/src/layout/stack.rs b/library/src/layout/stack.rs index 35a0ff6fd..afcb36967 100644 --- a/library/src/layout/stack.rs +++ b/library/src/layout/stack.rs @@ -200,7 +200,7 @@ impl<'a> StackLayouter<'a> { /// Add spacing along the spacing direction. fn layout_spacing(&mut self, spacing: Spacing) { match spacing { - Spacing::Relative(v) => { + Spacing::Rel(v) => { // Resolve the spacing and limit it to the remaining space. let resolved = v .resolve(self.styles) @@ -213,7 +213,7 @@ impl<'a> StackLayouter<'a> { self.used.main += limited; self.items.push(StackItem::Absolute(resolved)); } - Spacing::Fractional(v) => { + Spacing::Fr(v) => { self.fr += v; self.items.push(StackItem::Fractional(v)); } diff --git a/library/src/layout/table.rs b/library/src/layout/table.rs index 12d2455fc..1ceea9b94 100644 --- a/library/src/layout/table.rs +++ b/library/src/layout/table.rs @@ -1,4 +1,4 @@ -use crate::layout::{AlignNode, GridNode, TrackSizing, TrackSizings}; +use crate::layout::{AlignNode, GridNode, Sizing, TrackSizings}; use crate::prelude::*; /// # Table @@ -63,9 +63,9 @@ use crate::prelude::*; #[derive(Debug, Hash)] pub struct TableNode { /// Defines sizing for content rows and columns. - pub tracks: Axes>, + pub tracks: Axes>, /// Defines sizing of gutter rows and columns between content. - pub gutter: Axes>, + pub gutter: Axes>, /// The content to be arranged in the table. pub cells: Vec, } @@ -134,10 +134,10 @@ impl TableNode { fn field(&self, name: &str) -> Option { match name { - "columns" => Some(TrackSizing::encode_slice(&self.tracks.x)), - "rows" => Some(TrackSizing::encode_slice(&self.tracks.y)), - "column-gutter" => Some(TrackSizing::encode_slice(&self.gutter.x)), - "row-gutter" => Some(TrackSizing::encode_slice(&self.gutter.y)), + "columns" => Some(Sizing::encode_slice(&self.tracks.x)), + "rows" => Some(Sizing::encode_slice(&self.tracks.y)), + "column-gutter" => Some(Sizing::encode_slice(&self.gutter.x)), + "row-gutter" => Some(Sizing::encode_slice(&self.gutter.y)), "cells" => Some(Value::Array( self.cells.iter().cloned().map(Value::Content).collect(), )), diff --git a/library/src/layout/terms.rs b/library/src/layout/terms.rs index cf214084c..b1d399dba 100644 --- a/library/src/layout/terms.rs +++ b/library/src/layout/terms.rs @@ -1,4 +1,4 @@ -use crate::layout::{BlockNode, GridNode, HNode, ParNode, Spacing, TrackSizing}; +use crate::layout::{BlockNode, GridNode, HNode, ParNode, Sizing, Spacing}; use crate::prelude::*; use crate::text::{SpaceNode, TextNode}; @@ -136,8 +136,8 @@ impl Layout for TermsNode { GridNode { tracks: Axes::with_x(vec![ - TrackSizing::Relative((indent + body_indent).into()), - TrackSizing::Auto, + Sizing::Rel((indent + body_indent).into()), + Sizing::Auto, ]), gutter: Axes::with_y(vec![gutter.into()]), cells, diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index 76dcdc2ec..84af15cb6 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -277,7 +277,7 @@ impl LayoutMath for Content { } if let Some(node) = self.to::() { - if let Spacing::Relative(rel) = node.amount { + if let Spacing::Rel(rel) = node.amount { if rel.rel.is_zero() { ctx.push(MathFragment::Spacing(rel.abs.resolve(ctx.styles()))); } diff --git a/library/src/meta/outline.rs b/library/src/meta/outline.rs index 388021bb9..d28a0f086 100644 --- a/library/src/meta/outline.rs +++ b/library/src/meta/outline.rs @@ -1,5 +1,7 @@ use super::HeadingNode; -use crate::layout::{HNode, HideNode, ParbreakNode, RepeatNode, Spacing}; +use crate::layout::{ + BoxNode, HNode, HideNode, ParbreakNode, RepeatNode, Sizing, Spacing, +}; use crate::prelude::*; use crate::text::{LinebreakNode, SpaceNode, TextNode}; @@ -180,10 +182,18 @@ impl Show for OutlineNode { // Add filler symbols between the section name and page number. if let Some(filler) = styles.get(Self::FILL) { seq.push(SpaceNode.pack()); - seq.push(RepeatNode(filler.clone()).pack()); + seq.push( + BoxNode { + body: RepeatNode(filler.clone()).pack(), + width: Sizing::Fr(Fr::one()), + height: Smart::Auto, + baseline: Rel::zero(), + } + .pack(), + ); seq.push(SpaceNode.pack()); } else { - let amount = Spacing::Fractional(Fr::one()); + let amount = Spacing::Fr(Fr::one()); seq.push(HNode { amount, weak: false }.pack()); } diff --git a/src/doc.rs b/src/doc.rs index 9fbd94367..64f7ae91c 100644 --- a/src/doc.rs +++ b/src/doc.rs @@ -86,7 +86,7 @@ impl Frame { } /// Whether the frame has a non-default baseline. - pub fn has_baseline(&mut self) -> bool { + pub fn has_baseline(&self) -> bool { self.baseline.is_some() } diff --git a/tests/ref/layout/container.png b/tests/ref/layout/container.png index f82df1086cdf6e06f7e94607d892979ef19825a7..b825471cbcdd46981c22c5862ab65ee482e28556 100644 GIT binary patch literal 8332 zcma)iWmH^Sw`CO&s_-fZt_6WWaCdj-0>M34g1bWjMQ{laAUH{Y;O=e-P*{RnC?L4I zlg@qJultR0`}^LT|IRsU?6Jq#bFVqqS|?gdLlF;~0viAT;3+G~X#)U23;+O)2>j;- zpx;2;9sr=zQI?a|^<6kvwljD-OVNFDmTj6@iIz6N;r%SlAS(K+JzGnoK^VK4ag@TV z^0`X!erug5l^BKZU!q|zn52o==3XhY^_W$|V`A`d=vf~^BppMasKt>+`3N6@a#`%` zd_wkxc|B}5+hx9J705(j6Ti^?qIHrX4aJGbhdd3F(E?*j!d9hfepVe_CcH9)hNs ztUI?e^Dpu12Jj;wbl#+Xh&bDUBVrqj-#e4ViDjI)^39f16_Y!jt;rHSyShdf&;Pjv zcZE`J@o&kIjFj!a2@fxmk8A`I-Hy(@`~4qdvbX<7)#Px-(Pm@nC2v?njm4 z@_BugpO~ntlsUTY*ju>(`8h@b?(ZjQHUfPG#$$ve2+o1=%NQ<`yfIiyHxDK2H*svs zvg>XQ=#Sw9K)aFA=G7ov5w=f6wExtyW9dnISv3Fn(IYP++B;^#{POtX!ihzad)z$v zF;QlYS)x`G<5e`a#NS|~_(1wu^xXGpuUO5H1XK^12OxW{nU!Ae(}v#tHqkMWNU7@R zgH;}Q*)&D{!gQ=!06Y8#oOSw*SiLkCJ~5KcWBso-+2%d5Zm|tRWUXVp@itX#ll=aeHlycgftaAYZjl_L+Imq!@wUHAu0!i<3l!m}f6 z^bc4iH9Lt$UXrF>rC)Lx|3Qde&_35kAwJ< z%%84!i08@@)Cy)$gBAn5FS>LP&{k4pCcb%pQS>KSpb`$bfc+)o(hlQCr9up ziH~ubJKWx35YyVZF9!;|Ve1HMo)w}qi;$gV*E%D96o{LWP*m8QK{pV;q^ivXA@8n& zSu&Y@fF4TZ#Mj$$MgZs>&<5sK;VywP=v|~!-U3ekLM>yW8ohCS`7s7P>xeL#+qb<0 zxgp2XuWoO$Ff|61?vxZ#R{5q(#=el#{tfd&JeJI+gAz-=fd^-+6k?bZO}LBkHox|; zy;a19b0bk2-ZZK`+c5;3O~_jT=CxrnAGBOdpt(x_{%n|glgG`<-SIy_b>bc-4tr(> zLVdm_3tR?nAEicXn!=^X@itn~lCT&b$~irP@Zd4tp-Bs077XEkt?3`X??UGuxRd&{ z01&s~i}R2GIaYe5`FN7Ti7pc0d1tRtU+NRhWvavPzMxtT`+EX;H_8J-HA3<^d|3vf;-u+L*R$t4J{aEXx zPjr>nX*c=$%5mz8p<@@=gNh_-G%=iRpR7PRGP2SI;L6D)ex`Kob>{PvqE(WZj-TmW z{aoDK`(B90qy}_sa0Ay<6_YDU{bfYbIxl>=urQVX678MUN%6oyLyQdg`>Y1^rXA!5844i z-eIz(1;Dh=F(x^C%@z?#HJD^(`w|wA8bFU#p5KN5IrclNcO49BNgkcIyJePZi);wj z8bTU2|2D;~EqVKy^d@t}$na(Zbu9w|qF=ageAIcQ&j<02$q%?XTyC5Fwpv7N8GRkk z!h=0$#s_&ToZQk?1H$&jV+|ME+h8>=NL-gO#`i@ zZHOx%4B-QY$EmN}JqS;Rt)w3*Gpvtz(a`YQ%rIU|eW$!!$+JkRDb?=d`bBauln}o1^;L=HRo^$ND^xnC1-tun- znKK_F zSlamXPDX}|etlI@{eJsJw}~Ixnkbf_&-X_rRGFlS!u1E}$Gzya%8fAVNBX7Nrglu= zDTorGG>XC?#B{(X#_w(7p;8b-YMzRaZHq_i(S%u4k9bH8P+^t&gcghyFKk zt)Jii13Gfu0SW2A%&9u*Z7l|3rHbzbt`Lsl*IM85d`Ah7x6wles{ozv+PY_9V+&0C z??VBxXTXAls=wj#3JRp&L+6q?;}Vq_dIX$v8VZ4Rom8_;2=MMa^f-~ z(BEiJAR6`of9Jdd;4N&&u$aJIYz_MC&7_Aou(}r^gWXlF2o#tGGd%0q zVUv4L3<%aZJWgqggCm_9v=NnKA;A|Nrhx1cjt@j_5y~ z)&I49h!8NWaf{V<#ci_BT1tXds&UM4t z$X~1kdlr$+^&m{)PjER(bGF0S+`6QQKrAuyfNOdIHN9MFG3#nc{JXDBLYV&e$A0fG z^ZCrMsWY6#oTu!?Jvq4^gIt{~abMN!XTIh_hkXP7pvyrzDI6dWlaof+r_Q;*gv^ER z;*oJ#bi-{7r-7)LbyedZP7urv)6WYSfV4KO;w?#)AsI5sPusAhV-G3Hd2yVcV?(GM zV4Ibw9IRhSEQ~<%{s4q;h7zxK7tlu@SM;eO>Ge|&!0oJIVaZYNG9A2QEvVu69D8V^ zA@E}g8GHW~46EH8&)Jq$?KLy#zGicv2+N(L%*S2bzr5(Zc{;@M2fOm~0s2;92k<`0z~z%nLgYEIGaFP3Kx65L@IZgop}M z?aqL4Mf52wq>1JB?i|LOkX1T7jHT1Z10P`MpF<6XpPloEg-_PO*ycgUND;WKYWH|F z-X|t!fezoDL2jHBcoac>MVP*wVia})C@Mo_GwW;L^KhTC^Qy>k5zKvjws|gJLurO< zB(sAz<9GsHVbUfp?PdmJhncIJ5k|ICeub&}*(sh!34BLeg(ZH8Fzxq#9W|U!(3EGQ z7!{jyuZdv5LhwX<4jS2V@qcx#!bK zZdsf-A!MuYB*brR$E5Snjr_B`UNl6h{00qm(D+m%^%UcaLH=~lQ*@o*XsZM@U&R_` z>TN^Cve83%JVxaOqS3id2ACL7sdM1q%)9P4Hw+MhiIN`-(6_PHC?_XE-&qjZ^GXsS znZ^A4{1yh)dX+~H1c-4jGP80_leh_pA1f~mZSQQL;t~S&@*Zhcq2XjFv*E2nb-X#@ zz*a?iHKOQlNbytEzGf{dW>*ZIEC`3>wFicP-qYH{{(uQ&eW3w2?72)VURp1w&uX$* zvp!X!ChOi$mPYRk1_qS zC*4V~2EzG{-3IUQLl4ad@-_8250sa1P(zjh_z7OBe?9oIDZ zD&G`0f1Z(UWiX7PI}$m#(`Sd+v-n6+T~cE_n03+x*GUgDe?Q|amEDc(*b1p(PYD66 z8sBisMZ>y@)ddhllp{M7>tC7Ba0ZAuTH-+48*Y*yncdd9v{BpC%=Jc!PLIr^Oy}^V z%_+)3`)hkdl?lxpjWjW4SYlV#?>YSj{$%)!dw(XUIm;|mhz*k>A!*ZAID5sQ{juE? z$;t;p`vvD;=_@Nl1%Pg|uTljgE_}Maa`#<2r54_IfEz4wa_Po1hd@z0GyTBG|@^Ft^dDn-R z@n0ToWGEA3tLJo9R!o9DoF4G?wiBf@$9eH61(=^3*-d<8?N$kT2GNsU>8!pbqUoO> z3n@j4jCT5PkNR(?Zgn_b@z8Fu(>&hby&G3YvuGE(ulFo{=F7qHt1^ihireiEy{V@w zf2gm%3r{w2D3>G(@|3@5-H~q&a55LIfP9>qX#TkTEyG)Uvvb$}Wyk1$Cbe7&1oiDj z$ep_FHm$^vo$+VMt0`Fa0wx63`*@jkjkBH-*hmMxn1WpgyueTp$LbxL`Du3mTHRKB zsRDY!_$N#wk&^E@mc?H}?|O>idB1tBAHCqz(jTO(CZyqglc1ofE3e-Ue-^rh^|8-> zH>g8{XGs^sM<*X7VxI^>Jw1MdyCRBsdp&6H*rRwE_OryVQ<&j{`3S^I)<$FJ48|^*bLB+8r_wq^~iUumuf1=g9 z0X+E9mB^ooWwo?cp@(hE+GM3ASMY{jFjDXNz0YI&2rtPLF)byz!vsGSMdBQVG>Ulg z=|`r`u9`x@+hvad$RP|SVHfyy<1GdyqHIhyv|!8)#mV$J>4y;wzteVmHfW-mKk%a| z)|ELc6x8!WFmy!FhWXh}$GGh0;;1k#VbBjuk(@}WlOM^08O7+9Zyh%bik;`^Cd}I}ybel7-_uy~fl-O_$;)-c6O5f#!=?|SG#Zt%-* zr{qv`aB%lVgD+1&(=ivnT9x9IIYg(^<-^EGz*0QIaq@)xcU8`d+@dUr6e`K#R~!17 zvGO17O4rJf}N_qsr7eG2H4T4($a|jF4pYL ze$!D{4LKBT81raJSuyWwEE!)9YHt5}uST)MY%^cD*&+o)K)xSa?$t7q;x31b6)s4(~EXLR!_CEfA)s?5NC7Uf>@9KPn3s0YsPs zk(KOP=v2}qfHEXvLR@M)cdBrG(@cOkI)i`f3pvc@Ada9BaNkU7abyJFhI}HnoDHC* z1vC@6=Z2Tj$P6!#zjZ2_ZnL`$3FgOvaaeO!2Y;QbI)tCJ?E(1r=IK5M&Z=S{vbor! z38H~kEei^f%FxXNDP9gwai(eu*NWcwFLPVn9Pi__`0 zV-*Gf=YhIKpy3|pDmAGlNSHe6X{`gkl`mF)PE}mto1FmBLhe#gut?rXu z-1NoOKEe_b#3i7EdUEL8{)wyNI>^#pyt_D4hl-@Ddn-35aJ5okE==nQ$}*x6!omb7 z=f%6YYdoWlLeF@vDbozrbYAxcUmGmd1Sb)>=zZ5NnHd8B%^7;sILZnPA>xO<2y6GU{9#8oWh&z3>!1t0Mc^1efb& z7H+lfAvY=3yEd3f`5sFnnj=Y-F)ceTv20zu+8r42+uqKc4S+Dt+z2#_cJor=f{ph z0voU89{AC+v4e|0+A0P!sFi>Cco&^YcoshVjuIxF0POA==Ob`TvvgHPJZ} zTLCV@zN;;65D8?fkmb8;EwXzVspXs=z%<{^RM%;DsCl7;fr{zNx{0qg3|{x7ZS7d7 zGp7N!>mJfn8{hVje=`Jhh##Ui6#I{cvb%p^`Wb_AM;BTZ`Vr$gu~)HSg3 zViQcRkC|e4ER3c6Ln2n?^Nd1P&hyFX6=1bgm&)I6)!AT=4R}7E_LdMGfdHAYtGoh6ZRjc8HmETK; zq($>e4+U%POdRIkEdufalnjG%b^;ajN%Pa4OTA>-jdNeq*?darhUWMk-_Q}Th@*wZ z{e`bRJAR!Oz(wgr8QOdruYE1Ui8$NVgOoovYQ z;<+)Cz9&@pT?G!v!|j>6%NCRw+n%bFT14oW>v9rN1?GXFB)m@j`bA={LuE`FhFJ&Y z52w985&~ZFk|q*w&e3#?nstD(&yPZXp(?FZj-grcG`SLdC;*)Mdg6c}@=2x$il&l` zcT2Z^B;J+#8oPV&{F8w7*jjIf$kVORjm%Qcw4}-CbZwjN`#K%p;Yg4dEs&Kdct16Se2(z9S- zMtR4Z7sm<4Xx+RLX`X}2VyMdcpFHn~LKgu6{qD`SyVWk+PxKoY)GM~v>BWxFUB~@= zoe|~xV>j^-Ow=@*Ez&{Z)A#ENKQfQpfbW0z6sp<`jIbo1=j&}0o+L_f9Rb5pnYe&$ zQ#66t2}S-mKYDASc%DDp`812u0h=LV&HgNlo;>Yb6UCK!C-CU!@-5%KDE~U8&jq_p%fKrb${)| zO{&e+yR4LH0LVd`1l}d5bb`I)vrH=oKbu5Ay>DtxTwqJ@8PtyaIS4QuytmL~GdLY1 zvkp);IiAQgVp5CHxm3#4AU8~*9Q0MVfki2!Edpe$6UsSYJIB@u{9?Jtl=|S4t4P3v zF*2WngS%SlOxFGR-6k5VwNTReDS`FVJ)pIKTUdXMerSbzG4X0*#pdbcfFbIR3j8`3 z?+5&b1hKKknxEB<$!!I39ql2pbUYzLg^d?1ferth4UT10+v=;unkC89&NA6s<-M0R zq^3oPkeLxToO9QVb}y&mV57bR2AY==OPyca{0SYQE|gJBGat z^Qc2NYL$-@AA{K120;y`cI-i>oxlk*d~@EephE9%Rerp5HE8=%kl>Rge3Y$E{1=kcxv_Ps?@oN$lb27E zel}zWiz{^0Q}wd5u*P_GlI#n=rM#7bLtg@AN>!SgK3<%S7LGN|4iXY21J)i3q5K2_ zXHNvCejHhrJU(^(6sch^l7~KLpz5MLv((k;^yftWw6i>xy?^>M9$L&OA&E#lUY42eD7q#4FNV-! z7Oa`9J)Z!{6)UWzhTV^+ukHgaQ)LEO3we~yyLdP6$~}HB=C!Xjp$eRAH~RC#-6k>H zefl^crz$0Zt(BGPti`14=3K36CHQ8wJH%ORr8QPYeVDmF){}#DBbQj-=amIO@!O2t z1MoS$h!VVGkfB<4eC1Qu^Q2+?h3?He*gd-OEmviIbu*g`lwsKzT2;A0Faw>Sji&Q-l^sDa`vg^03+|3Y|Rk{q{lp z+2_`f29m^wbQ6@GVm^HxaetcWovv=Xu@9c^PyXK#HwB&H<*z#!m|wkl&9e81`14h0 z{06{4382nyTCfQT`+oU2;_J{k)!!a`qp(jXeZj`^&td$(Gviv|5L)T~GuQt!;Exeo b>w1wQZ-O%^Hu>i-1Awx;hFqnLMcDrWRT6Ku literal 7512 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 diff --git a/tests/ref/layout/repeat.png b/tests/ref/layout/repeat.png index fcd52987ce2dadcf6bbfcefbfa940708377790fb..e6a27ad99ffd40ae03023b1ab1d4f19015e84fe3 100644 GIT binary patch literal 9260 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 Oqese`N@WPku>S$Oj^I}S literal 9344 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 D@Oe6T diff --git a/tests/typ/layout/container.typ b/tests/typ/layout/container.typ index c69280744..0b30c4e1b 100644 --- a/tests/typ/layout/container.typ +++ b/tests/typ/layout/container.typ @@ -9,6 +9,10 @@ Spaced \ #box(height: 0.5cm) \ Apart +--- +// Test fr box. +Hello #box(width: 1fr, rect(height: 0.7em, width: 100%)) World + --- // Test block over multiple pages. diff --git a/tests/typ/layout/repeat.typ b/tests/typ/layout/repeat.typ index 05b055afc..03642164e 100644 --- a/tests/typ/layout/repeat.typ +++ b/tests/typ/layout/repeat.typ @@ -12,28 +12,28 @@ ) #for section in sections [ - #section.at(0) #repeat[.] #section.at(1) \ + #section.at(0) #box(width: 1fr, repeat[.]) #section.at(1) \ ] --- // Test dots with RTL. #set text(lang: "ar") -مقدمة #repeat[.] 15 +مقدمة #box(width: 1fr, repeat[.]) 15 --- // Test empty repeat. -A #repeat[] B +A #box(width: 1fr, repeat[]) B --- -// Test spaceless repeat. -A#repeat(rect(width: 2.5em, height: 1em))B +// Test unboxed repeat. +#repeat(rect(width: 2em, height: 1em)) --- // Test single repeat in both directions. -A#repeat(rect(width: 6em, height: 0.7em))B +A#box(width: 1fr, repeat(rect(width: 6em, height: 0.7em)))B #set align(center) -A#repeat(rect(width: 6em, height: 0.7em))B +A#box(width: 1fr, repeat(rect(width: 6em, height: 0.7em)))B #set text(dir: rtl) -ريجين#repeat(rect(width: 4em, height: 0.7em))سون +ريجين#box(width: 1fr, repeat(rect(width: 4em, height: 0.7em)))سون