diff --git a/src/eval/template.rs b/src/eval/template.rs index 59fe293aa..639163394 100644 --- a/src/eval/template.rs +++ b/src/eval/template.rs @@ -6,7 +6,7 @@ use std::rc::Rc; use super::Str; use crate::diag::StrResult; -use crate::geom::{Align, Dir, GenAxis, Length, Linear, Sides, Size}; +use crate::geom::{Align, Dir, GenAxis, Length, Linear, Sides, Size, SpecAxis}; use crate::layout::{ Decoration, LayoutNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild, StackNode, @@ -309,7 +309,7 @@ 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(amount, SpecAxis::Vertical)); } /// Apply a forced page break. @@ -335,7 +335,7 @@ impl Builder { /// 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::new(node, self.style.aligns.block)); self.parbreak(); } @@ -344,7 +344,7 @@ impl Builder { match axis { GenAxis::Block => { self.stack.finish_par(&self.style); - self.stack.push_hard(StackChild::Spacing(amount)); + self.stack.push_hard(StackChild::spacing(amount, SpecAxis::Vertical)); } GenAxis::Inline => { self.stack.par.push_hard(ParChild::Spacing(amount)); @@ -508,10 +508,8 @@ impl ParBuilder { fn build(self) -> Option { let Self { align, dir, line_spacing, children, .. } = self; - (!children.is_empty()).then(|| { - let node = ParNode { dir, line_spacing, children }; - StackChild::Any(node.into(), align) - }) + (!children.is_empty()) + .then(|| StackChild::new(ParNode { dir, line_spacing, children }, align)) } } diff --git a/src/eval/walk.rs b/src/eval/walk.rs index b76300ecc..24284e4e9 100644 --- a/src/eval/walk.rs +++ b/src/eval/walk.rs @@ -2,7 +2,7 @@ use std::rc::Rc; use super::{Eval, EvalContext, Str, Template, Value}; use crate::diag::TypResult; -use crate::geom::Align; +use crate::geom::{Align, SpecAxis}; use crate::layout::{ParChild, ParNode, StackChild, StackNode}; use crate::syntax::*; use crate::util::BoolExt; @@ -119,9 +119,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::new(label, Align::Start), + StackChild::spacing(style.text.size / 2.0, SpecAxis::Horizontal), + StackChild::new(body.to_stack(&style), Align::Start), ], } }); diff --git a/src/geom/gen.rs b/src/geom/gen.rs index 18801460b..ff8c881aa 100644 --- a/src/geom/gen.rs +++ b/src/geom/gen.rs @@ -98,7 +98,7 @@ impl Debug for Gen { } /// The two generic layouting axes. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum GenAxis { /// The axis words and lines are set along. Inline, diff --git a/src/geom/sides.rs b/src/geom/sides.rs index fc7fb3f4f..d1fcf9b78 100644 --- a/src/geom/sides.rs +++ b/src/geom/sides.rs @@ -76,7 +76,7 @@ impl Get for Sides { } /// The four sides of objects. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum Side { /// The left side. Left, diff --git a/src/geom/spec.rs b/src/geom/spec.rs index a62355482..a257214bf 100644 --- a/src/geom/spec.rs +++ b/src/geom/spec.rs @@ -101,7 +101,7 @@ impl Debug for Spec { } /// The two specific layouting axes. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum SpecAxis { /// The horizontal layouting axis. Horizontal, diff --git a/src/layout/grid.rs b/src/layout/grid.rs index 7ed5dd7ad..ddda27c01 100644 --- a/src/layout/grid.rs +++ b/src/layout/grid.rs @@ -330,18 +330,10 @@ impl<'a> GridLayouter<'a> { /// Layout the grid row-by-row. fn layout(mut self, ctx: &mut LayoutContext) -> Vec>> { - let base = self.regions.base.get(self.block); - for y in 0 .. self.rows.len() { match self.rows[y] { - TrackSizing::Auto => { - self.layout_auto_row(ctx, y); - } - TrackSizing::Linear(v) => { - let resolved = v.resolve(base); - let frame = self.layout_single_row(ctx, resolved, y); - self.push_row(ctx, frame); - } + 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.constraints.exact.set(self.block, Some(self.full)); @@ -395,7 +387,7 @@ impl<'a> GridLayouter<'a> { // Layout into a single region. if let &[first] = resolved.as_slice() { let frame = self.layout_single_row(ctx, first, y); - self.push_row(ctx, frame); + self.push_row(frame); return; } @@ -414,14 +406,39 @@ impl<'a> GridLayouter<'a> { let frames = self.layout_multi_row(ctx, &resolved, y); let len = frames.len(); for (i, frame) in frames.into_iter().enumerate() { + self.push_row(frame); if i + 1 < len { self.constraints.exact.set(self.block, Some(self.full)); + self.finish_region(ctx); } - self.push_row(ctx, frame); } } - /// Layout a row with a fixed size along the block axis. + /// Layout a row with linear sizing along the block axis. Such a row cannot + /// break across multiple regions, but it may force a region break. + fn layout_linear_row(&mut self, ctx: &mut LayoutContext, v: Linear, y: usize) { + let base = self.regions.base.get(self.block); + let resolved = v.resolve(base); + let frame = self.layout_single_row(ctx, resolved, y); + + // Skip to fitting region. + let length = frame.size.get(self.block); + while !self.regions.current.get(self.block).fits(length) + && !self.regions.in_full_last() + { + self.constraints.max.set(self.block, Some(self.used.block + length)); + self.finish_region(ctx); + + // Don't skip multiple regions for gutter and don't push a row. + if y % 2 == 1 { + return; + } + } + + self.push_row(frame); + } + + /// Layout a row with a fixed size along the block axis and return its frame. fn layout_single_row( &self, ctx: &mut LayoutContext, @@ -508,19 +525,9 @@ impl<'a> GridLayouter<'a> { outputs } - /// Push a row frame into the current or next fitting region, finishing - /// regions (including layouting fractional rows) if necessary. - fn push_row(&mut self, ctx: &mut LayoutContext, frame: Frame) { + /// Push a row frame into the current region. + fn push_row(&mut self, frame: Frame) { let length = frame.size.get(self.block); - - // Skip to fitting region. - while !self.regions.current.get(self.block).fits(length) - && !self.regions.in_full_last() - { - self.constraints.max.set(self.block, Some(self.used.block + length)); - self.finish_region(ctx); - } - *self.regions.current.get_mut(self.block) -= length; self.used.block += length; self.lrows.push(Row::Frame(frame)); diff --git a/src/layout/image.rs b/src/layout/image.rs index 4327375b7..eb2fc2217 100644 --- a/src/layout/image.rs +++ b/src/layout/image.rs @@ -23,18 +23,18 @@ impl Layout for ImageNode { let pixel_size = Spec::new(img.width() as f64, img.height() as f64); let pixel_ratio = pixel_size.x / pixel_size.y; - let mut constraints = Constraints::new(regions.expand); - constraints.set_base_if_linear(regions.base, Spec::new(self.width, self.height)); - let width = self.width.map(|w| w.resolve(regions.base.w)); let height = self.height.map(|w| w.resolve(regions.base.h)); + let mut cts = Constraints::new(regions.expand); + cts.set_base_if_linear(regions.base, Spec::new(self.width, self.height)); + let size = match (width, height) { (Some(width), Some(height)) => Size::new(width, height), (Some(width), None) => Size::new(width, width / pixel_ratio), (None, Some(height)) => Size::new(height * pixel_ratio, height), (None, None) => { - constraints.exact.x = Some(regions.current.w); + cts.exact.x = Some(regions.current.w); if regions.current.w.is_finite() { // Fit to width. Size::new(regions.current.w, regions.current.w / pixel_ratio) @@ -48,7 +48,7 @@ impl Layout for ImageNode { let mut frame = Frame::new(size, size.h); frame.push(Point::zero(), Element::Image(self.id, size)); - vec![frame.constrain(constraints)] + vec![frame.constrain(cts)] } } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index cd59e3d2c..ee74ff6fe 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -11,6 +11,7 @@ mod par; mod regions; mod shape; mod shaping; +mod spacing; mod stack; mod tree; @@ -25,6 +26,7 @@ pub use par::*; pub use regions::*; pub use shape::*; pub use shaping::*; +pub use spacing::*; pub use stack::*; pub use tree::*; diff --git a/src/layout/shape.rs b/src/layout/shape.rs index aa90707c0..3469f6602 100644 --- a/src/layout/shape.rs +++ b/src/layout/shape.rs @@ -46,7 +46,8 @@ impl Layout for ShapeNode { let mut cts = Constraints::new(regions.expand); cts.set_base_if_linear(regions.base, Spec::new(self.width, self.height)); - // Set exact and base constraint if child is automatically sized. + // Set tight exact and base constraints if the child is + // automatically sized since we don't know what the child might do. if self.width.is_none() { cts.exact.x = Some(regions.current.w); cts.base.x = Some(regions.base.w); diff --git a/src/layout/shaping.rs b/src/layout/shaping.rs index 4580ebf56..7bd2646d4 100644 --- a/src/layout/shaping.rs +++ b/src/layout/shaping.rs @@ -48,6 +48,7 @@ pub fn shape<'a>( /// This type contains owned or borrowed shaped text runs, which can be /// measured, used to reshape substrings more quickly and converted into a /// frame. +#[derive(Debug, Clone)] pub struct ShapedText<'a> { /// The text that was shaped. pub text: &'a str, @@ -64,7 +65,7 @@ pub struct ShapedText<'a> { } /// A single glyph resulting from shaping. -#[derive(Copy, Clone)] +#[derive(Debug, Copy, Clone)] pub struct ShapedGlyph { /// The font face the glyph is contained in. pub face_id: FaceId, diff --git a/src/layout/spacing.rs b/src/layout/spacing.rs new file mode 100644 index 000000000..68fab3e67 --- /dev/null +++ b/src/layout/spacing.rs @@ -0,0 +1,50 @@ +use super::*; + +/// Spacing between other nodes. +#[derive(Debug)] +#[cfg_attr(feature = "layout-cache", derive(Hash))] +pub struct SpacingNode { + /// Which axis to space on. + pub axis: SpecAxis, + /// How much spacing to add. + pub amount: Linear, +} + +impl Layout for SpacingNode { + fn layout( + &self, + _: &mut LayoutContext, + regions: &Regions, + ) -> Vec>> { + let base = regions.base.get(self.axis); + let resolved = self.amount.resolve(base); + let limit = regions.current.get(self.axis); + + // Generate constraints. + let mut cts = Constraints::new(regions.expand); + if self.amount.is_relative() { + cts.base.set(self.axis, Some(base)); + } + + // If the spacing fits into the region, any larger region would also do. + // If it was limited though, any change it region size might lead to + // different results. + if resolved < limit { + cts.min.set(self.axis, Some(resolved)); + } else { + cts.exact.set(self.axis, Some(limit)); + } + + // Create frame with limited spacing size along spacing axis and zero + // extent along the other axis. + let mut size = Size::zero(); + size.set(self.axis, resolved.min(limit)); + vec![Frame::new(size, size.h).constrain(cts)] + } +} + +impl From for LayoutNode { + fn from(spacing: SpacingNode) -> Self { + Self::new(spacing) + } +} diff --git a/src/layout/stack.rs b/src/layout/stack.rs index 765fb7ab0..4b9328a7f 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -12,15 +12,6 @@ pub struct StackNode { pub children: Vec, } -/// A child of a stack node. -#[cfg_attr(feature = "layout-cache", derive(Hash))] -pub enum StackChild { - /// Spacing between other nodes. - Spacing(Linear), - /// Any child node and how to align it in the stack. - Any(LayoutNode, Align), -} - impl Layout for StackNode { fn layout( &self, @@ -37,12 +28,31 @@ impl From for LayoutNode { } } +/// A child of a stack node. +#[cfg_attr(feature = "layout-cache", derive(Hash))] +pub struct StackChild { + /// The node itself. + pub node: LayoutNode, + /// How to align the node along the block axis. + pub align: Align, +} + +impl StackChild { + /// Create a new stack child. + pub fn new(node: impl Into, align: Align) -> Self { + Self { node: node.into(), align } + } + + /// Create a spacing stack child. + pub fn spacing(amount: impl Into, axis: SpecAxis) -> Self { + Self::new(SpacingNode { amount: amount.into(), axis }, Align::Start) + } +} + 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), - } + write!(f, "{:?}: ", self.align)?; + self.node.fmt(f) } } @@ -51,7 +61,7 @@ struct StackLayouter<'a> { /// The stack node to layout. stack: &'a StackNode, /// The axis of the block direction. - block: SpecAxis, + axis: SpecAxis, /// Whether the stack should expand to fill the region. expand: Spec, /// The region to layout into. @@ -63,10 +73,6 @@ struct StackLayouter<'a> { used: Gen, /// The alignment ruler for the current region. ruler: Align, - /// The constraints for the current region. - constraints: Constraints, - /// Whether the last region can fit all the remaining content. - overflowing: bool, /// 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)>, @@ -77,23 +83,21 @@ struct StackLayouter<'a> { impl<'a> StackLayouter<'a> { /// Create a new stack layouter. fn new(stack: &'a StackNode, mut regions: Regions) -> Self { - let block = stack.dir.axis(); + let axis = stack.dir.axis(); let full = regions.current; let expand = regions.expand; // Disable expansion along the block axis for children. - regions.expand.set(block, false); + regions.expand.set(axis, false); Self { stack, - block, + axis, expand, regions, full, used: Gen::zero(), ruler: Align::Start, - constraints: Constraints::new(expand), - overflowing: false, frames: vec![], finished: vec![], } @@ -102,17 +106,12 @@ impl<'a> StackLayouter<'a> { /// Layout all children. 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 nodes = node.layout(ctx, &self.regions); - let len = nodes.len(); - for (i, frame) in nodes.into_iter().enumerate() { - if i + 1 < len { - self.constraints.exact = self.full.to_spec().map(Some); - } - self.push_frame(frame.item, align); - } + let frames = child.node.layout(ctx, &self.regions); + let len = frames.len(); + for (i, frame) in frames.into_iter().enumerate() { + self.push_frame(frame.item, child.align); + if i + 1 < len { + self.finish_region(); } } } @@ -121,96 +120,37 @@ 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.block); - 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. - let remaining = self.regions.current.get_mut(self.block); - let capped = resolved.min(*remaining); - - // Grow our size and shrink the available space in the region. - self.used.block += capped; - *remaining -= capped; - } - - /// Push a frame into the current or next fitting region, finishing regions - /// if necessary. + /// Push a frame into the current region. fn push_frame(&mut self, frame: Rc, align: Align) { - let size = frame.size.to_gen(self.block); - - // Don't allow `Start` after `End` in the same region. - if align < self.ruler { - self.finish_region(); - } - - // Find a fitting region. - while !self.regions.current.get(self.block).fits(size.block) { - if self.regions.in_full_last() { - self.overflowing = true; - break; - } - - self.constraints - .max - .get_mut(self.block) - .set_min(self.used.block + size.block); - - self.finish_region(); - } - - // Shrink available space in the region. - *self.regions.current.get_mut(self.block) -= size.block; - // 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 = align; + self.ruler = self.ruler.max(align); - // Remember the frame with offset and alignment. - self.frames.push((offset, align, frame)); + // 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; } /// Finish the frame for one region. fn finish_region(&mut self) { - let expand = self.expand; - let used = self.used.to_size(self.block); - // Determine the stack's size dependening on whether the region expands. + let used = self.used.to_size(self.axis); let size = Size::new( - if expand.x { - self.constraints.exact.x = Some(self.full.w); - self.full.w - } else { - self.constraints.min.x = Some(used.w.min(self.full.w)); - used.w - }, - if expand.y { - self.constraints.exact.y = Some(self.full.h); - self.full.h - } else { - self.constraints.min.y = Some(used.h.min(self.full.h)); - used.h - }, + if self.expand.x { self.full.w } else { used.w }, + if self.expand.y { self.full.h } else { used.h }, ); - if self.overflowing { - self.constraints.min.y = None; - self.constraints.max.y = None; - self.constraints.exact = self.full.to_spec().map(Some); - } - let mut output = Frame::new(size, size.h); let mut first = true; // Place all frames. for (offset, align, frame) in self.frames.drain(..) { - let stack_size = size.to_gen(self.block); - let child_size = frame.size.to_gen(self.block); + let stack_size = size.to_gen(self.axis); + let child_size = frame.size.to_gen(self.axis); // Align along the block axis. let block = align.resolve( @@ -224,7 +164,7 @@ impl<'a> StackLayouter<'a> { }, ); - let pos = Gen::new(Length::zero(), block).to_point(self.block); + let pos = Gen::new(Length::zero(), block).to_point(self.axis); // The baseline of the stack is that of the first frame. if first { @@ -235,11 +175,15 @@ impl<'a> StackLayouter<'a> { output.push_frame(pos, frame); } + // Generate tight constraints for now. + let mut cts = Constraints::new(self.expand); + cts.exact = self.full.to_spec().map(Some); + cts.base = self.regions.base.to_spec().map(Some); + self.regions.next(); self.full = self.regions.current; self.used = Gen::zero(); self.ruler = Align::Start; - self.finished.push(output.constrain(self.constraints)); - self.constraints = Constraints::new(expand); + self.finished.push(output.constrain(cts)); } } diff --git a/src/layout/tree.rs b/src/layout/tree.rs index 900eb1a9a..181bb6113 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -103,6 +103,7 @@ impl Layout for LayoutNode { #[cfg(debug_assertions)] if !entry.check(regions) { + eprintln!("node: {:#?}", self.node); eprintln!("regions: {:#?}", regions); eprintln!( "constraints: {:#?}", diff --git a/src/library/layout.rs b/src/library/layout.rs index b98b08f13..ffe8bc69e 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -209,7 +209,7 @@ pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult { } 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| { @@ -220,16 +220,16 @@ pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult { for child in &list { match child { Child::Spacing(v) => { - children.push(StackChild::Spacing(*v)); + children.push(StackChild::spacing(*v, dir.axis())); delayed = None; } Child::Any(template) => { if let Some(v) = delayed { - children.push(StackChild::Spacing(v)); + children.push(StackChild::spacing(v, dir.axis())); } - let node = template.to_stack(style).into(); - children.push(StackChild::Any(node, style.aligns.block)); + let node = template.to_stack(style); + children.push(StackChild::new(node, style.aligns.block)); delayed = spacing; } } diff --git a/tests/ref/layout/grid-3.png b/tests/ref/layout/grid-3.png index d2882f231..f4d450f8e 100644 Binary files a/tests/ref/layout/grid-3.png and b/tests/ref/layout/grid-3.png differ