From b22ce6f8b84e0a75d162feb6f3699e26f86f2453 Mon Sep 17 00:00:00 2001 From: Martin Haug Date: Sat, 18 Dec 2021 18:04:26 +0100 Subject: Introduce equal-width columns --- src/eval/node.rs | 12 +++- src/library/columns.rs | 157 +++++++++++++++++++++++++++++++++++++++++++++++++ src/library/flow.rs | 24 +++++--- src/library/mod.rs | 4 ++ src/library/page.rs | 22 ++++++- 5 files changed, 208 insertions(+), 11 deletions(-) create mode 100644 src/library/columns.rs (limited to 'src') diff --git a/src/eval/node.rs b/src/eval/node.rs index 34a4f275..d5b67adb 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 @@ -345,7 +355,7 @@ 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); + child.styles_mut().map(|s| s.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..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 { + 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 { + 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>> { + // 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 { @@ -40,6 +40,10 @@ impl PageNode { pub const BOTTOM: Smart = Smart::Auto; /// The page's background color. pub const FILL: Option = 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); -- cgit v1.2.3 From 7efdcdf2472257102c6b4b30bf285e2058924070 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 26 Dec 2021 17:34:04 +0100 Subject: Apply simple suggestions from code review Co-Authored-By: Laurenz --- src/eval/node.rs | 4 ++-- src/library/columns.rs | 21 ++++++++++----------- src/library/flow.rs | 6 +++--- src/library/mod.rs | 10 ++++++++++ src/library/page.rs | 6 +++--- 5 files changed, 28 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/eval/node.rs b/src/eval/node.rs index d5b67adb..43cb906b 100644 --- a/src/eval/node.rs +++ b/src/eval/node.rs @@ -354,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().map(|s| s.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 index 88ad8172..3109eda3 100644 --- a/src/library/columns.rs +++ b/src/library/columns.rs @@ -3,12 +3,11 @@ use super::ParNode; /// `columns`: Stack children along an axis. pub fn columns(_: &mut EvalContext, args: &mut Args) -> TypResult { - let count = args.expect("column count")?; + 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: count, + columns, gutter, child: body.into_block(), })) @@ -23,7 +22,7 @@ pub fn colbreak(_: &mut EvalContext, _: &mut Args) -> TypResult { #[derive(Debug, Hash)] pub struct ColumnsNode { /// How many columns there should be. - pub columns: usize, + 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 @@ -50,11 +49,10 @@ impl Layout for ColumnsNode { // `region.backlog`. let mut sizes = vec![]; - // Assure there is at least one column. - let columns = self.columns.max(1); + let columns = self.columns.get(); for (current, base) in std::iter::once((regions.current, regions.base)) - .chain(regions.backlog.clone().into_iter().map(|s| (s, s))) + .chain(regions.backlog.as_slice().iter().map(|&s| (s, s))) { let gutter = self.gutter.resolve(base.x); gutters.push(gutter); @@ -68,8 +66,9 @@ impl Layout for ColumnsNode { } let first = sizes.remove(0); - let mut col_regions = Regions::one(first, first, regions.expand); - col_regions.backlog = sizes.clone().into_iter(); + let mut pod = + Regions::one(first, Spec::new(first.x, regions.base.y), regions.expand); + pod.backlog = sizes.clone().into_iter(); // We have to treat the last region separately. let last_column_gutter = regions.last.map(|last| { @@ -78,11 +77,11 @@ impl Layout for ColumnsNode { (last.x - gutter * (columns - 1) as f64) / columns as f64, last.y, ); - col_regions.last = Some(size); + pod.last = Some(size); (size, gutter) }); - let frames = self.child.layout(ctx, &col_regions); + let frames = self.child.layout(ctx, &pod); let dir = ctx.styles.get(ParNode::DIR); // Dealing with infinite height areas here. diff --git a/src/library/flow.rs b/src/library/flow.rs index ca730db1..6bfa3ddd 100644 --- a/src/library/flow.rs +++ b/src/library/flow.rs @@ -32,12 +32,12 @@ 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. Node(PackedNode), + /// Skip the rest of the region and move to the next. + Skip, } impl FlowChild { @@ -73,7 +73,7 @@ impl Debug for FlowChild { } Self::Spacing(node) => node.fmt(f), Self::Node(node) => node.fmt(f), - Self::Skip => write!(f, "Skip"), + Self::Skip => f.pad("Skip"), } } } diff --git a/src/library/mod.rs b/src/library/mod.rs index 313d423b..1c97f529 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -26,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; @@ -171,6 +172,15 @@ castable! { Value::Int(int) => int.try_into().map_err(|_| "must be at least zero")?, } +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", diff --git a/src/library/page.rs b/src/library/page.rs index 6585edb9..100b4d0c 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -41,7 +41,7 @@ impl PageNode { /// The page's background color. pub const FILL: Option = None; /// How many columns the page has. - pub const COLUMNS: usize = 1; + pub const COLUMNS: NonZeroUsize = NonZeroUsize::new(1).unwrap(); /// How many columns the page has. pub const COLUMN_GUTTER: Linear = Relative::new(0.04).into(); } @@ -119,11 +119,11 @@ impl PageNode { }; let columns = ctx.styles.get(Self::COLUMNS); - let child = if ctx.styles.get(Self::COLUMNS) > 1 { + let child = if columns.get() > 1 { ColumnsNode { - child: self.child.clone(), columns, gutter: ctx.styles.get(Self::COLUMN_GUTTER), + child: self.child.clone(), } .pack() } else { -- cgit v1.2.3 From ee12bf67161f20983eeaf90ddbcd2d6a849f47ba Mon Sep 17 00:00:00 2001 From: Martin Haug Date: Mon, 27 Dec 2021 13:41:57 +0100 Subject: Iterate over target regions instead in columns loop --- src/library/columns.rs | 76 ++++++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 39 deletions(-) (limited to 'src') diff --git a/src/library/columns.rs b/src/library/columns.rs index 3109eda3..779617ed 100644 --- a/src/library/columns.rs +++ b/src/library/columns.rs @@ -81,7 +81,10 @@ impl Layout for ColumnsNode { (size, gutter) }); - let frames = self.child.layout(ctx, &pod); + // We reverse the frames so they can be used as a stack. + let mut frames = self.child.layout(ctx, &pod); + frames.reverse(); + let dir = ctx.styles.get(ParNode::DIR); // Dealing with infinite height areas here. @@ -95,8 +98,6 @@ impl Layout for ColumnsNode { regions.current.y }; - let mut regions = regions.clone(); - let to = |cursor: Length, width: Length, regions: &Regions| { if dir.is_positive() { cursor @@ -108,47 +109,44 @@ impl Layout for ColumnsNode { 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 + let total_regions = (frames.len() as f32 / columns as f32).ceil() as usize; + + for (i, (current, base)) in regions.iter().take(total_regions).enumerate() { + for col in 0 .. columns { + let total_col = i * columns + col; + let child_frame = match frames.pop() { + Some(frame) => frame.item, + None => break, }; - 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) + let size = std::iter::once(&first) + .chain(sizes.iter()) + .nth(total_col) .copied() - .unwrap_or_else(|| last_column_gutter.unwrap().1); + .unwrap_or_else(|| last_column_gutter.unwrap().0); + + frame.push_frame( + Point::new(to(cursor, size.x, ®ions), Length::zero()), + child_frame, + ); + + cursor += size.x + + gutters + .get(i) + .copied() + .unwrap_or_else(|| last_column_gutter.unwrap().1) } - } - if !frame.elements.is_empty() { - res.push(frame.constrain(Constraints::tight(®ions))); + let old_frame = std::mem::replace( + &mut frame, + Frame::new(Spec::new(regions.current.x, height)), + ); + + let mut cts = Constraints::new(regions.expand); + cts.base = base.map(Some); + cts.exact = current.map(Some); + res.push(old_frame.constrain(cts)); + cursor = Length::zero(); } res -- cgit v1.2.3 From accf70949dba4237dbabff11f66fe6f4b6d731be Mon Sep 17 00:00:00 2001 From: Martin Haug Date: Mon, 27 Dec 2021 13:50:36 +0100 Subject: Make column child frames expand horizontally --- src/library/columns.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/library/columns.rs b/src/library/columns.rs index 779617ed..04b5ae47 100644 --- a/src/library/columns.rs +++ b/src/library/columns.rs @@ -69,6 +69,7 @@ impl Layout for ColumnsNode { let mut pod = Regions::one(first, Spec::new(first.x, regions.base.y), regions.expand); pod.backlog = sizes.clone().into_iter(); + pod.expand.x = true; // We have to treat the last region separately. let last_column_gutter = regions.last.map(|last| { @@ -81,9 +82,7 @@ impl Layout for ColumnsNode { (size, gutter) }); - // We reverse the frames so they can be used as a stack. - let mut frames = self.child.layout(ctx, &pod); - frames.reverse(); + let frames = self.child.layout(ctx, &pod); let dir = ctx.styles.get(ParNode::DIR); @@ -107,30 +106,26 @@ impl Layout for ColumnsNode { }; let mut cursor = Length::zero(); + let mut frames = frames.into_iter(); let mut res = vec![]; let mut frame = Frame::new(Spec::new(regions.current.x, height)); let total_regions = (frames.len() as f32 / columns as f32).ceil() as usize; for (i, (current, base)) in regions.iter().take(total_regions).enumerate() { - for col in 0 .. columns { - let total_col = i * columns + col; - let child_frame = match frames.pop() { + for _ in 0 .. columns { + let child_frame = match frames.next() { Some(frame) => frame.item, None => break, }; - let size = std::iter::once(&first) - .chain(sizes.iter()) - .nth(total_col) - .copied() - .unwrap_or_else(|| last_column_gutter.unwrap().0); + let size = child_frame.size.x; frame.push_frame( - Point::new(to(cursor, size.x, ®ions), Length::zero()), + Point::new(to(cursor, size, ®ions), Length::zero()), child_frame, ); - cursor += size.x + cursor += size + gutters .get(i) .copied() -- cgit v1.2.3 From 37328f11edfca7afe918bb297ed1656eb053f7a3 Mon Sep 17 00:00:00 2001 From: Martin Haug Date: Mon, 27 Dec 2021 19:01:36 +0100 Subject: Adjust column size and expansion handling --- src/library/columns.rs | 69 +++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 37 deletions(-) (limited to 'src') diff --git a/src/library/columns.rs b/src/library/columns.rs index 04b5ae47..225cba47 100644 --- a/src/library/columns.rs +++ b/src/library/columns.rs @@ -37,7 +37,8 @@ impl Layout for ColumnsNode { regions: &Regions, ) -> Vec>> { // Separating the infinite space into infinite columns does not make - // much sense. + // 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); } @@ -53,10 +54,11 @@ impl Layout for ColumnsNode { for (current, base) in std::iter::once((regions.current, regions.base)) .chain(regions.backlog.as_slice().iter().map(|&s| (s, s))) + .chain(regions.last.iter().map(|&s| (s, s))) { let gutter = self.gutter.resolve(base.x); gutters.push(gutter); - let size = Spec::new( + let size = Size::new( (current.x - gutter * (columns - 1) as f64) / columns as f64, current.y, ); @@ -67,36 +69,25 @@ impl Layout for ColumnsNode { let first = sizes.remove(0); let mut pod = - Regions::one(first, Spec::new(first.x, regions.base.y), regions.expand); - pod.backlog = sizes.clone().into_iter(); + Regions::one(first, Size::new(first.x, regions.base.y), regions.expand); pod.expand.x = true; - // 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, - ); + // 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); - (size, gutter) - }); + Some(gutter) + } else { + None + }; + + pod.backlog = sizes.into_iter(); let frames = self.child.layout(ctx, &pod); 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 to = |cursor: Length, width: Length, regions: &Regions| { if dir.is_positive() { cursor @@ -108,39 +99,43 @@ impl Layout for ColumnsNode { let mut frames = frames.into_iter(); let mut res = vec![]; - let mut frame = Frame::new(Spec::new(regions.current.x, height)); let total_regions = (frames.len() as f32 / columns as f32).ceil() as usize; for (i, (current, base)) in regions.iter().take(total_regions).enumerate() { + // 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)); + for _ in 0 .. columns { let child_frame = match frames.next() { Some(frame) => frame.item, None => break, }; - let size = child_frame.size.x; + let width = child_frame.size.x; + + if !regions.expand.y { + height = height.max(child_frame.size.y); + } frame.push_frame( - Point::new(to(cursor, size, ®ions), Length::zero()), + Point::new(to(cursor, width, ®ions), Length::zero()), child_frame, ); - cursor += size - + gutters - .get(i) - .copied() - .unwrap_or_else(|| last_column_gutter.unwrap().1) + cursor += width + + gutters.get(i).copied().unwrap_or_else(|| last_gutter.unwrap()); } - let old_frame = std::mem::replace( - &mut frame, - Frame::new(Spec::new(regions.current.x, height)), - ); + frame.size.y = height; let mut cts = Constraints::new(regions.expand); cts.base = base.map(Some); cts.exact = current.map(Some); - res.push(old_frame.constrain(cts)); + res.push(frame.constrain(cts)); cursor = Length::zero(); } -- cgit v1.2.3 From 7f7e14d95f7240727e0163451190ff63b230e393 Mon Sep 17 00:00:00 2001 From: Martin Haug Date: Tue, 28 Dec 2021 00:23:42 +0100 Subject: Minor changes to columns --- src/library/columns.rs | 59 +++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/library/columns.rs b/src/library/columns.rs index 225cba47..25d6da9d 100644 --- a/src/library/columns.rs +++ b/src/library/columns.rs @@ -43,18 +43,19 @@ impl Layout for ColumnsNode { return self.child.layout(ctx, regions); } - // All gutters in the document. (Can be different because the relative + // 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` and - // `region.backlog`. + + // 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 std::iter::once((regions.current, regions.base)) - .chain(regions.backlog.as_slice().iter().map(|&s| (s, s))) - .chain(regions.last.iter().map(|&s| (s, s))) + 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); @@ -68,9 +69,11 @@ impl Layout for ColumnsNode { } let first = sizes.remove(0); - let mut pod = - Regions::one(first, Size::new(first.x, regions.base.y), regions.expand); - pod.expand.x = true; + 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() { @@ -84,24 +87,18 @@ impl Layout for ColumnsNode { pod.backlog = sizes.into_iter(); - let frames = self.child.layout(ctx, &pod); + let mut frames = self.child.layout(ctx, &pod).into_iter(); let dir = ctx.styles.get(ParNode::DIR); - 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 frames = frames.into_iter(); - let mut res = vec![]; + let mut finished = vec![]; let total_regions = (frames.len() as f32 / columns as f32).ceil() as usize; - for (i, (current, base)) in regions.iter().take(total_regions).enumerate() { + 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 @@ -109,6 +106,8 @@ impl Layout for ColumnsNode { 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, @@ -118,16 +117,19 @@ impl Layout for ColumnsNode { let width = child_frame.size.x; if !regions.expand.y { - height = height.max(child_frame.size.y); + height.set_max(child_frame.size.y); } frame.push_frame( - Point::new(to(cursor, width, ®ions), Length::zero()), + Point::with_x(if dir.is_positive() { + cursor + } else { + regions.current.x - cursor - width + }), child_frame, ); - cursor += width - + gutters.get(i).copied().unwrap_or_else(|| last_gutter.unwrap()); + cursor += width + gutter; } frame.size.y = height; @@ -135,10 +137,9 @@ impl Layout for ColumnsNode { let mut cts = Constraints::new(regions.expand); cts.base = base.map(Some); cts.exact = current.map(Some); - res.push(frame.constrain(cts)); - cursor = Length::zero(); + finished.push(frame.constrain(cts)); } - res + finished } } -- cgit v1.2.3