diff options
| author | Martin Haug <mhaug@live.de> | 2021-12-18 18:04:26 +0100 |
|---|---|---|
| committer | Martin Haug <mhaug@live.de> | 2021-12-26 15:59:24 +0100 |
| commit | b22ce6f8b84e0a75d162feb6f3699e26f86f2453 (patch) | |
| tree | 76c564484d2d020d23ffbb75b133fc8bacae9454 /src/library | |
| parent | f6c7a8292dc1ab0560408fca9d74505e9d7cf13a (diff) | |
Introduce equal-width columns
Diffstat (limited to 'src/library')
| -rw-r--r-- | src/library/columns.rs | 157 | ||||
| -rw-r--r-- | src/library/flow.rs | 24 | ||||
| -rw-r--r-- | src/library/mod.rs | 4 | ||||
| -rw-r--r-- | src/library/page.rs | 22 |
4 files changed, 197 insertions, 10 deletions
diff --git a/src/library/columns.rs b/src/library/columns.rs new file mode 100644 index 00000000..88ad8172 --- /dev/null +++ b/src/library/columns.rs @@ -0,0 +1,157 @@ +use super::prelude::*; +use super::ParNode; + +/// `columns`: Stack children along an axis. +pub fn columns(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { + let count = args.expect("column count")?; + let gutter = args.named("gutter")?.unwrap_or(Relative::new(0.04).into()); + let body: Node = args.expect("body")?; + + Ok(Value::block(ColumnsNode { + columns: count, + gutter, + child: body.into_block(), + })) +} + +/// `colbreak`: Start a new column. +pub fn colbreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> { + Ok(Value::Node(Node::Colbreak)) +} + +/// A node that separates a region into multiple equally sized columns. +#[derive(Debug, Hash)] +pub struct ColumnsNode { + /// How many columns there should be. + pub columns: usize, + /// The size of the gutter space between each column. + pub gutter: Linear, + /// The child to be layouted into the columns. Most likely, this should be a + /// flow or stack node. + pub child: PackedNode, +} + +impl Layout for ColumnsNode { + fn layout( + &self, + ctx: &mut LayoutContext, + regions: &Regions, + ) -> Vec<Constrained<Rc<Frame>>> { + // Separating the infinite space into infinite columns does not make + // much sense. + if regions.current.x.is_infinite() { + return self.child.layout(ctx, regions); + } + + // All gutters in the document. (Can be different because the relative + // component is calculated seperately for each region.) + let mut gutters = vec![]; + // Sizes of all columns resulting from `region.current` and + // `region.backlog`. + let mut sizes = vec![]; + + // Assure there is at least one column. + let columns = self.columns.max(1); + + for (current, base) in std::iter::once((regions.current, regions.base)) + .chain(regions.backlog.clone().into_iter().map(|s| (s, s))) + { + let gutter = self.gutter.resolve(base.x); + gutters.push(gutter); + let size = Spec::new( + (current.x - gutter * (columns - 1) as f64) / columns as f64, + current.y, + ); + for _ in 0 .. columns { + sizes.push(size); + } + } + + let first = sizes.remove(0); + let mut col_regions = Regions::one(first, first, regions.expand); + col_regions.backlog = sizes.clone().into_iter(); + + // We have to treat the last region separately. + let last_column_gutter = regions.last.map(|last| { + let gutter = self.gutter.resolve(last.x); + let size = Spec::new( + (last.x - gutter * (columns - 1) as f64) / columns as f64, + last.y, + ); + col_regions.last = Some(size); + (size, gutter) + }); + + let frames = self.child.layout(ctx, &col_regions); + let dir = ctx.styles.get(ParNode::DIR); + + // Dealing with infinite height areas here. + let height = if regions.current.y.is_infinite() { + frames + .iter() + .map(|frame| frame.item.size.y) + .max() + .unwrap_or(Length::zero()) + } else { + regions.current.y + }; + + let mut regions = regions.clone(); + + let to = |cursor: Length, width: Length, regions: &Regions| { + if dir.is_positive() { + cursor + } else { + regions.current.x - cursor - width + } + }; + let mut cursor = Length::zero(); + + let mut res = vec![]; + let mut frame = Frame::new(Spec::new(regions.current.x, height)); + + for (i, child_frame) in frames.into_iter().enumerate() { + let region = i / columns; + let size = std::iter::once(&first) + .chain(sizes.iter()) + .nth(i) + .copied() + .unwrap_or_else(|| last_column_gutter.unwrap().0); + + frame.push_frame( + Point::new(to(cursor, size.x, ®ions), Length::zero()), + child_frame.item, + ); + + cursor += size.x; + + if i % columns == columns - 1 { + // Refresh column height for non-infinite regions here. + let height = if regions.current.y.is_infinite() { + height + } else { + regions.current.y + }; + + regions.next(); + let old_frame = std::mem::replace( + &mut frame, + Frame::new(Spec::new(regions.current.x, height)), + ); + res.push(old_frame.constrain(Constraints::tight(®ions))); + cursor = Length::zero(); + } else { + cursor += gutters + .get(region) + .copied() + .unwrap_or_else(|| last_column_gutter.unwrap().1); + } + } + + if !frame.elements.is_empty() { + res.push(frame.constrain(Constraints::tight(®ions))); + } + + res + } +} diff --git a/src/library/flow.rs b/src/library/flow.rs index cfa761b6..ca730db1 100644 --- a/src/library/flow.rs +++ b/src/library/flow.rs @@ -32,6 +32,8 @@ impl Debug for FlowNode { pub enum FlowChild { /// A paragraph/block break. Break(Styles), + /// Skip the rest of the region and move to the next. + Skip, /// Vertical spacing between other children. Spacing(SpacingNode), /// An arbitrary node. @@ -40,20 +42,22 @@ pub enum FlowChild { impl FlowChild { /// A reference to the child's styles. - pub fn styles(&self) -> &Styles { + pub fn styles(&self) -> Option<&Styles> { match self { - Self::Break(styles) => styles, - Self::Spacing(node) => &node.styles, - Self::Node(node) => &node.styles, + Self::Break(styles) => Some(styles), + Self::Spacing(node) => Some(&node.styles), + Self::Node(node) => Some(&node.styles), + Self::Skip => None, } } /// A mutable reference to the child's styles. - pub fn styles_mut(&mut self) -> &mut Styles { + pub fn styles_mut(&mut self) -> Option<&mut Styles> { match self { - Self::Break(styles) => styles, - Self::Spacing(node) => &mut node.styles, - Self::Node(node) => &mut node.styles, + Self::Break(styles) => Some(styles), + Self::Spacing(node) => Some(&mut node.styles), + Self::Node(node) => Some(&mut node.styles), + Self::Skip => None, } } } @@ -69,6 +73,7 @@ impl Debug for FlowChild { } Self::Spacing(node) => node.fmt(f), Self::Node(node) => node.fmt(f), + Self::Skip => write!(f, "Skip"), } } } @@ -138,6 +143,9 @@ impl<'a> FlowLayouter<'a> { let amount = chain.get(ParNode::SPACING).resolve(em); self.layout_absolute(amount.into()); } + FlowChild::Skip => { + self.finish_region(); + } FlowChild::Spacing(node) => match node.kind { SpacingKind::Linear(v) => self.layout_absolute(v), SpacingKind::Fractional(v) => { diff --git a/src/library/mod.rs b/src/library/mod.rs index b2dd0dbe..313d423b 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -4,6 +4,7 @@ //! definitions. mod align; +mod columns; mod flow; mod grid; mod heading; @@ -42,6 +43,7 @@ mod prelude { pub use self::image::*; pub use align::*; +pub use columns::*; pub use flow::*; pub use grid::*; pub use heading::*; @@ -83,6 +85,7 @@ pub fn new() -> Scope { // Break and spacing functions. std.def_func("pagebreak", pagebreak); + std.def_func("colbreak", colbreak); std.def_func("parbreak", parbreak); std.def_func("linebreak", linebreak); std.def_func("h", h); @@ -96,6 +99,7 @@ pub fn new() -> Scope { std.def_func("stack", stack); std.def_func("grid", grid); std.def_func("pad", pad); + std.def_func("columns", columns); std.def_func("align", align); std.def_func("place", place); std.def_func("move", move_); diff --git a/src/library/page.rs b/src/library/page.rs index 0e690770..6585edb9 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -4,7 +4,7 @@ use std::fmt::{self, Display, Formatter}; use std::str::FromStr; use super::prelude::*; -use super::PadNode; +use super::{ColumnsNode, PadNode}; /// `pagebreak`: Start a new page. pub fn pagebreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> { @@ -40,6 +40,10 @@ impl PageNode { pub const BOTTOM: Smart<Linear> = Smart::Auto; /// The page's background color. pub const FILL: Option<Paint> = None; + /// How many columns the page has. + pub const COLUMNS: usize = 1; + /// How many columns the page has. + pub const COLUMN_GUTTER: Linear = Relative::new(0.04).into(); } impl Construct for PageNode { @@ -76,6 +80,8 @@ impl Set for PageNode { styles.set_opt(Self::RIGHT, args.named("right")?.or(margins)); styles.set_opt(Self::BOTTOM, args.named("bottom")?.or(margins)); styles.set_opt(Self::FILL, args.named("fill")?); + styles.set_opt(Self::COLUMNS, args.named("columns")?); + styles.set_opt(Self::COLUMN_GUTTER, args.named("column-gutter")?); Ok(()) } @@ -112,8 +118,20 @@ impl PageNode { bottom: ctx.styles.get(Self::BOTTOM).unwrap_or(default.bottom), }; + let columns = ctx.styles.get(Self::COLUMNS); + let child = if ctx.styles.get(Self::COLUMNS) > 1 { + ColumnsNode { + child: self.child.clone(), + columns, + gutter: ctx.styles.get(Self::COLUMN_GUTTER), + } + .pack() + } else { + self.child.clone() + }; + // Pad the child. - let padded = PadNode { child: self.child.clone(), padding }.pack(); + let padded = PadNode { child, padding }.pack(); // Layout the child. let expand = size.map(Length::is_finite); |
