diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-03-25 21:32:33 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-03-25 21:32:33 +0100 |
| commit | 76fc4cca62f5b955200b2c62cc85b69eea491ece (patch) | |
| tree | 5b8492268c996cf23b13e26c7a4356fbd156286d /src/exec/context.rs | |
| parent | e8057a53856dc09594c9e5861f1cd328531616e0 (diff) | |
Refactor alignments & directions 📐
- Adds lang function
- Refactors execution context
- Adds StackChild and ParChild enums
Diffstat (limited to 'src/exec/context.rs')
| -rw-r--r-- | src/exec/context.rs | 322 |
1 files changed, 168 insertions, 154 deletions
diff --git a/src/exec/context.rs b/src/exec/context.rs index b6a67a2e..333ad3ba 100644 --- a/src/exec/context.rs +++ b/src/exec/context.rs @@ -4,15 +4,14 @@ use super::{Exec, FontFamily, State}; use crate::diag::{Diag, DiagSet, Pass}; use crate::env::Env; use crate::eval::TemplateValue; -use crate::geom::{Dir, Gen, Linear, Sides, Size}; +use crate::geom::{Align, Dir, Gen, GenAxis, Length, Linear, Sides, Size}; use crate::layout::{ - Node, PadNode, PageRun, ParNode, SpacingNode, StackNode, TextNode, Tree, + AnyNode, PadNode, PageRun, ParChild, ParNode, StackChild, StackNode, TextNode, Tree, }; use crate::parse::{is_newline, Scanner}; -use crate::syntax::{Span, Spanned}; +use crate::syntax::Span; /// The context for execution. -#[derive(Debug)] pub struct ExecContext<'a> { /// The environment from which resources are gathered. pub env: &'a mut Env, @@ -22,13 +21,11 @@ pub struct ExecContext<'a> { pub diags: DiagSet, /// The tree of finished page runs. tree: Tree, - /// Metrics of the active page. - page: Option<PageInfo>, - /// The content of the active stack. This may be the top-level stack for the - /// page or a lower one created by [`exec`](Self::exec). - stack: StackNode, - /// The content of the active paragraph. - par: ParNode, + /// When we are building the top-level stack, this contains metrics of the + /// page. While building a group stack through `exec_group`, this is `None`. + page: Option<PageBuilder>, + /// The currently built stack of paragraphs. + stack: StackBuilder, } impl<'a> ExecContext<'a> { @@ -38,9 +35,8 @@ impl<'a> ExecContext<'a> { env, diags: DiagSet::new(), tree: Tree { runs: vec![] }, - page: Some(PageInfo::new(&state, true)), - stack: StackNode::new(&state), - par: ParNode::new(&state), + page: Some(PageBuilder::new(&state, true)), + stack: StackBuilder::new(&state), state, } } @@ -50,45 +46,23 @@ impl<'a> ExecContext<'a> { self.diags.insert(diag); } - /// Set the directions. - /// - /// Produces an error if the axes aligned. - pub fn set_dirs(&mut self, new: Gen<Option<Spanned<Dir>>>) { - let dirs = Gen::new( - new.main.map(|s| s.v).unwrap_or(self.state.dirs.main), - new.cross.map(|s| s.v).unwrap_or(self.state.dirs.cross), - ); - - if dirs.main.axis() != dirs.cross.axis() { - self.state.dirs = dirs; - } else { - for dir in new.main.iter().chain(new.cross.iter()) { - self.diag(error!(dir.span, "aligned axis")); - } - } - } - /// Set the font to monospace. pub fn set_monospace(&mut self) { let families = self.state.font.families_mut(); families.list.insert(0, FontFamily::Monospace); } - /// Push a layout node into the active paragraph. - /// - /// Spacing nodes will be handled according to their - /// [`softness`](SpacingNode::softness). - pub fn push(&mut self, node: impl Into<Node>) { - push(&mut self.par.children, node.into()); - } + /// Execute a template and return the result as a stack node. + pub fn exec_group(&mut self, template: &TemplateValue) -> StackNode { + let snapshot = self.state.clone(); + let page = self.page.take(); + let stack = mem::replace(&mut self.stack, StackBuilder::new(&self.state)); - /// Push a word space into the active paragraph. - pub fn push_space(&mut self) { - let em = self.state.font.resolve_size(); - self.push(SpacingNode { - amount: self.state.par.word_spacing.resolve(em), - softness: 1, - }); + template.exec(self); + + self.state = snapshot; + self.page = page; + mem::replace(&mut self.stack, stack).build() } /// Push text into the active paragraph. @@ -97,96 +71,85 @@ impl<'a> ExecContext<'a> { pub fn push_text(&mut self, text: &str) { let mut scanner = Scanner::new(text); let mut line = String::new(); + let push = |this: &mut Self, text| { + let props = this.state.font.resolve_props(); + let node = TextNode { text, props }; + let align = this.state.aligns.cross; + this.stack.par.folder.push(ParChild::Text(node, align)) + }; while let Some(c) = scanner.eat_merging_crlf() { if is_newline(c) { - self.push(TextNode::new(mem::take(&mut line), &self.state)); + push(self, mem::take(&mut line)); self.push_linebreak(); } else { line.push(c); } } - self.push(TextNode::new(line, &self.state)); + push(self, line); + } + + /// Push a word space. + pub fn push_word_space(&mut self) { + let em = self.state.font.resolve_size(); + let amount = self.state.par.word_spacing.resolve(em); + self.push_spacing(GenAxis::Cross, amount, 1); } /// Apply a forced line break. pub fn push_linebreak(&mut self) { let em = self.state.font.resolve_size(); - self.push_into_stack(SpacingNode { - amount: self.state.par.leading.resolve(em), - softness: 2, - }); + let amount = self.state.par.leading.resolve(em); + self.push_spacing(GenAxis::Main, amount, 2); } /// Apply a forced paragraph break. pub fn push_parbreak(&mut self) { let em = self.state.font.resolve_size(); - self.push_into_stack(SpacingNode { - amount: self.state.par.spacing.resolve(em), - softness: 1, - }); - } - - /// Push a node directly into the stack above the paragraph. This finishes - /// the active paragraph and starts a new one. - pub fn push_into_stack(&mut self, node: impl Into<Node>) { - self.finish_par(); - push(&mut self.stack.children, node.into()); + let amount = self.state.par.spacing.resolve(em); + self.push_spacing(GenAxis::Main, amount, 1); } - /// Execute a template and return the result as a stack node. - pub fn exec(&mut self, template: &TemplateValue) -> StackNode { - let page = self.page.take(); - let stack = mem::replace(&mut self.stack, StackNode::new(&self.state)); - let par = mem::replace(&mut self.par, ParNode::new(&self.state)); - - template.exec(self); - let result = self.finish_stack(); - - self.page = page; - self.stack = stack; - self.par = par; - - result - } - - /// Finish the active paragraph. - fn finish_par(&mut self) { - let mut par = mem::replace(&mut self.par, ParNode::new(&self.state)); - trim(&mut par.children); - - if !par.children.is_empty() { - self.stack.children.push(par.into()); + /// Push spacing into paragraph or stack depending on `axis`. + /// + /// The `softness` configures how the spacing interacts with surrounding + /// spacing. + pub fn push_spacing(&mut self, axis: GenAxis, amount: Length, softness: u8) { + match axis { + GenAxis::Main => { + let spacing = StackChild::Spacing(amount); + self.stack.finish_par(&self.state); + self.stack.folder.push_soft(spacing, softness); + } + GenAxis::Cross => { + let spacing = ParChild::Spacing(amount); + self.stack.par.folder.push_soft(spacing, softness); + } } } - /// Finish the active stack. - fn finish_stack(&mut self) -> StackNode { - self.finish_par(); - - let mut stack = mem::replace(&mut self.stack, StackNode::new(&self.state)); - trim(&mut stack.children); + /// Push any node into the active paragraph. + pub fn push_into_par(&mut self, node: impl Into<AnyNode>) { + let align = self.state.aligns.cross; + self.stack.par.folder.push(ParChild::Any(node.into(), align)); + } - stack + /// Push any node directly into the stack of paragraphs. + /// + /// This finishes the active paragraph and starts a new one. + pub fn push_into_stack(&mut self, node: impl Into<AnyNode>) { + let aligns = self.state.aligns; + self.stack.finish_par(&self.state); + self.stack.folder.push(StackChild::Any(node.into(), aligns)); } /// Finish the active page. pub fn finish_page(&mut self, keep: bool, hard: bool, source: Span) { - if let Some(info) = &mut self.page { - let info = mem::replace(info, PageInfo::new(&self.state, hard)); - let stack = self.finish_stack(); - - if !stack.children.is_empty() || (keep && info.hard) { - self.tree.runs.push(PageRun { - size: info.size, - child: PadNode { - padding: info.padding, - child: stack.into(), - } - .into(), - }); - } + if let Some(builder) = &mut self.page { + let page = mem::replace(builder, PageBuilder::new(&self.state, hard)); + let stack = mem::replace(&mut self.stack, StackBuilder::new(&self.state)); + self.tree.runs.extend(page.build(stack.build(), keep)); } else { self.diag(error!(source, "cannot modify page from here")); } @@ -200,44 +163,13 @@ impl<'a> ExecContext<'a> { } } -/// Push a node into a list, taking care of spacing softness. -fn push(nodes: &mut Vec<Node>, node: Node) { - if let Node::Spacing(spacing) = node { - if nodes.is_empty() && spacing.softness > 0 { - return; - } - - if let Some(&Node::Spacing(other)) = nodes.last() { - if spacing.softness > 0 && spacing.softness >= other.softness { - return; - } - - if spacing.softness < other.softness { - nodes.pop(); - } - } - } - - nodes.push(node); -} - -/// Remove trailing soft spacing from a node list. -fn trim(nodes: &mut Vec<Node>) { - if let Some(&Node::Spacing(spacing)) = nodes.last() { - if spacing.softness > 0 { - nodes.pop(); - } - } -} - -#[derive(Debug)] -struct PageInfo { +struct PageBuilder { size: Size, padding: Sides<Linear>, hard: bool, } -impl PageInfo { +impl PageBuilder { fn new(state: &State, hard: bool) -> Self { Self { size: state.page.size, @@ -245,37 +177,119 @@ impl PageInfo { hard, } } + + fn build(self, child: StackNode, keep: bool) -> Option<PageRun> { + let Self { size, padding, hard } = self; + (!child.children.is_empty() || (keep && hard)).then(|| PageRun { + size, + child: PadNode { padding, child: child.into() }.into(), + }) + } } -impl StackNode { +struct StackBuilder { + dirs: Gen<Dir>, + folder: SoftFolder<StackChild>, + par: ParBuilder, +} + +impl StackBuilder { fn new(state: &State) -> Self { Self { - dirs: state.dirs, - aligns: state.aligns, - children: vec![], + dirs: Gen::new(Dir::TTB, state.lang.dir), + folder: SoftFolder::new(), + par: ParBuilder::new(state), } } + + fn finish_par(&mut self, state: &State) { + let par = mem::replace(&mut self.par, ParBuilder::new(state)); + self.folder.extend(par.build()); + } + + fn build(self) -> StackNode { + let Self { dirs, mut folder, par } = self; + folder.extend(par.build()); + StackNode { dirs, children: folder.finish() } + } } -impl ParNode { +struct ParBuilder { + aligns: Gen<Align>, + dir: Dir, + line_spacing: Length, + folder: SoftFolder<ParChild>, +} + +impl ParBuilder { fn new(state: &State) -> Self { let em = state.font.resolve_size(); Self { - dirs: state.dirs, aligns: state.aligns, + dir: state.lang.dir, line_spacing: state.par.leading.resolve(em), - children: vec![], + folder: SoftFolder::new(), } } + + fn build(self) -> Option<StackChild> { + let Self { aligns, dir, line_spacing, folder } = self; + let children = folder.finish(); + (!children.is_empty()).then(|| { + let node = ParNode { dir, line_spacing, children }; + StackChild::Any(node.into(), aligns) + }) + } } -impl TextNode { - fn new(text: String, state: &State) -> Self { - Self { - text, - dir: state.dirs.cross, - aligns: state.aligns, - props: state.font.resolve_props(), +/// This is used to remove leading and trailing word/line/paragraph spacing +/// as well as collapse sequences of spacings into just one. +struct SoftFolder<N> { + nodes: Vec<N>, + last: Last<N>, +} + +enum Last<N> { + None, + Hard, + Soft(N, u8), +} + +impl<N> SoftFolder<N> { + fn new() -> Self { + Self { nodes: vec![], last: Last::Hard } + } + + fn push(&mut self, node: N) { + let last = mem::replace(&mut self.last, Last::None); + if let Last::Soft(soft, _) = last { + self.nodes.push(soft); + } + self.nodes.push(node); + } + + fn push_soft(&mut self, node: N, softness: u8) { + if softness == 0 { + self.last = Last::Hard; + self.nodes.push(node); + } else { + match self.last { + Last::Hard => {} + Last::Soft(_, other) if softness >= other => {} + _ => self.last = Last::Soft(node, softness), + } + } + } + + fn finish(self) -> Vec<N> { + self.nodes + } +} + +impl<N> Extend<N> for SoftFolder<N> { + fn extend<T: IntoIterator<Item = N>>(&mut self, iter: T) { + for elem in iter { + self.push(elem); } } } |
