summaryrefslogtreecommitdiff
path: root/src/exec/context.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-03-12 14:16:59 +0100
committerLaurenz <laurmaedje@gmail.com>2021-03-12 14:16:59 +0100
commitc3acb491e38d333acff6897479f7dd9c86fba307 (patch)
tree7bf33511e7631c2ebff1b1f438eeefba6e3ae6a2 /src/exec/context.rs
parent584a43277dbfbdba834a2681afe63d10598db3f9 (diff)
Refactor execution context 🏗
- The execution context is a lot more structured: Instead of a magic stack of arbitrary objects there are static objects for pages, stacks and paragraphs - Page softness/keeping mechanic is now a lot simpler than before
Diffstat (limited to 'src/exec/context.rs')
-rw-r--r--src/exec/context.rs364
1 files changed, 164 insertions, 200 deletions
diff --git a/src/exec/context.rs b/src/exec/context.rs
index 3b0fd897..e10c28ff 100644
--- a/src/exec/context.rs
+++ b/src/exec/context.rs
@@ -1,11 +1,11 @@
-use std::any::Any;
+use std::mem;
use std::rc::Rc;
use fontdock::FontStyle;
use super::*;
use crate::diag::{Diag, DiagSet};
-use crate::geom::{Dir, Gen, LayoutAligns, LayoutDirs, Length, Linear, Sides, Size};
+use crate::geom::{Dir, Gen, Linear, Sides, Size};
use crate::layout::{
Node, NodePad, NodePages, NodePar, NodeSpacing, NodeStack, NodeText, Tree,
};
@@ -20,17 +20,15 @@ pub struct ExecContext<'a> {
pub state: State,
/// Execution diagnostics.
pub diags: DiagSet,
- /// The finished page runs.
- runs: Vec<NodePages>,
- /// The stack of logical groups (paragraphs and such).
- ///
- /// Each entry contains metadata about the group and nodes that are at the
- /// same level as the group, which will return to `inner` once the group is
- /// finished.
- groups: Vec<(Box<dyn Any>, Vec<Node>)>,
- /// The nodes in the current innermost group
- /// (whose metadata is in `groups.last()`).
- inner: Vec<Node>,
+ /// The tree of finished page runs.
+ tree: Tree,
+ /// Metrics of the active page.
+ page: PageData,
+ /// 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: NodeStack,
+ /// The content of the active paragraph.
+ par: NodePar,
}
impl<'a> ExecContext<'a> {
@@ -38,49 +36,53 @@ impl<'a> ExecContext<'a> {
pub fn new(env: &'a mut Env, state: State) -> Self {
Self {
env,
- state,
diags: DiagSet::new(),
- runs: vec![],
- groups: vec![],
- inner: vec![],
+ tree: Tree { runs: vec![] },
+ page: PageData::new(&state, Softness::Hard),
+ stack: NodeStack::new(&state),
+ par: NodePar::new(&state),
+ state,
}
}
- /// Finish execution and return the created layout tree.
- pub fn finish(self) -> Pass<Tree> {
- assert!(self.groups.is_empty(), "unfinished group");
- Pass::new(Tree { runs: self.runs }, self.diags)
- }
-
/// Add a diagnostic.
pub fn diag(&mut self, diag: Diag) {
self.diags.insert(diag);
}
- /// Push a layout node to the active group.
+ /// Set the directions.
///
- /// Spacing nodes will be handled according to their [`Softness`].
- pub fn push(&mut self, node: impl Into<Node>) {
- let node = node.into();
-
- if let Node::Spacing(this) = node {
- if this.softness == Softness::Soft && self.inner.is_empty() {
- return;
- }
+ /// 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 let Some(&Node::Spacing(other)) = self.inner.last() {
- if this.softness > other.softness {
- self.inner.pop();
- } else if this.softness == Softness::Soft {
- return;
- }
+ 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"));
}
}
+ }
- self.inner.push(node);
+ /// Set the font to monospace.
+ pub fn set_monospace(&mut self) {
+ let families = self.state.font.families_mut();
+ families.list.insert(0, "monospace".to_string());
+ families.flatten();
}
- /// Push a normal word space.
+ /// Push a layout node into the active paragraph.
+ ///
+ /// Spacing nodes will be handled according to their [`Softness`].
+ pub fn push(&mut self, node: impl Into<Node>) {
+ push(&mut self.par.children, node.into());
+ }
+
+ /// Push a word space into the active paragraph.
pub fn push_space(&mut self) {
let em = self.state.font.font_size();
self.push(NodeSpacing {
@@ -89,14 +91,14 @@ impl<'a> ExecContext<'a> {
});
}
- /// Push text into the context.
+ /// Push text into the active paragraph.
///
/// The text is split into lines at newlines.
pub fn push_text(&mut self, text: &str) {
let mut newline = false;
for line in text.split_terminator(is_newline) {
if newline {
- self.apply_linebreak();
+ self.push_linebreak();
}
let node = self.make_text_node(line.into());
@@ -105,155 +107,39 @@ impl<'a> ExecContext<'a> {
}
}
- /// Execute a template and return the result as a stack node.
- pub fn exec(&mut self, template: &ValueTemplate) -> Node {
- let dirs = self.state.dirs;
- let aligns = self.state.aligns;
-
- self.start_group(ContentGroup);
- self.start_par_group();
- template.exec(self);
- self.end_par_group();
- let children = self.end_group::<ContentGroup>().1;
-
- NodeStack { dirs, aligns, children }.into()
- }
-
- /// Start a page group based on the active page state.
- ///
- /// The `softness` is a hint on whether empty pages should be kept in the
- /// output.
- ///
- /// This also starts an inner paragraph.
- pub fn start_page_group(&mut self, softness: Softness) {
- self.start_group(PageGroup {
- size: self.state.page.size,
- padding: self.state.page.margins(),
- dirs: self.state.dirs,
- aligns: self.state.aligns,
- softness,
- });
- self.start_par_group();
- }
-
- /// End a page group, returning its [`Softness`].
- ///
- /// Whether the page is kept when it's empty is decided by `keep_empty`
- /// based on its softness. If kept, the page is pushed to the finished page
- /// runs.
- ///
- /// This also ends an inner paragraph.
- pub fn end_page_group<F>(&mut self, keep_empty: F) -> Softness
- where
- F: FnOnce(Softness) -> bool,
- {
- self.end_par_group();
- let (group, children) = self.end_group::<PageGroup>();
- if !children.is_empty() || keep_empty(group.softness) {
- self.runs.push(NodePages {
- size: group.size,
- child: NodePad {
- padding: group.padding,
- child: NodeStack {
- dirs: group.dirs,
- aligns: group.aligns,
- children,
- }
- .into(),
- }
- .into(),
- })
- }
- group.softness
+ /// Apply a forced line break.
+ pub fn push_linebreak(&mut self) {
+ self.finish_par();
}
- /// Start a paragraph group based on the active text state.
- pub fn start_par_group(&mut self) {
+ /// Apply a forced paragraph break.
+ pub fn push_parbreak(&mut self) {
let em = self.state.font.font_size();
- self.start_group(ParGroup {
- dirs: self.state.dirs,
- aligns: self.state.aligns,
- line_spacing: self.state.par.line_spacing.resolve(em),
+ self.push_into_stack(NodeSpacing {
+ amount: self.state.par.par_spacing.resolve(em),
+ softness: Softness::Soft,
});
}
- /// End a paragraph group and push it to its parent group if it's not empty.
- pub fn end_par_group(&mut self) {
- let (group, children) = self.end_group::<ParGroup>();
- if !children.is_empty() {
- self.push(NodePar {
- dirs: group.dirs,
- aligns: group.aligns,
- line_spacing: group.line_spacing,
- children,
- });
- }
+ /// 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());
}
- /// Start a layouting group.
- ///
- /// All further calls to [`push`](Self::push) will collect nodes for this
- /// group. The given metadata will be returned alongside the collected nodes
- /// in a matching call to [`end_group`](Self::end_group).
- fn start_group<T: 'static>(&mut self, meta: T) {
- self.groups.push((Box::new(meta), std::mem::take(&mut self.inner)));
- }
-
- /// End a layouting group started with [`start_group`](Self::start_group).
- ///
- /// This returns the stored metadata and the collected nodes.
- #[track_caller]
- fn end_group<T: 'static>(&mut self) -> (T, Vec<Node>) {
- if let Some(&Node::Spacing(spacing)) = self.inner.last() {
- if spacing.softness == Softness::Soft {
- self.inner.pop();
- }
- }
-
- let (any, outer) = self.groups.pop().expect("no pushed group");
- let group = *any.downcast::<T>().expect("bad group type");
- (group, std::mem::replace(&mut self.inner, outer))
- }
-
- /// Set the directions if they would apply to different axes, producing an
- /// appropriate error otherwise.
- 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"));
- }
- }
- }
+ /// Execute a template and return the result as a stack node.
+ pub fn exec(&mut self, template: &ValueTemplate) -> NodeStack {
+ let prev_par = mem::replace(&mut self.par, NodePar::new(&self.state));
+ let prev_stack = mem::replace(&mut self.stack, NodeStack::new(&self.state));
- /// Set the font to monospace.
- pub fn apply_monospace(&mut self) {
- let families = self.state.font.families_mut();
- families.list.insert(0, "monospace".to_string());
- families.flatten();
- }
+ template.exec(self);
+ let stack = self.finish_stack();
- /// Apply a forced line break.
- pub fn apply_linebreak(&mut self) {
- self.end_par_group();
- self.start_par_group();
- }
+ self.par = prev_par;
+ self.stack = prev_stack;
- /// Apply a forced paragraph break.
- pub fn apply_parbreak(&mut self) {
- self.end_par_group();
- let em = self.state.font.font_size();
- self.push(NodeSpacing {
- amount: self.state.par.par_spacing.resolve(em),
- softness: Softness::Soft,
- });
- self.start_par_group();
+ stack
}
/// Construct a text node from the given string based on the active text
@@ -282,35 +168,113 @@ impl<'a> ExecContext<'a> {
variant,
}
}
+
+ /// Finish the active paragraph.
+ fn finish_par(&mut self) {
+ let mut par = mem::replace(&mut self.par, NodePar::new(&self.state));
+ trim(&mut par.children);
+
+ if !par.children.is_empty() {
+ self.stack.children.push(par.into());
+ }
+ }
+
+ /// Finish the active stack.
+ fn finish_stack(&mut self) -> NodeStack {
+ self.finish_par();
+
+ let mut stack = mem::replace(&mut self.stack, NodeStack::new(&self.state));
+ trim(&mut stack.children);
+
+ stack
+ }
+
+ /// Finish the active page.
+ pub fn finish_page(&mut self, keep: bool, new_softnes: Softness) {
+ let stack = self.finish_stack();
+ let data = mem::replace(&mut self.page, PageData::new(&self.state, new_softnes));
+ if !stack.children.is_empty() || (keep && data.softness == Softness::Hard) {
+ self.tree.runs.push(NodePages {
+ size: data.size,
+ child: NodePad {
+ padding: data.padding,
+ child: stack.into(),
+ }
+ .into(),
+ });
+ }
+ }
+
+ /// Finish execution and return the created layout tree.
+ pub fn finish(mut self) -> Pass<Tree> {
+ self.finish_page(true, Softness::Soft);
+ Pass::new(self.tree, self.diags)
+ }
}
-/// Defines how an item interacts with surrounding items.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
-pub enum Softness {
- /// A soft item can be skipped in some circumstances.
- Soft,
- /// A hard item is always retained.
- Hard,
+/// 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 spacing.softness == Softness::Soft && nodes.is_empty() {
+ return;
+ }
+
+ if let Some(&Node::Spacing(other)) = nodes.last() {
+ if spacing.softness > other.softness {
+ nodes.pop();
+ } else if spacing.softness == Softness::Soft {
+ return;
+ }
+ }
+ }
+
+ 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 == Softness::Soft {
+ nodes.pop();
+ }
+ }
}
-/// A group for a page run.
#[derive(Debug)]
-struct PageGroup {
- dirs: LayoutDirs,
- aligns: LayoutAligns,
+struct PageData {
size: Size,
padding: Sides<Linear>,
softness: Softness,
}
-/// A group for generic content.
-#[derive(Debug)]
-struct ContentGroup;
+impl PageData {
+ fn new(state: &State, softness: Softness) -> Self {
+ Self {
+ size: state.page.size,
+ padding: state.page.margins(),
+ softness,
+ }
+ }
+}
-/// A group for a paragraph.
-#[derive(Debug)]
-struct ParGroup {
- dirs: LayoutDirs,
- aligns: LayoutAligns,
- line_spacing: Length,
+impl NodeStack {
+ fn new(state: &State) -> Self {
+ Self {
+ dirs: state.dirs,
+ aligns: state.aligns,
+ children: vec![],
+ }
+ }
+}
+
+impl NodePar {
+ fn new(state: &State) -> Self {
+ let em = state.font.font_size();
+ Self {
+ dirs: state.dirs,
+ aligns: state.aligns,
+ line_spacing: state.par.line_spacing.resolve(em),
+ children: vec![],
+ }
+ }
}