From 26bdc1f0f6fe8113d7fcfb4d5aca46aa5238ccd8 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sun, 5 Dec 2021 12:54:03 +0100 Subject: Set Rules Episode I: The Phantom Style --- src/eval/node.rs | 213 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 src/eval/node.rs (limited to 'src/eval/node.rs') diff --git a/src/eval/node.rs b/src/eval/node.rs new file mode 100644 index 00000000..58b29483 --- /dev/null +++ b/src/eval/node.rs @@ -0,0 +1,213 @@ +use std::convert::TryFrom; +use std::fmt::{self, Debug, Formatter}; +use std::hash::Hash; +use std::mem; +use std::ops::{Add, AddAssign}; + +use crate::diag::StrResult; +use crate::geom::SpecAxis; +use crate::layout::{Layout, PackedNode}; +use crate::library::{ + Decoration, DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, Spacing, +}; +use crate::util::EcoString; + +/// A partial representation of a layout node. +/// +/// A node is a composable intermediate representation that can be converted +/// into a proper layout node by lifting it to the block or page level. +#[derive(Clone)] +pub enum Node { + /// A word space. + Space, + /// A line break. + Linebreak, + /// A paragraph break. + Parbreak, + /// A page break. + Pagebreak, + /// Plain text. + Text(EcoString), + /// Spacing. + Spacing(SpecAxis, Spacing), + /// An inline node. + Inline(PackedNode), + /// A block node. + Block(PackedNode), + /// A sequence of nodes (which may themselves contain sequences). + Seq(Vec), +} + +impl Node { + /// Create an empty node. + pub fn new() -> Self { + Self::Seq(vec![]) + } + + /// Create an inline-level node. + pub fn inline(node: T) -> Self + where + T: Layout + Debug + Hash + 'static, + { + Self::Inline(node.pack()) + } + + /// Create a block-level node. + pub fn block(node: T) -> Self + where + T: Layout + Debug + Hash + 'static, + { + Self::Block(node.pack()) + } + + /// Decoration this node. + pub fn decorate(self, _: Decoration) -> Self { + // TODO(set): Actually decorate. + self + } + + /// Lift to a type-erased block-level node. + pub fn into_block(self) -> PackedNode { + if let Node::Block(packed) = self { + packed + } else { + let mut packer = NodePacker::new(); + packer.walk(self); + packer.into_block() + } + } + + /// Lift to a document node, the root of the layout tree. + pub fn into_document(self) -> DocumentNode { + let mut packer = NodePacker::new(); + packer.walk(self); + packer.into_document() + } + + /// Repeat this template `n` times. + pub fn repeat(&self, n: i64) -> StrResult { + let count = usize::try_from(n) + .map_err(|_| format!("cannot repeat this template {} times", n))?; + + // TODO(set): Make more efficient. + Ok(Self::Seq(vec![self.clone(); count])) + } +} + +impl Debug for Node { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad("") + } +} + +impl Default for Node { + fn default() -> Self { + Self::new() + } +} + +impl PartialEq for Node { + fn eq(&self, _: &Self) -> bool { + // TODO(set): Figure out what to do here. + false + } +} + +impl Add for Node { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + // TODO(set): Make more efficient. + Self::Seq(vec![self, rhs]) + } +} + +impl AddAssign for Node { + fn add_assign(&mut self, rhs: Self) { + *self = mem::take(self) + rhs; + } +} + +/// Packs a `Node` into a flow or whole document. +struct NodePacker { + document: Vec, + flow: Vec, + par: Vec, +} + +impl NodePacker { + fn new() -> Self { + Self { + document: vec![], + flow: vec![], + par: vec![], + } + } + + fn into_block(mut self) -> PackedNode { + self.parbreak(); + FlowNode(self.flow).pack() + } + + fn into_document(mut self) -> DocumentNode { + self.parbreak(); + self.pagebreak(); + DocumentNode(self.document) + } + + fn walk(&mut self, node: Node) { + match node { + Node::Space => { + self.push_inline(ParChild::Text(' '.into())); + } + Node::Linebreak => { + self.push_inline(ParChild::Text('\n'.into())); + } + Node::Parbreak => { + self.parbreak(); + } + Node::Pagebreak => { + self.pagebreak(); + } + Node::Text(text) => { + self.push_inline(ParChild::Text(text)); + } + Node::Spacing(axis, amount) => match axis { + SpecAxis::Horizontal => self.push_inline(ParChild::Spacing(amount)), + SpecAxis::Vertical => self.push_block(FlowChild::Spacing(amount)), + }, + Node::Inline(inline) => { + self.push_inline(ParChild::Node(inline)); + } + Node::Block(block) => { + self.push_block(FlowChild::Node(block)); + } + Node::Seq(list) => { + for node in list { + self.walk(node); + } + } + } + } + + fn parbreak(&mut self) { + let children = mem::take(&mut self.par); + if !children.is_empty() { + self.flow.push(FlowChild::Node(ParNode(children).pack())); + } + } + + fn pagebreak(&mut self) { + let children = mem::take(&mut self.flow); + self.document.push(PageNode(FlowNode(children).pack())); + } + + fn push_inline(&mut self, child: ParChild) { + self.par.push(child); + } + + fn push_block(&mut self, child: FlowChild) { + self.parbreak(); + self.flow.push(child); + } +} -- cgit v1.2.3 From 40b87d4066fe85cb3fde6cf84cd60d748273ae25 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 7 Dec 2021 16:36:39 +0100 Subject: Set Rules Episode II: Attack of the properties --- src/eval/node.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/eval/node.rs') diff --git a/src/eval/node.rs b/src/eval/node.rs index 58b29483..4d259e2d 100644 --- a/src/eval/node.rs +++ b/src/eval/node.rs @@ -9,6 +9,7 @@ use crate::geom::SpecAxis; use crate::layout::{Layout, PackedNode}; use crate::library::{ Decoration, DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, Spacing, + TextNode, }; use crate::util::EcoString; @@ -158,10 +159,10 @@ impl NodePacker { fn walk(&mut self, node: Node) { match node { Node::Space => { - self.push_inline(ParChild::Text(' '.into())); + self.push_inline(ParChild::Text(TextNode(' '.into()))); } Node::Linebreak => { - self.push_inline(ParChild::Text('\n'.into())); + self.push_inline(ParChild::Text(TextNode('\n'.into()))); } Node::Parbreak => { self.parbreak(); @@ -170,7 +171,7 @@ impl NodePacker { self.pagebreak(); } Node::Text(text) => { - self.push_inline(ParChild::Text(text)); + self.push_inline(ParChild::Text(TextNode(text))); } Node::Spacing(axis, amount) => match axis { SpecAxis::Horizontal => self.push_inline(ParChild::Spacing(amount)), -- cgit v1.2.3 From fe21c4d399d291e75165b664762f0aa8bdc4724a Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 9 Dec 2021 13:42:52 +0100 Subject: Set Rules Episode III: Revenge of the packer --- src/eval/node.rs | 213 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 174 insertions(+), 39 deletions(-) (limited to 'src/eval/node.rs') diff --git a/src/eval/node.rs b/src/eval/node.rs index 4d259e2d..d3bf9806 100644 --- a/src/eval/node.rs +++ b/src/eval/node.rs @@ -1,9 +1,10 @@ use std::convert::TryFrom; -use std::fmt::{self, Debug, Formatter}; +use std::fmt::Debug; use std::hash::Hash; use std::mem; use std::ops::{Add, AddAssign}; +use super::Styles; use crate::diag::StrResult; use crate::geom::SpecAxis; use crate::layout::{Layout, PackedNode}; @@ -16,8 +17,9 @@ use crate::util::EcoString; /// A partial representation of a layout node. /// /// A node is a composable intermediate representation that can be converted -/// into a proper layout node by lifting it to the block or page level. -#[derive(Clone)] +/// into a proper layout node by lifting it to a block-level or document node. +// TODO(set): Fix Debug impl leaking into user-facing repr. +#[derive(Debug, Clone)] pub enum Node { /// A word space. Space, @@ -36,13 +38,13 @@ pub enum Node { /// A block node. Block(PackedNode), /// A sequence of nodes (which may themselves contain sequences). - Seq(Vec), + Sequence(Vec<(Self, Styles)>), } impl Node { /// Create an empty node. pub fn new() -> Self { - Self::Seq(vec![]) + Self::Sequence(vec![]) } /// Create an inline-level node. @@ -61,8 +63,22 @@ impl Node { Self::Block(node.pack()) } - /// Decoration this node. - pub fn decorate(self, _: Decoration) -> Self { + /// Style this node. + pub fn styled(self, styles: Styles) -> Self { + match self { + Self::Inline(inline) => Self::Inline(inline.styled(styles)), + Self::Block(block) => Self::Block(block.styled(styles)), + other => Self::Sequence(vec![(other, styles)]), + } + } + + /// Style this node in monospace. + pub fn monospaced(self) -> Self { + self.styled(Styles::one(TextNode::MONOSPACE, true)) + } + + /// Decorate this node. + pub fn decorated(self, _: Decoration) -> Self { // TODO(set): Actually decorate. self } @@ -73,7 +89,7 @@ impl Node { packed } else { let mut packer = NodePacker::new(); - packer.walk(self); + packer.walk(self, Styles::new()); packer.into_block() } } @@ -81,7 +97,7 @@ impl Node { /// Lift to a document node, the root of the layout tree. pub fn into_document(self) -> DocumentNode { let mut packer = NodePacker::new(); - packer.walk(self); + packer.walk(self, Styles::new()); packer.into_document() } @@ -91,13 +107,7 @@ impl Node { .map_err(|_| format!("cannot repeat this template {} times", n))?; // TODO(set): Make more efficient. - Ok(Self::Seq(vec![self.clone(); count])) - } -} - -impl Debug for Node { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad("") + Ok(Self::Sequence(vec![(self.clone(), Styles::new()); count])) } } @@ -119,7 +129,7 @@ impl Add for Node { fn add(self, rhs: Self) -> Self::Output { // TODO(set): Make more efficient. - Self::Seq(vec![self, rhs]) + Self::Sequence(vec![(self, Styles::new()), (rhs, Styles::new())]) } } @@ -129,86 +139,211 @@ impl AddAssign for Node { } } -/// Packs a `Node` into a flow or whole document. +/// Packs a [`Node`] into a flow or whole document. struct NodePacker { + /// The accumulated page nodes. document: Vec, + /// The common style properties of all items on the current page. + page_styles: Styles, + /// The accumulated flow children. flow: Vec, + /// The accumulated paragraph children. par: Vec, + /// The common style properties of all items in the current paragraph. + par_styles: Styles, + /// The kind of thing that was last added to the current paragraph. + last: Last, +} + +/// The type of the last thing that was pushed into the paragraph. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +enum Last { + None, + Spacing, + Newline, + Space, + Other, } impl NodePacker { + /// Start a new node-packing session. fn new() -> Self { Self { document: vec![], + page_styles: Styles::new(), flow: vec![], par: vec![], + par_styles: Styles::new(), + last: Last::None, } } + /// Finish up and return the resulting flow. fn into_block(mut self) -> PackedNode { self.parbreak(); FlowNode(self.flow).pack() } + /// Finish up and return the resulting document. fn into_document(mut self) -> DocumentNode { - self.parbreak(); - self.pagebreak(); + self.pagebreak(true); DocumentNode(self.document) } - fn walk(&mut self, node: Node) { + /// Consider a node with the given styles. + fn walk(&mut self, node: Node, styles: Styles) { match node { Node::Space => { - self.push_inline(ParChild::Text(TextNode(' '.into()))); + // Only insert a space if the previous thing was actual content. + if self.last == Last::Other { + self.push_text(' '.into(), styles); + self.last = Last::Space; + } } Node::Linebreak => { - self.push_inline(ParChild::Text(TextNode('\n'.into()))); + self.trim(); + self.push_text('\n'.into(), styles); + self.last = Last::Newline; } Node::Parbreak => { self.parbreak(); } Node::Pagebreak => { - self.pagebreak(); + self.pagebreak(true); + self.page_styles = styles; } Node::Text(text) => { - self.push_inline(ParChild::Text(TextNode(text))); + self.push_text(text, styles); + } + Node::Spacing(SpecAxis::Horizontal, amount) => { + self.push_inline(ParChild::Spacing(amount), styles); + self.last = Last::Spacing; + } + Node::Spacing(SpecAxis::Vertical, amount) => { + self.push_block(FlowChild::Spacing(amount), styles); } - Node::Spacing(axis, amount) => match axis { - SpecAxis::Horizontal => self.push_inline(ParChild::Spacing(amount)), - SpecAxis::Vertical => self.push_block(FlowChild::Spacing(amount)), - }, Node::Inline(inline) => { - self.push_inline(ParChild::Node(inline)); + self.push_inline(ParChild::Node(inline), styles); } Node::Block(block) => { - self.push_block(FlowChild::Node(block)); + self.push_block(FlowChild::Node(block), styles); } - Node::Seq(list) => { - for node in list { - self.walk(node); + Node::Sequence(list) => { + for (node, mut inner) in list { + inner.apply(&styles); + self.walk(node, inner); } } } } + /// Remove a trailing space. + fn trim(&mut self) { + if self.last == Last::Space { + self.par.pop(); + self.last = Last::Other; + } + } + + /// Advance to the next paragraph. fn parbreak(&mut self) { + self.trim(); + let children = mem::take(&mut self.par); + let styles = mem::take(&mut self.par_styles); if !children.is_empty() { - self.flow.push(FlowChild::Node(ParNode(children).pack())); + // The paragraph's children are all compatible with the page, so the + // paragraph is too, meaning we don't need to check or intersect + // anything here. + let node = ParNode(children).pack().styled(styles); + self.flow.push(FlowChild::Node(node)); } + + self.last = Last::None; } - fn pagebreak(&mut self) { + /// Advance to the next page. + fn pagebreak(&mut self, keep: bool) { + self.parbreak(); let children = mem::take(&mut self.flow); - self.document.push(PageNode(FlowNode(children).pack())); + let styles = mem::take(&mut self.page_styles); + if keep || !children.is_empty() { + let node = PageNode { node: FlowNode(children).pack(), styles }; + self.document.push(node); + } + } + + /// Insert text into the current paragraph. + fn push_text(&mut self, text: EcoString, styles: Styles) { + // TODO(set): Join compatible text nodes. Take care with space + // coalescing. + let node = TextNode { text, styles: Styles::new() }; + self.push_inline(ParChild::Text(node), styles); } - fn push_inline(&mut self, child: ParChild) { + /// Insert an inline-level element into the current paragraph. + fn push_inline(&mut self, mut child: ParChild, styles: Styles) { + match &mut child { + ParChild::Spacing(_) => {} + ParChild::Text(node) => node.styles.apply(&styles), + ParChild::Node(node) => node.styles.apply(&styles), + ParChild::Decorate(_) => {} + ParChild::Undecorate => {} + } + + // The node must be both compatible with the current page and the + // current paragraph. + self.make_page_compatible(&styles); + self.make_par_compatible(&styles); self.par.push(child); + self.last = Last::Other; } - fn push_block(&mut self, child: FlowChild) { + /// Insert a block-level element into the current flow. + fn push_block(&mut self, mut child: FlowChild, styles: Styles) { self.parbreak(); + + match &mut child { + FlowChild::Spacing(_) => {} + FlowChild::Node(node) => node.styles.apply(&styles), + } + + // The node must be compatible with the current page. + self.make_page_compatible(&styles); self.flow.push(child); } + + /// Break to a new paragraph if the `styles` contain paragraph styles that + /// are incompatible with the current paragraph. + fn make_par_compatible(&mut self, styles: &Styles) { + if self.par.is_empty() { + self.par_styles = styles.clone(); + return; + } + + if !self.par_styles.compatible(&styles, ParNode::has_property) { + self.parbreak(); + self.par_styles = styles.clone(); + return; + } + + self.par_styles.intersect(&styles); + } + + /// Break to a new page if the `styles` contain page styles that are + /// incompatible with the current page. + fn make_page_compatible(&mut self, styles: &Styles) { + if self.flow.is_empty() && self.par.is_empty() { + self.page_styles = styles.clone(); + return; + } + + if !self.page_styles.compatible(&styles, PageNode::has_property) { + self.pagebreak(false); + self.page_styles = styles.clone(); + return; + } + + self.page_styles.intersect(styles); + } } -- cgit v1.2.3 From ae38be9097bbb32142ef776e77e627ac12379000 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 15 Dec 2021 11:11:57 +0100 Subject: Set Rules Episode IV: A New Fold --- src/eval/node.rs | 289 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 173 insertions(+), 116 deletions(-) (limited to 'src/eval/node.rs') diff --git a/src/eval/node.rs b/src/eval/node.rs index d3bf9806..52d9b244 100644 --- a/src/eval/node.rs +++ b/src/eval/node.rs @@ -9,8 +9,8 @@ use crate::diag::StrResult; use crate::geom::SpecAxis; use crate::layout::{Layout, PackedNode}; use crate::library::{ - Decoration, DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, Spacing, - TextNode, + DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, PlacedNode, + SpacingKind, SpacingNode, TextNode, }; use crate::util::EcoString; @@ -18,8 +18,7 @@ use crate::util::EcoString; /// /// A node is a composable intermediate representation that can be converted /// into a proper layout node by lifting it to a block-level or document node. -// TODO(set): Fix Debug impl leaking into user-facing repr. -#[derive(Debug, Clone)] +#[derive(Debug, PartialEq, Clone, Hash)] pub enum Node { /// A word space. Space, @@ -32,7 +31,7 @@ pub enum Node { /// Plain text. Text(EcoString), /// Spacing. - Spacing(SpecAxis, Spacing), + Spacing(SpecAxis, SpacingKind), /// An inline node. Inline(PackedNode), /// A block node. @@ -77,18 +76,12 @@ impl Node { self.styled(Styles::one(TextNode::MONOSPACE, true)) } - /// Decorate this node. - pub fn decorated(self, _: Decoration) -> Self { - // TODO(set): Actually decorate. - self - } - /// Lift to a type-erased block-level node. pub fn into_block(self) -> PackedNode { if let Node::Block(packed) = self { packed } else { - let mut packer = NodePacker::new(); + let mut packer = NodePacker::new(true); packer.walk(self, Styles::new()); packer.into_block() } @@ -96,7 +89,7 @@ impl Node { /// Lift to a document node, the root of the layout tree. pub fn into_document(self) -> DocumentNode { - let mut packer = NodePacker::new(); + let mut packer = NodePacker::new(false); packer.walk(self, Styles::new()); packer.into_document() } @@ -117,13 +110,6 @@ impl Default for Node { } } -impl PartialEq for Node { - fn eq(&self, _: &Self) -> bool { - // TODO(set): Figure out what to do here. - false - } -} - impl Add for Node { type Output = Self; @@ -141,92 +127,89 @@ impl AddAssign for Node { /// Packs a [`Node`] into a flow or whole document. struct NodePacker { + /// Whether packing should produce a block-level node. + block: bool, /// The accumulated page nodes. - document: Vec, - /// The common style properties of all items on the current page. - page_styles: Styles, + pages: Vec, /// The accumulated flow children. flow: Vec, + /// The common style properties of all items on the current flow. + flow_styles: Styles, + /// The kind of thing that was last added to the current flow. + flow_last: Last, /// The accumulated paragraph children. par: Vec, /// The common style properties of all items in the current paragraph. par_styles: Styles, /// The kind of thing that was last added to the current paragraph. - last: Last, -} - -/// The type of the last thing that was pushed into the paragraph. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -enum Last { - None, - Spacing, - Newline, - Space, - Other, + par_last: Last, } impl NodePacker { /// Start a new node-packing session. - fn new() -> Self { + fn new(block: bool) -> Self { Self { - document: vec![], - page_styles: Styles::new(), + block, + pages: vec![], flow: vec![], + flow_styles: Styles::new(), + flow_last: Last::None, par: vec![], par_styles: Styles::new(), - last: Last::None, + par_last: Last::None, } } /// Finish up and return the resulting flow. fn into_block(mut self) -> PackedNode { - self.parbreak(); + self.finish_par(); FlowNode(self.flow).pack() } /// Finish up and return the resulting document. fn into_document(mut self) -> DocumentNode { self.pagebreak(true); - DocumentNode(self.document) + DocumentNode(self.pages) } /// Consider a node with the given styles. fn walk(&mut self, node: Node, styles: Styles) { match node { Node::Space => { - // Only insert a space if the previous thing was actual content. - if self.last == Last::Other { - self.push_text(' '.into(), styles); - self.last = Last::Space; + if self.is_flow_compatible(&styles) && self.is_par_compatible(&styles) { + self.par_last.soft(ParChild::text(' ', styles)); } } Node::Linebreak => { - self.trim(); - self.push_text('\n'.into(), styles); - self.last = Last::Newline; + self.par_last.hard(); + self.push_inline(ParChild::text('\n', styles)); + self.par_last.hard(); } Node::Parbreak => { - self.parbreak(); + self.parbreak(Some(styles)); } Node::Pagebreak => { self.pagebreak(true); - self.page_styles = styles; + self.flow_styles = styles; } Node::Text(text) => { - self.push_text(text, styles); + self.push_inline(ParChild::text(text, styles)); } - Node::Spacing(SpecAxis::Horizontal, amount) => { - self.push_inline(ParChild::Spacing(amount), styles); - self.last = Last::Spacing; + Node::Spacing(SpecAxis::Horizontal, kind) => { + self.par_last.hard(); + self.push_inline(ParChild::Spacing(SpacingNode { kind, styles })); + self.par_last.hard(); } - Node::Spacing(SpecAxis::Vertical, amount) => { - self.push_block(FlowChild::Spacing(amount), styles); + Node::Spacing(SpecAxis::Vertical, kind) => { + self.finish_par(); + self.flow.push(FlowChild::Spacing(SpacingNode { kind, styles })); + self.flow_last.hard(); } Node::Inline(inline) => { - self.push_inline(ParChild::Node(inline), styles); + self.push_inline(ParChild::Node(inline.styled(styles))); } Node::Block(block) => { - self.push_block(FlowChild::Node(block), styles); + self.push_block(block.styled(styles)); } Node::Sequence(list) => { for (node, mut inner) in list { @@ -237,80 +220,118 @@ impl NodePacker { } } - /// Remove a trailing space. - fn trim(&mut self) { - if self.last == Last::Space { - self.par.pop(); - self.last = Last::Other; + /// Insert an inline-level element into the current paragraph. + fn push_inline(&mut self, child: ParChild) { + if let Some(child) = self.par_last.any() { + self.push_inline_impl(child); + } + + // The node must be both compatible with the current page and the + // current paragraph. + self.make_flow_compatible(child.styles()); + self.make_par_compatible(child.styles()); + self.push_inline_impl(child); + self.par_last = Last::Any; + } + + /// Push a paragraph child, coalescing text nodes with compatible styles. + fn push_inline_impl(&mut self, child: ParChild) { + if let ParChild::Text(right) = &child { + if let Some(ParChild::Text(left)) = self.par.last_mut() { + if left.styles.compatible(&right.styles, TextNode::has_property) { + left.text.push_str(&right.text); + return; + } + } + } + + self.par.push(child); + } + + /// Insert a block-level element into the current flow. + fn push_block(&mut self, node: PackedNode) { + let mut is_placed = false; + if let Some(placed) = node.downcast::() { + is_placed = true; + + // This prevents paragraph spacing after the placed node if it + // is completely out-of-flow. + if placed.out_of_flow() { + self.flow_last = Last::None; + } + } + + self.parbreak(None); + self.make_flow_compatible(&node.styles); + + if let Some(child) = self.flow_last.any() { + self.flow.push(child); + } + + self.flow.push(FlowChild::Node(node)); + self.parbreak(None); + + // This prevents paragraph spacing between the placed node and + // the paragraph below it. + if is_placed { + self.flow_last = Last::None; } } /// Advance to the next paragraph. - fn parbreak(&mut self) { - self.trim(); + fn parbreak(&mut self, break_styles: Option) { + self.finish_par(); - let children = mem::take(&mut self.par); + // Insert paragraph spacing. + self.flow_last + .soft(FlowChild::Parbreak(break_styles.unwrap_or_default())); + } + + fn finish_par(&mut self) { + let mut children = mem::take(&mut self.par); let styles = mem::take(&mut self.par_styles); + self.par_last = Last::None; + + // No empty paragraphs. if !children.is_empty() { + // Erase any styles that will be inherited anyway. + for child in &mut children { + child.styles_mut().erase(&styles); + } + + if let Some(child) = self.flow_last.any() { + self.flow.push(child); + } + // The paragraph's children are all compatible with the page, so the // paragraph is too, meaning we don't need to check or intersect // anything here. let node = ParNode(children).pack().styled(styles); self.flow.push(FlowChild::Node(node)); } - - self.last = Last::None; } /// Advance to the next page. fn pagebreak(&mut self, keep: bool) { - self.parbreak(); - let children = mem::take(&mut self.flow); - let styles = mem::take(&mut self.page_styles); - if keep || !children.is_empty() { - let node = PageNode { node: FlowNode(children).pack(), styles }; - self.document.push(node); + if self.block { + return; } - } - - /// Insert text into the current paragraph. - fn push_text(&mut self, text: EcoString, styles: Styles) { - // TODO(set): Join compatible text nodes. Take care with space - // coalescing. - let node = TextNode { text, styles: Styles::new() }; - self.push_inline(ParChild::Text(node), styles); - } - /// Insert an inline-level element into the current paragraph. - fn push_inline(&mut self, mut child: ParChild, styles: Styles) { - match &mut child { - ParChild::Spacing(_) => {} - ParChild::Text(node) => node.styles.apply(&styles), - ParChild::Node(node) => node.styles.apply(&styles), - ParChild::Decorate(_) => {} - ParChild::Undecorate => {} - } + self.finish_par(); - // The node must be both compatible with the current page and the - // current paragraph. - self.make_page_compatible(&styles); - self.make_par_compatible(&styles); - self.par.push(child); - self.last = Last::Other; - } + let styles = mem::take(&mut self.flow_styles); + let mut children = mem::take(&mut self.flow); + self.flow_last = Last::None; - /// Insert a block-level element into the current flow. - fn push_block(&mut self, mut child: FlowChild, styles: Styles) { - self.parbreak(); + if keep || !children.is_empty() { + // Erase any styles that will be inherited anyway. + for child in &mut children { + child.styles_mut().erase(&styles); + } - match &mut child { - FlowChild::Spacing(_) => {} - FlowChild::Node(node) => node.styles.apply(&styles), + let node = PageNode { node: FlowNode(children).pack(), styles }; + self.pages.push(node); } - - // The node must be compatible with the current page. - self.make_page_compatible(&styles); - self.flow.push(child); } /// Break to a new paragraph if the `styles` contain paragraph styles that @@ -321,8 +342,8 @@ impl NodePacker { return; } - if !self.par_styles.compatible(&styles, ParNode::has_property) { - self.parbreak(); + if !self.is_par_compatible(styles) { + self.parbreak(None); self.par_styles = styles.clone(); return; } @@ -331,19 +352,55 @@ impl NodePacker { } /// Break to a new page if the `styles` contain page styles that are - /// incompatible with the current page. - fn make_page_compatible(&mut self, styles: &Styles) { + /// incompatible with the current flow. + fn make_flow_compatible(&mut self, styles: &Styles) { if self.flow.is_empty() && self.par.is_empty() { - self.page_styles = styles.clone(); + self.flow_styles = styles.clone(); return; } - if !self.page_styles.compatible(&styles, PageNode::has_property) { + if !self.is_flow_compatible(styles) { self.pagebreak(false); - self.page_styles = styles.clone(); + self.flow_styles = styles.clone(); return; } - self.page_styles.intersect(styles); + self.flow_styles.intersect(styles); + } + + /// Whether the given styles are compatible with the current page. + fn is_par_compatible(&self, styles: &Styles) -> bool { + self.par_styles.compatible(&styles, ParNode::has_property) + } + + /// Whether the given styles are compatible with the current flow. + fn is_flow_compatible(&self, styles: &Styles) -> bool { + self.block || self.flow_styles.compatible(&styles, PageNode::has_property) + } +} + +/// Finite state machine for spacing coalescing. +enum Last { + None, + Any, + Soft(N), +} + +impl Last { + fn any(&mut self) -> Option { + match mem::replace(self, Self::Any) { + Self::Soft(soft) => Some(soft), + _ => None, + } + } + + fn soft(&mut self, soft: N) { + if let Self::Any = self { + *self = Self::Soft(soft); + } + } + + fn hard(&mut self) { + *self = Self::None; } } -- cgit v1.2.3 From 57f5c0a1b15775f9500335f455c7dc7d70cea9f5 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 15 Dec 2021 11:12:38 +0100 Subject: Set Rules Episode V: The Tests Strike Back --- src/eval/node.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/eval/node.rs') diff --git a/src/eval/node.rs b/src/eval/node.rs index 52d9b244..4aacf4c1 100644 --- a/src/eval/node.rs +++ b/src/eval/node.rs @@ -280,11 +280,11 @@ impl NodePacker { /// Advance to the next paragraph. fn parbreak(&mut self, break_styles: Option) { + let styles = break_styles.unwrap_or_else(|| self.par_styles.clone()); self.finish_par(); // Insert paragraph spacing. - self.flow_last - .soft(FlowChild::Parbreak(break_styles.unwrap_or_default())); + self.flow_last.soft(FlowChild::Parbreak(styles)); } fn finish_par(&mut self) { -- cgit v1.2.3 From 244ad386ec271ff86a2101eb4cc38d37a55552b9 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 15 Dec 2021 12:49:20 +0100 Subject: Set Rules Episode VI: Return of the Refactor --- src/eval/node.rs | 233 ++++++++++++++++++++++++++----------------------------- 1 file changed, 112 insertions(+), 121 deletions(-) (limited to 'src/eval/node.rs') diff --git a/src/eval/node.rs b/src/eval/node.rs index 4aacf4c1..5653beff 100644 --- a/src/eval/node.rs +++ b/src/eval/node.rs @@ -81,7 +81,7 @@ impl Node { if let Node::Block(packed) = self { packed } else { - let mut packer = NodePacker::new(true); + let mut packer = Packer::new(false); packer.walk(self, Styles::new()); packer.into_block() } @@ -89,7 +89,7 @@ impl Node { /// Lift to a document node, the root of the layout tree. pub fn into_document(self) -> DocumentNode { - let mut packer = NodePacker::new(false); + let mut packer = Packer::new(true); packer.walk(self, Styles::new()); packer.into_document() } @@ -126,49 +126,37 @@ impl AddAssign for Node { } /// Packs a [`Node`] into a flow or whole document. -struct NodePacker { - /// Whether packing should produce a block-level node. - block: bool, +struct Packer { + /// Whether this packer produces the top-level document. + top: bool, /// The accumulated page nodes. pages: Vec, /// The accumulated flow children. - flow: Vec, - /// The common style properties of all items on the current flow. - flow_styles: Styles, - /// The kind of thing that was last added to the current flow. - flow_last: Last, + flow: Builder, /// The accumulated paragraph children. - par: Vec, - /// The common style properties of all items in the current paragraph. - par_styles: Styles, - /// The kind of thing that was last added to the current paragraph. - par_last: Last, + par: Builder, } -impl NodePacker { +impl Packer { /// Start a new node-packing session. - fn new(block: bool) -> Self { + fn new(top: bool) -> Self { Self { - block, + top, pages: vec![], - flow: vec![], - flow_styles: Styles::new(), - flow_last: Last::None, - par: vec![], - par_styles: Styles::new(), - par_last: Last::None, + flow: Builder::default(), + par: Builder::default(), } } /// Finish up and return the resulting flow. fn into_block(mut self) -> PackedNode { - self.finish_par(); - FlowNode(self.flow).pack() + self.parbreak(None); + FlowNode(self.flow.children).pack() } /// Finish up and return the resulting document. fn into_document(mut self) -> DocumentNode { - self.pagebreak(true); + self.pagebreak(); DocumentNode(self.pages) } @@ -176,34 +164,49 @@ impl NodePacker { fn walk(&mut self, node: Node, styles: Styles) { match node { Node::Space => { - if self.is_flow_compatible(&styles) && self.is_par_compatible(&styles) { - self.par_last.soft(ParChild::text(' ', styles)); - } + // A text space is "soft", meaning that it can be eaten up by + // adjacent line breaks or explicit spacings. + self.par.last.soft(ParChild::text(' ', styles)); } Node::Linebreak => { - self.par_last.hard(); + // A line break eats up surrounding text spaces. + self.par.last.hard(); self.push_inline(ParChild::text('\n', styles)); - self.par_last.hard(); + self.par.last.hard(); } Node::Parbreak => { + // An explicit paragraph break is styled according to the active + // styles (`Some(_)`) whereas paragraph breaks forced by + // incompatibility take their styles from the preceding + // paragraph. self.parbreak(Some(styles)); } Node::Pagebreak => { - self.pagebreak(true); - self.flow_styles = styles; + // 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 + // all. + self.pagebreak(); + self.flow.styles = styles; } Node::Text(text) => { self.push_inline(ParChild::text(text, styles)); } Node::Spacing(SpecAxis::Horizontal, kind) => { - self.par_last.hard(); + // Just like a line break, explicit horizontal spacing eats up + // surrounding text spaces. + self.par.last.hard(); self.push_inline(ParChild::Spacing(SpacingNode { kind, styles })); - self.par_last.hard(); + self.par.last.hard(); } Node::Spacing(SpecAxis::Vertical, kind) => { - self.finish_par(); - self.flow.push(FlowChild::Spacing(SpacingNode { kind, styles })); - self.flow_last.hard(); + // Explicit vertical spacing ends the current paragraph and then + // discards the paragraph break. + self.parbreak(None); + self.make_flow_compatible(&styles); + self.flow + .children + .push(FlowChild::Spacing(SpacingNode { kind, styles })); + self.flow.last.hard(); } Node::Inline(inline) => { self.push_inline(ParChild::Node(inline.styled(styles))); @@ -212,6 +215,8 @@ impl NodePacker { self.push_block(block.styled(styles)); } Node::Sequence(list) => { + // For a list of nodes, we apply the list's styles to each node + // individually. for (node, mut inner) in list { inner.apply(&styles); self.walk(node, inner); @@ -222,22 +227,22 @@ impl NodePacker { /// Insert an inline-level element into the current paragraph. fn push_inline(&mut self, child: ParChild) { - if let Some(child) = self.par_last.any() { - self.push_inline_impl(child); + if let Some(child) = self.par.last.any() { + self.push_coalescing(child); } // The node must be both compatible with the current page and the // current paragraph. self.make_flow_compatible(child.styles()); self.make_par_compatible(child.styles()); - self.push_inline_impl(child); - self.par_last = Last::Any; + self.push_coalescing(child); + self.par.last.any(); } /// Push a paragraph child, coalescing text nodes with compatible styles. - fn push_inline_impl(&mut self, child: ParChild) { + fn push_coalescing(&mut self, child: ParChild) { if let ParChild::Text(right) = &child { - if let Some(ParChild::Text(left)) = self.par.last_mut() { + if let Some(ParChild::Text(left)) = self.par.children.last_mut() { if left.styles.compatible(&right.styles, TextNode::has_property) { left.text.push_str(&right.text); return; @@ -245,137 +250,122 @@ impl NodePacker { } } - self.par.push(child); + self.par.children.push(child); } /// Insert a block-level element into the current flow. fn push_block(&mut self, node: PackedNode) { - let mut is_placed = false; - if let Some(placed) = node.downcast::() { - is_placed = true; - - // This prevents paragraph spacing after the placed node if it - // is completely out-of-flow. - if placed.out_of_flow() { - self.flow_last = Last::None; - } - } + let placed = node.is::(); self.parbreak(None); self.make_flow_compatible(&node.styles); - - if let Some(child) = self.flow_last.any() { - self.flow.push(child); - } - - self.flow.push(FlowChild::Node(node)); + self.flow.children.extend(self.flow.last.any()); + self.flow.children.push(FlowChild::Node(node)); self.parbreak(None); - // This prevents paragraph spacing between the placed node and - // the paragraph below it. - if is_placed { - self.flow_last = Last::None; + // Prevent paragraph spacing between the placed node and the paragraph + // below it. + if placed { + self.flow.last.hard(); } } /// Advance to the next paragraph. fn parbreak(&mut self, break_styles: Option) { - let styles = break_styles.unwrap_or_else(|| self.par_styles.clone()); - self.finish_par(); - - // Insert paragraph spacing. - self.flow_last.soft(FlowChild::Parbreak(styles)); - } + // Erase any styles that will be inherited anyway. + let Builder { mut children, styles, .. } = mem::take(&mut self.par); + for child in &mut children { + child.styles_mut().erase(&styles); + } - fn finish_par(&mut self) { - let mut children = mem::take(&mut self.par); - let styles = mem::take(&mut self.par_styles); - self.par_last = Last::None; + // For explicit paragraph breaks, `break_styles` is already `Some(_)`. + // For page breaks due to incompatibility, we fall back to the styles + // of the preceding paragraph. + let break_styles = break_styles.unwrap_or_else(|| styles.clone()); - // No empty paragraphs. + // We don't want empty paragraphs. if !children.is_empty() { - // Erase any styles that will be inherited anyway. - for child in &mut children { - child.styles_mut().erase(&styles); - } - - if let Some(child) = self.flow_last.any() { - self.flow.push(child); - } - // The paragraph's children are all compatible with the page, so the // paragraph is too, meaning we don't need to check or intersect // anything here. - let node = ParNode(children).pack().styled(styles); - self.flow.push(FlowChild::Node(node)); + let par = ParNode(children).pack().styled(styles); + self.flow.children.extend(self.flow.last.any()); + self.flow.children.push(FlowChild::Node(par)); } + + // Insert paragraph spacing. + self.flow.last.soft(FlowChild::Break(break_styles)); } /// Advance to the next page. - fn pagebreak(&mut self, keep: bool) { - if self.block { - return; - } - - self.finish_par(); - - let styles = mem::take(&mut self.flow_styles); - let mut children = mem::take(&mut self.flow); - self.flow_last = Last::None; + fn pagebreak(&mut self) { + if self.top { + self.parbreak(None); - if keep || !children.is_empty() { - // Erase any styles that will be inherited anyway. + // 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); } - let node = PageNode { node: FlowNode(children).pack(), styles }; - self.pages.push(node); + let flow = FlowNode(children).pack(); + let page = PageNode { child: flow, styles }; + self.pages.push(page); } } /// Break to a new paragraph if the `styles` contain paragraph styles that /// are incompatible with the current paragraph. fn make_par_compatible(&mut self, styles: &Styles) { - if self.par.is_empty() { - self.par_styles = styles.clone(); + if self.par.children.is_empty() { + self.par.styles = styles.clone(); return; } - if !self.is_par_compatible(styles) { + if !self.par.styles.compatible(&styles, ParNode::has_property) { self.parbreak(None); - self.par_styles = styles.clone(); + self.par.styles = styles.clone(); return; } - self.par_styles.intersect(&styles); + self.par.styles.intersect(&styles); } /// Break to a new page if the `styles` contain page styles that are /// incompatible with the current flow. fn make_flow_compatible(&mut self, styles: &Styles) { - if self.flow.is_empty() && self.par.is_empty() { - self.flow_styles = styles.clone(); + if self.flow.children.is_empty() && self.par.children.is_empty() { + self.flow.styles = styles.clone(); return; } - if !self.is_flow_compatible(styles) { - self.pagebreak(false); - self.flow_styles = styles.clone(); + if self.top && !self.flow.styles.compatible(&styles, PageNode::has_property) { + self.pagebreak(); + self.flow.styles = styles.clone(); return; } - self.flow_styles.intersect(styles); + self.flow.styles.intersect(styles); } +} - /// Whether the given styles are compatible with the current page. - fn is_par_compatible(&self, styles: &Styles) -> bool { - self.par_styles.compatible(&styles, ParNode::has_property) - } +/// Container for building a flow or paragraph. +struct Builder { + /// The intersection of the style properties of all `children`. + styles: Styles, + /// The accumulated flow or paragraph children. + children: Vec, + /// The kind of thing that was last added. + last: Last, +} - /// Whether the given styles are compatible with the current flow. - fn is_flow_compatible(&self, styles: &Styles) -> bool { - self.block || self.flow_styles.compatible(&styles, PageNode::has_property) +impl Default for Builder { + fn default() -> Self { + Self { + styles: Styles::new(), + children: vec![], + last: Last::None, + } } } @@ -383,6 +373,7 @@ impl NodePacker { enum Last { None, Any, + Hard, Soft(N), } @@ -401,6 +392,6 @@ impl Last { } fn hard(&mut self) { - *self = Self::None; + *self = Self::Hard; } } -- cgit v1.2.3 From 2a3d0f4b390457174ed09347dd29e97ff9a783e4 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 15 Dec 2021 20:27:41 +0100 Subject: Set Rules Episode VII: The Set Awakens --- src/eval/node.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'src/eval/node.rs') diff --git a/src/eval/node.rs b/src/eval/node.rs index 5653beff..a04fe84b 100644 --- a/src/eval/node.rs +++ b/src/eval/node.rs @@ -36,6 +36,8 @@ pub enum Node { Inline(PackedNode), /// A block node. Block(PackedNode), + /// A page node. + Page(PackedNode), /// A sequence of nodes (which may themselves contain sequences). Sequence(Vec<(Self, Styles)>), } @@ -214,6 +216,14 @@ impl Packer { Node::Block(block) => { self.push_block(block.styled(styles)); } + Node::Page(flow) => { + if self.top { + self.pagebreak(); + self.pages.push(PageNode { child: flow, styles }); + } else { + self.push_block(flow.styled(styles)); + } + } Node::Sequence(list) => { // For a list of nodes, we apply the list's styles to each node // individually. -- cgit v1.2.3 From 958f74f77707340f34ee36d09492bdb74523aa2a Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 16 Dec 2021 14:43:02 +0100 Subject: Set Rules Episode VIII: The First Macro --- src/eval/node.rs | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/eval/node.rs') diff --git a/src/eval/node.rs b/src/eval/node.rs index a04fe84b..acdf4ed6 100644 --- a/src/eval/node.rs +++ b/src/eval/node.rs @@ -1,6 +1,7 @@ use std::convert::TryFrom; use std::fmt::Debug; use std::hash::Hash; +use std::iter::Sum; use std::mem; use std::ops::{Add, AddAssign}; @@ -127,6 +128,12 @@ impl AddAssign for Node { } } +impl Sum for Node { + fn sum>(iter: I) -> Self { + Self::Sequence(iter.map(|n| (n, Styles::new())).collect()) + } +} + /// Packs a [`Node`] into a flow or whole document. struct Packer { /// Whether this packer produces the top-level document. -- cgit v1.2.3 From 11565a40b315212474f52eb576a9fd92b11f1132 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 20 Dec 2021 14:18:29 +0100 Subject: Set Rules Episode IX: The Rise of Testing --- src/eval/node.rs | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) (limited to 'src/eval/node.rs') diff --git a/src/eval/node.rs b/src/eval/node.rs index acdf4ed6..e2b02955 100644 --- a/src/eval/node.rs +++ b/src/eval/node.rs @@ -8,17 +8,18 @@ use std::ops::{Add, AddAssign}; use super::Styles; use crate::diag::StrResult; use crate::geom::SpecAxis; -use crate::layout::{Layout, PackedNode}; +use crate::layout::{Layout, PackedNode, RootNode}; use crate::library::{ - DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, PlacedNode, - SpacingKind, SpacingNode, TextNode, + FlowChild, FlowNode, PageNode, ParChild, ParNode, PlacedNode, SpacingKind, + SpacingNode, TextNode, }; use crate::util::EcoString; /// A partial representation of a layout node. /// /// A node is a composable intermediate representation that can be converted -/// into a proper layout node by lifting it to a block-level or document node. +/// into a proper layout node by lifting it to a [block-level](PackedNode) or +/// [root node](RootNode). #[derive(Debug, PartialEq, Clone, Hash)] pub enum Node { /// A word space. @@ -90,19 +91,19 @@ impl Node { } } - /// Lift to a document node, the root of the layout tree. - pub fn into_document(self) -> DocumentNode { + /// Lift to a root layout tree node. + pub fn into_root(self) -> RootNode { let mut packer = Packer::new(true); packer.walk(self, Styles::new()); - packer.into_document() + packer.into_root() } - /// Repeat this template `n` times. + /// Repeat this node `n` times. pub fn repeat(&self, n: i64) -> StrResult { let count = usize::try_from(n) .map_err(|_| format!("cannot repeat this template {} times", n))?; - // TODO(set): Make more efficient. + // TODO(style): Make more efficient. Ok(Self::Sequence(vec![(self.clone(), Styles::new()); count])) } } @@ -117,7 +118,7 @@ impl Add for Node { type Output = Self; fn add(self, rhs: Self) -> Self::Output { - // TODO(set): Make more efficient. + // TODO(style): Make more efficient. Self::Sequence(vec![(self, Styles::new()), (rhs, Styles::new())]) } } @@ -134,9 +135,9 @@ impl Sum for Node { } } -/// Packs a [`Node`] into a flow or whole document. +/// Packs a [`Node`] into a flow or root node. struct Packer { - /// Whether this packer produces the top-level document. + /// Whether this packer produces a root node. top: bool, /// The accumulated page nodes. pages: Vec, @@ -163,10 +164,10 @@ impl Packer { FlowNode(self.flow.children).pack() } - /// Finish up and return the resulting document. - fn into_document(mut self) -> DocumentNode { + /// Finish up and return the resulting root node. + fn into_root(mut self) -> RootNode { self.pagebreak(); - DocumentNode(self.pages) + RootNode(self.pages) } /// Consider a node with the given styles. -- cgit v1.2.3 From 438255519e88bb790480306b9a9b452aaf054519 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 22 Dec 2021 19:04:35 +0100 Subject: Review One: A Set Rules Story --- src/eval/node.rs | 48 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 5 deletions(-) (limited to 'src/eval/node.rs') diff --git a/src/eval/node.rs b/src/eval/node.rs index e2b02955..34a4f275 100644 --- a/src/eval/node.rs +++ b/src/eval/node.rs @@ -20,6 +20,10 @@ use crate::util::EcoString; /// A node is a composable intermediate representation that can be converted /// into a proper layout node by lifting it to a [block-level](PackedNode) or /// [root node](RootNode). +/// +/// When you write `[Hi] + [you]` in Typst, this type's [`Add`] implementation +/// is invoked. There, multiple nodes are combined into a single +/// [`Sequence`](Self::Sequence) node. #[derive(Debug, PartialEq, Clone, Hash)] pub enum Node { /// A word space. @@ -39,8 +43,24 @@ pub enum Node { /// A block node. Block(PackedNode), /// A page node. - Page(PackedNode), - /// A sequence of nodes (which may themselves contain sequences). + Page(PageNode), + /// Multiple nodes with attached styles. + /// + /// For example, the Typst template `[Hi *you!*]` would result in the + /// sequence: + /// ```ignore + /// Sequence([ + /// (Text("Hi"), {}), + /// (Space, {}), + /// (Text("you!"), { TextNode::STRONG: true }), + /// ]) + /// ``` + /// A sequence may contain nested sequences (meaning this variant + /// effectively allows nodes to form trees). All nested sequences can + /// equivalently be represented as a single flat sequence, but allowing + /// nesting doesn't hurt since we can just recurse into the nested sequences + /// during packing. Also, in theory, this allows better complexity when + /// adding (large) sequence nodes (just like for a text rope). Sequence(Vec<(Self, Styles)>), } @@ -71,6 +91,7 @@ impl Node { match self { Self::Inline(inline) => Self::Inline(inline.styled(styles)), Self::Block(block) => Self::Block(block.styled(styles)), + Self::Page(page) => Self::Page(page.styled(styles)), other => Self::Sequence(vec![(other, styles)]), } } @@ -224,11 +245,12 @@ impl Packer { Node::Block(block) => { self.push_block(block.styled(styles)); } - Node::Page(flow) => { + Node::Page(page) => { if self.top { self.pagebreak(); - self.pages.push(PageNode { child: flow, styles }); + self.pages.push(page.styled(styles)); } else { + let flow = page.child.styled(page.styles); self.push_block(flow.styled(styles)); } } @@ -387,15 +409,27 @@ impl Default for Builder { } } -/// Finite state machine for spacing coalescing. +/// The kind of node that was last added to a flow or paragraph. A small finite +/// state machine used to coalesce spaces. +/// +/// Soft nodes can only exist when surrounded by `Any` nodes. Not at the +/// start, end or next to hard nodes. This way, spaces at start and end of +/// paragraphs and next to `#h(..)` goes away. enum Last { + /// Start state, nothing there. None, + /// Text or a block node or something. Any, + /// Hard nodes: Linebreaks and explicit spacing. Hard, + /// Soft nodes: Word spaces and paragraph breaks. These are saved here + /// temporarily and then applied once an `Any` node appears. Soft(N), } impl Last { + /// Transition into the `Any` state and return a soft node to really add + /// now if currently in `Soft` state. fn any(&mut self) -> Option { match mem::replace(self, Self::Any) { Self::Soft(soft) => Some(soft), @@ -403,12 +437,16 @@ impl Last { } } + /// Transition into the `Soft` state, but only if in `Any`. Otherwise, the + /// soft node is discarded. fn soft(&mut self, soft: N) { if let Self::Any = self { *self = Self::Soft(soft); } } + /// Transition into the `Hard` state, discarding a possibly existing soft + /// node and preventing further soft nodes from being added. fn hard(&mut self) { *self = Self::Hard; } -- cgit v1.2.3