summaryrefslogtreecommitdiff
path: root/src/exec
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-08-17 22:04:18 +0200
committerLaurenz <laurmaedje@gmail.com>2021-08-17 22:20:37 +0200
commit594809e35b9e768f1a50926cf5e7a9df41ba7d16 (patch)
tree488f201599a67329d7916b9b3ecb73dd27ad24d7 /src/exec
parentc53d98a22f367a9eecfb45d1b22f1be5c6cf908d (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.rs302
-rw-r--r--src/exec/mod.rs173
-rw-r--r--src/exec/state.rs265
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,
-}