diff --git a/bench/src/bench.rs b/bench/src/bench.rs index d4e297bf3..832d538f2 100644 --- a/bench/src/bench.rs +++ b/bench/src/bench.rs @@ -13,7 +13,7 @@ use typst::typeset; const FONT_DIR: &str = "../fonts"; const TYP_DIR: &str = "../tests/typ"; -const CASES: &[&str] = &["full/coma.typ", "text/basic.typ"]; +const CASES: &[&str] = &["coma.typ", "text/basic.typ"]; fn benchmarks(c: &mut Criterion) { let mut loader = FsLoader::new(); diff --git a/src/exec/context.rs b/src/exec/context.rs index e03a7c677..3994fdb97 100644 --- a/src/exec/context.rs +++ b/src/exec/context.rs @@ -74,17 +74,6 @@ impl ExecContext { mem::replace(&mut self.stack, stack).build() } - /// Push any node into the active paragraph. - pub fn push(&mut self, node: impl Into) { - let align = self.state.aligns.cross; - self.stack.par.push(ParChild::Any(node.into(), align)); - } - - /// Push a word space into the active paragraph. - pub fn push_word_space(&mut self) { - self.stack.par.push_soft(self.make_text_node(" ")); - } - /// Push text into the active paragraph. /// /// The text is split into lines at newlines. @@ -92,11 +81,30 @@ impl ExecContext { self.stack.par.push(self.make_text_node(text)); } - /// Push spacing into paragraph or stack depending on `axis`. + /// Push a word space into the active paragraph. + pub fn push_word_space(&mut self) { + self.stack.par.push_soft(self.make_text_node(" ")); + } + + /// Push any node into the active paragraph. + pub fn push_into_par(&mut self, node: impl Into) { + let align = self.state.aligns.cross; + self.stack.par.push(ParChild::Any(node.into(), align)); + } + + /// Push any node into the active stack. + pub fn push_into_stack(&mut self, node: impl Into) { + self.parbreak(); + let aligns = self.state.aligns; + self.stack.push(StackChild::Any(node.into(), aligns)); + self.parbreak(); + } + + /// Push spacing into the active paragraph or stack depending on the `axis`. pub fn push_spacing(&mut self, axis: GenAxis, amount: Length) { match axis { GenAxis::Main => { - self.stack.parbreak(&self.state); + self.stack.finish_par(&self.state); self.stack.push_hard(StackChild::Spacing(amount)); } GenAxis::Cross => { @@ -113,18 +121,18 @@ impl ExecContext { /// Apply a forced paragraph break. pub fn parbreak(&mut self) { let amount = self.state.par.spacing.resolve(self.state.font.size); - self.stack.parbreak(&self.state); + self.stack.finish_par(&self.state); self.stack.push_soft(StackChild::Spacing(amount)); } /// Apply a forced page break. - pub fn pagebreak(&mut self, keep: bool, hard: bool, source: Span) { + pub fn pagebreak(&mut self, keep: bool, hard: bool, span: Span) { if let Some(builder) = &mut self.page { let page = mem::replace(builder, PageBuilder::new(&self.state, hard)); let stack = mem::replace(&mut self.stack, StackBuilder::new(&self.state)); self.tree.runs.extend(page.build(stack.build(), keep)); } else { - self.diag(error!(source, "cannot modify page from here")); + self.diag(error!(span, "cannot modify page from here")); } } @@ -185,6 +193,11 @@ impl StackBuilder { } } + fn push(&mut self, child: StackChild) { + self.children.extend(self.last.any()); + self.children.push(child); + } + fn push_soft(&mut self, child: StackChild) { self.last.soft(child); } @@ -194,11 +207,10 @@ impl StackBuilder { self.children.push(child); } - fn parbreak(&mut self, state: &State) { + fn finish_par(&mut self, state: &State) { let par = mem::replace(&mut self.par, ParBuilder::new(state)); if let Some(par) = par.build() { - self.children.extend(self.last.any()); - self.children.push(par); + self.push(par); } } diff --git a/src/exec/mod.rs b/src/exec/mod.rs index 8b088f85a..fea6de337 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -11,7 +11,7 @@ use std::rc::Rc; use crate::diag::Pass; use crate::eval::{ExprMap, TemplateFunc, TemplateNode, TemplateValue, Value}; use crate::geom::{Dir, Gen}; -use crate::layout::{self, FixedNode, StackChild, StackNode}; +use crate::layout::{self, StackChild, StackNode}; use crate::pretty::pretty; use crate::syntax; @@ -127,12 +127,7 @@ fn exec_item(ctx: &mut ExecContext, label: String, body: &syntax::Tree, map: &Ex ], }; - ctx.push(FixedNode { - width: None, - height: None, - child: stack.into(), - }); - + ctx.push_into_stack(stack); ctx.parbreak(); } diff --git a/src/layout/fixed.rs b/src/layout/fixed.rs index 7c28e8e5c..233c27f36 100644 --- a/src/layout/fixed.rs +++ b/src/layout/fixed.rs @@ -19,8 +19,8 @@ impl Layout for FixedNode { self.height.map_or(current.height, |h| h.resolve(base.height)), ); - let fixed = Spec::new(self.width.is_some(), self.height.is_some()); - let regions = Regions::one(size, fixed); + let expand = Spec::new(self.width.is_some(), self.height.is_some()); + let regions = Regions::one(size, expand); self.child.layout(ctx, ®ions) } } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 9d8549e67..4153fc3cd 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -64,8 +64,8 @@ impl PageRun { // When one of the lengths is infinite the page fits its content along // that axis. let Size { width, height } = self.size; - let fixed = Spec::new(width.is_finite(), height.is_finite()); - let regions = Regions::repeat(self.size, fixed); + let expand = Spec::new(width.is_finite(), height.is_finite()); + let regions = Regions::repeat(self.size, expand); self.child.layout(ctx, ®ions) } } @@ -214,34 +214,34 @@ pub struct Regions { pub backlog: Vec, /// The final region that is repeated once the backlog is drained. pub last: Option, - /// Whether layouting into these regions should produce frames of the exact - /// size of `current` instead of shrinking to fit the content. + /// Whether nodes should expand to fill the regions instead of shrinking to + /// fit the content. /// /// This property is only handled by nodes that have the ability to control /// their own size. - pub fixed: Spec, + pub expand: Spec, } impl Regions { /// Create a new region sequence with exactly one region. - pub fn one(size: Size, fixed: Spec) -> Self { + pub fn one(size: Size, expand: Spec) -> Self { Self { current: size, base: size, backlog: vec![], last: None, - fixed, + expand, } } /// Create a new sequence of same-size regions that repeats indefinitely. - pub fn repeat(size: Size, fixed: Spec) -> Self { + pub fn repeat(size: Size, expand: Spec) -> Self { Self { current: size, base: size, backlog: vec![], last: Some(size), - fixed, + expand, } } @@ -255,7 +255,7 @@ impl Regions { base: f(self.base), backlog: self.backlog.iter().copied().map(|s| f(s)).collect(), last: self.last.map(f), - fixed: self.fixed, + expand: self.expand, } } diff --git a/src/layout/par.rs b/src/layout/par.rs index d51bad3eb..241854581 100644 --- a/src/layout/par.rs +++ b/src/layout/par.rs @@ -92,7 +92,7 @@ impl Debug for ParChild { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::Spacing(amount) => write!(f, "Spacing({:?})", amount), - Self::Text(text, _, align) => write!(f, "Text({:?}, {:?})", text, align), + Self::Text(text, align, _) => write!(f, "Text({:?}, {:?})", text, align), Self::Any(any, align) => { f.debug_tuple("Any").field(any).field(align).finish() } @@ -304,7 +304,7 @@ impl<'a> LineStack<'a> { } fn finish_region(&mut self, ctx: &LayoutContext) { - if self.regions.fixed.horizontal { + if self.regions.expand.horizontal { self.size.width = self.regions.current.width; } diff --git a/src/layout/stack.rs b/src/layout/stack.rs index e4c0708d5..7049f60cc 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -44,6 +44,8 @@ struct StackLayouter<'a> { stack: &'a StackNode, /// The axis of the main direction. main: SpecAxis, + /// Whether the stack should expand to fill the region. + expand: Spec, /// The region to layout into. regions: Regions, /// Offset, alignment and frame for all children that fit into the current @@ -62,19 +64,27 @@ struct StackLayouter<'a> { impl<'a> StackLayouter<'a> { fn new(stack: &'a StackNode, mut regions: Regions) -> Self { + let main = stack.dirs.main.axis(); + let full = regions.current; + let expand = regions.expand; + + // Disable expansion on the main axis for children. + *regions.expand.get_mut(main) = false; + if let Some(aspect) = stack.aspect { regions.apply_aspect_ratio(aspect); } Self { stack, - main: stack.dirs.main.axis(), + main, + expand, + regions, finished: vec![], frames: vec![], - full: regions.current, + full, used: Gen::zero(), ruler: Align::Start, - regions, } } @@ -138,13 +148,13 @@ impl<'a> StackLayouter<'a> { fn finish_region(&mut self) { let used = self.used.to_size(self.main); - let fixed = self.regions.fixed; + let expand = self.expand; // Determine the stack's size dependening on whether the region is // fixed. let mut stack_size = Size::new( - if fixed.horizontal { self.full.width } else { used.width }, - if fixed.vertical { self.full.height } else { used.height }, + if expand.horizontal { self.full.width } else { used.width }, + if expand.vertical { self.full.height } else { used.height }, ); // Make sure the stack's size satisfies the aspect ratio. diff --git a/src/library/grid.rs b/src/library/grid.rs index cfe46bf46..79070e796 100644 --- a/src/library/grid.rs +++ b/src/library/grid.rs @@ -47,7 +47,7 @@ pub fn grid(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { .map(|child| ctx.exec_template_stack(child).into()) .collect(); - ctx.push(GridNode { + ctx.push_into_stack(GridNode { column_dir: column_dir.unwrap_or(ctx.state.lang.dir), children, tracks: Gen::new(columns.clone(), rows.clone()), diff --git a/src/library/image.rs b/src/library/image.rs index 7fabfe357..e926b9557 100644 --- a/src/library/image.rs +++ b/src/library/image.rs @@ -33,7 +33,7 @@ pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { Value::template("image", move |ctx| { if let Some(node) = node { - ctx.push(node); + ctx.push_into_par(node); } }) } diff --git a/src/library/pad.rs b/src/library/pad.rs index 6b944c9c8..5fa565674 100644 --- a/src/library/pad.rs +++ b/src/library/pad.rs @@ -32,6 +32,6 @@ pub fn pad(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { Value::template("pad", move |ctx| { let child = ctx.exec_template_stack(&body).into(); - ctx.push(PadNode { padding, child }); + ctx.push_into_stack(PadNode { padding, child }); }) } diff --git a/src/library/shapes.rs b/src/library/shapes.rs index 287b1c107..3c6f801d1 100644 --- a/src/library/shapes.rs +++ b/src/library/shapes.rs @@ -67,13 +67,13 @@ fn rect_impl( let fixed = FixedNode { width, height, child: stack.into() }; if let Some(color) = fill { - ctx.push(BackgroundNode { + ctx.push_into_par(BackgroundNode { shape: BackgroundShape::Rect, fill: Fill::Color(color), child: fixed.into(), }); } else { - ctx.push(fixed); + ctx.push_into_par(fixed); } }) } @@ -151,13 +151,13 @@ fn ellipse_impl( }; if let Some(color) = fill { - ctx.push(BackgroundNode { + ctx.push_into_par(BackgroundNode { shape: BackgroundShape::Ellipse, fill: Fill::Color(color), child: fixed.into(), }); } else { - ctx.push(fixed); + ctx.push_into_par(fixed); } }) } diff --git a/src/library/stack.rs b/src/library/stack.rs index 03bdc6a1d..672cfbeff 100644 --- a/src/library/stack.rs +++ b/src/library/stack.rs @@ -31,7 +31,7 @@ pub fn stack(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { }) .collect(); - ctx.push(StackNode { + ctx.push_into_stack(StackNode { dirs: Gen::new(ctx.state.lang.dir, dir), aspect: None, children, diff --git a/tests/ref/layout/fixed.png b/tests/ref/layout/fixed.png index e07e15e8c..5ee9c4ff4 100644 Binary files a/tests/ref/layout/fixed.png and b/tests/ref/layout/fixed.png differ diff --git a/tests/ref/layout/pad.png b/tests/ref/layout/pad.png index 0bf0adde7..645c8db47 100644 Binary files a/tests/ref/layout/pad.png and b/tests/ref/layout/pad.png differ diff --git a/tests/typ/layout/fixed.typ b/tests/typ/layout/fixed.typ index bcb88fe1a..51646eaa4 100644 --- a/tests/typ/layout/fixed.typ +++ b/tests/typ/layout/fixed.typ @@ -1,14 +1,14 @@ -// Test shrink-to-fit vs fixed. +// Test shrink-to-fit vs expand. --- #let right(body) = align(right, body) #let pad(body) = pad(left: 10pt, right: 10pt, body) -// Top-level paragraph fills page, boxed paragraph only when width is fixed. +// Top-level paragraph fills page, boxed paragraph only when the width is set. L #right[R] \ #rect(width: 50pt)[L #right[R]] \ #rect[L #right[R]] // Pad inherits expansion behaviour. -#pad[PL #right[PR]] \ #rect(pad[PL #right[PR]]) +#pad[PL #right[PR]] diff --git a/tests/typ/layout/pad.typ b/tests/typ/layout/pad.typ index 3726ce53d..8c23549a8 100644 --- a/tests/typ/layout/pad.typ +++ b/tests/typ/layout/pad.typ @@ -5,9 +5,19 @@ #pad(left: 10pt, [Indented!]) // All sides together. -#rect(fill: conifer, - pad(10pt, right: 20pt, - rect(width: 20pt, height: 20pt, fill: #eb5278))) +#rect(fill: conifer)[ + #pad(10pt, right: 20pt)[ + #rect(width: 20pt, height: 20pt, fill: #eb5278) + ] +] // Error: 14-24 missing argument: body Hi #rect(pad(left: 10pt)) there + +--- +// Test that the pad node doesn't consume the whole region. + +#page(width: 4cm, height: 5cm) +#align(left)[Before] +#pad(10pt, image("../../res/tiger.jpg")) +#align(right)[After]