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 f82df1086..b825471cb 100644 Binary files a/tests/ref/layout/container.png and b/tests/ref/layout/container.png differ diff --git a/tests/ref/layout/repeat.png b/tests/ref/layout/repeat.png index fcd52987c..e6a27ad99 100644 Binary files a/tests/ref/layout/repeat.png and b/tests/ref/layout/repeat.png differ 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)))سون