From 0f7c70fd93db23ec866ae13aa2f146b7787afabf Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sun, 4 Oct 2020 19:06:20 +0200 Subject: [PATCH] =?UTF-8?q?Separate=20state=20and=20constraints=20?= =?UTF-8?q?=F0=9F=A7=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- benches/benchmarks.rs | 6 +- main/src/main.rs | 6 +- src/eval/mod.rs | 2 + src/eval/scope.rs | 1 + src/{style.rs => eval/state.rs} | 48 ++++++++++---- src/eval/value.rs | 6 +- src/layout/mod.rs | 67 +++++++++++--------- src/layout/tree.rs | 109 ++++++++++++++++---------------- src/lib.rs | 9 +-- src/library/align.rs | 32 ++++------ src/library/boxed.rs | 43 ++++++------- src/library/color.rs | 19 +++--- src/library/font.rs | 25 ++++---- src/library/mod.rs | 2 +- src/library/page.rs | 56 ++++++++-------- src/library/spacing.rs | 31 ++++----- src/shaping.rs | 20 +++--- src/syntax/ast/expr.rs | 52 +++++++-------- src/syntax/ast/lit.rs | 14 ++-- tests/test_typeset.rs | 6 +- 20 files changed, 273 insertions(+), 281 deletions(-) rename src/{style.rs => eval/state.rs} (82%) diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index 62f729a00..3ffee01ef 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -5,6 +5,7 @@ use criterion::{criterion_group, criterion_main, Criterion}; use fontdock::fs::{FsIndex, FsProvider}; use futures_executor::block_on; +use typstc::eval::State; use typstc::font::FontLoader; use typstc::parse::parse; use typstc::typeset; @@ -25,10 +26,9 @@ fn typesetting_benchmark(c: &mut Criterion) { let loader = FontLoader::new(Box::new(provider), descriptors); let loader = Rc::new(RefCell::new(loader)); - let style = Default::default(); - let scope = typstc::library::_std(); + let state = State::default(); c.bench_function("typeset-coma", |b| { - b.iter(|| block_on(typeset(COMA, &style, &scope, Rc::clone(&loader)))) + b.iter(|| block_on(typeset(COMA, state.clone(), Rc::clone(&loader)))) }); } diff --git a/main/src/main.rs b/main/src/main.rs index 7abef0349..21b96a256 100644 --- a/main/src/main.rs +++ b/main/src/main.rs @@ -7,6 +7,7 @@ use std::rc::Rc; use fontdock::fs::{FsIndex, FsProvider}; use futures_executor::block_on; +use typstc::eval::State; use typstc::export::pdf; use typstc::font::FontLoader; use typstc::parse::LineMap; @@ -41,12 +42,11 @@ fn main() { let loader = FontLoader::new(Box::new(provider), descriptors); let loader = Rc::new(RefCell::new(loader)); - let style = Default::default(); - let scope = typstc::library::_std(); + let state = State::default(); let Pass { output: layouts, feedback: Feedback { mut diagnostics, .. }, - } = block_on(typeset(&src, &style, &scope, Rc::clone(&loader))); + } = block_on(typeset(&src, state, Rc::clone(&loader))); if !diagnostics.is_empty() { diagnostics.sort(); diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 65fc71c42..d21fbc7ff 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -2,8 +2,10 @@ mod dict; mod scope; +mod state; mod value; pub use dict::*; pub use scope::*; +pub use state::*; pub use value::*; diff --git a/src/eval/scope.rs b/src/eval/scope.rs index 8e6576d18..d4b248800 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -6,6 +6,7 @@ use std::fmt::{self, Debug, Formatter}; use super::value::FuncValue; /// A map from identifiers to functions. +#[derive(Clone, PartialEq)] pub struct Scope { functions: HashMap, } diff --git a/src/style.rs b/src/eval/state.rs similarity index 82% rename from src/style.rs rename to src/eval/state.rs index 74deeeb95..d6e0b1952 100644 --- a/src/style.rs +++ b/src/eval/state.rs @@ -1,23 +1,43 @@ -//! Styles for text and pages. +//! State. use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, FontWeight}; +use super::Scope; use crate::geom::{Insets, Linear, Sides, Size}; +use crate::layout::{Dir, GenAlign, LayoutAlign, LayoutSystem}; use crate::length::Length; use crate::paper::{Paper, PaperClass, PAPER_A4}; -/// Defines properties of pages and text. -#[derive(Debug, Default, Clone, PartialEq)] -pub struct LayoutStyle { - /// The style for text. - pub text: TextStyle, - /// The style for pages. - pub page: PageStyle, +/// The active evaluation state. +#[derive(Debug, Clone, PartialEq)] +pub struct State { + /// The scope that contains function definitions. + pub scope: Scope, + /// The text state. + pub text: TextState, + /// The page state. + pub page: PageState, + /// The active layouting system. + pub sys: LayoutSystem, + /// The active alignments. + pub align: LayoutAlign, +} + +impl Default for State { + fn default() -> Self { + Self { + scope: crate::library::_std(), + text: TextState::default(), + page: PageState::default(), + sys: LayoutSystem::new(Dir::LTR, Dir::TTB), + align: LayoutAlign::new(GenAlign::Start, GenAlign::Start), + } + } } /// Defines which fonts to use and how to space text. #[derive(Debug, Clone, PartialEq)] -pub struct TextStyle { +pub struct TextState { /// A tree of font family names and generic class names. pub fallback: FallbackTree, /// The selected font variant. @@ -38,7 +58,7 @@ pub struct TextStyle { pub par_spacing: Linear, } -impl TextStyle { +impl TextState { /// The absolute font size. pub fn font_size(&self) -> f64 { self.font_size.eval() @@ -60,7 +80,7 @@ impl TextStyle { } } -impl Default for TextStyle { +impl Default for TextState { fn default() -> Self { Self { fallback: fallback! { @@ -120,7 +140,7 @@ impl FontSize { /// Defines the size and margins of a page. #[derive(Debug, Copy, Clone, PartialEq)] -pub struct PageStyle { +pub struct PageState { /// The class of this page. pub class: PaperClass, /// The width and height of the page. @@ -130,7 +150,7 @@ pub struct PageStyle { pub margins: Sides>, } -impl PageStyle { +impl PageState { /// The default page style for the given paper. pub fn new(paper: Paper) -> Self { Self { @@ -153,7 +173,7 @@ impl PageStyle { } } -impl Default for PageStyle { +impl Default for PageState { fn default() -> Self { Self::new(PAPER_A4) } diff --git a/src/eval/value.rs b/src/eval/value.rs index f6c1fcd7f..f88c07044 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -12,7 +12,7 @@ use crate::geom::Linear; use crate::layout::{Command, Commands, Dir, LayoutContext, SpecAlign}; use crate::paper::Paper; use crate::syntax::{Ident, Span, SpanWith, Spanned, SynNode, SynTree}; -use crate::{DynFuture, Feedback, Pass}; +use crate::{DynFuture, Feedback}; /// A computational value. #[derive(Clone, PartialEq)] @@ -149,13 +149,13 @@ impl Debug for Value { pub struct FuncValue(pub Rc); /// The signature of executable functions. -type FuncType = dyn Fn(Span, DictValue, LayoutContext<'_>) -> DynFuture>; +type FuncType = dyn Fn(DictValue, &mut LayoutContext) -> DynFuture; impl FuncValue { /// Create a new function value from a rust function or closure. pub fn new(f: F) -> Self where - F: Fn(Span, DictValue, LayoutContext<'_>) -> DynFuture>, + F: Fn(DictValue, &mut LayoutContext) -> DynFuture, { Self(Rc::new(f)) } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 8156f596e..1ee862e3b 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -15,36 +15,40 @@ pub use tree::*; use crate::geom::{Insets, Point, Rect, RectExt, Sides, Size, SizeExt}; -use crate::eval::Scope; +use crate::eval::{PageState, State, TextState}; use crate::font::SharedFontLoader; -use crate::style::{LayoutStyle, PageStyle, TextStyle}; use crate::syntax::SynTree; -use crate::Pass; +use crate::{Feedback, Pass}; /// Layout a syntax tree and return the produced layout. pub async fn layout( tree: &SynTree, - style: &LayoutStyle, - scope: &Scope, + state: State, loader: SharedFontLoader, ) -> Pass { let space = LayoutSpace { - size: style.page.size, - insets: style.page.insets(), + size: state.page.size, + insets: state.page.insets(), expansion: LayoutExpansion::new(true, true), }; - tree::layout_tree(&tree, LayoutContext { - loader, - scope, - style, + + let constraints = LayoutConstraints { + root: true, base: space.usable(), spaces: vec![space], repeat: true, - sys: LayoutSystem::new(Dir::LTR, Dir::TTB), - align: LayoutAlign::new(GenAlign::Start, GenAlign::Start), - root: true, - }) - .await + }; + + let mut ctx = LayoutContext { + loader, + state, + constraints, + f: Feedback::new(), + }; + + let layouts = layout_tree(&tree, &mut ctx).await; + + Pass::new(layouts, ctx.f) } /// A collection of layouts. @@ -63,13 +67,22 @@ pub struct BoxLayout { /// The context for layouting. #[derive(Debug, Clone)] -pub struct LayoutContext<'a> { +pub struct LayoutContext { /// The font loader to query fonts from when typesetting text. pub loader: SharedFontLoader, - /// The function scope. - pub scope: &'a Scope, - /// The style for pages and text. - pub style: &'a LayoutStyle, + /// The active state. + pub state: State, + /// The active constraints. + pub constraints: LayoutConstraints, + /// The accumulated feedback. + pub f: Feedback, +} + +/// The constraints for layouting a single node. +#[derive(Debug, Clone)] +pub struct LayoutConstraints { + /// Whether this layouting process is the root page-building process. + pub root: bool, /// The unpadded size of this container (the base 100% for relative sizes). pub base: Size, /// The spaces to layout into. @@ -77,14 +90,6 @@ pub struct LayoutContext<'a> { /// Whether to spill over into copies of the last space or finish layouting /// when the last space is used up. pub repeat: bool, - /// The system into which content is laid out. - pub sys: LayoutSystem, - /// The alignment of the _resulting_ layout. This does not effect the line - /// layouting itself, but rather how the finished layout will be positioned - /// in a parent layout. - pub align: LayoutAlign, - /// Whether this layouting process is the root page-building process. - pub root: bool, } /// A collection of layout spaces. @@ -156,9 +161,9 @@ pub enum Command { BreakPage, /// Update the text style. - SetTextStyle(TextStyle), + SetTextState(TextState), /// Update the page style. - SetPageStyle(PageStyle), + SetPageState(PageState), /// Update the alignment for future boxes added to this layouting process. SetAlignment(LayoutAlign), diff --git a/src/layout/tree.rs b/src/layout/tree.rs index df1745441..e26b0eb26 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -1,48 +1,45 @@ //! Layouting of syntax trees. -use std::rc::Rc; - use super::*; use crate::shaping; -use crate::style::LayoutStyle; use crate::syntax::{ Decoration, Expr, NodeHeading, NodeRaw, Span, SpanWith, Spanned, SynNode, SynTree, }; -use crate::{DynFuture, Feedback, Pass}; +use crate::DynFuture; /// Layout a syntax tree in a given context. -pub async fn layout_tree(tree: &SynTree, ctx: LayoutContext<'_>) -> Pass { +pub async fn layout_tree(tree: &SynTree, ctx: &mut LayoutContext) -> MultiLayout { let mut layouter = TreeLayouter::new(ctx); layouter.layout_tree(tree).await; layouter.finish() } -/// Performs the tree layouting. +/// Layouts trees. struct TreeLayouter<'a> { - ctx: LayoutContext<'a>, + ctx: &'a mut LayoutContext, + constraints: LayoutConstraints, layouter: LineLayouter, - style: LayoutStyle, - feedback: Feedback, } impl<'a> TreeLayouter<'a> { - fn new(ctx: LayoutContext<'a>) -> Self { + fn new(ctx: &'a mut LayoutContext) -> Self { + let layouter = LineLayouter::new(LineContext { + spaces: ctx.constraints.spaces.clone(), + sys: ctx.state.sys, + align: ctx.state.align, + repeat: ctx.constraints.repeat, + line_spacing: ctx.state.text.line_spacing(), + }); + Self { - layouter: LineLayouter::new(LineContext { - spaces: ctx.spaces.clone(), - sys: ctx.sys, - align: ctx.align, - repeat: ctx.repeat, - line_spacing: ctx.style.text.line_spacing(), - }), - style: ctx.style.clone(), + layouter, + constraints: ctx.constraints.clone(), ctx, - feedback: Feedback::new(), } } - fn finish(self) -> Pass { - Pass::new(self.layouter.finish(), self.feedback) + fn finish(self) -> MultiLayout { + self.layouter.finish() } fn layout_tree<'t>(&'t mut self, tree: &'t SynTree) -> DynFuture<'t, ()> { @@ -55,16 +52,16 @@ impl<'a> TreeLayouter<'a> { async fn layout_node(&mut self, node: &Spanned) { let decorate = |this: &mut Self, deco: Decoration| { - this.feedback.decorations.push(deco.span_with(node.span)); + this.ctx.f.decorations.push(deco.span_with(node.span)); }; match &node.v { SynNode::Space => self.layout_space(), SynNode::Text(text) => { - if self.style.text.emph { + if self.ctx.state.text.emph { decorate(self, Decoration::Emph); } - if self.style.text.strong { + if self.ctx.state.text.strong { decorate(self, Decoration::Strong); } self.layout_text(text).await; @@ -73,11 +70,11 @@ impl<'a> TreeLayouter<'a> { SynNode::Linebreak => self.layouter.finish_line(), SynNode::Parbreak => self.layout_parbreak(), SynNode::Emph => { - self.style.text.emph = !self.style.text.emph; + self.ctx.state.text.emph ^= true; decorate(self, Decoration::Emph); } SynNode::Strong => { - self.style.text.strong = !self.style.text.strong; + self.ctx.state.text.strong ^= true; decorate(self, Decoration::Strong); } @@ -92,12 +89,12 @@ impl<'a> TreeLayouter<'a> { fn layout_space(&mut self) { self.layouter - .add_primary_spacing(self.style.text.word_spacing(), SpacingKind::WORD); + .add_primary_spacing(self.ctx.state.text.word_spacing(), SpacingKind::WORD); } fn layout_parbreak(&mut self) { self.layouter.add_secondary_spacing( - self.style.text.paragraph_spacing(), + self.ctx.state.text.paragraph_spacing(), SpacingKind::PARAGRAPH, ); } @@ -106,9 +103,9 @@ impl<'a> TreeLayouter<'a> { self.layouter.add( shaping::shape( text, - self.ctx.sys.primary, - self.ctx.align, - &self.style.text, + self.ctx.state.sys.primary, + self.ctx.state.align, + &self.ctx.state.text, &mut self.ctx.loader.borrow_mut(), ) .await, @@ -116,17 +113,17 @@ impl<'a> TreeLayouter<'a> { } async fn layout_heading(&mut self, heading: &NodeHeading) { - let style = self.style.text.clone(); + let style = self.ctx.state.text.clone(); let factor = 1.5 - 0.1 * heading.level.v as f64; - self.style.text.font_size.scale *= factor; - self.style.text.strong = true; + self.ctx.state.text.font_size.scale *= factor; + self.ctx.state.text.strong = true; self.layout_parbreak(); self.layout_tree(&heading.contents).await; self.layout_parbreak(); - self.style.text = style; + self.ctx.state.text = style; } async fn layout_raw(&mut self, raw: &NodeRaw) { @@ -135,9 +132,9 @@ impl<'a> TreeLayouter<'a> { } // TODO: Make this more efficient. - let fallback = self.style.text.fallback.clone(); - self.style.text.fallback.list.insert(0, "monospace".to_string()); - self.style.text.fallback.flatten(); + let fallback = self.ctx.state.text.fallback.clone(); + self.ctx.state.text.fallback.list.insert(0, "monospace".to_string()); + self.ctx.state.text.fallback.flatten(); let mut first = true; for line in &raw.lines { @@ -148,7 +145,7 @@ impl<'a> TreeLayouter<'a> { self.layout_text(line).await; } - self.style.text.fallback = fallback; + self.ctx.state.text.fallback = fallback; if !raw.inline { self.layout_parbreak(); @@ -156,15 +153,15 @@ impl<'a> TreeLayouter<'a> { } async fn layout_expr(&mut self, expr: Spanned<&Expr>) { - let ctx = LayoutContext { - style: &self.style, - spaces: self.layouter.remaining(), + self.ctx.constraints = LayoutConstraints { root: false, - loader: Rc::clone(&self.ctx.loader), - ..self.ctx + base: self.constraints.base, + spaces: self.layouter.remaining(), + repeat: self.constraints.repeat, }; - let val = expr.v.eval(&ctx, &mut self.feedback).await; + let val = expr.v.eval(self.ctx).await; + let commands = val.span_with(expr.span).into_commands(); for command in commands { @@ -186,23 +183,23 @@ impl<'a> TreeLayouter<'a> { BreakLine => self.layouter.finish_line(), BreakPage => { - if self.ctx.root { + if self.constraints.root { self.layouter.finish_space(true) } else { error!( - @self.feedback, span, + @self.ctx.f, span, "page break cannot only be issued from root context", ); } } - SetTextStyle(style) => { + SetTextState(style) => { self.layouter.set_line_spacing(style.line_spacing()); - self.style.text = style; + self.ctx.state.text = style; } - SetPageStyle(style) => { - if self.ctx.root { - self.style.page = style; + SetPageState(style) => { + if self.constraints.root { + self.ctx.state.page = style; // The line layouter has no idea of page styles and thus we // need to recompute the layouting space resulting of the @@ -212,20 +209,20 @@ impl<'a> TreeLayouter<'a> { insets: style.insets(), expansion: LayoutExpansion::new(true, true), }; - self.ctx.base = space.usable(); + self.constraints.base = space.usable(); self.layouter.set_spaces(vec![space], true); } else { error!( - @self.feedback, span, + @self.ctx.f, span, "page style cannot only be changed from root context", ); } } - SetAlignment(align) => self.ctx.align = align, + SetAlignment(align) => self.ctx.state.align = align, SetSystem(sys) => { self.layouter.set_sys(sys); - self.ctx.sys = sys; + self.ctx.state.sys = sys; } } } diff --git a/src/lib.rs b/src/lib.rs index c4a14c2ed..21fabbd2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,7 +36,6 @@ pub mod paper; pub mod parse; pub mod prelude; pub mod shaping; -pub mod style; pub mod syntax; use std::fmt::Debug; @@ -44,21 +43,19 @@ use std::future::Future; use std::pin::Pin; use crate::diagnostic::Diagnostic; -use crate::eval::{Scope, Value}; +use crate::eval::{State, Value}; use crate::font::SharedFontLoader; use crate::layout::{Commands, MultiLayout}; -use crate::style::LayoutStyle; use crate::syntax::{Decoration, Offset, Pos, SpanVec}; /// Process source code directly into a collection of layouts. pub async fn typeset( src: &str, - style: &LayoutStyle, - scope: &Scope, + state: State, loader: SharedFontLoader, ) -> Pass { let parsed = parse::parse(src); - let layouted = layout::layout(&parsed.output, style, scope, loader).await; + let layouted = layout::layout(&parsed.output, state, loader).await; let feedback = Feedback::merge(parsed.feedback, layouted.feedback); Pass::new(layouted.output, feedback) } diff --git a/src/library/align.rs b/src/library/align.rs index d36108a48..c3512b7fc 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -14,19 +14,17 @@ use super::*; /// - `vertical`: Any of `top`, `bottom` or `center`. /// /// There may not be two alignment specifications for the same axis. -pub async fn align(_: Span, mut args: DictValue, ctx: LayoutContext<'_>) -> Pass { - let mut f = Feedback::new(); - +pub async fn align(mut args: DictValue, ctx: &mut LayoutContext) -> Value { let content = args.take::(); - let h = args.take_key::>("horizontal", &mut f); - let v = args.take_key::>("vertical", &mut f); + let h = args.take_key::>("horizontal", &mut ctx.f); + let v = args.take_key::>("vertical", &mut ctx.f); let all = args .take_all_num_vals::>() .map(|align| (align.v.axis(), align)) .chain(h.into_iter().map(|align| (Some(SpecAxis::Horizontal), align))) .chain(v.into_iter().map(|align| (Some(SpecAxis::Vertical), align))); - let mut aligns = ctx.align; + let mut aligns = ctx.state.align; let mut had = [false; 2]; let mut deferred_center = false; @@ -37,19 +35,19 @@ pub async fn align(_: Span, mut args: DictValue, ctx: LayoutContext<'_>) -> Pass if let Some(axis) = axis { if align.v.axis().map_or(false, |a| a != axis) { error!( - @f, align.span, + @ctx.f, align.span, "invalid alignment {} for {} axis", align.v, axis, ); } else if had[axis as usize] { - error!(@f, align.span, "duplicate alignment for {} axis", axis); + error!(@ctx.f, align.span, "duplicate alignment for {} axis", axis); } else { - let gen_align = align.v.to_gen(ctx.sys); - *aligns.get_mut(axis.to_gen(ctx.sys)) = gen_align; + let gen_align = align.v.to_gen(ctx.state.sys); + *aligns.get_mut(axis.to_gen(ctx.state.sys)) = gen_align; had[axis as usize] = true; } } else { if had == [true, true] { - error!(@f, align.span, "duplicate alignment"); + error!(@ctx.f, align.span, "duplicate alignment"); } else if deferred_center { // We have two unflushed centers, meaning we know that both axes // are to be centered. @@ -69,7 +67,7 @@ pub async fn align(_: Span, mut args: DictValue, ctx: LayoutContext<'_>) -> Pass SpecAxis::Vertical }; - *aligns.get_mut(axis.to_gen(ctx.sys)) = GenAlign::Center; + *aligns.get_mut(axis.to_gen(ctx.state.sys)) = GenAlign::Center; had[axis as usize] = true; deferred_center = false; @@ -82,15 +80,13 @@ pub async fn align(_: Span, mut args: DictValue, ctx: LayoutContext<'_>) -> Pass aligns.primary = GenAlign::Center; } - let commands = match content { + args.unexpected(&mut ctx.f); + Value::Commands(match content { Some(tree) => vec![ SetAlignment(aligns), LayoutSyntaxTree(tree), - SetAlignment(ctx.align), + SetAlignment(ctx.state.align), ], None => vec![SetAlignment(aligns)], - }; - - args.unexpected(&mut f); - Pass::commands(commands, f) + }) } diff --git a/src/library/boxed.rs b/src/library/boxed.rs index fe0272bfb..94aac48a7 100644 --- a/src/library/boxed.rs +++ b/src/library/boxed.rs @@ -6,37 +6,32 @@ use crate::geom::Linear; /// # Keyword arguments /// - `width`: The width of the box (length or relative to parent's width). /// - `height`: The height of the box (length or relative to parent's height). -pub async fn boxed( - _: Span, - mut args: DictValue, - mut ctx: LayoutContext<'_>, -) -> Pass { - let mut f = Feedback::new(); - +pub async fn boxed(mut args: DictValue, ctx: &mut LayoutContext) -> Value { let content = args.take::().unwrap_or_default(); - ctx.base = ctx.spaces[0].size; - ctx.spaces.truncate(1); - ctx.repeat = false; + let constraints = &mut ctx.constraints; + constraints.base = constraints.spaces[0].size; + constraints.spaces.truncate(1); + constraints.repeat = false; - if let Some(width) = args.take_key::("width", &mut f) { - let abs = width.eval(ctx.base.width); - ctx.base.width = abs; - ctx.spaces[0].size.width = abs; - ctx.spaces[0].expansion.horizontal = true; + if let Some(width) = args.take_key::("width", &mut ctx.f) { + let abs = width.eval(constraints.base.width); + constraints.base.width = abs; + constraints.spaces[0].size.width = abs; + constraints.spaces[0].expansion.horizontal = true; } - if let Some(height) = args.take_key::("height", &mut f) { - let abs = height.eval(ctx.base.height); - ctx.base.height = abs; - ctx.spaces[0].size.height = abs; - ctx.spaces[0].expansion.vertical = true; + if let Some(height) = args.take_key::("height", &mut ctx.f) { + let abs = height.eval(constraints.base.height); + constraints.base.height = abs; + constraints.spaces[0].size.height = abs; + constraints.spaces[0].expansion.vertical = true; } + args.unexpected(&mut ctx.f); + let layouted = layout_tree(&content, ctx).await; - let layout = layouted.output.into_iter().next().unwrap(); - f.extend(layouted.feedback); + let layout = layouted.into_iter().next().unwrap(); - args.unexpected(&mut f); - Pass::commands(vec![Add(layout)], f) + Value::Commands(vec![Add(layout)]) } diff --git a/src/library/color.rs b/src/library/color.rs index a11966ea4..631c36684 100644 --- a/src/library/color.rs +++ b/src/library/color.rs @@ -2,12 +2,12 @@ use super::*; use crate::color::RgbaColor; /// `rgb`: Create an RGB(A) color. -pub async fn rgb(span: Span, mut args: DictValue, _: LayoutContext<'_>) -> Pass { +pub async fn rgb(mut args: DictValue, ctx: &mut LayoutContext) -> Value { let mut f = Feedback::new(); - let r = args.expect::>("red value", span, &mut f); - let g = args.expect::>("green value", span, &mut f); - let b = args.expect::>("blue value", span, &mut f); + let r = args.expect::>("red value", Span::ZERO, &mut f); + let g = args.expect::>("green value", Span::ZERO, &mut f); + let b = args.expect::>("blue value", Span::ZERO, &mut f); let a = args.take::>(); let mut clamp = |component: Option>, default| { @@ -19,8 +19,11 @@ pub async fn rgb(span: Span, mut args: DictValue, _: LayoutContext<'_>) -> Pass< }) }; - let color = RgbaColor::new(clamp(r, 0), clamp(g, 0), clamp(b, 0), clamp(a, 255)); - - args.unexpected(&mut f); - Pass::new(Value::Color(color), f) + args.unexpected(&mut ctx.f); + Value::Color(RgbaColor::new( + clamp(r, 0), + clamp(g, 0), + clamp(b, 0), + clamp(a, 255), + )) } diff --git a/src/library/font.rs b/src/library/font.rs index e12bda2f1..40d8d30b6 100644 --- a/src/library/font.rs +++ b/src/library/font.rs @@ -49,9 +49,8 @@ use crate::geom::Linear; /// ```typst /// [font: "My Serif", serif] /// ``` -pub async fn font(_: Span, mut args: DictValue, ctx: LayoutContext<'_>) -> Pass { - let mut f = Feedback::new(); - let mut text = ctx.style.text.clone(); +pub async fn font(mut args: DictValue, ctx: &mut LayoutContext) -> Value { + let mut text = ctx.state.text.clone(); let mut updated_fallback = false; let content = args.take::(); @@ -75,15 +74,15 @@ pub async fn font(_: Span, mut args: DictValue, ctx: LayoutContext<'_>) -> Pass< updated_fallback = true; } - if let Some(style) = args.take_key::("style", &mut f) { + if let Some(style) = args.take_key::("style", &mut ctx.f) { text.variant.style = style; } - if let Some(weight) = args.take_key::("weight", &mut f) { + if let Some(weight) = args.take_key::("weight", &mut ctx.f) { text.variant.weight = weight; } - if let Some(stretch) = args.take_key::("stretch", &mut f) { + if let Some(stretch) = args.take_key::("stretch", &mut ctx.f) { text.variant.stretch = stretch; } @@ -101,15 +100,13 @@ pub async fn font(_: Span, mut args: DictValue, ctx: LayoutContext<'_>) -> Pass< text.fallback.flatten(); } - let commands = match content { + args.unexpected(&mut ctx.f); + Value::Commands(match content { Some(tree) => vec![ - SetTextStyle(text), + SetTextState(text), LayoutSyntaxTree(tree), - SetTextStyle(ctx.style.text.clone()), + SetTextState(ctx.state.text.clone()), ], - None => vec![SetTextStyle(text)], - }; - - args.unexpected(&mut f); - Pass::commands(commands, f) + None => vec![SetTextState(text)], + }) } diff --git a/src/library/mod.rs b/src/library/mod.rs index 43f743187..4db544e93 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -30,7 +30,7 @@ macro_rules! std { macro_rules! wrap { ($func:expr) => { - FuncValue::new(|name, args, ctx| Box::pin($func(name, args, ctx))) + FuncValue::new(|args, ctx| Box::pin($func(args, ctx))) }; } diff --git a/src/library/page.rs b/src/library/page.rs index 77eb6244f..5fda9d5d7 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -19,56 +19,54 @@ use crate::paper::{Paper, PaperClass}; /// - `top`: The top margin (length or relative to height). /// - `bottom`: The bottom margin (length or relative to height). /// - `flip`: Flips custom or paper-defined width and height (boolean). -pub async fn page(_: Span, mut args: DictValue, ctx: LayoutContext<'_>) -> Pass { - let mut f = Feedback::new(); - let mut style = ctx.style.page; +pub async fn page(mut args: DictValue, ctx: &mut LayoutContext) -> Value { + let mut page = ctx.state.page.clone(); if let Some(paper) = args.take::() { - style.class = paper.class; - style.size = paper.size(); + page.class = paper.class; + page.size = paper.size(); } - if let Some(Abs(width)) = args.take_key::("width", &mut f) { - style.class = PaperClass::Custom; - style.size.width = width; + if let Some(Abs(width)) = args.take_key::("width", &mut ctx.f) { + page.class = PaperClass::Custom; + page.size.width = width; } - if let Some(Abs(height)) = args.take_key::("height", &mut f) { - style.class = PaperClass::Custom; - style.size.height = height; + if let Some(Abs(height)) = args.take_key::("height", &mut ctx.f) { + page.class = PaperClass::Custom; + page.size.height = height; } - if let Some(margins) = args.take_key::("margins", &mut f) { - style.margins = Sides::uniform(Some(margins)); + if let Some(margins) = args.take_key::("margins", &mut ctx.f) { + page.margins = Sides::uniform(Some(margins)); } - if let Some(left) = args.take_key::("left", &mut f) { - style.margins.left = Some(left); + if let Some(left) = args.take_key::("left", &mut ctx.f) { + page.margins.left = Some(left); } - if let Some(top) = args.take_key::("top", &mut f) { - style.margins.top = Some(top); + if let Some(top) = args.take_key::("top", &mut ctx.f) { + page.margins.top = Some(top); } - if let Some(right) = args.take_key::("right", &mut f) { - style.margins.right = Some(right); + if let Some(right) = args.take_key::("right", &mut ctx.f) { + page.margins.right = Some(right); } - if let Some(bottom) = args.take_key::("bottom", &mut f) { - style.margins.bottom = Some(bottom); + if let Some(bottom) = args.take_key::("bottom", &mut ctx.f) { + page.margins.bottom = Some(bottom); } - if args.take_key::("flip", &mut f).unwrap_or(false) { - mem::swap(&mut style.size.width, &mut style.size.height); + if args.take_key::("flip", &mut ctx.f).unwrap_or(false) { + mem::swap(&mut page.size.width, &mut page.size.height); } - args.unexpected(&mut f); - Pass::commands(vec![SetPageStyle(style)], f) + args.unexpected(&mut ctx.f); + Value::Commands(vec![SetPageState(page)]) } /// `pagebreak`: Ends the current page. -pub async fn pagebreak(_: Span, args: DictValue, _: LayoutContext<'_>) -> Pass { - let mut f = Feedback::new(); - args.unexpected(&mut f); - Pass::commands(vec![BreakPage], f) +pub async fn pagebreak(args: DictValue, ctx: &mut LayoutContext) -> Value { + args.unexpected(&mut ctx.f); + Value::Commands(vec![BreakPage]) } diff --git a/src/library/spacing.rs b/src/library/spacing.rs index 03f52ba4d..91f407fe8 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -6,35 +6,26 @@ use crate::layout::SpacingKind; /// /// # Positional arguments /// - The spacing (length or relative to font size). -pub async fn h(name: Span, args: DictValue, ctx: LayoutContext<'_>) -> Pass { - spacing(name, args, ctx, SpecAxis::Horizontal) +pub async fn h(args: DictValue, ctx: &mut LayoutContext) -> Value { + spacing(args, ctx, SpecAxis::Horizontal) } /// `v`: Add vertical spacing. /// /// # Positional arguments /// - The spacing (length or relative to font size). -pub async fn v(name: Span, args: DictValue, ctx: LayoutContext<'_>) -> Pass { - spacing(name, args, ctx, SpecAxis::Vertical) +pub async fn v(args: DictValue, ctx: &mut LayoutContext) -> Value { + spacing(args, ctx, SpecAxis::Vertical) } -fn spacing( - name: Span, - mut args: DictValue, - ctx: LayoutContext<'_>, - axis: SpecAxis, -) -> Pass { - let mut f = Feedback::new(); - - let spacing = args.expect::("spacing", name, &mut f); - let commands = if let Some(spacing) = spacing { - let axis = axis.to_gen(ctx.sys); - let spacing = spacing.eval(ctx.style.text.font_size()); +fn spacing(mut args: DictValue, ctx: &mut LayoutContext, axis: SpecAxis) -> Value { + let spacing = args.expect::("spacing", Span::ZERO, &mut ctx.f); + args.unexpected(&mut ctx.f); + Value::Commands(if let Some(spacing) = spacing { + let axis = axis.to_gen(ctx.state.sys); + let spacing = spacing.eval(ctx.state.text.font_size()); vec![AddSpacing(spacing, SpacingKind::Hard, axis)] } else { vec![] - }; - - args.unexpected(&mut f); - Pass::commands(commands, f) + }) } diff --git a/src/shaping.rs b/src/shaping.rs index d9e6e9fdb..092f5e6e9 100644 --- a/src/shaping.rs +++ b/src/shaping.rs @@ -7,20 +7,20 @@ use fontdock::{FaceId, FaceQuery, FallbackTree, FontStyle, FontVariant}; use ttf_parser::GlyphId; +use crate::eval::TextState; use crate::font::FontLoader; use crate::geom::{Point, Size}; use crate::layout::{BoxLayout, Dir, LayoutAlign, LayoutElement, LayoutElements, Shaped}; -use crate::style::TextStyle; /// Shape text into a box. pub async fn shape( text: &str, dir: Dir, align: LayoutAlign, - style: &TextStyle, + state: &TextState, loader: &mut FontLoader, ) -> BoxLayout { - Shaper::new(text, dir, align, style, loader).shape().await + Shaper::new(text, dir, align, state, loader).shape().await } /// Performs super-basic text shaping. @@ -40,16 +40,16 @@ impl<'a> Shaper<'a> { text: &'a str, dir: Dir, align: LayoutAlign, - style: &'a TextStyle, + state: &'a TextState, loader: &'a mut FontLoader, ) -> Self { - let mut variant = style.variant; + let mut variant = state.variant; - if style.strong { + if state.strong { variant.weight = variant.weight.thicken(300); } - if style.emph { + if state.emph { variant.style = match variant.style { FontStyle::Normal => FontStyle::Italic, FontStyle::Italic => FontStyle::Normal, @@ -61,11 +61,11 @@ impl<'a> Shaper<'a> { text, dir, variant, - fallback: &style.fallback, + fallback: &state.fallback, loader, - shaped: Shaped::new(FaceId::MAX, style.font_size()), + shaped: Shaped::new(FaceId::MAX, state.font_size()), layout: BoxLayout { - size: Size::new(0.0, style.font_size()), + size: Size::new(0.0, state.font_size()), align, elements: LayoutElements::new(), }, diff --git a/src/syntax/ast/expr.rs b/src/syntax/ast/expr.rs index 4f0671459..5cd5187b0 100644 --- a/src/syntax/ast/expr.rs +++ b/src/syntax/ast/expr.rs @@ -3,7 +3,7 @@ use crate::eval::Value; use crate::layout::LayoutContext; use crate::syntax::{Decoration, Ident, Lit, LitDict, SpanWith, Spanned}; -use crate::{DynFuture, Feedback}; +use crate::DynFuture; /// An expression. #[derive(Debug, Clone, PartialEq)] @@ -20,17 +20,13 @@ pub enum Expr { impl Expr { /// Evaluate the expression to a value. - pub fn eval<'a>( - &'a self, - ctx: &'a LayoutContext<'a>, - f: &'a mut Feedback, - ) -> DynFuture<'a, Value> { + pub fn eval<'a>(&'a self, ctx: &'a mut LayoutContext) -> DynFuture<'a, Value> { Box::pin(async move { match self { - Self::Lit(lit) => lit.eval(ctx, f).await, - Self::Unary(unary) => unary.eval(ctx, f).await, - Self::Binary(binary) => binary.eval(ctx, f).await, - Self::Call(call) => call.eval(ctx, f).await, + Self::Lit(lit) => lit.eval(ctx).await, + Self::Unary(unary) => unary.eval(ctx).await, + Self::Binary(binary) => binary.eval(ctx).await, + Self::Call(call) => call.eval(ctx).await, } }) } @@ -47,10 +43,10 @@ pub struct ExprUnary { impl ExprUnary { /// Evaluate the expression to a value. - pub async fn eval(&self, ctx: &LayoutContext<'_>, f: &mut Feedback) -> Value { + pub async fn eval(&self, ctx: &mut LayoutContext) -> Value { use Value::*; - let value = self.expr.v.eval(ctx, f).await; + let value = self.expr.v.eval(ctx).await; if value == Error { return Error; } @@ -64,7 +60,7 @@ impl ExprUnary { Relative(x) => Relative(-x), Linear(x) => Linear(-x), v => { - error!(@f, span, "cannot negate {}", v.name()); + error!(@ctx.f, span, "cannot negate {}", v.name()); Value::Error } }, @@ -92,12 +88,12 @@ pub struct ExprBinary { impl ExprBinary { /// Evaluate the expression to a value. - pub async fn eval(&self, ctx: &LayoutContext<'_>, f: &mut Feedback) -> Value { + pub async fn eval(&self, ctx: &mut LayoutContext) -> Value { use crate::geom::Linear as Lin; use Value::*; - let lhs = self.lhs.v.eval(ctx, f).await; - let rhs = self.rhs.v.eval(ctx, f).await; + let lhs = self.lhs.v.eval(ctx).await; + let rhs = self.rhs.v.eval(ctx).await; if lhs == Error || rhs == Error { return Error; @@ -131,7 +127,7 @@ impl ExprBinary { (Commands(a), Commands(b)) => Commands(concat(a, b)), (a, b) => { - error!(@f, span, "cannot add {} and {}", a.name(), b.name()); + error!(@ctx.f, span, "cannot add {} and {}", a.name(), b.name()); Value::Error } }, @@ -155,7 +151,7 @@ impl ExprBinary { (Linear(a), Linear(b)) => Linear(a - b), (a, b) => { - error!(@f, span, "cannot subtract {1} from {0}", a.name(), b.name()); + error!(@ctx.f, span, "cannot subtract {1} from {0}", a.name(), b.name()); Value::Error } }, @@ -186,7 +182,7 @@ impl ExprBinary { (Str(a), Int(b)) => Str(a.repeat(b.max(0) as usize)), (a, b) => { - error!(@f, span, "cannot multiply {} with {}", a.name(), b.name()); + error!(@ctx.f, span, "cannot multiply {} with {}", a.name(), b.name()); Value::Error } }, @@ -207,7 +203,7 @@ impl ExprBinary { (Linear(a), Float(b)) => Linear(a / b), (a, b) => { - error!(@f, span, "cannot divide {} by {}", a.name(), b.name()); + error!(@ctx.f, span, "cannot divide {} by {}", a.name(), b.name()); Value::Error } }, @@ -248,20 +244,18 @@ pub struct ExprCall { impl ExprCall { /// Evaluate the call expression to a value. - pub async fn eval(&self, ctx: &LayoutContext<'_>, f: &mut Feedback) -> Value { + pub async fn eval(&self, ctx: &mut LayoutContext) -> Value { let name = &self.name.v; let span = self.name.span; - let args = self.args.eval(ctx, f).await; + let args = self.args.eval(ctx).await; - if let Some(func) = ctx.scope.func(name) { - let pass = func(span, args, ctx.clone()).await; - f.extend(pass.feedback); - f.decorations.push(Decoration::Resolved.span_with(span)); - pass.output + if let Some(func) = ctx.state.scope.func(name) { + ctx.f.decorations.push(Decoration::Resolved.span_with(span)); + (func.clone())(args, ctx).await } else { if !name.is_empty() { - error!(@f, span, "unknown function"); - f.decorations.push(Decoration::Unresolved.span_with(span)); + error!(@ctx.f, span, "unknown function"); + ctx.f.decorations.push(Decoration::Unresolved.span_with(span)); } Value::Dict(args) } diff --git a/src/syntax/ast/lit.rs b/src/syntax/ast/lit.rs index 3b6441e7a..acc3aa0bc 100644 --- a/src/syntax/ast/lit.rs +++ b/src/syntax/ast/lit.rs @@ -5,7 +5,7 @@ use crate::eval::{DictKey, DictValue, SpannedEntry, Value}; use crate::layout::LayoutContext; use crate::length::Length; use crate::syntax::{Expr, Ident, SpanWith, Spanned, SynTree}; -use crate::{DynFuture, Feedback}; +use crate::DynFuture; /// A literal. #[derive(Debug, Clone, PartialEq)] @@ -39,7 +39,7 @@ pub enum Lit { impl Lit { /// Evaluate the dictionary literal to a dictionary value. - pub async fn eval(&self, ctx: &LayoutContext<'_>, f: &mut Feedback) -> Value { + pub async fn eval(&self, ctx: &mut LayoutContext) -> Value { match *self { Lit::Ident(ref i) => Value::Ident(i.clone()), Lit::Bool(b) => Value::Bool(b), @@ -49,7 +49,7 @@ impl Lit { Lit::Percent(p) => Value::Relative(p / 100.0), Lit::Color(c) => Value::Color(c), Lit::Str(ref s) => Value::Str(s.clone()), - Lit::Dict(ref d) => Value::Dict(d.eval(ctx, f).await), + Lit::Dict(ref d) => Value::Dict(d.eval(ctx).await), Lit::Content(ref c) => Value::Tree(c.clone()), } } @@ -66,16 +66,12 @@ impl LitDict { } /// Evaluate the dictionary literal to a dictionary value. - pub fn eval<'a>( - &'a self, - ctx: &'a LayoutContext<'a>, - f: &'a mut Feedback, - ) -> DynFuture<'a, DictValue> { + pub fn eval<'a>(&'a self, ctx: &'a mut LayoutContext) -> DynFuture<'a, DictValue> { Box::pin(async move { let mut dict = DictValue::new(); for entry in &self.0 { - let val = entry.expr.v.eval(ctx, f).await; + let val = entry.expr.v.eval(ctx).await; let spanned = val.span_with(entry.expr.span); if let Some(key) = &entry.key { dict.insert(&key.v, SpannedEntry::new(key.span, spanned)); diff --git a/tests/test_typeset.rs b/tests/test_typeset.rs index 4ed82032f..4c30f0ed4 100644 --- a/tests/test_typeset.rs +++ b/tests/test_typeset.rs @@ -11,6 +11,7 @@ use futures_executor::block_on; use raqote::{DrawTarget, PathBuilder, SolidSource, Source, Transform, Vector}; use ttf_parser::OutlineBuilder; +use typstc::eval::State; use typstc::export::pdf; use typstc::font::{FontLoader, SharedFontLoader}; use typstc::geom::{Point, Vec2}; @@ -69,12 +70,11 @@ fn main() { fn test(name: &str, src: &str, src_path: &Path, loader: &SharedFontLoader) { println!("Testing {}.", name); - let style = Default::default(); - let scope = typstc::library::_std(); + let state = State::default(); let Pass { output: layouts, feedback: Feedback { mut diagnostics, .. }, - } = block_on(typeset(&src, &style, &scope, Rc::clone(loader))); + } = block_on(typeset(&src, state, Rc::clone(loader))); if !diagnostics.is_empty() { diagnostics.sort();