diff --git a/src/eval/template.rs b/src/eval/template.rs index fe3d0ccaf..b1559b8fa 100644 --- a/src/eval/template.rs +++ b/src/eval/template.rs @@ -8,8 +8,8 @@ use super::Str; use crate::diag::StrResult; use crate::geom::{Align, Dir, GenAxis, Length, Linear, Sides, Size}; use crate::layout::{ - BlockNode, Decoration, InlineNode, PadNode, PageNode, ParChild, ParNode, StackChild, - StackNode, + BlockNode, Decoration, InlineNode, PadNode, PageNode, ParChild, ParNode, Spacing, + StackChild, StackNode, }; use crate::style::Style; use crate::util::EcoString; @@ -32,7 +32,7 @@ enum TemplateNode { /// Plain text. Text(EcoString), /// Spacing. - Spacing(GenAxis, Linear), + Spacing(GenAxis, Spacing), /// A decorated template. Decorated(Decoration, Template), /// An inline node builder. @@ -107,7 +107,7 @@ impl Template { } /// Add spacing along an axis. - pub fn spacing(&mut self, axis: GenAxis, spacing: Linear) { + pub fn spacing(&mut self, axis: GenAxis, spacing: Spacing) { self.make_mut().push(TemplateNode::Spacing(axis, spacing)); } @@ -308,7 +308,8 @@ impl Builder { fn parbreak(&mut self) { let amount = self.style.par_spacing(); self.stack.finish_par(&self.style); - self.stack.push_soft(StackChild::Spacing(amount.into())); + self.stack + .push_soft(StackChild::Spacing(Spacing::Linear(amount.into()))); } /// Apply a forced page break. @@ -328,25 +329,26 @@ impl Builder { /// Push an inline node into the active paragraph. fn inline(&mut self, node: impl Into) { let align = self.style.aligns.inline; - self.stack.par.push(ParChild::Any(node.into(), align)); + self.stack.par.push(ParChild::Node(node.into(), align)); } /// Push a block node into the active stack, finishing the active paragraph. fn block(&mut self, node: impl Into) { self.parbreak(); - self.stack.push(StackChild::Any(node.into(), self.style.aligns.block)); + self.stack + .push(StackChild::Node(node.into(), self.style.aligns.block)); self.parbreak(); } /// Push spacing into the active paragraph or stack depending on the `axis`. - fn spacing(&mut self, axis: GenAxis, amount: Linear) { + fn spacing(&mut self, axis: GenAxis, spacing: Spacing) { match axis { GenAxis::Block => { self.stack.finish_par(&self.style); - self.stack.push_hard(StackChild::Spacing(amount)); + self.stack.push_hard(StackChild::Spacing(spacing)); } GenAxis::Inline => { - self.stack.par.push_hard(ParChild::Spacing(amount)); + self.stack.par.push_hard(ParChild::Spacing(spacing)); } } } @@ -500,7 +502,7 @@ impl ParBuilder { fn build(self) -> Option { let Self { align, dir, leading, children, .. } = self; (!children.is_empty()) - .then(|| StackChild::Any(ParNode { dir, leading, children }.into(), align)) + .then(|| StackChild::Node(ParNode { dir, leading, children }.into(), align)) } } diff --git a/src/eval/walk.rs b/src/eval/walk.rs index 6ac301507..9dd7cd2e9 100644 --- a/src/eval/walk.rs +++ b/src/eval/walk.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use super::{Eval, EvalContext, Str, Template, Value}; use crate::diag::TypResult; use crate::geom::Align; -use crate::layout::{ParChild, ParNode, StackChild, StackNode}; +use crate::layout::{ParChild, ParNode, Spacing, StackChild, StackNode}; use crate::syntax::*; use crate::util::BoolExt; @@ -118,9 +118,9 @@ fn walk_item(ctx: &mut EvalContext, label: Str, body: Template) { StackNode { dir: style.dir, children: vec![ - StackChild::Any(label.into(), Align::Start), - StackChild::Spacing((style.text.size / 2.0).into()), - StackChild::Any(body.to_stack(&style).into(), Align::Start), + StackChild::Node(label.into(), Align::Start), + StackChild::Spacing(Spacing::Linear((style.text.size / 2.0).into())), + StackChild::Node(body.to_stack(&style).into(), Align::Start), ], } }); diff --git a/src/geom/size.rs b/src/geom/size.rs index 506e1c8fa..2143c46bb 100644 --- a/src/geom/size.rs +++ b/src/geom/size.rs @@ -25,14 +25,6 @@ impl Size { Self { w: v, h: v } } - /// Limit width and height at that of another size. - pub fn cap(self, limit: Self) -> Self { - Self { - w: self.w.min(limit.w), - h: self.h.min(limit.h), - } - } - /// Whether the other size fits into this one (smaller width and height). pub fn fits(self, other: Self) -> bool { self.w.fits(other.w) && self.h.fits(other.h) diff --git a/src/layout/grid.rs b/src/layout/grid.rs index 22581696e..7220d7c2c 100644 --- a/src/layout/grid.rs +++ b/src/layout/grid.rs @@ -17,7 +17,7 @@ pub struct GridNode { pub enum TrackSizing { /// Fit the cell to its contents. Auto, - /// A length stated in absolute values and fractions of the parent's size. + /// A length stated in absolute values and/or relative to the parent's size. Linear(Linear), /// A length that is the fraction of the remaining free space in the parent. Fractional(Fractional), @@ -124,26 +124,23 @@ impl<'a> GridLayouter<'a> { cols.pop(); rows.pop(); - let full = regions.current.h; - let rcols = vec![Length::zero(); cols.len()]; - // We use the regions only for auto row measurement and constraints. let expand = regions.expand; regions.expand = Spec::new(true, false); Self { - cols, - rows, children: &grid.children, cts: Constraints::new(expand), - regions, + full: regions.current.h, expand, - rcols, + rcols: vec![Length::zero(); cols.len()], lrows: vec![], - full, used: Size::zero(), fr: Fractional::zero(), finished: vec![], + cols, + rows, + regions, } } @@ -313,9 +310,9 @@ impl<'a> GridLayouter<'a> { TrackSizing::Auto => self.layout_auto_row(ctx, y), TrackSizing::Linear(v) => self.layout_linear_row(ctx, v, y), TrackSizing::Fractional(v) => { - self.fr += v; self.cts.exact.y = Some(self.full); self.lrows.push(Row::Fr(v, y)); + self.fr += v; } } } @@ -498,22 +495,22 @@ impl<'a> GridLayouter<'a> { /// Finish rows for one region. fn finish_region(&mut self, ctx: &mut LayoutContext) { - // Determine the height of the region's frame. - let height = if self.fr.is_zero() || self.full.is_infinite() { - self.used.h - } else { - self.full - }; - - self.cts.min.y = Some(height); - - // The frame for the region. - let mut output = Frame::new(Size::new(self.used.w, height), height); - let mut pos = Point::zero(); - // Determine the size that remains for fractional rows. let remaining = self.full - self.used.h; + // Determine the size of the grid in this region, expanding fully if + // there are fr rows. + let mut size = self.used; + if !self.fr.is_zero() && self.full.is_finite() { + size.h = self.full; + } + + self.cts.min.y = Some(size.h); + + // The frame for the region. + let mut output = Frame::new(size, size.h); + let mut pos = Point::zero(); + // Place finished rows and layout fractional rows. for row in std::mem::take(&mut self.lrows) { let frame = match row { diff --git a/src/layout/mod.rs b/src/layout/mod.rs index d03137789..651daa21a 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -276,3 +276,12 @@ fn hash_node(node: &(impl Hash + 'static)) -> u64 { node.hash(&mut state); state.finish() } + +/// Kinds of spacing. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum Spacing { + /// A length stated in absolute values and/or relative to the parent's size. + Linear(Linear), + /// A length that is the fraction of the remaining free space in the parent. + Fractional(Fractional), +} diff --git a/src/layout/par.rs b/src/layout/par.rs index 4a1e37434..64265b63e 100644 --- a/src/layout/par.rs +++ b/src/layout/par.rs @@ -19,7 +19,7 @@ pub struct ParNode { pub dir: Dir, /// The spacing to insert between each line. pub leading: Length, - /// The nodes to be arranged in a paragraph. + /// The children to be arranged in a paragraph. pub children: Vec, } @@ -27,11 +27,11 @@ pub struct ParNode { #[cfg_attr(feature = "layout-cache", derive(Hash))] pub enum ParChild { /// Spacing between other nodes. - Spacing(Linear), + Spacing(Spacing), /// A run of text and how to align it in its line. Text(EcoString, Align, Rc), /// Any child node and how to align it in its line. - Any(InlineNode, Align), + Node(InlineNode, Align), /// A decoration that applies until a matching `Undecorate`. Decorate(Decoration), /// The end of a decoration. @@ -87,7 +87,7 @@ impl ParNode { self.children.iter().map(|child| match child { ParChild::Spacing(_) => " ", ParChild::Text(ref piece, ..) => piece, - ParChild::Any(..) => "\u{FFFC}", + ParChild::Node(..) => "\u{FFFC}", ParChild::Decorate(_) | ParChild::Undecorate => "", }) } @@ -104,7 +104,7 @@ impl Debug for ParChild { match self { Self::Spacing(v) => write!(f, "Spacing({:?})", v), Self::Text(text, ..) => write!(f, "Text({:?})", text), - Self::Any(node, ..) => node.fmt(f), + Self::Node(node, ..) => node.fmt(f), Self::Decorate(deco) => write!(f, "Decorate({:?})", deco), Self::Undecorate => write!(f, "Undecorate"), } @@ -120,7 +120,7 @@ struct ParLayouter<'a> { leading: Length, /// Bidirectional text embedding levels for the paragraph. bidi: BidiInfo<'a>, - /// Layouted children and separated text runs. + /// Spacing, separated text runs and layouted nodes. items: Vec>, /// The ranges of the items in `bidi.text`. ranges: Vec, @@ -130,8 +130,10 @@ struct ParLayouter<'a> { /// A prepared item in a paragraph layout. enum ParItem<'a> { - /// Spacing between other items. - Spacing(Length), + /// Absolute spacing between other items. + Absolute(Length), + /// Fractional spacing between other items. + Fractional(Fractional), /// A shaped text run with consistent direction. Text(ShapedText<'a>, Align), /// A layouted child node. @@ -153,27 +155,35 @@ impl<'a> ParLayouter<'a> { // Layout the children and collect them into items. for (range, child) in par.ranges().zip(&par.children) { - match child { - ParChild::Spacing(amount) => { - let resolved = amount.resolve(regions.current.w); - items.push(ParItem::Spacing(resolved)); + match *child { + ParChild::Spacing(Spacing::Linear(v)) => { + let resolved = v.resolve(regions.current.w); + items.push(ParItem::Absolute(resolved)); ranges.push(range); } - ParChild::Text(_, align, style) => { + ParChild::Spacing(Spacing::Fractional(v)) => { + items.push(ParItem::Fractional(v)); + ranges.push(range); + } + ParChild::Text(_, align, ref style) => { // TODO: Also split by language and script. - for (subrange, dir) in split_runs(&bidi, range) { + let mut cursor = range.start; + for (level, group) in bidi.levels[range].group_by_key(|&lvl| lvl) { + let start = cursor; + cursor += group.len(); + let subrange = start .. cursor; let text = &bidi.text[subrange.clone()]; - let shaped = shape(ctx, text, style, dir); - items.push(ParItem::Text(shaped, *align)); + let shaped = shape(ctx, text, style, level.dir()); + items.push(ParItem::Text(shaped, align)); ranges.push(subrange); } } - ParChild::Any(node, align) => { + ParChild::Node(ref node, align) => { let frame = node.layout(ctx, regions.current.w, regions.base); - items.push(ParItem::Frame(frame, *align)); + items.push(ParItem::Frame(frame, align)); ranges.push(range); } - ParChild::Decorate(deco) => { + ParChild::Decorate(ref deco) => { starts.push((range.start, deco)); } ParChild::Undecorate => { @@ -298,41 +308,6 @@ impl<'a> ParLayouter<'a> { } } -/// Split a range of text into runs of consistent direction. -fn split_runs<'a>( - bidi: &'a BidiInfo, - range: Range, -) -> impl Iterator + 'a { - let mut cursor = range.start; - bidi.levels[range] - .group_by_key(|&level| level) - .map(move |(level, group)| { - let start = cursor; - cursor += group.len(); - (start .. cursor, level.dir()) - }) -} - -impl ParItem<'_> { - /// The size of the item. - pub fn size(&self) -> Size { - match self { - Self::Spacing(amount) => Size::new(*amount, Length::zero()), - Self::Text(shaped, ..) => shaped.size, - Self::Frame(frame, ..) => frame.size, - } - } - - /// The baseline of the item. - pub fn baseline(&self) -> Length { - match self { - Self::Spacing(_) => Length::zero(), - Self::Text(shaped, ..) => shaped.baseline, - Self::Frame(frame, ..) => frame.baseline, - } - } -} - /// A lightweight representation of a line that spans a specific range in a /// paragraph's text. This type enables you to cheaply measure the size of a /// line in a range before comitting to building the line's frame. @@ -356,6 +331,8 @@ struct LineLayout<'a> { size: Size, /// The baseline of the line. baseline: Length, + /// The sum of fractional ratios in the line. + fr: Fractional, } impl<'a> LineLayout<'a> { @@ -422,14 +399,20 @@ impl<'a> LineLayout<'a> { let mut width = Length::zero(); let mut top = Length::zero(); let mut bottom = Length::zero(); + let mut fr = Fractional::zero(); // Measure the size of the line. for item in first.iter().chain(items).chain(&last) { - let size = item.size(); - let baseline = item.baseline(); - width += size.w; - top.set_max(baseline); - bottom.set_max(size.h - baseline); + match *item { + ParItem::Absolute(v) => width += v, + ParItem::Fractional(v) => fr += v, + ParItem::Text(ShapedText { size, baseline, .. }, _) + | ParItem::Frame(Frame { size, baseline, .. }, _) => { + width += size.w; + top.set_max(baseline); + bottom.set_max(size.h - baseline); + } + } } Self { @@ -441,13 +424,14 @@ impl<'a> LineLayout<'a> { ranges, size: Size::new(width, top + bottom), baseline: top, + fr, } } /// Build the line's frame. fn build(&self, ctx: &LayoutContext, width: Length) -> Frame { let size = Size::new(self.size.w.max(width), self.size.h); - let free = size.w - self.size.w; + let remaining = size.w - self.size.w; let mut output = Frame::new(size, self.baseline); let mut offset = Length::zero(); @@ -464,7 +448,7 @@ impl<'a> LineLayout<'a> { // FIXME: Ruler alignment for RTL. ruler = ruler.max(align); - let x = ruler.resolve(self.par.dir, offset .. free + offset); + let x = ruler.resolve(self.par.dir, offset .. remaining + offset); let y = self.baseline - frame.baseline; offset += frame.size.w; @@ -473,7 +457,13 @@ impl<'a> LineLayout<'a> { }; match *item { - ParItem::Spacing(amount) => offset += amount, + ParItem::Absolute(v) => offset += v, + ParItem::Fractional(v) => { + let ratio = v / self.fr; + if remaining.is_finite() && ratio.is_finite() { + offset += ratio * remaining; + } + } ParItem::Text(ref shaped, align) => position(shaped.build(), align), ParItem::Frame(ref frame, align) => position(frame.clone(), align), } @@ -539,6 +529,7 @@ struct LineStack<'a> { finished: Vec>>, cts: Constraints, overflowing: bool, + fractional: bool, } impl<'a> LineStack<'a> { @@ -553,6 +544,7 @@ impl<'a> LineStack<'a> { lines: vec![], finished: vec![], overflowing: false, + fractional: false, } } @@ -566,12 +558,13 @@ impl<'a> LineStack<'a> { self.size.h += self.leading; } + self.fractional |= !line.fr.is_zero(); self.lines.push(line); } /// Finish the frame for one region. fn finish_region(&mut self, ctx: &LayoutContext) { - if self.regions.expand.x { + if self.regions.expand.x || self.fractional { self.size.w = self.regions.current.w; self.cts.exact.x = Some(self.regions.current.w); } diff --git a/src/layout/shape.rs b/src/layout/shape.rs index 13d5418f4..2e66a0ddf 100644 --- a/src/layout/shape.rs +++ b/src/layout/shape.rs @@ -55,8 +55,7 @@ impl InlineLevel for ShapeNode { // The "pod" is the region into which the child will be layouted. let mut pod = { - let size = - Size::new(width.unwrap_or(space), height.unwrap_or(Length::inf())); + let size = Size::new(width.unwrap_or(space), height.unwrap_or(base.h)); let base = Size::new( if width.is_some() { size.w } else { base.w }, diff --git a/src/layout/stack.rs b/src/layout/stack.rs index bbaf022b4..dbd9ddb08 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -8,7 +8,7 @@ use super::*; pub struct StackNode { /// The stacking direction. pub dir: Dir, - /// The nodes to be stacked. + /// The children to be stacked. pub children: Vec, } @@ -16,9 +16,9 @@ pub struct StackNode { #[cfg_attr(feature = "layout-cache", derive(Hash))] pub enum StackChild { /// Spacing between other nodes. - Spacing(Linear), + Spacing(Spacing), /// Any block node and how to align it in the stack. - Any(BlockNode, Align), + Node(BlockNode, Align), } impl BlockLevel for StackNode { @@ -41,7 +41,7 @@ impl Debug for StackChild { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::Spacing(v) => write!(f, "Spacing({:?})", v), - Self::Any(node, _) => node.fmt(f), + Self::Node(node, _) => node.fmt(f), } } } @@ -61,34 +61,41 @@ struct StackLayouter<'a> { full: Size, /// The generic size used by the frames for the current region. used: Gen, - /// The alignment ruler for the current region. - ruler: Align, - /// Offset, alignment and frame for all children that fit into the current - /// region. The exact positions are not known yet. - frames: Vec<(Length, Align, Rc)>, + /// The sum of fractional ratios in the current region. + fr: Fractional, + /// Spacing and layouted nodes. + items: Vec, /// Finished frames for previous regions. finished: Vec>>, } +/// A prepared item in a stack layout. +enum StackItem { + /// Absolute spacing between other items. + Absolute(Length), + /// Fractional spacing between other items. + Fractional(Fractional), + /// A layouted child node. + Frame(Rc, Align), +} + impl<'a> StackLayouter<'a> { /// Create a new stack layouter. fn new(stack: &'a StackNode, mut regions: Regions) -> Self { - let axis = stack.dir.axis(); - let full = regions.current; - let expand = regions.expand; - // Disable expansion along the block axis for children. + let axis = stack.dir.axis(); + let expand = regions.expand; regions.expand.set(axis, false); Self { stack, axis, expand, + full: regions.current, regions, - full, used: Gen::zero(), - ruler: Align::Start, - frames: vec![], + fr: Fractional::zero(), + items: vec![], finished: vec![], } } @@ -97,16 +104,15 @@ impl<'a> StackLayouter<'a> { fn layout(mut self, ctx: &mut LayoutContext) -> Vec>> { for child in &self.stack.children { match *child { - StackChild::Spacing(amount) => self.space(amount), - StackChild::Any(ref node, align) => { - let frames = node.layout(ctx, &self.regions); - let len = frames.len(); - for (i, frame) in frames.into_iter().enumerate() { - self.push_frame(frame.item, align); - if i + 1 < len { - self.finish_region(); - } - } + StackChild::Spacing(Spacing::Linear(v)) => { + self.layout_absolute(v); + } + StackChild::Spacing(Spacing::Fractional(v)) => { + self.items.push(StackItem::Fractional(v)); + self.fr += v; + } + StackChild::Node(ref node, align) => { + self.layout_node(ctx, node, align); } } } @@ -115,75 +121,102 @@ impl<'a> StackLayouter<'a> { self.finished } - /// Add block-axis spacing into the current region. - fn space(&mut self, amount: Linear) { - // Resolve the linear. - let full = self.full.get(self.axis); - let resolved = amount.resolve(full); - - // Cap the spacing to the remaining available space. This action does - // not directly affect the constraints because of the cap. + /// Layout absolute spacing. + fn layout_absolute(&mut self, amount: Linear) { + // Resolve the linear, limiting it to the remaining available space. let remaining = self.regions.current.get_mut(self.axis); - let capped = resolved.min(*remaining); - - // Grow our size and shrink the available space in the region. - self.used.block += capped; - *remaining -= capped; + let resolved = amount.resolve(self.full.get(self.axis)); + let limited = resolved.min(*remaining); + *remaining -= limited; + self.used.block += limited; + self.items.push(StackItem::Absolute(resolved)); } - /// Push a frame into the current region. - fn push_frame(&mut self, frame: Rc, align: Align) { - // Grow our size. - let offset = self.used.block; - let size = frame.size.to_gen(self.axis); - self.used.block += size.block; - self.used.inline.set_max(size.inline); - self.ruler = self.ruler.max(align); + /// Layout a block node. + fn layout_node(&mut self, ctx: &mut LayoutContext, node: &BlockNode, align: Align) { + let frames = node.layout(ctx, &self.regions); + let len = frames.len(); + for (i, frame) in frames.into_iter().enumerate() { + // Grow our size. + let size = frame.item.size.to_gen(self.axis); + self.used.block += size.block; + self.used.inline.set_max(size.inline); - // Remember the frame and shrink available space in the region for the - // following children. - self.frames.push((offset, self.ruler, frame)); - *self.regions.current.get_mut(self.axis) -= size.block; + // Remember the frame and shrink available space in the region for the + // following children. + self.items.push(StackItem::Frame(frame.item, align)); + *self.regions.current.get_mut(self.axis) -= size.block; + + if i + 1 < len { + self.finish_region(); + } + } } /// Finish the frame for one region. fn finish_region(&mut self) { - // Determine the stack's size dependening on whether the region expands. + // Determine the size that remains for fractional spacing. + let remaining = self.full.get(self.axis) - self.used.block; + + // Determine the size of the stack in this region dependening on whether + // the region expands. let used = self.used.to_size(self.axis); - let size = Size::new( + let mut size = Size::new( if self.expand.x { self.full.w } else { used.w }, if self.expand.y { self.full.h } else { used.h }, ); + // Expand fully if there are fr spacings. + let full = self.full.get(self.axis); + if !self.fr.is_zero() && full.is_finite() { + size.set(self.axis, full); + } + let mut output = Frame::new(size, size.h); + let mut before = Length::zero(); + let mut ruler = Align::Start; let mut first = true; // Place all frames. - for (offset, align, frame) in self.frames.drain(..) { - let stack_size = size.to_gen(self.axis); - let child_size = frame.size.to_gen(self.axis); + for item in self.items.drain(..) { + match item { + StackItem::Absolute(v) => before += v, + StackItem::Fractional(v) => { + let ratio = v / self.fr; + if remaining.is_finite() && ratio.is_finite() { + before += ratio * remaining; + } + } + StackItem::Frame(frame, align) => { + ruler = ruler.max(align); - // Align along the block axis. - let block = align.resolve( - self.stack.dir, - if self.stack.dir.is_positive() { - offset .. stack_size.block - self.used.block + offset - } else { - let offset_with_self = offset + child_size.block; - self.used.block - offset_with_self - .. stack_size.block - offset_with_self - }, - ); + let parent = size.to_gen(self.axis); + let child = frame.size.to_gen(self.axis); - let pos = Gen::new(Length::zero(), block).to_point(self.axis); + // Align along the block axis. + let block = ruler.resolve( + self.stack.dir, + if self.stack.dir.is_positive() { + let after = self.used.block - before; + before .. parent.block - after + } else { + let before_with_self = before + child.block; + let after = self.used.block - before_with_self; + after .. parent.block - before_with_self + }, + ); - // The baseline of the stack is that of the first frame. - if first { - output.baseline = pos.y + frame.baseline; - first = false; + let pos = Gen::new(Length::zero(), block).to_point(self.axis); + if first { + // The baseline of the stack is that of the first frame. + output.baseline = pos.y + frame.baseline; + first = false; + } + + output.push_frame(pos, frame); + before += child.block; + } } - - output.push_frame(pos, frame); } // Generate tight constraints for now. @@ -194,7 +227,7 @@ impl<'a> StackLayouter<'a> { self.regions.next(); self.full = self.regions.current; self.used = Gen::zero(); - self.ruler = Align::Start; + self.fr = Fractional::zero(); self.finished.push(output.constrain(cts)); } } diff --git a/src/library/layout.rs b/src/library/layout.rs index 4b416156f..4ef0926fd 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -130,17 +130,15 @@ pub fn align(ctx: &mut EvalContext, args: &mut Args) -> TypResult { /// `h`: Horizontal spacing. pub fn h(_: &mut EvalContext, args: &mut Args) -> TypResult { - let spacing = args.expect("spacing")?; let mut template = Template::new(); - template.spacing(GenAxis::Inline, spacing); + template.spacing(GenAxis::Inline, args.expect("spacing")?); Ok(Value::Template(template)) } /// `v`: Vertical spacing. pub fn v(_: &mut EvalContext, args: &mut Args) -> TypResult { - let spacing = args.expect("spacing")?; let mut template = Template::new(); - template.spacing(GenAxis::Block, spacing); + template.spacing(GenAxis::Block, args.expect("spacing")?); Ok(Value::Template(template)) } @@ -196,20 +194,21 @@ pub fn pad(_: &mut EvalContext, args: &mut Args) -> TypResult { /// `stack`: Stack children along an axis. pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult { enum Child { - Spacing(Linear), + Spacing(Spacing), Any(Template), } castable! { - Child: "linear or template", - Value::Length(v) => Self::Spacing(v.into()), - Value::Relative(v) => Self::Spacing(v.into()), - Value::Linear(v) => Self::Spacing(v), + Child: "linear, fractional or template", + Value::Length(v) => Self::Spacing(Spacing::Linear(v.into())), + Value::Relative(v) => Self::Spacing(Spacing::Linear(v.into())), + Value::Linear(v) => Self::Spacing(Spacing::Linear(v)), + Value::Fractional(v) => Self::Spacing(Spacing::Fractional(v)), Value::Template(v) => Self::Any(v), } let dir = args.named("dir")?.unwrap_or(Dir::TTB); - let spacing = args.named::("spacing")?; + let spacing = args.named("spacing")?; let list: Vec = args.all().collect(); Ok(Value::Template(Template::from_block(move |style| { @@ -229,7 +228,7 @@ pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult { } let node = template.to_stack(style).into(); - children.push(StackChild::Any(node, style.aligns.block)); + children.push(StackChild::Node(node, style.aligns.block)); delayed = spacing; } } diff --git a/src/library/mod.rs b/src/library/mod.rs index 919f75320..6f8881c3c 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -20,6 +20,7 @@ use crate::diag::{At, TypResult}; use crate::eval::{Args, Array, EvalContext, Scope, Str, Template, Value}; use crate::font::{FontFamily, FontStretch, FontStyle, FontWeight, VerticalFontMetric}; use crate::geom::*; +use crate::layout::Spacing; use crate::style::Style; use crate::syntax::{Span, Spanned}; @@ -144,3 +145,11 @@ dynamic! { Value::Relative(v) => Self::Linear(v.into()), Value::Linear(v) => Self::Linear(v), } + +castable! { + Spacing: "linear or fractional", + Value::Length(v) => Self::Linear(v.into()), + Value::Relative(v) => Self::Linear(v.into()), + Value::Linear(v) => Self::Linear(v), + Value::Fractional(v) => Self::Fractional(v), +} diff --git a/tests/ref/layout/spacing.png b/tests/ref/layout/spacing.png index bffa95075..c653df6d7 100644 Binary files a/tests/ref/layout/spacing.png and b/tests/ref/layout/spacing.png differ diff --git a/tests/ref/layout/stack.png b/tests/ref/layout/stack-1.png similarity index 100% rename from tests/ref/layout/stack.png rename to tests/ref/layout/stack-1.png diff --git a/tests/ref/layout/stack-2.png b/tests/ref/layout/stack-2.png new file mode 100644 index 000000000..3e503e653 Binary files /dev/null and b/tests/ref/layout/stack-2.png differ diff --git a/tests/typ/layout/spacing.typ b/tests/typ/layout/spacing.typ index 3794612cc..7cf6760fa 100644 --- a/tests/typ/layout/spacing.typ +++ b/tests/typ/layout/spacing.typ @@ -14,6 +14,9 @@ Add #h(10pt) #h(10pt) up #let x = 25% - 4pt | #h(x) | #h(x) | #h(x) | #h(x) | +// Fractional. +| #h(1fr) | #h(2fr) | #h(1fr) | + --- // Missing spacing. // Error: 11-13 missing argument: spacing diff --git a/tests/typ/layout/stack.typ b/tests/typ/layout/stack-1.typ similarity index 100% rename from tests/typ/layout/stack.typ rename to tests/typ/layout/stack-1.typ diff --git a/tests/typ/layout/stack-2.typ b/tests/typ/layout/stack-2.typ new file mode 100644 index 000000000..afc9de394 --- /dev/null +++ b/tests/typ/layout/stack-2.typ @@ -0,0 +1,24 @@ +// Test fr units in stacks. + +--- +#page(height: 3.5cm) +#stack( + dir: ltr, + spacing: 1fr, + ..for c in "ABCDEFGHI" {([#c],)} +) + +Hello +#v(2fr) +from #h(1fr) the #h(1fr) wonderful +#v(1fr) +World! 🌍 + +--- +#page(height: 2cm) +#font(white) +#box(fill: forest)[ + #v(1fr) + #h(1fr) Hi you! #h(5pt) + #v(5pt) +]