diff --git a/Cargo.toml b/Cargo.toml index 2778fd434..7667942e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,17 +19,19 @@ debug = 0 opt-level = 2 [dependencies] -fxhash = "0.2.1" +fxhash = "0.2" image = { version = "0.23", default-features = false, features = ["png", "jpeg"] } itertools = "0.10" miniz_oxide = "0.4" +once_cell = "1" pdf-writer = "0.4" rustybuzz = "0.4" serde = { version = "1", features = ["derive", "rc"] } svg2pdf = { version = "0.1", default-features = false, features = ["text", "png", "jpeg"] } ttf-parser = "0.12" +typst-macros = { path = "./macros" } unicode-bidi = "0.3.5" -unicode-segmentation = "1.8" +unicode-segmentation = "1" unicode-xid = "0.2" usvg = { version = "0.19", default-features = false, features = ["text"] } xi-unicode = "0.3" diff --git a/benches/bench.typ b/benches/bench.typ index f290844bf..75c97d025 100644 --- a/benches/bench.typ +++ b/benches/bench.typ @@ -1,5 +1,5 @@ // Configuration with `page` and `font` functions. -#page(width: 450pt, margins: 1cm) +#set page(width: 450pt, margins: 1cm) // There are variables and they can take normal values like strings, ... #let city = "Berlin" diff --git a/benches/oneshot.rs b/benches/oneshot.rs index 6bfb923b4..d3e2ff8e4 100644 --- a/benches/oneshot.rs +++ b/benches/oneshot.rs @@ -2,8 +2,6 @@ use std::path::Path; use iai::{black_box, main, Iai}; -use typst::eval::eval; -use typst::layout::layout; use typst::loading::MemLoader; use typst::parse::{parse, Scanner, TokenMode, Tokens}; use typst::source::SourceId; @@ -53,20 +51,14 @@ fn bench_parse(iai: &mut Iai) { fn bench_eval(iai: &mut Iai) { let (mut ctx, id) = context(); - let ast = ctx.sources.get(id).ast().unwrap(); - iai.run(|| eval(&mut ctx, id, &ast).unwrap()); -} - -fn bench_to_tree(iai: &mut Iai) { - let (mut ctx, id) = context(); - let module = ctx.evaluate(id).unwrap(); - iai.run(|| module.template.to_document(ctx.style())); + iai.run(|| ctx.evaluate(id).unwrap()); } fn bench_layout(iai: &mut Iai) { let (mut ctx, id) = context(); - let tree = ctx.execute(id).unwrap(); - iai.run(|| layout(&mut ctx, &tree)); + let module = ctx.evaluate(id).unwrap(); + let tree = module.into_root(); + iai.run(|| tree.layout(&mut ctx)); } main!( @@ -75,6 +67,5 @@ main!( bench_tokenize, bench_parse, bench_eval, - bench_to_tree, bench_layout ); diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 000000000..6a7f55224 --- /dev/null +++ b/macros/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "typst-macros" +version = "0.1.0" +authors = ["The Typst Project Developers"] +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1" +quote = "1" +syn = { version = "1", features = ["full"] } diff --git a/macros/src/lib.rs b/macros/src/lib.rs new file mode 100644 index 000000000..3b1d05488 --- /dev/null +++ b/macros/src/lib.rs @@ -0,0 +1,171 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::parse_quote; +use syn::spanned::Spanned; +use syn::{Error, Result}; + +/// Generate node properties. +#[proc_macro_attribute] +pub fn properties(_: TokenStream, item: TokenStream) -> TokenStream { + let impl_block = syn::parse_macro_input!(item as syn::ItemImpl); + expand(impl_block).unwrap_or_else(|err| err.to_compile_error()).into() +} + +/// Expand a property impl block for a node. +fn expand(mut impl_block: syn::ItemImpl) -> Result { + // Split the node type into name and generic type arguments. + let (self_name, self_args) = parse_self(&*impl_block.self_ty)?; + + // Rewrite the const items from values to keys. + let mut style_ids = vec![]; + let mut modules = vec![]; + for item in &mut impl_block.items { + if let syn::ImplItem::Const(item) = item { + let (style_id, module) = process_const(item, &self_name, &self_args)?; + style_ids.push(style_id); + modules.push(module); + } + } + + // Here, we use the collected `style_ids` to provide a function that checks + // whether a property belongs to the node. + impl_block.items.insert(0, parse_quote! { + /// Check whether the property with the given type id belongs to `Self`. + pub fn has_property(id: StyleId) -> bool { + [#(#style_ids),*].contains(&id) + } + }); + + // Put everything into a module with a hopefully unique type to isolate + // it from the outside. + let module = quote::format_ident!("{}_types", self_name); + Ok(quote! { + #[allow(non_snake_case)] + mod #module { + use std::marker::PhantomData; + use once_cell::sync::Lazy; + use crate::eval::{Property, StyleId}; + use super::*; + + #impl_block + #(#modules)* + } + }) +} + +/// Parse the name and generic type arguments of the node type. +fn parse_self(self_ty: &syn::Type) -> Result<(String, Vec<&syn::Type>)> { + // Extract the node type for which we want to generate properties. + let path = match self_ty { + syn::Type::Path(path) => path, + ty => return Err(Error::new(ty.span(), "must be a path type")), + }; + + // Split up the type into its name and its generic type arguments. + let last = path.path.segments.last().unwrap(); + let self_name = last.ident.to_string(); + let self_args = match &last.arguments { + syn::PathArguments::AngleBracketed(args) => args + .args + .iter() + .filter_map(|arg| match arg { + syn::GenericArgument::Type(ty) => Some(ty), + _ => None, + }) + .collect(), + _ => vec![], + }; + + Ok((self_name, self_args)) +} + +/// Process a single const item. +fn process_const( + item: &mut syn::ImplItemConst, + self_name: &str, + self_args: &[&syn::Type], +) -> Result<(syn::Expr, syn::ItemMod)> { + // The type of the property's value is what the user of our macro wrote + // as type of the const ... + let value_ty = &item.ty; + + // ... but the real type of the const becomes Key<#key_param>. + let key_param = if self_args.is_empty() { + quote! { #value_ty } + } else { + quote! { (#value_ty, #(#self_args),*) } + }; + + // The display name, e.g. `TextNode::STRONG`. + let name = format!("{}::{}", self_name, &item.ident); + + // The default value of the property is what the user wrote as + // initialization value of the const. + let default = &item.expr; + + // Look for a folding function like `#[fold(u64::add)]`. + let mut combinator = None; + for attr in &item.attrs { + if attr.path.is_ident("fold") { + let fold: syn::Expr = attr.parse_args()?; + combinator = Some(quote! { + fn combine(inner: Self::Value, outer: Self::Value) -> Self::Value { + let f: fn(Self::Value, Self::Value) -> Self::Value = #fold; + f(inner, outer) + } + }); + } + } + + // The implementation of the `Property` trait. + let property_impl = quote! { + impl Property for Key { + type Value = #value_ty; + + const NAME: &'static str = #name; + + fn default() -> Self::Value { + #default + } + + fn default_ref() -> &'static Self::Value { + static LAZY: Lazy<#value_ty> = Lazy::new(|| #default); + &*LAZY + } + + #combinator + } + }; + + // The module that will contain the `Key` type. + let module_name = &item.ident; + + // Generate the style id and module code. + let style_id = parse_quote! { StyleId::of::<#module_name::Key<#key_param>>() }; + let module = parse_quote! { + #[allow(non_snake_case)] + mod #module_name { + use super::*; + + pub struct Key(pub PhantomData); + impl Copy for Key {} + impl Clone for Key { + fn clone(&self) -> Self { + *self + } + } + + #property_impl + } + }; + + // Replace type and initializer expression with the `Key`. + item.attrs.retain(|attr| !attr.path.is_ident("fold")); + item.ty = parse_quote! { #module_name::Key<#key_param> }; + item.expr = parse_quote! { #module_name::Key(PhantomData) }; + + Ok((style_id, module)) +} diff --git a/src/eval/class.rs b/src/eval/class.rs new file mode 100644 index 000000000..c4393b8a2 --- /dev/null +++ b/src/eval/class.rs @@ -0,0 +1,139 @@ +use std::fmt::{self, Debug, Formatter, Write}; +use std::marker::PhantomData; +use std::rc::Rc; + +use super::{Args, EvalContext, Node, Styles}; +use crate::diag::TypResult; +use crate::util::EcoString; + +/// A class of [nodes](Node). +/// +/// You can [construct] an instance of a class in Typst code by invoking the +/// class as a callable. This always produces some node, but not necessarily one +/// of fixed type. For example, the `text` constructor does not actually create +/// a [`TextNode`]. Instead it applies styling to whatever node you pass in and +/// returns it structurally unchanged. +/// +/// The arguments you can pass to a class constructor fall into two categories: +/// Data that is inherent to the instance (e.g. the text of a heading) and style +/// properties (e.g. the fill color of a heading). As the latter are often +/// shared by many instances throughout a document, they can also be +/// conveniently configured through class's [`set`] rule. Then, they apply to +/// all nodes that are instantiated into the template where the `set` was +/// executed. +/// +/// ```typst +/// This is normal. +/// [ +/// #set text(weight: "bold") +/// #set heading(fill: blue) +/// = A blue & bold heading +/// ] +/// Normal again. +/// ``` +/// +/// [construct]: Self::construct +/// [`TextNode`]: crate::library::TextNode +/// [`set`]: Self::set +#[derive(Clone)] +pub struct Class(Rc>); + +/// The unsized structure behind the [`Rc`]. +struct Inner { + name: EcoString, + shim: T, +} + +impl Class { + /// Create a new class. + pub fn new(name: EcoString) -> Self + where + T: Construct + Set + 'static, + { + // By specializing the shim to `T`, its vtable will contain T's + // `Construct` and `Set` impls (through the `Bounds` trait), enabling us + // to use them in the class's methods. + Self(Rc::new(Inner { name, shim: Shim::(PhantomData) })) + } + + /// The name of the class. + pub fn name(&self) -> &EcoString { + &self.0.name + } + + /// Construct an instance of the class. + /// + /// This parses both property and data arguments (in this order) and styles + /// the node constructed from the data with the style properties. + pub fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult { + let mut styles = Styles::new(); + self.set(args, &mut styles)?; + let node = self.0.shim.construct(ctx, args)?; + Ok(node.styled(styles)) + } + + /// Execute the class's set rule. + /// + /// This parses property arguments and writes the resulting styles into the + /// given style map. There are no further side effects. + pub fn set(&self, args: &mut Args, styles: &mut Styles) -> TypResult<()> { + self.0.shim.set(args, styles) + } +} + +impl Debug for Class { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_str("') + } +} + +impl PartialEq for Class { + fn eq(&self, other: &Self) -> bool { + // We cast to thin pointers for comparison because we don't want to + // compare vtables (there can be duplicate vtables across codegen units). + std::ptr::eq( + Rc::as_ptr(&self.0) as *const (), + Rc::as_ptr(&other.0) as *const (), + ) + } +} + +/// Construct an instance of a class. +pub trait Construct { + /// Construct an instance of this class from the arguments. + /// + /// This is passed only the arguments that remain after execution of the + /// class's set rule. + fn construct(ctx: &mut EvalContext, args: &mut Args) -> TypResult; +} + +/// Set style properties of a class. +pub trait Set { + /// Parse the arguments and insert style properties of this class into the + /// given style map. + fn set(args: &mut Args, styles: &mut Styles) -> TypResult<()>; +} + +/// Rewires the operations available on a class in an object-safe way. This is +/// only implemented by the zero-sized `Shim` struct. +trait Bounds { + fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult; + fn set(&self, args: &mut Args, styles: &mut Styles) -> TypResult<()>; +} + +struct Shim(PhantomData); + +impl Bounds for Shim +where + T: Construct + Set, +{ + fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult { + T::construct(ctx, args) + } + + fn set(&self, args: &mut Args, styles: &mut Styles) -> TypResult<()> { + T::set(args, styles) + } +} diff --git a/src/eval/mod.rs b/src/eval/mod.rs index a0c31e983..17cc46ef3 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -6,21 +6,24 @@ mod array; mod dict; #[macro_use] mod value; +#[macro_use] +mod styles; mod capture; +mod class; mod function; +mod node; mod ops; mod scope; -mod template; -mod walk; pub use array::*; pub use capture::*; +pub use class::*; pub use dict::*; pub use function::*; +pub use node::*; pub use scope::*; -pub use template::*; +pub use styles::*; pub use value::*; -pub use walk::*; use std::cell::RefMut; use std::collections::HashMap; @@ -33,6 +36,8 @@ use unicode_segmentation::UnicodeSegmentation; use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult}; use crate::geom::{Angle, Fractional, Length, Relative}; use crate::image::ImageStore; +use crate::layout::RootNode; +use crate::library::{self, TextNode}; use crate::loading::Loader; use crate::source::{SourceId, SourceStore}; use crate::syntax::ast::*; @@ -40,20 +45,30 @@ use crate::syntax::{Span, Spanned}; use crate::util::{EcoString, RefMutExt}; use crate::Context; -/// Evaluate a parsed source file into a module. -pub fn eval(ctx: &mut Context, source: SourceId, markup: &Markup) -> TypResult { - let mut ctx = EvalContext::new(ctx, source); - let template = markup.eval(&mut ctx)?; - Ok(Module { scope: ctx.scopes.top, template }) -} - -/// An evaluated module, ready for importing or instantiation. +/// An evaluated module, ready for importing or conversion to a root layout +/// tree. #[derive(Debug, Default, Clone)] pub struct Module { /// The top-level definitions that were bound in this module. pub scope: Scope, - /// The template defined by this module. - pub template: Template, + /// The module's layoutable contents. + pub node: Node, +} + +impl Module { + /// Convert this module's node into a layout tree. + pub fn into_root(self) -> RootNode { + self.node.into_root() + } +} + +/// Evaluate an expression. +pub trait Eval { + /// The output of evaluating the expression. + type Output; + + /// Evaluate the expression to the output value. + fn eval(&self, ctx: &mut EvalContext) -> TypResult; } /// The context for evaluation. @@ -70,8 +85,8 @@ pub struct EvalContext<'a> { pub modules: HashMap, /// The active scopes. pub scopes: Scopes<'a>, - /// The currently built template. - pub template: Template, + /// The active styles. + pub styles: Styles, } impl<'a> EvalContext<'a> { @@ -84,7 +99,7 @@ impl<'a> EvalContext<'a> { route: vec![source], modules: HashMap::new(), scopes: Scopes::new(Some(&ctx.std)), - template: Template::new(), + styles: Styles::new(), } } @@ -115,18 +130,20 @@ impl<'a> EvalContext<'a> { // Prepare the new context. let new_scopes = Scopes::new(self.scopes.base); - let old_scopes = mem::replace(&mut self.scopes, new_scopes); + let prev_scopes = mem::replace(&mut self.scopes, new_scopes); + let prev_styles = mem::take(&mut self.styles); self.route.push(id); // Evaluate the module. - let template = ast.eval(self).trace(|| Tracepoint::Import, span)?; + let node = ast.eval(self).trace(|| Tracepoint::Import, span)?; // Restore the old context. - let new_scopes = mem::replace(&mut self.scopes, old_scopes); + let new_scopes = mem::replace(&mut self.scopes, prev_scopes); + self.styles = prev_styles; self.route.pop().unwrap(); // Save the evaluated module. - let module = Module { scope: new_scopes.top, template }; + let module = Module { scope: new_scopes.top, node }; self.modules.insert(id, module); Ok(id) @@ -145,29 +162,108 @@ impl<'a> EvalContext<'a> { } } -/// Evaluate an expression. -pub trait Eval { - /// The output of evaluating the expression. - type Output; - - /// Evaluate the expression to the output value. - fn eval(&self, ctx: &mut EvalContext) -> TypResult; -} - impl Eval for Markup { - type Output = Template; + type Output = Node; fn eval(&self, ctx: &mut EvalContext) -> TypResult { - Ok({ - let prev = mem::take(&mut ctx.template); - ctx.template.save(); - self.walk(ctx)?; - ctx.template.restore(); - mem::replace(&mut ctx.template, prev) + let prev = mem::take(&mut ctx.styles); + let nodes = self.nodes(); + let upper = nodes.size_hint().1.unwrap_or_default(); + let mut seq = Vec::with_capacity(upper); + for piece in nodes { + seq.push((piece.eval(ctx)?, ctx.styles.clone())); + } + ctx.styles = prev; + Ok(Node::Sequence(seq)) + } +} + +impl Eval for MarkupNode { + type Output = Node; + + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + Ok(match self { + Self::Space => Node::Space, + Self::Linebreak => Node::Linebreak, + Self::Parbreak => Node::Parbreak, + Self::Strong => { + ctx.styles.toggle(TextNode::STRONG); + Node::new() + } + Self::Emph => { + ctx.styles.toggle(TextNode::EMPH); + Node::new() + } + Self::Text(text) => Node::Text(text.clone()), + Self::Raw(raw) => raw.eval(ctx)?, + Self::Math(math) => math.eval(ctx)?, + Self::Heading(heading) => heading.eval(ctx)?, + Self::List(list) => list.eval(ctx)?, + Self::Enum(enum_) => enum_.eval(ctx)?, + Self::Expr(expr) => expr.eval(ctx)?.show(), }) } } +impl Eval for RawNode { + type Output = Node; + + fn eval(&self, _: &mut EvalContext) -> TypResult { + let text = Node::Text(self.text.clone()).monospaced(); + Ok(if self.block { + Node::Block(text.into_block()) + } else { + text + }) + } +} + +impl Eval for MathNode { + type Output = Node; + + fn eval(&self, _: &mut EvalContext) -> TypResult { + let text = Node::Text(self.formula.trim().into()).monospaced(); + Ok(if self.display { + Node::Block(text.into_block()) + } else { + text + }) + } +} + +impl Eval for HeadingNode { + type Output = Node; + + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + Ok(Node::block(library::HeadingNode { + child: self.body().eval(ctx)?.into_block(), + level: self.level(), + })) + } +} + +impl Eval for ListNode { + type Output = Node; + + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + Ok(Node::block(library::ListNode { + child: self.body().eval(ctx)?.into_block(), + labelling: library::Unordered, + })) + } +} + +impl Eval for EnumNode { + type Output = Node; + + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + Ok(Node::block(library::ListNode { + child: self.body().eval(ctx)?.into_block(), + labelling: library::Ordered(self.number()), + })) + } +} + impl Eval for Expr { type Output = Value; @@ -177,7 +273,7 @@ impl Eval for Expr { Self::Ident(v) => v.eval(ctx), Self::Array(v) => v.eval(ctx).map(Value::Array), Self::Dict(v) => v.eval(ctx).map(Value::Dict), - Self::Template(v) => v.eval(ctx).map(Value::Template), + Self::Template(v) => v.eval(ctx).map(Value::Node), Self::Group(v) => v.eval(ctx), Self::Block(v) => v.eval(ctx), Self::Call(v) => v.eval(ctx), @@ -186,6 +282,7 @@ impl Eval for Expr { Self::Unary(v) => v.eval(ctx), Self::Binary(v) => v.eval(ctx), Self::Let(v) => v.eval(ctx), + Self::Set(v) => v.eval(ctx), Self::If(v) => v.eval(ctx), Self::While(v) => v.eval(ctx), Self::For(v) => v.eval(ctx), @@ -244,7 +341,7 @@ impl Eval for DictExpr { } impl Eval for TemplateExpr { - type Output = Template; + type Output = Node; fn eval(&self, ctx: &mut EvalContext) -> TypResult { self.body().eval(ctx) @@ -372,9 +469,15 @@ impl Eval for CallExpr { Ok(value) } + Value::Class(class) => { + let node = class.construct(ctx, &mut args)?; + args.finish()?; + Ok(Value::Node(node)) + } + v => bail!( self.callee().span(), - "expected function or collection, found {}", + "expected callable or collection, found {}", v.type_name(), ), } @@ -541,6 +644,19 @@ impl Eval for LetExpr { } } +impl Eval for SetExpr { + type Output = Value; + + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + let class = self.class(); + let class = class.eval(ctx)?.cast::().at(class.span())?; + let mut args = self.args().eval(ctx)?; + class.set(&mut args, &mut ctx.styles)?; + args.finish()?; + Ok(Value::None) + } +} + impl Eval for IfExpr { type Output = Value; @@ -665,7 +781,7 @@ impl Eval for IncludeExpr { let resolved = path.eval(ctx)?.cast::().at(path.span())?; let file = ctx.import(&resolved, path.span())?; let module = &ctx.modules[&file]; - Ok(Value::Template(module.template.clone())) + Ok(Value::Node(module.node.clone())) } } diff --git a/src/eval/node.rs b/src/eval/node.rs new file mode 100644 index 000000000..34a4f275a --- /dev/null +++ b/src/eval/node.rs @@ -0,0 +1,453 @@ +use std::convert::TryFrom; +use std::fmt::Debug; +use std::hash::Hash; +use std::iter::Sum; +use std::mem; +use std::ops::{Add, AddAssign}; + +use super::Styles; +use crate::diag::StrResult; +use crate::geom::SpecAxis; +use crate::layout::{Layout, PackedNode, RootNode}; +use crate::library::{ + FlowChild, FlowNode, PageNode, ParChild, ParNode, PlacedNode, SpacingKind, + SpacingNode, TextNode, +}; +use crate::util::EcoString; + +/// A partial representation of a layout node. +/// +/// A node is a composable intermediate representation that can be converted +/// into a proper layout node by lifting it to a [block-level](PackedNode) or +/// [root node](RootNode). +/// +/// When you write `[Hi] + [you]` in Typst, this type's [`Add`] implementation +/// is invoked. There, multiple nodes are combined into a single +/// [`Sequence`](Self::Sequence) node. +#[derive(Debug, PartialEq, Clone, Hash)] +pub enum Node { + /// A word space. + Space, + /// A line break. + Linebreak, + /// A paragraph break. + Parbreak, + /// A page break. + Pagebreak, + /// Plain text. + Text(EcoString), + /// Spacing. + Spacing(SpecAxis, SpacingKind), + /// An inline node. + Inline(PackedNode), + /// A block node. + Block(PackedNode), + /// A page node. + Page(PageNode), + /// Multiple nodes with attached styles. + /// + /// For example, the Typst template `[Hi *you!*]` would result in the + /// sequence: + /// ```ignore + /// Sequence([ + /// (Text("Hi"), {}), + /// (Space, {}), + /// (Text("you!"), { TextNode::STRONG: true }), + /// ]) + /// ``` + /// A sequence may contain nested sequences (meaning this variant + /// effectively allows nodes to form trees). All nested sequences can + /// equivalently be represented as a single flat sequence, but allowing + /// nesting doesn't hurt since we can just recurse into the nested sequences + /// during packing. Also, in theory, this allows better complexity when + /// adding (large) sequence nodes (just like for a text rope). + Sequence(Vec<(Self, Styles)>), +} + +impl Node { + /// Create an empty node. + pub fn new() -> Self { + Self::Sequence(vec![]) + } + + /// Create an inline-level node. + pub fn inline(node: T) -> Self + where + T: Layout + Debug + Hash + 'static, + { + Self::Inline(node.pack()) + } + + /// Create a block-level node. + pub fn block(node: T) -> Self + where + T: Layout + Debug + Hash + 'static, + { + Self::Block(node.pack()) + } + + /// Style this node. + pub fn styled(self, styles: Styles) -> Self { + match self { + Self::Inline(inline) => Self::Inline(inline.styled(styles)), + Self::Block(block) => Self::Block(block.styled(styles)), + Self::Page(page) => Self::Page(page.styled(styles)), + other => Self::Sequence(vec![(other, styles)]), + } + } + + /// Style this node in monospace. + pub fn monospaced(self) -> Self { + self.styled(Styles::one(TextNode::MONOSPACE, true)) + } + + /// Lift to a type-erased block-level node. + pub fn into_block(self) -> PackedNode { + if let Node::Block(packed) = self { + packed + } else { + let mut packer = Packer::new(false); + packer.walk(self, Styles::new()); + packer.into_block() + } + } + + /// Lift to a root layout tree node. + pub fn into_root(self) -> RootNode { + let mut packer = Packer::new(true); + packer.walk(self, Styles::new()); + packer.into_root() + } + + /// Repeat this node `n` times. + pub fn repeat(&self, n: i64) -> StrResult { + let count = usize::try_from(n) + .map_err(|_| format!("cannot repeat this template {} times", n))?; + + // TODO(style): Make more efficient. + Ok(Self::Sequence(vec![(self.clone(), Styles::new()); count])) + } +} + +impl Default for Node { + fn default() -> Self { + Self::new() + } +} + +impl Add for Node { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + // TODO(style): Make more efficient. + Self::Sequence(vec![(self, Styles::new()), (rhs, Styles::new())]) + } +} + +impl AddAssign for Node { + fn add_assign(&mut self, rhs: Self) { + *self = mem::take(self) + rhs; + } +} + +impl Sum for Node { + fn sum>(iter: I) -> Self { + Self::Sequence(iter.map(|n| (n, Styles::new())).collect()) + } +} + +/// Packs a [`Node`] into a flow or root node. +struct Packer { + /// Whether this packer produces a root node. + top: bool, + /// The accumulated page nodes. + pages: Vec, + /// The accumulated flow children. + flow: Builder, + /// The accumulated paragraph children. + par: Builder, +} + +impl Packer { + /// Start a new node-packing session. + fn new(top: bool) -> Self { + Self { + top, + pages: vec![], + flow: Builder::default(), + par: Builder::default(), + } + } + + /// Finish up and return the resulting flow. + fn into_block(mut self) -> PackedNode { + self.parbreak(None); + FlowNode(self.flow.children).pack() + } + + /// Finish up and return the resulting root node. + fn into_root(mut self) -> RootNode { + self.pagebreak(); + RootNode(self.pages) + } + + /// Consider a node with the given styles. + fn walk(&mut self, node: Node, styles: Styles) { + match node { + Node::Space => { + // A text space is "soft", meaning that it can be eaten up by + // adjacent line breaks or explicit spacings. + self.par.last.soft(ParChild::text(' ', styles)); + } + Node::Linebreak => { + // A line break eats up surrounding text spaces. + self.par.last.hard(); + self.push_inline(ParChild::text('\n', styles)); + self.par.last.hard(); + } + Node::Parbreak => { + // An explicit paragraph break is styled according to the active + // styles (`Some(_)`) whereas paragraph breaks forced by + // incompatibility take their styles from the preceding + // paragraph. + self.parbreak(Some(styles)); + } + Node::Pagebreak => { + // We must set the flow styles after the page break such that an + // empty page created by two page breaks in a row has styles at + // all. + self.pagebreak(); + self.flow.styles = styles; + } + Node::Text(text) => { + self.push_inline(ParChild::text(text, styles)); + } + Node::Spacing(SpecAxis::Horizontal, kind) => { + // Just like a line break, explicit horizontal spacing eats up + // surrounding text spaces. + self.par.last.hard(); + self.push_inline(ParChild::Spacing(SpacingNode { kind, styles })); + self.par.last.hard(); + } + Node::Spacing(SpecAxis::Vertical, kind) => { + // Explicit vertical spacing ends the current paragraph and then + // discards the paragraph break. + self.parbreak(None); + self.make_flow_compatible(&styles); + self.flow + .children + .push(FlowChild::Spacing(SpacingNode { kind, styles })); + self.flow.last.hard(); + } + Node::Inline(inline) => { + self.push_inline(ParChild::Node(inline.styled(styles))); + } + Node::Block(block) => { + self.push_block(block.styled(styles)); + } + Node::Page(page) => { + if self.top { + self.pagebreak(); + self.pages.push(page.styled(styles)); + } else { + let flow = page.child.styled(page.styles); + self.push_block(flow.styled(styles)); + } + } + Node::Sequence(list) => { + // For a list of nodes, we apply the list's styles to each node + // individually. + for (node, mut inner) in list { + inner.apply(&styles); + self.walk(node, inner); + } + } + } + } + + /// Insert an inline-level element into the current paragraph. + fn push_inline(&mut self, child: ParChild) { + if let Some(child) = self.par.last.any() { + self.push_coalescing(child); + } + + // The node must be both compatible with the current page and the + // current paragraph. + self.make_flow_compatible(child.styles()); + self.make_par_compatible(child.styles()); + self.push_coalescing(child); + self.par.last.any(); + } + + /// Push a paragraph child, coalescing text nodes with compatible styles. + fn push_coalescing(&mut self, child: ParChild) { + if let ParChild::Text(right) = &child { + if let Some(ParChild::Text(left)) = self.par.children.last_mut() { + if left.styles.compatible(&right.styles, TextNode::has_property) { + left.text.push_str(&right.text); + return; + } + } + } + + self.par.children.push(child); + } + + /// Insert a block-level element into the current flow. + fn push_block(&mut self, node: PackedNode) { + let placed = node.is::(); + + self.parbreak(None); + self.make_flow_compatible(&node.styles); + self.flow.children.extend(self.flow.last.any()); + self.flow.children.push(FlowChild::Node(node)); + self.parbreak(None); + + // Prevent paragraph spacing between the placed node and the paragraph + // below it. + if placed { + self.flow.last.hard(); + } + } + + /// Advance to the next paragraph. + fn parbreak(&mut self, break_styles: Option) { + // Erase any styles that will be inherited anyway. + let Builder { mut children, styles, .. } = mem::take(&mut self.par); + for child in &mut children { + child.styles_mut().erase(&styles); + } + + // For explicit paragraph breaks, `break_styles` is already `Some(_)`. + // For page breaks due to incompatibility, we fall back to the styles + // of the preceding paragraph. + let break_styles = break_styles.unwrap_or_else(|| styles.clone()); + + // We don't want empty paragraphs. + if !children.is_empty() { + // The paragraph's children are all compatible with the page, so the + // paragraph is too, meaning we don't need to check or intersect + // anything here. + let par = ParNode(children).pack().styled(styles); + self.flow.children.extend(self.flow.last.any()); + self.flow.children.push(FlowChild::Node(par)); + } + + // Insert paragraph spacing. + self.flow.last.soft(FlowChild::Break(break_styles)); + } + + /// Advance to the next page. + fn pagebreak(&mut self) { + if self.top { + self.parbreak(None); + + // Take the flow and erase any styles that will be inherited anyway. + let Builder { mut children, styles, .. } = mem::take(&mut self.flow); + for child in &mut children { + child.styles_mut().erase(&styles); + } + + let flow = FlowNode(children).pack(); + let page = PageNode { child: flow, styles }; + self.pages.push(page); + } + } + + /// Break to a new paragraph if the `styles` contain paragraph styles that + /// are incompatible with the current paragraph. + fn make_par_compatible(&mut self, styles: &Styles) { + if self.par.children.is_empty() { + self.par.styles = styles.clone(); + return; + } + + if !self.par.styles.compatible(&styles, ParNode::has_property) { + self.parbreak(None); + self.par.styles = styles.clone(); + return; + } + + self.par.styles.intersect(&styles); + } + + /// Break to a new page if the `styles` contain page styles that are + /// incompatible with the current flow. + fn make_flow_compatible(&mut self, styles: &Styles) { + if self.flow.children.is_empty() && self.par.children.is_empty() { + self.flow.styles = styles.clone(); + return; + } + + if self.top && !self.flow.styles.compatible(&styles, PageNode::has_property) { + self.pagebreak(); + self.flow.styles = styles.clone(); + return; + } + + self.flow.styles.intersect(styles); + } +} + +/// Container for building a flow or paragraph. +struct Builder { + /// The intersection of the style properties of all `children`. + styles: Styles, + /// The accumulated flow or paragraph children. + children: Vec, + /// The kind of thing that was last added. + last: Last, +} + +impl Default for Builder { + fn default() -> Self { + Self { + styles: Styles::new(), + children: vec![], + last: Last::None, + } + } +} + +/// The kind of node that was last added to a flow or paragraph. A small finite +/// state machine used to coalesce spaces. +/// +/// Soft nodes can only exist when surrounded by `Any` nodes. Not at the +/// start, end or next to hard nodes. This way, spaces at start and end of +/// paragraphs and next to `#h(..)` goes away. +enum Last { + /// Start state, nothing there. + None, + /// Text or a block node or something. + Any, + /// Hard nodes: Linebreaks and explicit spacing. + Hard, + /// Soft nodes: Word spaces and paragraph breaks. These are saved here + /// temporarily and then applied once an `Any` node appears. + Soft(N), +} + +impl Last { + /// Transition into the `Any` state and return a soft node to really add + /// now if currently in `Soft` state. + fn any(&mut self) -> Option { + match mem::replace(self, Self::Any) { + Self::Soft(soft) => Some(soft), + _ => None, + } + } + + /// Transition into the `Soft` state, but only if in `Any`. Otherwise, the + /// soft node is discarded. + fn soft(&mut self, soft: N) { + if let Self::Any = self { + *self = Self::Soft(soft); + } + } + + /// Transition into the `Hard` state, discarding a possibly existing soft + /// node and preventing further soft nodes from being added. + fn hard(&mut self) { + *self = Self::Hard; + } +} diff --git a/src/eval/ops.rs b/src/eval/ops.rs index ede1230fa..23530c101 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -22,9 +22,9 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult { (Str(a), Str(b)) => Str(a + b), (Array(a), Array(b)) => Array(a + b), (Dict(a), Dict(b)) => Dict(a + b), - (Template(a), Template(b)) => Template(a + b), - (Template(a), Str(b)) => Template(a + b), - (Str(a), Template(b)) => Template(a + b), + (Node(a), Node(b)) => Node(a + b), + (Node(a), Str(b)) => Node(a + super::Node::Text(b)), + (Str(a), Node(b)) => Node(super::Node::Text(a) + b), (a, b) => mismatch!("cannot join {} with {}", a, b), }) } @@ -84,9 +84,9 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult { (Str(a), Str(b)) => Str(a + b), (Array(a), Array(b)) => Array(a + b), (Dict(a), Dict(b)) => Dict(a + b), - (Template(a), Template(b)) => Template(a + b), - (Template(a), Str(b)) => Template(a + b), - (Str(a), Template(b)) => Template(a + b), + (Node(a), Node(b)) => Node(a + b), + (Node(a), Str(b)) => Node(a + super::Node::Text(b)), + (Str(a), Node(b)) => Node(super::Node::Text(a) + b), (a, b) => { if let (Dyn(a), Dyn(b)) = (&a, &b) { @@ -179,8 +179,8 @@ pub fn mul(lhs: Value, rhs: Value) -> StrResult { (Int(a), Str(b)) => Str(repeat_str(b, a)?), (Array(a), Int(b)) => Array(a.repeat(b)?), (Int(a), Array(b)) => Array(b.repeat(a)?), - (Template(a), Int(b)) => Template(a.repeat(b)?), - (Int(a), Template(b)) => Template(b.repeat(a)?), + (Node(a), Int(b)) => Node(a.repeat(b)?), + (Int(a), Node(b)) => Node(b.repeat(a)?), (a, b) => mismatch!("cannot multiply {} with {}", a, b), }) @@ -297,7 +297,7 @@ pub fn equal(lhs: &Value, rhs: &Value) -> bool { (Str(a), Str(b)) => a == b, (Array(a), Array(b)) => a == b, (Dict(a), Dict(b)) => a == b, - (Template(a), Template(b)) => a == b, + (Node(a), Node(b)) => a == b, (Func(a), Func(b)) => a == b, (Dyn(a), Dyn(b)) => a == b, diff --git a/src/eval/scope.rs b/src/eval/scope.rs index 2290affdb..5178c8191 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Formatter}; use std::iter; use std::rc::Rc; -use super::{Args, EvalContext, Function, Value}; +use super::{Args, Class, Construct, EvalContext, Function, Set, Value}; use crate::diag::TypResult; use crate::util::EcoString; @@ -88,15 +88,6 @@ impl Scope { self.values.insert(var.into(), Rc::new(cell)); } - /// Define a constant function. - pub fn def_func(&mut self, name: impl Into, f: F) - where - F: Fn(&mut EvalContext, &mut Args) -> TypResult + 'static, - { - let name = name.into(); - self.def_const(name.clone(), Function::new(Some(name), f)); - } - /// Define a mutable variable with a value. pub fn def_mut(&mut self, var: impl Into, value: impl Into) { self.values.insert(var.into(), Rc::new(RefCell::new(value.into()))); @@ -107,6 +98,24 @@ impl Scope { self.values.insert(var.into(), slot); } + /// Define a constant function. + pub fn def_func(&mut self, name: &str, f: F) + where + F: Fn(&mut EvalContext, &mut Args) -> TypResult + 'static, + { + let name = EcoString::from(name); + self.def_const(name.clone(), Function::new(Some(name), f)); + } + + /// Define a constant class. + pub fn def_class(&mut self, name: &str) + where + T: Construct + Set + 'static, + { + let name = EcoString::from(name); + self.def_const(name.clone(), Class::new::(name)); + } + /// Look up the value of a variable. pub fn get(&self, var: &str) -> Option<&Slot> { self.values.get(var) @@ -120,6 +129,7 @@ impl Scope { impl Debug for Scope { fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_str("Scope ")?; f.debug_map() .entries(self.values.iter().map(|(k, v)| (k, v.borrow()))) .finish() diff --git a/src/eval/styles.rs b/src/eval/styles.rs new file mode 100644 index 000000000..1c4b17aec --- /dev/null +++ b/src/eval/styles.rs @@ -0,0 +1,292 @@ +use std::any::{Any, TypeId}; +use std::fmt::{self, Debug, Formatter}; +use std::hash::{Hash, Hasher}; +use std::rc::Rc; + +// TODO(style): Possible optimizations: +// - Ref-count map for cheaper cloning and smaller footprint +// - Store map in `Option` to make empty maps non-allocating +// - Store small properties inline + +/// A map of style properties. +#[derive(Default, Clone, Hash)] +pub struct Styles { + map: Vec<(StyleId, Entry)>, +} + +impl Styles { + /// Create a new, empty style map. + pub fn new() -> Self { + Self { map: vec![] } + } + + /// Whether this map contains no styles. + pub fn is_empty(&self) -> bool { + self.map.is_empty() + } + + /// Create a style map with a single property-value pair. + pub fn one(key: P, value: P::Value) -> Self { + let mut styles = Self::new(); + styles.set(key, value); + styles + } + + /// Set the value for a style property. + pub fn set(&mut self, key: P, value: P::Value) { + let id = StyleId::of::

(); + for pair in &mut self.map { + if pair.0 == id { + let prev = pair.1.downcast::().unwrap(); + let folded = P::combine(value, prev.clone()); + pair.1 = Entry::new(key, folded); + return; + } + } + + self.map.push((id, Entry::new(key, value))); + } + + /// Set a value for a style property if it is `Some(_)`. + pub fn set_opt(&mut self, key: P, value: Option) { + if let Some(value) = value { + self.set(key, value); + } + } + + /// Toggle a boolean style property. + pub fn toggle>(&mut self, key: P) { + let id = StyleId::of::

(); + for (i, pair) in self.map.iter_mut().enumerate() { + if pair.0 == id { + self.map.swap_remove(i); + return; + } + } + + self.map.push((id, Entry::new(key, true))); + } + + /// Get the value of a copyable style property. + /// + /// Returns the property's default value if the map does not contain an + /// entry for it. + pub fn get(&self, key: P) -> P::Value + where + P::Value: Copy, + { + self.get_direct(key) + .map(|&v| P::combine(v, P::default())) + .unwrap_or_else(P::default) + } + + /// Get a reference to a style property. + /// + /// Returns a reference to the property's default value if the map does not + /// contain an entry for it. + pub fn get_ref(&self, key: P) -> &P::Value { + self.get_direct(key).unwrap_or_else(|| P::default_ref()) + } + + /// Get a reference to a style directly in this map (no default value). + fn get_direct(&self, _: P) -> Option<&P::Value> { + self.map + .iter() + .find(|pair| pair.0 == StyleId::of::

()) + .and_then(|pair| pair.1.downcast()) + } + + /// Create new styles combining `self` with `outer`. + /// + /// Properties from `self` take precedence over the ones from `outer`. + pub fn chain(&self, outer: &Self) -> Self { + let mut styles = self.clone(); + styles.apply(outer); + styles + } + + /// Apply styles from `outer` in-place. + /// + /// Properties from `self` take precedence over the ones from `outer`. + pub fn apply(&mut self, outer: &Self) { + 'outer: for pair in &outer.map { + for (id, entry) in &mut self.map { + if pair.0 == *id { + entry.apply(&pair.1); + continue 'outer; + } + } + + self.map.push(pair.clone()); + } + } + + /// Keep only those styles that are not also in `other`. + pub fn erase(&mut self, other: &Self) { + self.map.retain(|a| other.map.iter().all(|b| a != b)); + } + + /// Keep only those styles that are also in `other`. + pub fn intersect(&mut self, other: &Self) { + self.map.retain(|a| other.map.iter().any(|b| a == b)); + } + + /// Whether two style maps are equal when filtered down to the given + /// properties. + pub fn compatible(&self, other: &Self, filter: F) -> bool + where + F: Fn(StyleId) -> bool, + { + // TODO(style): Filtered length + one direction equal should suffice. + let f = |e: &&(StyleId, Entry)| filter(e.0); + self.map.iter().filter(f).all(|pair| other.map.contains(pair)) + && other.map.iter().filter(f).all(|pair| self.map.contains(pair)) + } +} + +impl Debug for Styles { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + if f.alternate() { + for pair in &self.map { + writeln!(f, "{:#?}", pair.1)?; + } + Ok(()) + } else { + f.write_str("Styles ")?; + f.debug_set().entries(self.map.iter().map(|pair| &pair.1)).finish() + } + } +} + +impl PartialEq for Styles { + fn eq(&self, other: &Self) -> bool { + self.compatible(other, |_| true) + } +} + +/// An entry for a single style property. +#[derive(Clone)] +pub(crate) struct Entry(Rc); + +impl Entry { + fn new(key: P, value: P::Value) -> Self { + Self(Rc::new((key, value))) + } + + fn downcast(&self) -> Option<&T> { + self.0.as_any().downcast_ref() + } + + fn apply(&mut self, outer: &Self) { + *self = self.0.combine(outer); + } +} + +impl Debug for Entry { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.0.dyn_fmt(f) + } +} + +impl PartialEq for Entry { + fn eq(&self, other: &Self) -> bool { + self.0.dyn_eq(other) + } +} + +impl Hash for Entry { + fn hash(&self, state: &mut H) { + state.write_u64(self.0.hash64()); + } +} + +trait Bounds: 'static { + fn as_any(&self) -> &dyn Any; + fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result; + fn dyn_eq(&self, other: &Entry) -> bool; + fn hash64(&self) -> u64; + fn combine(&self, outer: &Entry) -> Entry; +} + +// `P` is always zero-sized. We only implement the trait for a pair of key and +// associated value so that `P` is a constrained type parameter that we can use +// in `dyn_fmt` to access the property's name. This way, we can effectively +// store the property's name in its vtable instead of having an actual runtime +// string somewhere in `Entry`. +impl Bounds for (P, P::Value) { + fn as_any(&self) -> &dyn Any { + &self.1 + } + + fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result { + if f.alternate() { + write!(f, "#[{} = {:?}]", P::NAME, self.1) + } else { + write!(f, "{}: {:?}", P::NAME, self.1) + } + } + + fn dyn_eq(&self, other: &Entry) -> bool { + if let Some(other) = other.downcast::() { + &self.1 == other + } else { + false + } + } + + fn hash64(&self) -> u64 { + // No need to hash the TypeId since there's only one + // valid value type per property. + fxhash::hash64(&self.1) + } + + fn combine(&self, outer: &Entry) -> Entry { + let outer = outer.downcast::().unwrap(); + let combined = P::combine(self.1.clone(), outer.clone()); + Entry::new(self.0, combined) + } +} + +/// Style property keys. +/// +/// This trait is not intended to be implemented manually, but rather through +/// the `#[properties]` proc-macro. +pub trait Property: Copy + 'static { + /// The type of value that is returned when getting this property from a + /// style map. For example, this could be [`Length`](crate::geom::Length) + /// for a `WIDTH` property. + type Value: Debug + Clone + PartialEq + Hash + 'static; + + /// The name of the property, used for debug printing. + const NAME: &'static str; + + /// The default value of the property. + fn default() -> Self::Value; + + /// A static reference to the default value of the property. + /// + /// This is automatically implemented through lazy-initialization in the + /// `#[properties]` macro. This way, expensive defaults don't need to be + /// recreated all the time. + fn default_ref() -> &'static Self::Value; + + /// Fold the property with an outer value. + /// + /// For example, this would combine a relative font size with an outer + /// absolute font size. + #[allow(unused_variables)] + fn combine(inner: Self::Value, outer: Self::Value) -> Self::Value { + inner + } +} + +/// A unique identifier for a style property. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct StyleId(TypeId); + +impl StyleId { + /// The style id of the property. + pub fn of() -> Self { + Self(TypeId::of::

()) + } +} diff --git a/src/eval/template.rs b/src/eval/template.rs deleted file mode 100644 index 9c57bbf34..000000000 --- a/src/eval/template.rs +++ /dev/null @@ -1,547 +0,0 @@ -use std::convert::TryFrom; -use std::fmt::{self, Debug, Formatter}; -use std::hash::Hash; -use std::mem; -use std::ops::{Add, AddAssign}; -use std::rc::Rc; - -use crate::diag::StrResult; -use crate::geom::{Align, Dir, Length, Linear, Paint, Sides, Size, SpecAxis}; -use crate::layout::{Layout, PackedNode}; -use crate::library::{ - Decoration, DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, - PlacedNode, Spacing, -}; -use crate::style::Style; -use crate::util::EcoString; - -/// A template value: `[*Hi* there]`. -#[derive(Default, Clone)] -pub struct Template(Rc>); - -/// One node in a template. -#[derive(Clone)] -enum TemplateNode { - /// A word space. - Space, - /// A line break. - Linebreak, - /// A paragraph break. - Parbreak, - /// A page break. - Pagebreak(bool), - /// Plain text. - Text(EcoString), - /// Spacing. - Spacing(SpecAxis, Spacing), - /// A decorated template. - Decorated(Decoration, Template), - /// An inline node builder. - Inline(Rc PackedNode>), - /// A block node builder. - Block(Rc PackedNode>), - /// Save the current style. - Save, - /// Restore the last saved style. - Restore, - /// A function that can modify the current style. - Modify(Rc), -} - -impl Template { - /// Create a new, empty template. - pub fn new() -> Self { - Self(Rc::new(vec![])) - } - - /// Create a template from a builder for an inline-level node. - pub fn from_inline(f: F) -> Self - where - F: Fn(&Style) -> T + 'static, - T: Layout + Debug + Hash + 'static, - { - let node = TemplateNode::Inline(Rc::new(move |s| f(s).pack())); - Self(Rc::new(vec![node])) - } - - /// Create a template from a builder for a block-level node. - pub fn from_block(f: F) -> Self - where - F: Fn(&Style) -> T + 'static, - T: Layout + Debug + Hash + 'static, - { - let node = TemplateNode::Block(Rc::new(move |s| f(s).pack())); - Self(Rc::new(vec![node])) - } - - /// Add a word space to the template. - pub fn space(&mut self) { - self.make_mut().push(TemplateNode::Space); - } - - /// Add a line break to the template. - pub fn linebreak(&mut self) { - self.make_mut().push(TemplateNode::Linebreak); - } - - /// Add a paragraph break to the template. - pub fn parbreak(&mut self) { - self.make_mut().push(TemplateNode::Parbreak); - } - - /// Add a page break to the template. - pub fn pagebreak(&mut self, keep: bool) { - self.make_mut().push(TemplateNode::Pagebreak(keep)); - } - - /// Add text to the template. - pub fn text(&mut self, text: impl Into) { - self.make_mut().push(TemplateNode::Text(text.into())); - } - - /// Add text, but in monospace. - pub fn monospace(&mut self, text: impl Into) { - self.save(); - self.modify(|style| style.text_mut().monospace = true); - self.text(text); - self.restore(); - } - - /// Add spacing along an axis. - pub fn spacing(&mut self, axis: SpecAxis, spacing: Spacing) { - self.make_mut().push(TemplateNode::Spacing(axis, spacing)); - } - - /// Register a restorable snapshot. - pub fn save(&mut self) { - self.make_mut().push(TemplateNode::Save); - } - - /// Ensure that later nodes are untouched by style modifications made since - /// the last snapshot. - pub fn restore(&mut self) { - self.make_mut().push(TemplateNode::Restore); - } - - /// Modify the style. - pub fn modify(&mut self, f: F) - where - F: Fn(&mut Style) + 'static, - { - self.make_mut().push(TemplateNode::Modify(Rc::new(f))); - } - - /// Return a new template which is modified from start to end. - pub fn modified(self, f: F) -> Self - where - F: Fn(&mut Style) + 'static, - { - let mut wrapper = Self::new(); - wrapper.save(); - wrapper.modify(f); - wrapper += self; - wrapper.restore(); - wrapper - } - - /// Add a decoration to all contained nodes. - pub fn decorate(self, deco: Decoration) -> Self { - Self(Rc::new(vec![TemplateNode::Decorated(deco, self)])) - } - - /// Pack the template into a layout node. - pub fn pack(&self, style: &Style) -> PackedNode { - if let [TemplateNode::Block(f)] = self.0.as_slice() { - f(style) - } else { - let mut builder = Builder::new(style, false); - builder.template(self); - builder.build_flow().pack() - } - } - - /// Build the layout tree resulting from instantiating the template with the - /// given style. - pub fn to_document(&self, style: &Style) -> DocumentNode { - let mut builder = Builder::new(style, true); - builder.template(self); - builder.build_document() - } - - /// Repeat this template `n` times. - pub fn repeat(&self, n: i64) -> StrResult { - let count = usize::try_from(n) - .ok() - .and_then(|n| self.0.len().checked_mul(n)) - .ok_or_else(|| format!("cannot repeat this template {} times", n))?; - - Ok(Self(Rc::new( - self.0.iter().cloned().cycle().take(count).collect(), - ))) - } - - /// Return a mutable reference to the inner vector. - fn make_mut(&mut self) -> &mut Vec { - Rc::make_mut(&mut self.0) - } -} - -impl Debug for Template { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad("