summaryrefslogtreecommitdiff
path: root/src/eval/node.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/eval/node.rs')
-rw-r--r--src/eval/node.rs213
1 files changed, 174 insertions, 39 deletions
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<Self>),
+ 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("<node>")
+ 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<PageNode>,
+ /// The common style properties of all items on the current page.
+ page_styles: Styles,
+ /// The accumulated flow children.
flow: Vec<FlowChild>,
+ /// The accumulated paragraph children.
par: Vec<ParChild>,
+ /// 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);
+ }
}