diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-12-28 00:29:15 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-12-28 00:29:15 +0100 |
| commit | 9624ad635bd8adb0e421c37c63c7310ecc71a708 (patch) | |
| tree | 0062b38db54c1fd785eedecbd9b77a12800bc340 /src | |
| parent | f6c7a8292dc1ab0560408fca9d74505e9d7cf13a (diff) | |
| parent | 7f7e14d95f7240727e0163451190ff63b230e393 (diff) | |
Merge pull request #52 from typst/basicc-cols
Introduce equal-width columns
Diffstat (limited to 'src')
| -rw-r--r-- | src/eval/node.rs | 14 | ||||
| -rw-r--r-- | src/library/columns.rs | 145 | ||||
| -rw-r--r-- | src/library/flow.rs | 24 | ||||
| -rw-r--r-- | src/library/mod.rs | 14 | ||||
| -rw-r--r-- | src/library/page.rs | 22 |
5 files changed, 207 insertions, 12 deletions
diff --git a/src/eval/node.rs b/src/eval/node.rs index 34a4f275..43cb906b 100644 --- a/src/eval/node.rs +++ b/src/eval/node.rs @@ -32,6 +32,8 @@ pub enum Node { Linebreak, /// A paragraph break. Parbreak, + /// A column break. + Colbreak, /// A page break. Pagebreak, /// Plain text. @@ -212,6 +214,14 @@ impl Packer { // paragraph. self.parbreak(Some(styles)); } + Node::Colbreak => { + // Explicit column breaks end the current paragraph and then + // discards the paragraph break. + self.parbreak(None); + self.make_flow_compatible(&styles); + self.flow.children.push(FlowChild::Skip); + self.flow.last.hard(); + } Node::Pagebreak => { // We must set the flow styles after the page break such that an // empty page created by two page breaks in a row has styles at @@ -344,8 +354,8 @@ impl Packer { // Take the flow and erase any styles that will be inherited anyway. let Builder { mut children, styles, .. } = mem::take(&mut self.flow); - for child in &mut children { - child.styles_mut().erase(&styles); + for local in children.iter_mut().filter_map(FlowChild::styles_mut) { + local.erase(&styles); } let flow = FlowNode(children).pack(); diff --git a/src/library/columns.rs b/src/library/columns.rs new file mode 100644 index 00000000..25d6da9d --- /dev/null +++ b/src/library/columns.rs @@ -0,0 +1,145 @@ +use super::prelude::*; +use super::ParNode; + +/// `columns`: Stack children along an axis. +pub fn columns(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { + let columns = 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, + 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: NonZeroUsize, + /// 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. Note that this line assumes that no infinitely wide + // region will follow if the first region's width is finite. + if regions.current.x.is_infinite() { + return self.child.layout(ctx, regions); + } + + // Gutter width for each region. (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`, + // `region.backlog` and `regions.last`. + let mut sizes = vec![]; + + let columns = self.columns.get(); + + for (current, base) in regions + .iter() + .take(1 + regions.backlog.len() + if regions.last.is_some() { 1 } else { 0 }) + { + let gutter = self.gutter.resolve(base.x); + gutters.push(gutter); + let size = Size::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 pod = Regions::one( + first, + Size::new(first.x, regions.base.y), + Spec::new(true, regions.expand.y), + ); + + // Retrieve elements for the last region from the vectors. + let last_gutter = if regions.last.is_some() { + let gutter = gutters.pop().unwrap(); + let size = sizes.drain(sizes.len() - columns ..).next().unwrap(); + pod.last = Some(size); + Some(gutter) + } else { + None + }; + + pod.backlog = sizes.into_iter(); + + let mut frames = self.child.layout(ctx, &pod).into_iter(); + + let dir = ctx.styles.get(ParNode::DIR); + + let mut finished = vec![]; + let total_regions = (frames.len() as f32 / columns as f32).ceil() as usize; + + for ((current, base), gutter) in regions + .iter() + .take(total_regions) + .zip(gutters.into_iter().chain(last_gutter.into_iter().cycle())) + { + // The height should be the parent height if the node shall expand. + // Otherwise its the maximum column height for the frame. In that + // case, the frame is first created with zero height and then + // resized. + let mut height = if regions.expand.y { current.y } else { Length::zero() }; + let mut frame = Frame::new(Spec::new(regions.current.x, height)); + + let mut cursor = Length::zero(); + + for _ in 0 .. columns { + let child_frame = match frames.next() { + Some(frame) => frame.item, + None => break, + }; + + let width = child_frame.size.x; + + if !regions.expand.y { + height.set_max(child_frame.size.y); + } + + frame.push_frame( + Point::with_x(if dir.is_positive() { + cursor + } else { + regions.current.x - cursor - width + }), + child_frame, + ); + + cursor += width + gutter; + } + + frame.size.y = height; + + let mut cts = Constraints::new(regions.expand); + cts.base = base.map(Some); + cts.exact = current.map(Some); + finished.push(frame.constrain(cts)); + } + + finished + } +} diff --git a/src/library/flow.rs b/src/library/flow.rs index cfa761b6..6bfa3ddd 100644 --- a/src/library/flow.rs +++ b/src/library/flow.rs @@ -36,24 +36,28 @@ pub enum FlowChild { Spacing(SpacingNode), /// An arbitrary node. Node(PackedNode), + /// Skip the rest of the region and move to the next. + Skip, } 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 => f.pad("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..1c97f529 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; @@ -25,6 +26,7 @@ mod utility; /// Helpful imports for creating library functionality. mod prelude { pub use std::fmt::{self, Debug, Formatter}; + pub use std::num::NonZeroUsize; pub use std::rc::Rc; pub use typst_macros::properties; @@ -42,6 +44,7 @@ mod prelude { pub use self::image::*; pub use align::*; +pub use columns::*; pub use flow::*; pub use grid::*; pub use heading::*; @@ -83,6 +86,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 +100,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_); @@ -168,6 +173,15 @@ castable! { } castable! { + prelude::NonZeroUsize, + Expected: "positive integer", + Value::Int(int) => int + .try_into() + .and_then(|n: usize| n.try_into()) + .map_err(|_| "must be positive")?, +} + +castable! { String, Expected: "string", Value::Str(string) => string.into(), diff --git a/src/library/page.rs b/src/library/page.rs index 0e690770..100b4d0c 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: NonZeroUsize = NonZeroUsize::new(1).unwrap(); + /// 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 columns.get() > 1 { + ColumnsNode { + columns, + gutter: ctx.styles.get(Self::COLUMN_GUTTER), + child: self.child.clone(), + } + .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); |
