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/library/columns.rs | |
| parent | f6c7a8292dc1ab0560408fca9d74505e9d7cf13a (diff) | |
| parent | 7f7e14d95f7240727e0163451190ff63b230e393 (diff) | |
Merge pull request #52 from typst/basicc-cols
Introduce equal-width columns
Diffstat (limited to 'src/library/columns.rs')
| -rw-r--r-- | src/library/columns.rs | 145 |
1 files changed, 145 insertions, 0 deletions
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 + } +} |
