summaryrefslogtreecommitdiff
path: root/src/library
diff options
context:
space:
mode:
authorMartin Haug <mhaug@live.de>2021-12-18 18:04:26 +0100
committerMartin Haug <mhaug@live.de>2021-12-26 15:59:24 +0100
commitb22ce6f8b84e0a75d162feb6f3699e26f86f2453 (patch)
tree76c564484d2d020d23ffbb75b133fc8bacae9454 /src/library
parentf6c7a8292dc1ab0560408fca9d74505e9d7cf13a (diff)
Introduce equal-width columns
Diffstat (limited to 'src/library')
-rw-r--r--src/library/columns.rs157
-rw-r--r--src/library/flow.rs24
-rw-r--r--src/library/mod.rs4
-rw-r--r--src/library/page.rs22
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, &regions), 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(&regions)));
+ 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(&regions)));
+ }
+
+ 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);