diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-08-17 22:04:18 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-08-17 22:20:37 +0200 |
| commit | 594809e35b9e768f1a50926cf5e7a9df41ba7d16 (patch) | |
| tree | 488f201599a67329d7916b9b3ecb73dd27ad24d7 /src/exec | |
| parent | c53d98a22f367a9eecfb45d1b22f1be5c6cf908d (diff) | |
Library functions behave more imperatively
- Templates scope state changes
- State-modifying function operate in place instead of returning a template
- Internal template representation contains actual owned nodes instead of a pointer to a syntax tree + an expression map
- No more wide calls
Diffstat (limited to 'src/exec')
| -rw-r--r-- | src/exec/context.rs | 302 | ||||
| -rw-r--r-- | src/exec/mod.rs | 173 | ||||
| -rw-r--r-- | src/exec/state.rs | 265 |
3 files changed, 0 insertions, 740 deletions
diff --git a/src/exec/context.rs b/src/exec/context.rs deleted file mode 100644 index d8ce6528..00000000 --- a/src/exec/context.rs +++ /dev/null @@ -1,302 +0,0 @@ -use std::mem; -use std::rc::Rc; - -use super::{Exec, ExecWithMap, State}; -use crate::eval::{ExprMap, Template}; -use crate::geom::{Align, Dir, Gen, GenAxis, Length, Linear, Sides, Size}; -use crate::layout::{ - LayoutNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild, StackNode, -}; -use crate::syntax::SyntaxTree; -use crate::util::EcoString; -use crate::Context; - -/// The context for execution. -pub struct ExecContext { - /// The active execution state. - pub state: State, - /// The tree of finished page runs. - tree: LayoutTree, - /// 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 ExecContext { - /// Create a new execution context with a base state. - pub fn new(ctx: &mut Context) -> Self { - Self { - state: ctx.state.clone(), - tree: LayoutTree { runs: vec![] }, - page: Some(PageBuilder::new(&ctx.state, true)), - stack: StackBuilder::new(&ctx.state), - } - } - - /// Push a word space into the active paragraph. - pub fn space(&mut self) { - self.stack.par.push_soft(self.make_text_node(' ')); - } - - /// Apply a forced line break. - pub fn linebreak(&mut self) { - self.stack.par.push_hard(self.make_text_node('\n')); - } - - /// Apply a forced paragraph break. - pub fn parbreak(&mut self) { - let amount = self.state.par_spacing(); - self.stack.finish_par(&self.state); - self.stack.push_soft(StackChild::Spacing(amount.into())); - } - - /// Apply a forced page break. - pub fn pagebreak(&mut self, keep: bool, hard: bool) { - 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)); - } - } - - /// Push text into the active paragraph. - /// - /// The text is split into lines at newlines. - pub fn text(&mut self, text: impl Into<EcoString>) { - self.stack.par.push(self.make_text_node(text)); - } - - /// Push text, but in monospace. - pub fn text_mono(&mut self, text: impl Into<EcoString>) { - let prev = Rc::clone(&self.state.font); - self.state.font_mut().monospace = true; - self.text(text); - self.state.font = prev; - } - - /// Push an inline node into the active paragraph. - pub fn inline(&mut self, node: impl Into<LayoutNode>) { - let align = self.state.aligns.cross; - self.stack.par.push(ParChild::Any(node.into(), align)); - } - - /// Push a block node into the active stack, finishing the active paragraph. - pub fn block(&mut self, node: impl Into<LayoutNode>) { - self.parbreak(); - let aligns = self.state.aligns; - self.stack.push(StackChild::Any(node.into(), aligns)); - self.parbreak(); - } - - /// Push spacing into the active paragraph or stack depending on the `axis`. - pub fn spacing(&mut self, axis: GenAxis, amount: Linear) { - match axis { - GenAxis::Main => { - self.stack.finish_par(&self.state); - self.stack.push_hard(StackChild::Spacing(amount)); - } - GenAxis::Cross => { - self.stack.par.push_hard(ParChild::Spacing(amount)); - } - } - } - - /// Execute a template and return the result as a stack node. - pub fn exec_template(&mut self, template: &Template) -> StackNode { - self.exec_to_stack(|ctx| template.exec(ctx)) - } - - /// Execute a syntax tree with a map and return the result as a stack node. - pub fn exec_tree(&mut self, tree: &SyntaxTree, map: &ExprMap) -> StackNode { - self.exec_to_stack(|ctx| tree.exec_with_map(ctx, map)) - } - - /// Execute something and return the result as a stack node. - pub fn exec_to_stack(&mut self, f: impl FnOnce(&mut Self)) -> StackNode { - let snapshot = self.state.clone(); - let page = self.page.take(); - let stack = mem::replace(&mut self.stack, StackBuilder::new(&self.state)); - - f(self); - - self.state = snapshot; - self.page = page; - mem::replace(&mut self.stack, stack).build() - } - - /// Finish execution and return the created layout tree. - pub fn finish(mut self) -> LayoutTree { - assert!(self.page.is_some()); - self.pagebreak(true, false); - self.tree - } - - /// Construct a text node with the given text and settings from the active - /// state. - fn make_text_node(&self, text: impl Into<EcoString>) -> ParChild { - ParChild::Text( - text.into(), - self.state.aligns.cross, - Rc::clone(&self.state.font), - ) - } -} - -struct PageBuilder { - size: Size, - padding: Sides<Linear>, - hard: bool, -} - -impl PageBuilder { - fn new(state: &State, hard: bool) -> Self { - Self { - size: state.page.size, - padding: state.page.margins(), - 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(), - }) - } -} - -struct StackBuilder { - dirs: Gen<Dir>, - children: Vec<StackChild>, - last: Last<StackChild>, - par: ParBuilder, -} - -impl StackBuilder { - fn new(state: &State) -> Self { - Self { - dirs: state.dirs, - children: vec![], - last: Last::None, - par: ParBuilder::new(state), - } - } - - fn push(&mut self, child: StackChild) { - self.children.extend(self.last.any()); - self.children.push(child); - } - - fn push_soft(&mut self, child: StackChild) { - self.last.soft(child); - } - - fn push_hard(&mut self, child: StackChild) { - self.last.hard(); - self.children.push(child); - } - - fn finish_par(&mut self, state: &State) { - let par = mem::replace(&mut self.par, ParBuilder::new(state)); - if let Some(par) = par.build() { - self.push(par); - } - } - - fn build(self) -> StackNode { - let Self { dirs, mut children, par, mut last } = self; - if let Some(par) = par.build() { - children.extend(last.any()); - children.push(par); - } - StackNode { dirs, aspect: None, children } - } -} - -struct ParBuilder { - aligns: Gen<Align>, - dir: Dir, - line_spacing: Length, - children: Vec<ParChild>, - last: Last<ParChild>, -} - -impl ParBuilder { - fn new(state: &State) -> Self { - Self { - aligns: state.aligns, - dir: state.dirs.cross, - line_spacing: state.line_spacing(), - children: vec![], - last: Last::None, - } - } - - fn push(&mut self, child: ParChild) { - if let Some(soft) = self.last.any() { - self.push_inner(soft); - } - self.push_inner(child); - } - - fn push_soft(&mut self, child: ParChild) { - self.last.soft(child); - } - - fn push_hard(&mut self, child: ParChild) { - self.last.hard(); - self.push_inner(child); - } - - fn push_inner(&mut self, child: ParChild) { - if let ParChild::Text(curr_text, curr_props, curr_align) = &child { - if let Some(ParChild::Text(prev_text, prev_props, prev_align)) = - self.children.last_mut() - { - if prev_align == curr_align && prev_props == curr_props { - prev_text.push_str(&curr_text); - return; - } - } - } - - self.children.push(child); - } - - fn build(self) -> Option<StackChild> { - let Self { aligns, dir, line_spacing, children, .. } = self; - (!children.is_empty()).then(|| { - let node = ParNode { dir, line_spacing, children }; - StackChild::Any(node.into(), aligns) - }) - } -} - -/// Finite state machine for spacing coalescing. -enum Last<N> { - None, - Any, - Soft(N), -} - -impl<N> Last<N> { - fn any(&mut self) -> Option<N> { - 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; - } -} diff --git a/src/exec/mod.rs b/src/exec/mod.rs deleted file mode 100644 index 16fd75f8..00000000 --- a/src/exec/mod.rs +++ /dev/null @@ -1,173 +0,0 @@ -//! Execution of syntax trees. - -mod context; -mod state; - -pub use context::*; -pub use state::*; - -use std::fmt::Write; - -use crate::eval::{ExprMap, Template, TemplateFunc, TemplateNode, TemplateTree, Value}; -use crate::geom::Gen; -use crate::layout::{LayoutTree, StackChild, StackNode}; -use crate::syntax::*; -use crate::util::EcoString; -use crate::Context; - -/// Execute a template to produce a layout tree. -pub fn exec(ctx: &mut Context, template: &Template) -> LayoutTree { - let mut ctx = ExecContext::new(ctx); - template.exec(&mut ctx); - ctx.finish() -} - -/// Execute a node. -/// -/// This manipulates active styling and document state and produces layout -/// nodes. Because syntax nodes and layout nodes do not correspond one-to-one, -/// constructed layout nodes are pushed into the context instead of returned. -/// The context takes care of reshaping the nodes into the correct tree -/// structure. -pub trait Exec { - /// Execute the node. - fn exec(&self, ctx: &mut ExecContext); -} - -/// Execute a node with an expression map that applies to it. -pub trait ExecWithMap { - /// Execute the node. - fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap); -} - -impl ExecWithMap for SyntaxTree { - fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) { - for node in self { - node.exec_with_map(ctx, map); - } - } -} - -impl ExecWithMap for SyntaxNode { - fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) { - match self { - Self::Space => ctx.space(), - Self::Text(text) => ctx.text(text), - Self::Linebreak(_) => ctx.linebreak(), - Self::Parbreak(_) => ctx.parbreak(), - Self::Strong(_) => ctx.state.font_mut().strong ^= true, - Self::Emph(_) => ctx.state.font_mut().emph ^= true, - Self::Raw(n) => n.exec(ctx), - Self::Heading(n) => n.exec_with_map(ctx, map), - Self::List(n) => n.exec_with_map(ctx, map), - Self::Enum(n) => n.exec_with_map(ctx, map), - Self::Expr(n) => map[&(n as *const _)].exec(ctx), - } - } -} - -impl Exec for RawNode { - fn exec(&self, ctx: &mut ExecContext) { - if self.block { - ctx.parbreak(); - } - - ctx.text_mono(&self.text); - - if self.block { - ctx.parbreak(); - } - } -} - -impl ExecWithMap for HeadingNode { - fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) { - ctx.parbreak(); - - let snapshot = ctx.state.clone(); - let font = ctx.state.font_mut(); - let upscale = 1.6 - 0.1 * self.level as f64; - font.size *= upscale; - font.strong = true; - - self.body.exec_with_map(ctx, map); - ctx.state = snapshot; - - ctx.parbreak(); - } -} - -impl ExecWithMap for ListItem { - fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) { - exec_item(ctx, '•'.into(), &self.body, map); - } -} - -impl ExecWithMap for EnumItem { - fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) { - let mut label = EcoString::new(); - write!(&mut label, "{}.", self.number.unwrap_or(1)).unwrap(); - exec_item(ctx, label, &self.body, map); - } -} - -fn exec_item(ctx: &mut ExecContext, label: EcoString, body: &SyntaxTree, map: &ExprMap) { - let label = ctx.exec_to_stack(|ctx| ctx.text(label)); - let body = ctx.exec_tree(body, map); - ctx.block(StackNode { - dirs: Gen::new(ctx.state.dirs.main, ctx.state.dirs.cross), - aspect: None, - children: vec![ - StackChild::Any(label.into(), Gen::default()), - StackChild::Spacing((ctx.state.font.size / 2.0).into()), - StackChild::Any(body.into(), Gen::default()), - ], - }); -} - -impl Exec for Value { - fn exec(&self, ctx: &mut ExecContext) { - match self { - Value::None => {} - Value::Int(v) => ctx.text(v.to_string()), - Value::Float(v) => ctx.text(v.to_string()), - Value::Str(v) => ctx.text(v), - Value::Template(v) => v.exec(ctx), - // For values which can't be shown "naturally", we print the - // representation in monospace. - other => ctx.text_mono(other.to_string()), - } - } -} - -impl Exec for Template { - fn exec(&self, ctx: &mut ExecContext) { - for node in self.iter() { - node.exec(ctx); - } - } -} - -impl Exec for TemplateNode { - fn exec(&self, ctx: &mut ExecContext) { - match self { - Self::Tree(v) => v.exec(ctx), - Self::Func(v) => v.exec(ctx), - Self::Str(v) => ctx.text(v), - } - } -} - -impl Exec for TemplateTree { - fn exec(&self, ctx: &mut ExecContext) { - self.tree.exec_with_map(ctx, &self.map) - } -} - -impl Exec for TemplateFunc { - fn exec(&self, ctx: &mut ExecContext) { - let snapshot = ctx.state.clone(); - self(ctx); - ctx.state = snapshot; - } -} diff --git a/src/exec/state.rs b/src/exec/state.rs deleted file mode 100644 index 56cf5f2e..00000000 --- a/src/exec/state.rs +++ /dev/null @@ -1,265 +0,0 @@ -use std::rc::Rc; - -use crate::color::{Color, RgbaColor}; -use crate::font::{ - FontFamily, FontStretch, FontStyle, FontVariant, FontWeight, VerticalFontMetric, -}; -use crate::geom::*; -use crate::layout::Paint; -use crate::paper::{PaperClass, PAPER_A4}; - -/// The execution state. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct State { - /// The direction for text and other inline objects. - pub dirs: Gen<Dir>, - /// The current alignments of layouts in their parents. - pub aligns: Gen<Align>, - /// The current page settings. - pub page: Rc<PageState>, - /// The current paragraph settings. - pub par: Rc<ParState>, - /// The current font settings. - pub font: Rc<FontState>, -} - -impl State { - /// Access the `page` state mutably. - pub fn page_mut(&mut self) -> &mut PageState { - Rc::make_mut(&mut self.page) - } - - /// Access the `par` state mutably. - pub fn par_mut(&mut self) -> &mut ParState { - Rc::make_mut(&mut self.par) - } - - /// Access the `font` state mutably. - pub fn font_mut(&mut self) -> &mut FontState { - Rc::make_mut(&mut self.font) - } - - /// The resolved line spacing. - pub fn line_spacing(&self) -> Length { - self.par.line_spacing.resolve(self.font.size) - } - - /// The resolved paragraph spacing. - pub fn par_spacing(&self) -> Length { - self.par.par_spacing.resolve(self.font.size) - } -} - -impl Default for State { - fn default() -> Self { - Self { - dirs: Gen::new(Dir::LTR, Dir::TTB), - aligns: Gen::splat(Align::Start), - page: Rc::new(PageState::default()), - par: Rc::new(ParState::default()), - font: Rc::new(FontState::default()), - } - } -} - -/// Defines page properties. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct PageState { - /// The class of this page. - pub class: PaperClass, - /// The width and height of the page. - pub size: Size, - /// The amount of white space on each side of the page. If a side is set to - /// `None`, the default for the paper class is used. - pub margins: Sides<Option<Linear>>, -} - -impl PageState { - /// The resolved margins. - pub fn margins(&self) -> Sides<Linear> { - let default = self.class.default_margins(); - Sides { - left: self.margins.left.unwrap_or(default.left), - top: self.margins.top.unwrap_or(default.top), - right: self.margins.right.unwrap_or(default.right), - bottom: self.margins.bottom.unwrap_or(default.bottom), - } - } -} - -impl Default for PageState { - fn default() -> Self { - let paper = PAPER_A4; - Self { - class: paper.class(), - size: paper.size(), - margins: Sides::splat(None), - } - } -} - -/// Style paragraph properties. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct ParState { - /// The spacing between paragraphs (dependent on scaled font size). - pub par_spacing: Linear, - /// The spacing between lines (dependent on scaled font size). - pub line_spacing: Linear, -} - -impl Default for ParState { - fn default() -> Self { - Self { - par_spacing: Relative::new(1.0).into(), - line_spacing: Relative::new(0.5).into(), - } - } -} - -/// Defines font properties. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct FontState { - /// Whether the strong toggle is active or inactive. This determines - /// whether the next `*` adds or removes font weight. - pub strong: bool, - /// Whether the emphasis toggle is active or inactive. This determines - /// whether the next `_` makes italic or non-italic. - pub emph: bool, - /// Whether the monospace toggle is active or inactive. - pub monospace: bool, - /// The font size. - pub size: Length, - /// The selected font variant (the final variant also depends on `strong` - /// and `emph`). - pub variant: FontVariant, - /// The top end of the text bounding box. - pub top_edge: VerticalFontMetric, - /// The bottom end of the text bounding box. - pub bottom_edge: VerticalFontMetric, - /// Glyph color. - pub fill: Paint, - /// A list of font families with generic class definitions (the final - /// family list also depends on `monospace`). - pub families: Rc<FamilyState>, - /// The specifications for a strikethrough line, if any. - pub strikethrough: Option<Rc<LineState>>, - /// The specifications for a underline, if any. - pub underline: Option<Rc<LineState>>, - /// The specifications for a overline line, if any. - pub overline: Option<Rc<LineState>>, -} - -impl FontState { - /// The resolved variant with `strong` and `emph` factored in. - pub fn variant(&self) -> FontVariant { - let mut variant = self.variant; - - if self.strong { - variant.weight = variant.weight.thicken(300); - } - - if self.emph { - variant.style = match variant.style { - FontStyle::Normal => FontStyle::Italic, - FontStyle::Italic => FontStyle::Normal, - FontStyle::Oblique => FontStyle::Normal, - } - } - - variant - } - - /// The resolved family iterator. - pub fn families(&self) -> impl Iterator<Item = &str> + Clone { - let head = self - .monospace - .then(|| self.families.monospace.as_slice()) - .unwrap_or_default(); - - let core = self.families.list.iter().flat_map(move |family| match family { - FontFamily::Named(name) => std::slice::from_ref(name), - FontFamily::Serif => &self.families.serif, - FontFamily::SansSerif => &self.families.sans_serif, - FontFamily::Monospace => &self.families.monospace, - }); - - head.iter() - .chain(core) - .chain(self.families.base.iter()) - .map(String::as_str) - } - - /// Access the `families` state mutably. - pub fn families_mut(&mut self) -> &mut FamilyState { - Rc::make_mut(&mut self.families) - } -} - -impl Default for FontState { - fn default() -> Self { - Self { - families: Rc::new(FamilyState::default()), - variant: FontVariant { - style: FontStyle::Normal, - weight: FontWeight::REGULAR, - stretch: FontStretch::NORMAL, - }, - strong: false, - emph: false, - monospace: false, - size: Length::pt(11.0), - top_edge: VerticalFontMetric::CapHeight, - bottom_edge: VerticalFontMetric::Baseline, - fill: Paint::Color(Color::Rgba(RgbaColor::BLACK)), - strikethrough: None, - underline: None, - overline: None, - } - } -} - -/// Font family definitions. -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct FamilyState { - /// The user-defined list of font families. - pub list: Rc<Vec<FontFamily>>, - /// Definition of serif font families. - pub serif: Rc<Vec<String>>, - /// Definition of sans-serif font families. - pub sans_serif: Rc<Vec<String>>, - /// Definition of monospace font families used for raw text. - pub monospace: Rc<Vec<String>>, - /// Base fonts that are tried as last resort. - pub base: Rc<Vec<String>>, -} - -impl Default for FamilyState { - fn default() -> Self { - Self { - list: Rc::new(vec![FontFamily::Serif]), - serif: Rc::new(vec!["eb garamond".into()]), - sans_serif: Rc::new(vec!["pt sans".into()]), - monospace: Rc::new(vec!["inconsolata".into()]), - base: Rc::new(vec![ - "twitter color emoji".into(), - "latin modern math".into(), - ]), - } - } -} - -/// Defines a line that is positioned over, under or on top of text. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct LineState { - /// Stroke color of the line, defaults to the text color if `None`. - pub stroke: Option<Paint>, - /// Thickness of the line's strokes (dependent on scaled font size), read - /// from the font tables if `None`. - pub thickness: Option<Linear>, - /// Position of the line relative to the baseline (dependent on scaled font - /// size), read from the font tables if `None`. - pub offset: Option<Linear>, - /// Amount that the line will be longer or shorter than its associated text - /// (dependent on scaled font size). - pub extent: Linear, -} |
