diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/eval/mod.rs | 147 | ||||
| -rw-r--r-- | src/eval/node.rs | 213 | ||||
| -rw-r--r-- | src/eval/ops.rs | 18 | ||||
| -rw-r--r-- | src/eval/template.rs | 547 | ||||
| -rw-r--r-- | src/eval/value.rs | 51 | ||||
| -rw-r--r-- | src/eval/walk.rs | 141 | ||||
| -rw-r--r-- | src/lib.rs | 2 | ||||
| -rw-r--r-- | src/library/align.rs | 11 | ||||
| -rw-r--r-- | src/library/deco.rs | 11 | ||||
| -rw-r--r-- | src/library/document.rs | 7 | ||||
| -rw-r--r-- | src/library/flow.rs | 39 | ||||
| -rw-r--r-- | src/library/grid.rs | 11 | ||||
| -rw-r--r-- | src/library/image.rs | 6 | ||||
| -rw-r--r-- | src/library/mod.rs | 2 | ||||
| -rw-r--r-- | src/library/pad.rs | 6 | ||||
| -rw-r--r-- | src/library/page.rs | 99 | ||||
| -rw-r--r-- | src/library/par.rs | 66 | ||||
| -rw-r--r-- | src/library/placed.rs | 10 | ||||
| -rw-r--r-- | src/library/shape.rs | 12 | ||||
| -rw-r--r-- | src/library/sized.rs | 14 | ||||
| -rw-r--r-- | src/library/spacing.rs | 14 | ||||
| -rw-r--r-- | src/library/stack.rs | 44 | ||||
| -rw-r--r-- | src/library/text.rs | 70 | ||||
| -rw-r--r-- | src/library/transform.rs | 9 |
24 files changed, 572 insertions, 978 deletions
diff --git a/src/eval/mod.rs b/src/eval/mod.rs index a0c31e98..e0143f6c 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -8,19 +8,17 @@ mod dict; mod value; mod capture; mod function; +mod node; mod ops; mod scope; -mod template; -mod walk; pub use array::*; pub use capture::*; pub use dict::*; pub use function::*; +pub use node::*; pub use scope::*; -pub use template::*; pub use value::*; -pub use walk::*; use std::cell::RefMut; use std::collections::HashMap; @@ -31,29 +29,31 @@ use std::path::PathBuf; use unicode_segmentation::UnicodeSegmentation; use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult}; -use crate::geom::{Angle, Fractional, Length, Relative}; +use crate::geom::{Angle, Fractional, Length, Relative, Spec}; use crate::image::ImageStore; +use crate::library::{GridNode, TrackSizing}; use crate::loading::Loader; use crate::source::{SourceId, SourceStore}; +use crate::style::Style; use crate::syntax::ast::*; use crate::syntax::{Span, Spanned}; -use crate::util::{EcoString, RefMutExt}; +use crate::util::{BoolExt, 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 }) + let node = markup.eval(&mut ctx)?; + Ok(Module { scope: ctx.scopes.top, node }) } /// An evaluated module, ready for importing or instantiation. -#[derive(Debug, Default, Clone)] +#[derive(Debug, 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 node defined by this module. + pub node: Node, } /// The context for evaluation. @@ -70,8 +70,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 style. + pub style: Style, } impl<'a> EvalContext<'a> { @@ -84,7 +84,7 @@ impl<'a> EvalContext<'a> { route: vec![source], modules: HashMap::new(), scopes: Scopes::new(Some(&ctx.std)), - template: Template::new(), + style: ctx.style.clone(), } } @@ -126,7 +126,7 @@ impl<'a> EvalContext<'a> { self.route.pop().unwrap(); // Save the evaluated module. - let module = Module { scope: new_scopes.top, template }; + let module = Module { scope: new_scopes.top, node: template }; self.modules.insert(id, module); Ok(id) @@ -155,19 +155,116 @@ pub trait Eval { } impl Eval for Markup { - type Output = Template; + 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) + let snapshot = ctx.style.clone(); + + let mut result = Node::new(); + for piece in self.nodes() { + result += piece.eval(ctx)?; + } + + ctx.style = snapshot; + Ok(result) + } +} + +impl Eval for MarkupNode { + type Output = Node; + + fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { + Ok(match self { + Self::Space => Node::Space, + Self::Linebreak => Node::Linebreak, + Self::Parbreak => Node::Parbreak, + Self::Strong => { + ctx.style.text_mut().strong.flip(); + Node::new() + } + Self::Emph => { + ctx.style.text_mut().emph.flip(); + 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)?.display(), }) } } +impl Eval for RawNode { + type Output = Node; + + fn eval(&self, _: &mut EvalContext) -> TypResult<Self::Output> { + // TODO(set): Styled in monospace. + let text = Node::Text(self.text.clone()); + 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> { + // TODO(set): Styled in monospace. + let text = Node::Text(self.formula.clone()); + 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> { + // TODO(set): Styled appropriately. + Ok(Node::Block(self.body().eval(ctx)?.into_block())) + } +} + +impl Eval for ListNode { + type Output = Node; + + fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { + let body = self.body().eval(ctx)?; + labelled(ctx, '•'.into(), body) + } +} + +impl Eval for EnumNode { + type Output = Node; + + fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { + let body = self.body().eval(ctx)?; + let label = format_eco!("{}.", self.number().unwrap_or(1)); + labelled(ctx, label, body) + } +} + +/// Evaluate a labelled list / enum. +fn labelled(_: &mut EvalContext, label: EcoString, body: Node) -> TypResult<Node> { + // Create a grid containing the label, a bit of gutter space and then + // the item's body. + // TODO: Switch to em units for gutter once available. + Ok(Node::block(GridNode { + tracks: Spec::new(vec![TrackSizing::Auto; 2], vec![]), + gutter: Spec::new(vec![TrackSizing::Linear(Length::pt(6.0).into())], vec![]), + children: vec![Node::Text(label).into_block(), body.into_block()], + })) +} + impl Eval for Expr { type Output = Value; @@ -177,7 +274,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), @@ -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) @@ -665,7 +762,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..58b29483 --- /dev/null +++ b/src/eval/node.rs @@ -0,0 +1,213 @@ +use std::convert::TryFrom; +use std::fmt::{self, Debug, Formatter}; +use std::hash::Hash; +use std::mem; +use std::ops::{Add, AddAssign}; + +use crate::diag::StrResult; +use crate::geom::SpecAxis; +use crate::layout::{Layout, PackedNode}; +use crate::library::{ + Decoration, DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, Spacing, +}; +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 the block or page level. +#[derive(Clone)] +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, Spacing), + /// An inline node. + Inline(PackedNode), + /// A block node. + Block(PackedNode), + /// A sequence of nodes (which may themselves contain sequences). + Seq(Vec<Self>), +} + +impl Node { + /// Create an empty node. + pub fn new() -> Self { + Self::Seq(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()) + } + + /// Decoration this node. + pub fn decorate(self, _: Decoration) -> Self { + // TODO(set): Actually decorate. + self + } + + /// 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 = NodePacker::new(); + packer.walk(self); + packer.into_block() + } + } + + /// Lift to a document node, the root of the layout tree. + pub fn into_document(self) -> DocumentNode { + let mut packer = NodePacker::new(); + packer.walk(self); + packer.into_document() + } + + /// Repeat this template `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(set): Make more efficient. + Ok(Self::Seq(vec![self.clone(); count])) + } +} + +impl Debug for Node { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad("<node>") + } +} + +impl Default for Node { + fn default() -> Self { + Self::new() + } +} + +impl PartialEq for Node { + fn eq(&self, _: &Self) -> bool { + // TODO(set): Figure out what to do here. + false + } +} + +impl Add for Node { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + // TODO(set): Make more efficient. + Self::Seq(vec![self, rhs]) + } +} + +impl AddAssign for Node { + fn add_assign(&mut self, rhs: Self) { + *self = mem::take(self) + rhs; + } +} + +/// Packs a `Node` into a flow or whole document. +struct NodePacker { + document: Vec<PageNode>, + flow: Vec<FlowChild>, + par: Vec<ParChild>, +} + +impl NodePacker { + fn new() -> Self { + Self { + document: vec![], + flow: vec![], + par: vec![], + } + } + + fn into_block(mut self) -> PackedNode { + self.parbreak(); + FlowNode(self.flow).pack() + } + + fn into_document(mut self) -> DocumentNode { + self.parbreak(); + self.pagebreak(); + DocumentNode(self.document) + } + + fn walk(&mut self, node: Node) { + match node { + Node::Space => { + self.push_inline(ParChild::Text(' '.into())); + } + Node::Linebreak => { + self.push_inline(ParChild::Text('\n'.into())); + } + Node::Parbreak => { + self.parbreak(); + } + Node::Pagebreak => { + self.pagebreak(); + } + Node::Text(text) => { + self.push_inline(ParChild::Text(text)); + } + Node::Spacing(axis, amount) => match axis { + SpecAxis::Horizontal => self.push_inline(ParChild::Spacing(amount)), + SpecAxis::Vertical => self.push_block(FlowChild::Spacing(amount)), + }, + Node::Inline(inline) => { + self.push_inline(ParChild::Node(inline)); + } + Node::Block(block) => { + self.push_block(FlowChild::Node(block)); + } + Node::Seq(list) => { + for node in list { + self.walk(node); + } + } + } + } + + fn parbreak(&mut self) { + let children = mem::take(&mut self.par); + if !children.is_empty() { + self.flow.push(FlowChild::Node(ParNode(children).pack())); + } + } + + fn pagebreak(&mut self) { + let children = mem::take(&mut self.flow); + self.document.push(PageNode(FlowNode(children).pack())); + } + + fn push_inline(&mut self, child: ParChild) { + self.par.push(child); + } + + fn push_block(&mut self, child: FlowChild) { + self.parbreak(); + self.flow.push(child); + } +} 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/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..a6230956 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, 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; @@ -40,8 +42,8 @@ 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 dynamic value. @@ -49,6 +51,22 @@ pub enum Value { } 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,7 +84,7 @@ 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::Dyn(v) => v.type_name(), } @@ -80,14 +98,29 @@ 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 a value in form of a node. + pub fn display(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. + // TODO(set): Styled in monospace. + v => Node::Text(v.repr()), + } } } @@ -114,7 +147,7 @@ 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(v) => Debug::fmt(v, f), Self::Func(v) => Debug::fmt(v, f), Self::Dyn(v) => Debug::fmt(v, f), } @@ -360,7 +393,7 @@ primitive! { Color: "color", Color } primitive! { EcoString: "string", Str } primitive! { Array: "array", Array } primitive! { Dict: "dictionary", Dict } -primitive! { Template: "template", Template } +primitive! { Node: "node", Node } primitive! { Function: "function", Func } impl Cast<Value> for Value { 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)], - } - }); -} @@ -110,7 +110,7 @@ impl Context { /// Execute a source file and produce the resulting page nodes. pub fn execute(&mut self, id: SourceId) -> TypResult<DocumentNode> { let module = self.evaluate(id)?; - Ok(module.template.to_document(&self.style)) + Ok(module.node.into_document()) } /// Typeset a source file into a collection of layouted frames. diff --git a/src/library/align.rs b/src/library/align.rs index 18920369..e2e7addb 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -15,15 +15,10 @@ pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { } let aligns = args.expect::<Spec<_>>("alignment")?; - let body = args.expect::<Template>("body")?; - Ok(Value::Template(Template::from_block(move |style| { - let mut style = style.clone(); - if let Some(x) = aligns.x { - style.par_mut().align = x; - } + let body = args.expect::<Node>("body")?; - body.pack(&style).aligned(aligns) - }))) + // TODO(set): Style paragraphs with x alignment. + Ok(Value::block(body.into_block().aligned(aligns))) } /// A node that aligns its child. diff --git a/src/library/deco.rs b/src/library/deco.rs index cb065689..3576e8fe 100644 --- a/src/library/deco.rs +++ b/src/library/deco.rs @@ -21,8 +21,8 @@ fn line_impl(args: &mut Args, kind: LineKind) -> TypResult<Value> { let thickness = args.named::<Linear>("thickness")?.or_else(|| args.find()); let offset = args.named("offset")?; let extent = args.named("extent")?.unwrap_or_default(); - let body: Template = args.expect("body")?; - Ok(Value::Template(body.decorate(Decoration::Line( + let body: Node = args.expect("body")?; + Ok(Value::Node(body.decorate(Decoration::Line( LineDecoration { kind, stroke, thickness, offset, extent }, )))) } @@ -31,12 +31,9 @@ fn line_impl(args: &mut Args, kind: LineKind) -> TypResult<Value> { pub fn link(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { let url = args.expect::<EcoString>("url")?; let body = args.find().unwrap_or_else(|| { - let mut template = Template::new(); - template.text(url.trim_start_matches("mailto:").trim_start_matches("tel:")); - template + Node::Text(url.trim_start_matches("mailto:").trim_start_matches("tel:").into()) }); - - Ok(Value::Template(body.decorate(Decoration::Link(url)))) + Ok(Value::Node(body.decorate(Decoration::Link(url)))) } /// A decoration for a frame. diff --git a/src/library/document.rs b/src/library/document.rs index fe01d2df..b9a00f9b 100644 --- a/src/library/document.rs +++ b/src/library/document.rs @@ -3,14 +3,11 @@ use super::PageNode; /// The root layout node, a document consisting of top-level page runs. #[derive(Debug, Hash)] -pub struct DocumentNode { - /// The page runs. - pub pages: Vec<PageNode>, -} +pub struct DocumentNode(pub Vec<PageNode>); impl DocumentNode { /// Layout the document into a sequence of frames, one per page. pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> { - self.pages.iter().flat_map(|node| node.layout(ctx)).collect() + self.0.iter().flat_map(|node| node.layout(ctx)).collect() } } diff --git a/src/library/flow.rs b/src/library/flow.rs index 98b518b7..dddd38a4 100644 --- a/src/library/flow.rs +++ b/src/library/flow.rs @@ -1,13 +1,13 @@ use std::fmt::{self, Debug, Formatter}; use super::prelude::*; -use super::{AlignNode, ParNode, PlacedNode, Spacing}; +use super::{AlignNode, PlacedNode, Spacing}; /// `flow`: A vertical flow of paragraphs and other layout nodes. pub fn flow(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { enum Child { Spacing(Spacing), - Any(Template), + Any(Node), } castable! { @@ -17,22 +17,18 @@ pub fn flow(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { Value::Relative(v) => Self::Spacing(Spacing::Linear(v.into())), Value::Linear(v) => Self::Spacing(Spacing::Linear(v)), Value::Fractional(v) => Self::Spacing(Spacing::Fractional(v)), - Value::Template(v) => Self::Any(v), + Value::Node(v) => Self::Any(v), } - let children: Vec<Child> = args.all().collect(); + let children = args + .all() + .map(|child| match child { + Child::Spacing(spacing) => FlowChild::Spacing(spacing), + Child::Any(node) => FlowChild::Node(node.into_block()), + }) + .collect(); - Ok(Value::Template(Template::from_block(move |style| { - let children = children - .iter() - .map(|child| match child { - Child::Spacing(spacing) => FlowChild::Spacing(*spacing), - Child::Any(node) => FlowChild::Node(node.pack(style)), - }) - .collect(); - - FlowNode { children } - }))) + Ok(Value::block(FlowNode(children))) } /// A vertical flow of content consisting of paragraphs and other layout nodes. @@ -40,11 +36,7 @@ pub fn flow(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { /// This node is reponsible for layouting both the top-level content flow and /// the contents of boxes. #[derive(Debug, Hash)] -pub struct FlowNode { - /// The children that compose the flow. There are different kinds of - /// children for different purposes. - pub children: Vec<FlowChild>, -} +pub struct FlowNode(pub Vec<FlowChild>); impl Layout for FlowNode { fn layout( @@ -118,7 +110,7 @@ impl<'a> FlowLayouter<'a> { regions.expand.y = false; Self { - children: &flow.children, + children: &flow.0, expand, full, regions, @@ -175,9 +167,8 @@ impl<'a> FlowLayouter<'a> { } let aligns = Spec::new( - // For non-expanding paragraphs it is crucial that we align the - // whole paragraph according to its internal alignment. - node.downcast::<ParNode>().map_or(Align::Left, |par| par.align), + // TODO(set): Align paragraph according to its internal alignment. + Align::Left, // Vertical align node alignment is respected by the flow node. node.downcast::<AlignNode>() .and_then(|aligned| aligned.aligns.y) diff --git a/src/library/grid.rs b/src/library/grid.rs index 7a9d88c3..ba4ce11f 100644 --- a/src/library/grid.rs +++ b/src/library/grid.rs @@ -39,15 +39,8 @@ pub fn grid(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { row_gutter.unwrap_or(base_gutter), ); - let children: Vec<Template> = args.all().collect(); - - Ok(Value::Template(Template::from_block(move |style| { - GridNode { - tracks: tracks.clone(), - gutter: gutter.clone(), - children: children.iter().map(|child| child.pack(style)).collect(), - } - }))) + let children = args.all().map(Node::into_block).collect(); + Ok(Value::block(GridNode { tracks, gutter, children })) } /// A node that arranges its children in a grid. diff --git a/src/library/image.rs b/src/library/image.rs index 08ed5069..562574f9 100644 --- a/src/library/image.rs +++ b/src/library/image.rs @@ -20,9 +20,9 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { }) })?; - Ok(Value::Template(Template::from_inline(move |_| { - ImageNode { id, fit }.pack().sized(Spec::new(width, height)) - }))) + Ok(Value::inline( + ImageNode { id, fit }.pack().sized(Spec::new(width, height)), + )) } /// An image node. diff --git a/src/library/mod.rs b/src/library/mod.rs index d60a13ea..e1988635 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -26,7 +26,7 @@ mod prelude { pub use std::rc::Rc; pub use crate::diag::{At, TypResult}; - pub use crate::eval::{Args, EvalContext, Smart, Template, Value}; + pub use crate::eval::{Args, EvalContext, Node, Smart, Value}; pub use crate::frame::*; pub use crate::geom::*; pub use crate::layout::*; diff --git a/src/library/pad.rs b/src/library/pad.rs index 681da73e..75fea2e5 100644 --- a/src/library/pad.rs +++ b/src/library/pad.rs @@ -7,7 +7,7 @@ pub fn pad(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { let top = args.named("top")?; let right = args.named("right")?; let bottom = args.named("bottom")?; - let body: Template = args.expect("body")?; + let body: Node = args.expect("body")?; let padding = Sides::new( left.or(all).unwrap_or_default(), top.or(all).unwrap_or_default(), @@ -15,9 +15,7 @@ pub fn pad(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { bottom.or(all).unwrap_or_default(), ); - Ok(Value::Template(Template::from_block(move |style| { - body.pack(style).padded(padding) - }))) + Ok(Value::block(body.into_block().padded(padding))) } /// A node that adds padding to its child. diff --git a/src/library/page.rs b/src/library/page.rs index 0d29ddb6..9def5400 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -1,4 +1,5 @@ use super::prelude::*; +use super::PadNode; use crate::style::{Paper, PaperClass}; /// `page`: Configure pages. @@ -20,90 +21,82 @@ pub fn page(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { let bottom = args.named("bottom")?; let fill = args.named("fill")?; - ctx.template.modify(move |style| { - let page = style.page_mut(); + let page = ctx.style.page_mut(); - if let Some(paper) = paper { - page.class = paper.class(); - page.size = paper.size(); - } - - if let Some(width) = width { - page.class = PaperClass::Custom; - page.size.x = width.unwrap_or(Length::inf()); - } + if let Some(paper) = paper { + page.class = paper.class(); + page.size = paper.size(); + } - if let Some(height) = height { - page.class = PaperClass::Custom; - page.size.y = height.unwrap_or(Length::inf()); - } + if let Some(width) = width { + page.class = PaperClass::Custom; + page.size.x = width.unwrap_or(Length::inf()); + } - if flip.unwrap_or(false) { - std::mem::swap(&mut page.size.x, &mut page.size.y); - } + if let Some(height) = height { + page.class = PaperClass::Custom; + page.size.y = height.unwrap_or(Length::inf()); + } - if let Some(margins) = margins { - page.margins = Sides::splat(margins); - } + if flip.unwrap_or(false) { + std::mem::swap(&mut page.size.x, &mut page.size.y); + } - if let Some(left) = left { - page.margins.left = left; - } + if let Some(margins) = margins { + page.margins = Sides::splat(margins); + } - if let Some(top) = top { - page.margins.top = top; - } + if let Some(left) = left { + page.margins.left = left; + } - if let Some(right) = right { - page.margins.right = right; - } + if let Some(top) = top { + page.margins.top = top; + } - if let Some(bottom) = bottom { - page.margins.bottom = bottom; - } + if let Some(right) = right { + page.margins.right = right; + } - if let Some(fill) = fill { - page.fill = fill; - } - }); + if let Some(bottom) = bottom { + page.margins.bottom = bottom; + } - ctx.template.pagebreak(false); + if let Some(fill) = fill { + page.fill = fill; + } Ok(Value::None) } /// `pagebreak`: Start a new page. pub fn pagebreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> { - let mut template = Template::new(); - template.pagebreak(true); - Ok(Value::Template(template)) + Ok(Value::Node(Node::Pagebreak)) } /// Layouts its children onto one or multiple pages. #[derive(Debug, Hash)] -pub struct PageNode { - /// The size of the page. - pub size: Size, - /// The background fill. - pub fill: Option<Paint>, - /// The node that produces the actual pages. - pub child: PackedNode, -} +pub struct PageNode(pub PackedNode); impl PageNode { /// Layout the page run into a sequence of frames, one per page. pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> { + // TODO(set): Get style from styles. + let style = crate::style::PageStyle::default(); + // When one of the lengths is infinite the page fits its content along // that axis. - let expand = self.size.map(Length::is_finite); - let regions = Regions::repeat(self.size, self.size, expand); + let expand = style.size.map(Length::is_finite); + let regions = Regions::repeat(style.size, style.size, expand); // Layout the child. + let padding = style.margins(); + let padded = PadNode { child: self.0.clone(), padding }.pack(); let mut frames: Vec<_> = - self.child.layout(ctx, ®ions).into_iter().map(|c| c.item).collect(); + padded.layout(ctx, ®ions).into_iter().map(|c| c.item).collect(); // Add background fill if requested. - if let Some(fill) = self.fill { + if let Some(fill) = style.fill { for frame in &mut frames { let shape = Shape::filled(Geometry::Rect(frame.size), fill); Rc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape)); diff --git a/src/library/par.rs b/src/library/par.rs index 6abfa7af..6da1ee95 100644 --- a/src/library/par.rs +++ b/src/library/par.rs @@ -38,44 +38,31 @@ pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { align = Some(v); } - ctx.template.modify(move |style| { - let par = style.par_mut(); + let par = ctx.style.par_mut(); - if let Some(dir) = dir { - par.dir = dir; - par.align = if dir == Dir::LTR { Align::Left } else { Align::Right }; - } - - if let Some(align) = align { - par.align = align; - } + if let Some(dir) = dir { + par.dir = dir; + par.align = if dir == Dir::LTR { Align::Left } else { Align::Right }; + } - if let Some(leading) = leading { - par.leading = leading; - } + if let Some(align) = align { + par.align = align; + } - if let Some(spacing) = spacing { - par.spacing = spacing; - } - }); + if let Some(leading) = leading { + par.leading = leading; + } - ctx.template.parbreak(); + if let Some(spacing) = spacing { + par.spacing = spacing; + } Ok(Value::None) } /// A node that arranges its children into a paragraph. #[derive(Debug, Hash)] -pub struct ParNode { - /// The text direction (either LTR or RTL). - pub dir: Dir, - /// How to align text in its line. - pub align: Align, - /// The spacing to insert between each line. - pub leading: Length, - /// The children to be arranged in a paragraph. - pub children: Vec<ParChild>, -} +pub struct ParNode(pub Vec<ParChild>); impl Layout for ParNode { fn layout( @@ -87,11 +74,14 @@ impl Layout for ParNode { let text = self.collect_text(); // Find out the BiDi embedding levels. - let bidi = BidiInfo::new(&text, Level::from_dir(self.dir)); + // TODO(set): Get dir from styles. + let bidi = BidiInfo::new(&text, Level::from_dir(Dir::LTR)); // Prepare paragraph layout by building a representation on which we can // do line breaking without layouting each and every line from scratch. - let layouter = ParLayouter::new(self, ctx, regions, bidi); + // TODO(set): Get text style from styles. + let style = crate::style::TextStyle::default(); + let layouter = ParLayouter::new(self, ctx, regions, bidi, &style); // Find suitable linebreaks. layouter.layout(ctx, regions.clone()) @@ -123,7 +113,7 @@ impl ParNode { /// The string representation of each child. fn strings(&self) -> impl Iterator<Item = &str> { - self.children.iter().map(|child| match child { + self.0.iter().map(|child| match child { ParChild::Spacing(_) => " ", ParChild::Text(ref piece, ..) => piece, ParChild::Node(..) => "\u{FFFC}", @@ -138,7 +128,7 @@ pub enum ParChild { /// Spacing between other nodes. Spacing(Spacing), /// A run of text and how to align it in its line. - Text(EcoString, Rc<TextStyle>), + Text(EcoString), /// Any child node and how to align it in its line. Node(PackedNode), /// A decoration that applies until a matching `Undecorate`. @@ -151,7 +141,7 @@ impl Debug for ParChild { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::Spacing(v) => write!(f, "Spacing({:?})", v), - Self::Text(text, _) => write!(f, "Text({:?})", text), + Self::Text(text) => write!(f, "Text({:?})", text), Self::Node(node) => node.fmt(f), Self::Decorate(deco) => write!(f, "Decorate({:?})", deco), Self::Undecorate => write!(f, "Undecorate"), @@ -198,6 +188,7 @@ impl<'a> ParLayouter<'a> { ctx: &mut LayoutContext, regions: &Regions, bidi: BidiInfo<'a>, + style: &'a TextStyle, ) -> Self { let mut items = vec![]; let mut ranges = vec![]; @@ -205,7 +196,7 @@ impl<'a> ParLayouter<'a> { let mut decos = vec![]; // Layout the children and collect them into items. - for (range, child) in par.ranges().zip(&par.children) { + for (range, child) in par.ranges().zip(&par.0) { match *child { ParChild::Spacing(Spacing::Linear(v)) => { let resolved = v.resolve(regions.current.x); @@ -216,7 +207,7 @@ impl<'a> ParLayouter<'a> { items.push(ParItem::Fractional(v)); ranges.push(range); } - ParChild::Text(_, ref style) => { + ParChild::Text(_) => { // TODO: Also split by language and script. let mut cursor = range.start; for (level, group) in bidi.levels[range].group_by_key(|&lvl| lvl) { @@ -252,8 +243,9 @@ impl<'a> ParLayouter<'a> { } Self { - align: par.align, - leading: par.leading, + // TODO(set): Get alignment and leading from styles. + align: Align::Left, + leading: Length::pt(6.0), bidi, items, ranges, diff --git a/src/library/placed.rs b/src/library/placed.rs index 722e0035..ce76a969 100644 --- a/src/library/placed.rs +++ b/src/library/placed.rs @@ -6,12 +6,10 @@ pub fn place(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { let aligns = args.find().unwrap_or(Spec::new(Some(Align::Left), None)); let tx = args.named("dx")?.unwrap_or_default(); let ty = args.named("dy")?.unwrap_or_default(); - let body: Template = args.expect("body")?; - Ok(Value::Template(Template::from_block(move |style| { - PlacedNode { - child: body.pack(style).moved(Point::new(tx, ty)).aligned(aligns), - } - }))) + let body: Node = args.expect("body")?; + Ok(Value::block(PlacedNode { + child: body.into_block().moved(Point::new(tx, ty)).aligned(aligns), + })) } /// A node that places its child absolutely. diff --git a/src/library/shape.rs b/src/library/shape.rs index 61c0d6e3..c83a1c49 100644 --- a/src/library/shape.rs +++ b/src/library/shape.rs @@ -76,20 +76,18 @@ fn shape_impl( } // The shape's contents. - let body = args.find::<Template>(); + let body = args.find::<Node>(); - Ok(Value::Template(Template::from_inline(move |style| { + Ok(Value::inline( ShapeNode { kind, fill, stroke, - child: body - .as_ref() - .map(|body| body.pack(style).padded(Sides::splat(padding))), + child: body.map(|body| body.into_block().padded(Sides::splat(padding))), } .pack() - .sized(Spec::new(width, height)) - }))) + .sized(Spec::new(width, height)), + )) } /// Places its child into a sizable and fillable shape. diff --git a/src/library/sized.rs b/src/library/sized.rs index dfdc721d..6d677ca8 100644 --- a/src/library/sized.rs +++ b/src/library/sized.rs @@ -4,18 +4,16 @@ use super::prelude::*; pub fn box_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { let width = args.named("width")?; let height = args.named("height")?; - let body: Template = args.find().unwrap_or_default(); - Ok(Value::Template(Template::from_inline(move |style| { - body.pack(style).sized(Spec::new(width, height)) - }))) + let body: Node = args.find().unwrap_or_default(); + Ok(Value::inline( + body.into_block().sized(Spec::new(width, height)), + )) } /// `block`: Place content into the flow. pub fn block(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - let body: Template = args.find().unwrap_or_default(); - Ok(Value::Template(Template::from_block(move |style| { - body.pack(style) - }))) + let body: Node = args.find().unwrap_or_default(); + Ok(Value::block(body.into_block())) } /// A node that sizes its child. diff --git a/src/library/spacing.rs b/src/library/spacing.rs index 59911dc7..f5de8359 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -2,16 +2,18 @@ use super::prelude::*; /// `h`: Horizontal spacing. pub fn h(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - let mut template = Template::new(); - template.spacing(SpecAxis::Horizontal, args.expect("spacing")?); - Ok(Value::Template(template)) + Ok(Value::Node(Node::Spacing( + SpecAxis::Horizontal, + args.expect("spacing")?, + ))) } /// `v`: Vertical spacing. pub fn v(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - let mut template = Template::new(); - template.spacing(SpecAxis::Vertical, args.expect("spacing")?); - Ok(Value::Template(template)) + Ok(Value::Node(Node::Spacing( + SpecAxis::Vertical, + args.expect("spacing")?, + ))) } /// Kinds of spacing. diff --git a/src/library/stack.rs b/src/library/stack.rs index 2b1371ab..606632af 100644 --- a/src/library/stack.rs +++ b/src/library/stack.rs @@ -7,7 +7,7 @@ use super::{AlignNode, Spacing}; pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { enum Child { Spacing(Spacing), - Any(Template), + Any(Node), } castable! { @@ -17,38 +17,34 @@ pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { Value::Relative(v) => Self::Spacing(Spacing::Linear(v.into())), Value::Linear(v) => Self::Spacing(Spacing::Linear(v)), Value::Fractional(v) => Self::Spacing(Spacing::Fractional(v)), - Value::Template(v) => Self::Any(v), + Value::Node(v) => Self::Any(v), } let dir = args.named("dir")?.unwrap_or(Dir::TTB); let spacing = args.named("spacing")?; - let list: Vec<Child> = args.all().collect(); - - Ok(Value::Template(Template::from_block(move |style| { - let mut children = vec![]; - let mut delayed = None; - - // Build the list of stack children. - for child in &list { - match child { - Child::Spacing(v) => { - children.push(StackChild::Spacing(*v)); - delayed = None; - } - Child::Any(child) => { - if let Some(v) = delayed { - children.push(StackChild::Spacing(v)); - } - let node = child.pack(style); - children.push(StackChild::Node(node)); - delayed = spacing; + let mut children = vec![]; + let mut delayed = None; + + // Build the list of stack children. + for child in args.all() { + match child { + Child::Spacing(v) => { + children.push(StackChild::Spacing(v)); + delayed = None; + } + Child::Any(child) => { + if let Some(v) = delayed { + children.push(StackChild::Spacing(v)); } + + children.push(StackChild::Node(child.into_block())); + delayed = spacing; } } + } - StackNode { dir, children } - }))) + Ok(Value::block(StackNode { dir, children })) } /// A node that stacks its children. diff --git a/src/library/text.rs b/src/library/text.rs index 0790196d..d96f7666 100644 --- a/src/library/text.rs +++ b/src/library/text.rs @@ -12,8 +12,8 @@ use crate::font::{ }; use crate::geom::{Dir, Em, Length, Point, Size}; use crate::style::{ - FontFamily, FontFeatures, NumberPosition, NumberType, NumberWidth, Style, - StylisticSet, TextStyle, + FontFamily, FontFeatures, NumberPosition, NumberType, NumberWidth, StylisticSet, + TextStyle, }; use crate::util::{EcoString, SliceExt}; @@ -179,7 +179,6 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { let slashed_zero = args.named("slashed-zero")?; let fractions = args.named("fractions")?; let features = args.named("features")?; - let body = args.find::<Template>(); macro_rules! set { ($target:expr => $source:expr) => { @@ -189,42 +188,35 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { }; } - let f = move |style_: &mut Style| { - let text = style_.text_mut(); - set!(text.families_mut().list => list.clone()); - set!(text.families_mut().serif => serif.clone()); - set!(text.families_mut().sans_serif => sans_serif.clone()); - set!(text.families_mut().monospace => monospace.clone()); - set!(text.fallback => fallback); - set!(text.variant.style => style); - set!(text.variant.weight => weight); - set!(text.variant.stretch => stretch); - set!(text.size => size.map(|v| v.resolve(text.size))); - set!(text.tracking => tracking); - set!(text.top_edge => top_edge); - set!(text.bottom_edge => bottom_edge); - set!(text.fill => fill); - set!(text.features_mut().kerning => kerning); - set!(text.features_mut().smallcaps => smallcaps); - set!(text.features_mut().alternates => alternates); - set!(text.features_mut().stylistic_set => stylistic_set); - set!(text.features_mut().ligatures.standard => ligatures); - set!(text.features_mut().ligatures.discretionary => discretionary_ligatures); - set!(text.features_mut().ligatures.historical => historical_ligatures); - set!(text.features_mut().numbers.type_ => number_type); - set!(text.features_mut().numbers.width => number_width); - set!(text.features_mut().numbers.position => number_position); - set!(text.features_mut().numbers.slashed_zero => slashed_zero); - set!(text.features_mut().numbers.fractions => fractions); - set!(text.features_mut().raw => features.clone()); - }; - - Ok(if let Some(body) = body { - Value::Template(body.modified(f)) - } else { - ctx.template.modify(f); - Value::None - }) + let text = ctx.style.text_mut(); + set!(text.families_mut().list => list.clone()); + set!(text.families_mut().serif => serif.clone()); + set!(text.families_mut().sans_serif => sans_serif.clone()); + set!(text.families_mut().monospace => monospace.clone()); + set!(text.fallback => fallback); + set!(text.variant.style => style); + set!(text.variant.weight => weight); + set!(text.variant.stretch => stretch); + set!(text.size => size.map(|v| v.resolve(text.size))); + set!(text.tracking => tracking); + set!(text.top_edge => top_edge); + set!(text.bottom_edge => bottom_edge); + set!(text.fill => fill); + set!(text.features_mut().kerning => kerning); + set!(text.features_mut().smallcaps => smallcaps); + set!(text.features_mut().alternates => alternates); + set!(text.features_mut().stylistic_set => stylistic_set); + set!(text.features_mut().ligatures.standard => ligatures); + set!(text.features_mut().ligatures.discretionary => discretionary_ligatures); + set!(text.features_mut().ligatures.historical => historical_ligatures); + set!(text.features_mut().numbers.type_ => number_type); + set!(text.features_mut().numbers.width => number_width); + set!(text.features_mut().numbers.position => number_position); + set!(text.features_mut().numbers.slashed_zero => slashed_zero); + set!(text.features_mut().numbers.fractions => fractions); + set!(text.features_mut().raw => features.clone()); + + Ok(Value::None) } /// Shape text into [`ShapedText`]. diff --git a/src/library/transform.rs b/src/library/transform.rs index 9ba71ecf..1b30c5b0 100644 --- a/src/library/transform.rs +++ b/src/library/transform.rs @@ -26,15 +26,14 @@ pub fn rotate(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { } fn transform_impl(args: &mut Args, transform: Transform) -> TypResult<Value> { - let body: Template = args.expect("body")?; + let body: Node = args.expect("body")?; let origin = args .named("origin")? .unwrap_or(Spec::splat(None)) .unwrap_or(Align::CENTER_HORIZON); - - Ok(Value::Template(Template::from_inline(move |style| { - body.pack(style).transformed(transform, origin) - }))) + Ok(Value::inline( + body.into_block().transformed(transform, origin), + )) } /// A node that transforms its child without affecting layout. |
