summaryrefslogtreecommitdiff
path: root/src/exec/context.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/exec/context.rs')
-rw-r--r--src/exec/context.rs322
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);
}
}
}