summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/class.rs139
-rw-r--r--src/eval/mod.rs194
-rw-r--r--src/eval/node.rs453
-rw-r--r--src/eval/ops.rs18
-rw-r--r--src/eval/scope.rs30
-rw-r--r--src/eval/styles.rs292
-rw-r--r--src/eval/template.rs547
-rw-r--r--src/eval/value.rs61
-rw-r--r--src/eval/walk.rs141
9 files changed, 1117 insertions, 758 deletions
diff --git a/src/eval/class.rs b/src/eval/class.rs
new file mode 100644
index 00000000..c4393b8a
--- /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<Inner<dyn Bounds>>);
+
+/// The unsized structure behind the [`Rc`].
+struct Inner<T: ?Sized> {
+ name: EcoString,
+ shim: T,
+}
+
+impl Class {
+ /// Create a new class.
+ pub fn new<T>(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::<T>(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<Node> {
+ 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("<class ")?;
+ f.write_str(&self.0.name)?;
+ f.write_char('>')
+ }
+}
+
+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<Node>;
+}
+
+/// 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<Node>;
+ fn set(&self, args: &mut Args, styles: &mut Styles) -> TypResult<()>;
+}
+
+struct Shim<T>(PhantomData<T>);
+
+impl<T> Bounds for Shim<T>
+where
+ T: Construct + Set,
+{
+ fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
+ 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 a0c31e98..17cc46ef 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<Module> {
- 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<Self::Output>;
}
/// The context for evaluation.
@@ -70,8 +85,8 @@ pub struct EvalContext<'a> {
pub modules: HashMap<SourceId, Module>,
/// 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;
+impl Eval for Markup {
+ type Output = Node;
- /// Evaluate the expression to the output value.
- fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output>;
+ fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
+ 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 Markup {
- type Output = Template;
+impl Eval for MarkupNode {
+ type Output = Node;
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
- Ok({
- let prev = mem::take(&mut ctx.template);
- ctx.template.save();
- self.walk(ctx)?;
- ctx.template.restore();
- mem::replace(&mut ctx.template, prev)
+ 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<Self::Output> {
+ 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<Self::Output> {
+ 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<Self::Output> {
+ 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<Self::Output> {
+ 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<Self::Output> {
+ 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::Output> {
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<Self::Output> {
+ let class = self.class();
+ let class = class.eval(ctx)?.cast::<Class>().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::<EcoString>().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 00000000..34a4f275
--- /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<T>(node: T) -> Self
+ where
+ T: Layout + Debug + Hash + 'static,
+ {
+ Self::Inline(node.pack())
+ }
+
+ /// Create a block-level node.
+ pub fn block<T>(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<Self> {
+ 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<I: Iterator<Item = Self>>(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<PageNode>,
+ /// The accumulated flow children.
+ flow: Builder<FlowChild>,
+ /// The accumulated paragraph children.
+ par: Builder<ParChild>,
+}
+
+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::<PlacedNode>();
+
+ 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<Styles>) {
+ // 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<T> {
+ /// The intersection of the style properties of all `children`.
+ styles: Styles,
+ /// The accumulated flow or paragraph children.
+ children: Vec<T>,
+ /// The kind of thing that was last added.
+ last: Last<T>,
+}
+
+impl<T> Default for Builder<T> {
+ 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<N> {
+ /// 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<N> Last<N> {
+ /// Transition into the `Any` state and return a soft node to really add
+ /// now if currently in `Soft` state.
+ fn any(&mut self) -> Option<N> {
+ 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 ede1230f..23530c10 100644
--- a/src/eval/ops.rs
+++ b/src/eval/ops.rs
@@ -22,9 +22,9 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> {
(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<Value> {
(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<Value> {
(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 2290affd..5178c819 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<F>(&mut self, name: impl Into<EcoString>, f: F)
- where
- F: Fn(&mut EvalContext, &mut Args) -> TypResult<Value> + '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<EcoString>, value: impl Into<Value>) {
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<F>(&mut self, name: &str, f: F)
+ where
+ F: Fn(&mut EvalContext, &mut Args) -> TypResult<Value> + 'static,
+ {
+ let name = EcoString::from(name);
+ self.def_const(name.clone(), Function::new(Some(name), f));
+ }
+
+ /// Define a constant class.
+ pub fn def_class<T>(&mut self, name: &str)
+ where
+ T: Construct + Set + 'static,
+ {
+ let name = EcoString::from(name);
+ self.def_const(name.clone(), Class::new::<T>(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 00000000..1c4b17ae
--- /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<P: Property>(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<P: Property>(&mut self, key: P, value: P::Value) {
+ let id = StyleId::of::<P>();
+ for pair in &mut self.map {
+ if pair.0 == id {
+ let prev = pair.1.downcast::<P::Value>().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<P: Property>(&mut self, key: P, value: Option<P::Value>) {
+ if let Some(value) = value {
+ self.set(key, value);
+ }
+ }
+
+ /// Toggle a boolean style property.
+ pub fn toggle<P: Property<Value = bool>>(&mut self, key: P) {
+ let id = StyleId::of::<P>();
+ 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<P: Property>(&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<P: Property>(&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<P: Property>(&self, _: P) -> Option<&P::Value> {
+ self.map
+ .iter()
+ .find(|pair| pair.0 == StyleId::of::<P>())
+ .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<F>(&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<dyn Bounds>);
+
+impl Entry {
+ fn new<P: Property>(key: P, value: P::Value) -> Self {
+ Self(Rc::new((key, value)))
+ }
+
+ fn downcast<T: 'static>(&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<H: Hasher>(&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<P: Property> 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::<P::Value>() {
+ &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::<P::Value>().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<P: Property>() -> Self {
+ Self(TypeId::of::<P>())
+ }
+}
diff --git a/src/eval/template.rs b/src/eval/template.rs
deleted file mode 100644
index 9c57bbf3..00000000
--- 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<Vec<TemplateNode>>);
-
-/// 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<dyn Fn(&Style) -> PackedNode>),
- /// A block node builder.
- Block(Rc<dyn Fn(&Style) -> PackedNode>),
- /// Save the current style.
- Save,
- /// Restore the last saved style.
- Restore,
- /// A function that can modify the current style.
- Modify(Rc<dyn Fn(&mut Style)>),
-}
-
-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, T>(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, T>(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<EcoString>) {
- self.make_mut().push(TemplateNode::Text(text.into()));
- }
-
- /// Add text, but in monospace.
- pub fn monospace(&mut self, text: impl Into<EcoString>) {
- 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<F>(&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<F>(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<Self> {
- 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<TemplateNode> {
- Rc::make_mut(&mut self.0)
- }
-}
-
-impl Debug for Template {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.pad("<template>")
- }
-}
-
-impl PartialEq for Template {
- fn eq(&self, other: &Self) -> bool {
- Rc::ptr_eq(&self.0, &other.0)
- }
-}
-
-impl Add for Template {
- type Output = Self;
-
- fn add(mut self, rhs: Self) -> Self::Output {
- self += rhs;
- self
- }
-}
-
-impl AddAssign for Template {
- fn add_assign(&mut self, rhs: Template) {
- let sink = Rc::make_mut(&mut self.0);
- match Rc::try_unwrap(rhs.0) {
- Ok(source) => sink.extend(source),
- Err(rc) => sink.extend(rc.iter().cloned()),
- }
- }
-}
-
-impl Add<EcoString> for Template {
- type Output = Self;
-
- fn add(mut self, rhs: EcoString) -> Self::Output {
- Rc::make_mut(&mut self.0).push(TemplateNode::Text(rhs));
- self
- }
-}
-
-impl Add<Template> for EcoString {
- type Output = Template;
-
- fn add(self, mut rhs: Template) -> Self::Output {
- Rc::make_mut(&mut rhs.0).insert(0, TemplateNode::Text(self));
- rhs
- }
-}
-
-/// Transforms from template to layout representation.
-struct Builder {
- /// The current style.
- style: Style,
- /// Snapshots of the style.
- snapshots: Vec<Style>,
- /// The finished page nodes.
- finished: Vec<PageNode>,
- /// When we are building the top-level layout trees, this contains metrics
- /// of the page. While building a flow, this is `None`.
- page: Option<PageBuilder>,
- /// The currently built flow of paragraphs.
- flow: FlowBuilder,
-}
-
-impl Builder {
- /// Create a new builder with a base style.
- fn new(style: &Style, pages: bool) -> Self {
- Self {
- style: style.clone(),
- snapshots: vec![],
- finished: vec![],
- page: pages.then(|| PageBuilder::new(style, true)),
- flow: FlowBuilder::new(style),
- }
- }
-
- /// Build a template.
- fn template(&mut self, template: &Template) {
- for node in template.0.iter() {
- self.node(node);
- }
- }
-
- /// Build a template node.
- fn node(&mut self, node: &TemplateNode) {
- match node {
- TemplateNode::Save => self.snapshots.push(self.style.clone()),
- TemplateNode::Restore => {
- let style = self.snapshots.pop().unwrap();
- let newpage = style.page != self.style.page;
- self.style = style;
- if newpage {
- self.pagebreak(true, false);
- }
- }
- TemplateNode::Space => self.space(),
- TemplateNode::Linebreak => self.linebreak(),
- TemplateNode::Parbreak => self.parbreak(),
- TemplateNode::Pagebreak(keep) => self.pagebreak(*keep, true),
- TemplateNode::Text(text) => self.text(text),
- TemplateNode::Spacing(axis, amount) => self.spacing(*axis, *amount),
- TemplateNode::Decorated(deco, template) => {
- self.flow.par.push(ParChild::Decorate(deco.clone()));
- self.template(template);
- self.flow.par.push(ParChild::Undecorate);
- }
- TemplateNode::Inline(f) => self.inline(f(&self.style)),
- TemplateNode::Block(f) => self.block(f(&self.style)),
- TemplateNode::Modify(f) => f(&mut self.style),
- }
- }
-
- /// Push a word space into the active paragraph.
- fn space(&mut self) {
- self.flow.par.push_soft(self.make_text_node(' '));
- }
-
- /// Apply a forced line break.
- fn linebreak(&mut self) {
- self.flow.par.push_hard(self.make_text_node('\n'));
- }
-
- /// Apply a forced paragraph break.
- fn parbreak(&mut self) {
- let amount = self.style.par_spacing();
- self.flow.finish_par(&self.style);
- self.flow
- .push_soft(FlowChild::Spacing(Spacing::Linear(amount.into())));
- }
-
- /// Apply a forced page break.
- fn pagebreak(&mut self, keep: bool, hard: bool) {
- if let Some(builder) = &mut self.page {
- let page = mem::replace(builder, PageBuilder::new(&self.style, hard));
- let flow = mem::replace(&mut self.flow, FlowBuilder::new(&self.style));
- self.finished.extend(page.build(flow.build(), keep));
- }
- }
-
- /// Push text into the active paragraph.
- fn text(&mut self, text: impl Into<EcoString>) {
- self.flow.par.push(self.make_text_node(text));
- }
-
- /// Push an inline node into the active paragraph.
- fn inline(&mut self, node: PackedNode) {
- self.flow.par.push(ParChild::Node(node.into()));
- }
-
- /// Push a block node into the active flow, finishing the active paragraph.
- fn block(&mut self, node: PackedNode) {
- let mut is_placed = false;
- if let Some(placed) = node.downcast::<PlacedNode>() {
- is_placed = true;
-
- // This prevents paragraph spacing after the placed node if it
- // is completely out-of-flow.
- if placed.out_of_flow() {
- self.flow.last = Last::None;
- }
- }
-
- self.parbreak();
- self.flow.push(FlowChild::Node(node));
- self.parbreak();
-
- // This prevents paragraph spacing between the placed node and
- // the paragraph below it.
- if is_placed {
- self.flow.last = Last::None;
- }
- }
-
- /// Push spacing into the active paragraph or flow depending on the `axis`.
- fn spacing(&mut self, axis: SpecAxis, spacing: Spacing) {
- match axis {
- SpecAxis::Vertical => {
- self.flow.finish_par(&self.style);
- self.flow.push_hard(FlowChild::Spacing(spacing));
- }
- SpecAxis::Horizontal => {
- self.flow.par.push_hard(ParChild::Spacing(spacing));
- }
- }
- }
-
- /// Finish building and return the created flow.
- fn build_flow(self) -> FlowNode {
- assert!(self.page.is_none());
- self.flow.build()
- }
-
- /// Finish building and return the created layout tree.
- fn build_document(mut self) -> DocumentNode {
- assert!(self.page.is_some());
- self.pagebreak(true, false);
- DocumentNode { pages: self.finished }
- }
-
- /// Construct a text node with the given text and settings from the current
- /// style.
- fn make_text_node(&self, text: impl Into<EcoString>) -> ParChild {
- ParChild::Text(text.into(), Rc::clone(&self.style.text))
- }
-}
-
-struct PageBuilder {
- size: Size,
- padding: Sides<Linear>,
- fill: Option<Paint>,
- hard: bool,
-}
-
-impl PageBuilder {
- fn new(style: &Style, hard: bool) -> Self {
- Self {
- size: style.page.size,
- padding: style.page.margins(),
- fill: style.page.fill,
- hard,
- }
- }
-
- fn build(self, child: FlowNode, keep: bool) -> Option<PageNode> {
- let Self { size, padding, fill, hard } = self;
- (!child.children.is_empty() || (keep && hard)).then(|| PageNode {
- child: child.pack().padded(padding),
- size,
- fill,
- })
- }
-}
-
-struct FlowBuilder {
- children: Vec<FlowChild>,
- last: Last<FlowChild>,
- par: ParBuilder,
-}
-
-impl FlowBuilder {
- fn new(style: &Style) -> Self {
- Self {
- children: vec![],
- last: Last::None,
- par: ParBuilder::new(style),
- }
- }
-
- fn push(&mut self, child: FlowChild) {
- self.children.extend(self.last.any());
- self.children.push(child);
- }
-
- fn push_soft(&mut self, child: FlowChild) {
- self.last.soft(child);
- }
-
- fn push_hard(&mut self, child: FlowChild) {
- self.last.hard();
- self.children.push(child);
- }
-
- fn finish_par(&mut self, style: &Style) {
- let par = mem::replace(&mut self.par, ParBuilder::new(style));
- if let Some(par) = par.build() {
- self.push(par);
- }
- }
-
- fn build(self) -> FlowNode {
- let Self { mut children, par, mut last } = self;
- if let Some(par) = par.build() {
- children.extend(last.any());
- children.push(par);
- }
- FlowNode { children }
- }
-}
-
-struct ParBuilder {
- dir: Dir,
- align: Align,
- leading: Length,
- children: Vec<ParChild>,
- last: Last<ParChild>,
-}
-
-impl ParBuilder {
- fn new(style: &Style) -> Self {
- Self {
- dir: style.par.dir,
- align: style.par.align,
- leading: style.leading(),
- 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(text2, style2) = &child {
- if let Some(ParChild::Text(text1, style1)) = self.children.last_mut() {
- if Rc::ptr_eq(style1, style2) {
- text1.push_str(text2);
- return;
- }
- }
- }
-
- self.children.push(child);
- }
-
- fn build(self) -> Option<FlowChild> {
- let Self { dir, align, leading, children, .. } = self;
- (!children.is_empty())
- .then(|| FlowChild::Node(ParNode { dir, align, leading, children }.pack()))
- }
-}
-
-/// 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/eval/value.rs b/src/eval/value.rs
index 16e8b810..0995ab75 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -1,11 +1,13 @@
use std::any::Any;
use std::cmp::Ordering;
use std::fmt::{self, Debug, Formatter};
+use std::hash::Hash;
use std::rc::Rc;
-use super::{ops, Array, Dict, Function, Template};
+use super::{ops, Array, Class, Dict, Function, Node};
use crate::diag::StrResult;
use crate::geom::{Angle, Color, Fractional, Length, Linear, Relative, RgbaColor};
+use crate::layout::Layout;
use crate::syntax::Spanned;
use crate::util::EcoString;
@@ -24,7 +26,7 @@ pub enum Value {
Float(f64),
/// A length: `12pt`, `3cm`.
Length(Length),
- /// An angle: `1.5rad`, `90deg`.
+ /// An angle: `1.5rad`, `90deg`.
Angle(Angle),
/// A relative value: `50%`.
Relative(Relative),
@@ -40,15 +42,33 @@ pub enum Value {
Array(Array),
/// A dictionary value: `(color: #f79143, pattern: dashed)`.
Dict(Dict),
- /// A template value: `[*Hi* there]`.
- Template(Template),
+ /// A node value: `[*Hi* there]`.
+ Node(Node),
/// An executable function.
Func(Function),
+ /// A class of nodes.
+ Class(Class),
/// A dynamic value.
Dyn(Dynamic),
}
impl Value {
+ /// Create an inline-level node value.
+ pub fn inline<T>(node: T) -> Self
+ where
+ T: Layout + Debug + Hash + 'static,
+ {
+ Self::Node(Node::inline(node))
+ }
+
+ /// Create a block-level node value.
+ pub fn block<T>(node: T) -> Self
+ where
+ T: Layout + Debug + Hash + 'static,
+ {
+ Self::Node(Node::block(node))
+ }
+
/// The name of the stored value's type.
pub fn type_name(&self) -> &'static str {
match self {
@@ -66,8 +86,9 @@ impl Value {
Self::Str(_) => EcoString::TYPE_NAME,
Self::Array(_) => Array::TYPE_NAME,
Self::Dict(_) => Dict::TYPE_NAME,
- Self::Template(_) => Template::TYPE_NAME,
+ Self::Node(_) => Node::TYPE_NAME,
Self::Func(_) => Function::TYPE_NAME,
+ Self::Class(_) => Class::TYPE_NAME,
Self::Dyn(v) => v.type_name(),
}
}
@@ -80,14 +101,28 @@ impl Value {
T::cast(self)
}
+ /// Join the value with another value.
+ pub fn join(self, rhs: Self) -> StrResult<Self> {
+ ops::join(self, rhs)
+ }
+
/// Return the debug representation of the value.
pub fn repr(&self) -> EcoString {
format_eco!("{:?}", self)
}
- /// Join the value with another value.
- pub fn join(self, rhs: Self) -> StrResult<Self> {
- ops::join(self, rhs)
+ /// Return the display representation of the value.
+ pub fn show(self) -> Node {
+ match self {
+ Value::None => Node::new(),
+ Value::Int(v) => Node::Text(format_eco!("{}", v)),
+ Value::Float(v) => Node::Text(format_eco!("{}", v)),
+ Value::Str(v) => Node::Text(v),
+ Value::Node(v) => v,
+ // For values which can't be shown "naturally", we print the
+ // representation in monospace.
+ v => Node::Text(v.repr()).monospaced(),
+ }
}
}
@@ -114,8 +149,9 @@ impl Debug for Value {
Self::Str(v) => Debug::fmt(v, f),
Self::Array(v) => Debug::fmt(v, f),
Self::Dict(v) => Debug::fmt(v, f),
- Self::Template(v) => Debug::fmt(v, f),
+ Self::Node(_) => f.pad("<template>"),
Self::Func(v) => Debug::fmt(v, f),
+ Self::Class(v) => Debug::fmt(v, f),
Self::Dyn(v) => Debug::fmt(v, f),
}
}
@@ -354,14 +390,15 @@ primitive! { f64: "float", Float, Int(v) => v as f64 }
primitive! { Length: "length", Length }
primitive! { Angle: "angle", Angle }
primitive! { Relative: "relative", Relative }
-primitive! { Linear: "linear", Linear, Length(v) => v.into(), Relative(v) => v.into() }
-primitive! { Fractional: "fractional", Fractional }
+primitive! { Linear: "relative length", Linear, Length(v) => v.into(), Relative(v) => v.into() }
+primitive! { Fractional: "fractional length", Fractional }
primitive! { Color: "color", Color }
primitive! { EcoString: "string", Str }
primitive! { Array: "array", Array }
primitive! { Dict: "dictionary", Dict }
-primitive! { Template: "template", Template }
+primitive! { Node: "template", Node }
primitive! { Function: "function", Func }
+primitive! { Class: "class", Class }
impl Cast<Value> for Value {
fn is(_: &Value) -> bool {
diff --git a/src/eval/walk.rs b/src/eval/walk.rs
deleted file mode 100644
index 0898f20b..00000000
--- a/src/eval/walk.rs
+++ /dev/null
@@ -1,141 +0,0 @@
-use std::rc::Rc;
-
-use super::{Eval, EvalContext, Template, Value};
-use crate::diag::TypResult;
-use crate::geom::Spec;
-use crate::layout::Layout;
-use crate::library::{GridNode, ParChild, ParNode, TrackSizing};
-use crate::syntax::ast::*;
-use crate::util::{BoolExt, EcoString};
-
-/// Walk markup, filling the currently built template.
-pub trait Walk {
- /// Walk the node.
- fn walk(&self, ctx: &mut EvalContext) -> TypResult<()>;
-}
-
-impl Walk for Markup {
- fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
- for node in self.nodes() {
- node.walk(ctx)?;
- }
- Ok(())
- }
-}
-
-impl Walk for MarkupNode {
- fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
- match self {
- Self::Space => ctx.template.space(),
- Self::Linebreak => ctx.template.linebreak(),
- Self::Parbreak => ctx.template.parbreak(),
- Self::Strong => ctx.template.modify(|s| s.text_mut().strong.flip()),
- Self::Emph => ctx.template.modify(|s| s.text_mut().emph.flip()),
- Self::Text(text) => ctx.template.text(text),
- Self::Raw(raw) => raw.walk(ctx)?,
- Self::Math(math) => math.walk(ctx)?,
- Self::Heading(heading) => heading.walk(ctx)?,
- Self::List(list) => list.walk(ctx)?,
- Self::Enum(enum_) => enum_.walk(ctx)?,
- Self::Expr(expr) => match expr.eval(ctx)? {
- Value::None => {}
- Value::Int(v) => ctx.template.text(format_eco!("{}", v)),
- Value::Float(v) => ctx.template.text(format_eco!("{}", v)),
- Value::Str(v) => ctx.template.text(v),
- Value::Template(v) => ctx.template += v,
- // For values which can't be shown "naturally", we print the
- // representation in monospace.
- other => ctx.template.monospace(other.repr()),
- },
- }
- Ok(())
- }
-}
-
-impl Walk for RawNode {
- fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
- if self.block {
- ctx.template.parbreak();
- }
-
- ctx.template.monospace(&self.text);
-
- if self.block {
- ctx.template.parbreak();
- }
-
- Ok(())
- }
-}
-
-impl Walk for MathNode {
- fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
- if self.display {
- ctx.template.parbreak();
- }
-
- ctx.template.monospace(self.formula.trim());
-
- if self.display {
- ctx.template.parbreak();
- }
-
- Ok(())
- }
-}
-
-impl Walk for HeadingNode {
- fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
- let level = self.level();
- let body = self.body().eval(ctx)?;
-
- ctx.template.parbreak();
- ctx.template.save();
- ctx.template.modify(move |style| {
- let text = style.text_mut();
- let upscale = (1.6 - 0.1 * level as f64).max(0.75);
- text.size *= upscale;
- text.strong = true;
- });
- ctx.template += body;
- ctx.template.restore();
- ctx.template.parbreak();
-
- Ok(())
- }
-}
-
-impl Walk for ListNode {
- fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
- let body = self.body().eval(ctx)?;
- walk_item(ctx, EcoString::from('•'), body);
- Ok(())
- }
-}
-
-impl Walk for EnumNode {
- fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
- let body = self.body().eval(ctx)?;
- let label = format_eco!("{}.", self.number().unwrap_or(1));
- walk_item(ctx, label, body);
- Ok(())
- }
-}
-
-fn walk_item(ctx: &mut EvalContext, label: EcoString, body: Template) {
- ctx.template += Template::from_block(move |style| {
- let label = Layout::pack(ParNode {
- dir: style.par.dir,
- align: style.par.align,
- leading: style.leading(),
- children: vec![ParChild::Text(label.clone(), Rc::clone(&style.text))],
- });
-
- let spacing = style.text.size / 2.0;
- GridNode {
- tracks: Spec::new(vec![TrackSizing::Auto; 2], vec![]),
- gutter: Spec::new(vec![TrackSizing::Linear(spacing.into())], vec![]),
- children: vec![label, body.pack(style)],
- }
- });
-}