summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/eval/mod.rs147
-rw-r--r--src/eval/node.rs213
-rw-r--r--src/eval/ops.rs18
-rw-r--r--src/eval/template.rs547
-rw-r--r--src/eval/value.rs51
-rw-r--r--src/eval/walk.rs141
-rw-r--r--src/lib.rs2
-rw-r--r--src/library/align.rs11
-rw-r--r--src/library/deco.rs11
-rw-r--r--src/library/document.rs7
-rw-r--r--src/library/flow.rs39
-rw-r--r--src/library/grid.rs11
-rw-r--r--src/library/image.rs6
-rw-r--r--src/library/mod.rs2
-rw-r--r--src/library/pad.rs6
-rw-r--r--src/library/page.rs99
-rw-r--r--src/library/par.rs66
-rw-r--r--src/library/placed.rs10
-rw-r--r--src/library/shape.rs12
-rw-r--r--src/library/sized.rs14
-rw-r--r--src/library/spacing.rs14
-rw-r--r--src/library/stack.rs44
-rw-r--r--src/library/text.rs70
-rw-r--r--src/library/transform.rs9
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)],
- }
- });
-}
diff --git a/src/lib.rs b/src/lib.rs
index b05a57b3..096995b7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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, &regions).into_iter().map(|c| c.item).collect();
+ padded.layout(ctx, &regions).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.