diff --git a/bench/src/bench.rs b/bench/src/bench.rs index 947a02c52..b8093d531 100644 --- a/bench/src/bench.rs +++ b/bench/src/bench.rs @@ -33,18 +33,18 @@ fn benchmarks(c: &mut Criterion) { // Prepare intermediate results and run warm. let state = State::default(); - let tree = parse(COMA).output; - let document = eval(&tree, Rc::clone(&env), state.clone()).output; - let layouts = layout(&document, Rc::clone(&env)); + let syntax_tree = parse(COMA).output; + let layout_tree = eval(&syntax_tree, Rc::clone(&env), state.clone()).output; + let frames = layout(&layout_tree, Rc::clone(&env)); // Bench! bench!("parse-coma": parse(COMA)); - bench!("eval-coma": eval(&tree, Rc::clone(&env), state.clone())); - bench!("layout-coma": layout(&document, Rc::clone(&env))); + bench!("eval-coma": eval(&syntax_tree, Rc::clone(&env), state.clone())); + bench!("layout-coma": layout(&layout_tree, Rc::clone(&env))); bench!("typeset-coma": typeset(COMA, Rc::clone(&env), state.clone())); let env = env.borrow(); - bench!("export-pdf-coma": pdf::export(&layouts, &env)); + bench!("export-pdf-coma": pdf::export(&frames, &env)); } criterion_group!(benches, benchmarks); diff --git a/src/diag.rs b/src/diag.rs index 2872ce5a1..a7bea6f1b 100644 --- a/src/diag.rs +++ b/src/diag.rs @@ -1,8 +1,6 @@ //! Diagnostics and decorations for source code. //! -//! There are no fatal errors. The document will always compile and yield a -//! layout on a best effort process, but diagnostics are nevertheless generated -//! for incorrect things. +//! Errors are never fatal, the document will always compile and yield a layout. use crate::syntax::SpanVec; use std::fmt::{self, Display, Formatter}; @@ -21,22 +19,9 @@ impl Pass { pub fn new(output: T, feedback: Feedback) -> Self { Self { output, feedback } } - - /// Create a new pass with empty feedback. - pub fn okay(output: T) -> Self { - Self { output, feedback: Feedback::new() } - } - - /// Map the output type and keep the feedback data. - pub fn map(self, f: impl FnOnce(T) -> U) -> Pass { - Pass { - output: f(self.output), - feedback: self.feedback, - } - } } -/// Diagnostic and semantic syntax highlighting data. +/// Diagnostics and semantic syntax highlighting information. #[derive(Debug, Default, Clone, Eq, PartialEq)] pub struct Feedback { /// Diagnostics about the source code. @@ -64,7 +49,7 @@ impl Feedback { } } -/// A diagnostic that arose in parsing or layouting. +/// A diagnostic with severity level and message. #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct Diag { diff --git a/src/env.rs b/src/env.rs index a3e147155..58c44a5fc 100644 --- a/src/env.rs +++ b/src/env.rs @@ -42,12 +42,13 @@ impl ResourceLoader { Self { paths: HashMap::new(), entries: vec![] } } - /// Load a resource from a path. - pub fn load( - &mut self, - path: impl AsRef, - parse: impl FnOnce(Vec) -> Option, - ) -> Option<(ResourceId, &R)> { + /// Load a resource from a path and parse it. + pub fn load(&mut self, path: P, parse: F) -> Option<(ResourceId, &R)> + where + P: AsRef, + F: FnOnce(Vec) -> Option, + R: 'static, + { let path = path.as_ref(); let id = match self.paths.entry(path.to_owned()) { Entry::Occupied(entry) => *entry.get(), diff --git a/src/eval/args.rs b/src/eval/call.rs similarity index 76% rename from src/eval/args.rs rename to src/eval/call.rs index ea248ec42..186e7630d 100644 --- a/src/eval/args.rs +++ b/src/eval/call.rs @@ -1,14 +1,37 @@ use super::*; +use crate::diag::Deco; -/// Evaluated arguments to a function. -#[derive(Debug)] -pub struct Args { - span: Span, - pos: SpanVec, - named: Vec<(Spanned, Spanned)>, +impl Eval for Spanned<&ExprCall> { + type Output = Value; + + fn eval(self, ctx: &mut EvalContext) -> Self::Output { + let name = &self.v.name.v; + let span = self.v.name.span; + + if let Some(value) = ctx.state.scope.get(name) { + if let Value::Func(func) = value { + let func = func.clone(); + ctx.deco(Deco::Resolved.with_span(span)); + + let mut args = self.v.args.as_ref().eval(ctx); + let returned = func(ctx, &mut args); + args.finish(ctx); + + return returned; + } else { + let ty = value.type_name(); + ctx.diag(error!(span, "a value of type {} is not callable", ty)); + } + } else if !name.is_empty() { + ctx.diag(error!(span, "unknown function")); + } + + ctx.deco(Deco::Unresolved.with_span(span)); + Value::Error + } } -impl Eval for Spanned<&Arguments> { +impl Eval for Spanned<&ExprArgs> { type Output = Args; fn eval(self, ctx: &mut EvalContext) -> Self::Output { @@ -33,6 +56,14 @@ impl Eval for Spanned<&Arguments> { } } +/// Evaluated arguments to a function. +#[derive(Debug)] +pub struct Args { + span: Span, + pos: SpanVec, + named: Vec<(Spanned, Spanned)>, +} + impl Args { /// Find the first convertible positional argument. pub fn find(&mut self, ctx: &mut EvalContext) -> Option @@ -66,9 +97,8 @@ impl Args { self.pos.iter_mut().filter_map(move |slot| try_cast(ctx, slot)) } - /// Convert the value for the given named argument. - /// - /// Generates an error if the conversion fails. + /// Convert the value for the given named argument, producing an error if + /// the conversion fails. pub fn get<'a, T>(&mut self, ctx: &mut EvalContext, name: &str) -> Option where T: Cast>, @@ -78,7 +108,7 @@ impl Args { cast(ctx, value) } - /// Generate "unexpected argument" errors for all remaining arguments. + /// Produce "unexpected argument" errors for all remaining arguments. pub fn finish(self, ctx: &mut EvalContext) { let a = self.pos.iter().map(|v| v.as_ref()); let b = self.named.iter().map(|(k, v)| (&v.v).with_span(k.span.join(v.span))); diff --git a/src/eval/context.rs b/src/eval/context.rs new file mode 100644 index 000000000..ece33146e --- /dev/null +++ b/src/eval/context.rs @@ -0,0 +1,298 @@ +use std::any::Any; +use std::rc::Rc; + +use fontdock::FontStyle; + +use super::*; +use crate::diag::Diag; +use crate::diag::{Deco, Feedback, Pass}; +use crate::env::SharedEnv; +use crate::geom::{ChildAlign, Dir, Gen, LayoutDirs, Length, Linear, Sides, Size}; +use crate::layout::{ + Expansion, Node, NodePad, NodePages, NodePar, NodeSpacing, NodeStack, NodeText, Tree, +}; + +/// The context for evaluation. +#[derive(Debug)] +pub struct EvalContext { + /// The environment from which resources are gathered. + pub env: SharedEnv, + /// The active evaluation state. + pub state: State, + /// The accumulated feedback. + feedback: Feedback, + /// The finished page runs. + runs: Vec, + /// The stack of logical groups (paragraphs and such). + /// + /// Each entry contains metadata about the group and nodes that are at the + /// same level as the group, which will return to `inner` once the group is + /// finished. + groups: Vec<(Box, Vec)>, + /// The nodes in the current innermost group + /// (whose metadata is in `groups.last()`). + inner: Vec, +} + +impl EvalContext { + /// Create a new evaluation context with a base state. + pub fn new(env: SharedEnv, state: State) -> Self { + Self { + env, + state, + groups: vec![], + inner: vec![], + runs: vec![], + feedback: Feedback::new(), + } + } + + /// Finish evaluation and return the created document. + pub fn finish(self) -> Pass { + assert!(self.groups.is_empty(), "unfinished group"); + Pass::new(Tree { runs: self.runs }, self.feedback) + } + + /// Add a diagnostic to the feedback. + pub fn diag(&mut self, diag: Spanned) { + self.feedback.diags.push(diag); + } + + /// Add a decoration to the feedback. + pub fn deco(&mut self, deco: Spanned) { + self.feedback.decos.push(deco); + } + + /// Push a layout node to the active group. + /// + /// Spacing nodes will be handled according to their [`Softness`]. + pub fn push(&mut self, node: impl Into) { + let node = node.into(); + + if let Node::Spacing(this) = node { + if this.softness == Softness::Soft && self.inner.is_empty() { + return; + } + + if let Some(&Node::Spacing(other)) = self.inner.last() { + if this.softness > other.softness { + self.inner.pop(); + } else if this.softness == Softness::Soft { + return; + } + } + } + + self.inner.push(node); + } + + /// Start a page group based on the active page state. + /// + /// The `softness` is a hint on whether empty pages should be kept in the + /// output. + /// + /// This also starts an inner paragraph. + pub fn start_page_group(&mut self, softness: Softness) { + self.start_group(PageGroup { + size: self.state.page.size, + padding: self.state.page.margins(), + dirs: self.state.dirs, + align: self.state.align, + softness, + }); + self.start_par_group(); + } + + /// End a page group, returning its [`Softness`]. + /// + /// Whether the page is kept when it's empty is decided by `keep_empty` + /// based on its softness. If kept, the page is pushed to the finished page + /// runs. + /// + /// This also ends an inner paragraph. + pub fn end_page_group(&mut self, keep_empty: F) -> Softness + where + F: FnOnce(Softness) -> bool, + { + self.end_par_group(); + let (group, children) = self.end_group::(); + if !children.is_empty() || keep_empty(group.softness) { + self.runs.push(NodePages { + size: group.size, + child: Node::any(NodePad { + padding: group.padding, + child: Node::any(NodeStack { + dirs: group.dirs, + align: group.align, + expansion: Gen::uniform(Expansion::Fill), + children, + }), + }), + }) + } + group.softness + } + + /// Start a content group. + /// + /// This also starts an inner paragraph. + pub fn start_content_group(&mut self) { + self.start_group(ContentGroup); + self.start_par_group(); + } + + /// End a content group and return the resulting nodes. + /// + /// This also ends an inner paragraph. + pub fn end_content_group(&mut self) -> Vec { + self.end_par_group(); + self.end_group::().1 + } + + /// Start a paragraph group based on the active text state. + pub fn start_par_group(&mut self) { + let em = self.state.font.font_size(); + self.start_group(ParGroup { + dirs: self.state.dirs, + align: self.state.align, + line_spacing: self.state.par.line_spacing.resolve(em), + }); + } + + /// End a paragraph group and push it to its parent group if it's not empty. + pub fn end_par_group(&mut self) { + let (group, children) = self.end_group::(); + if !children.is_empty() { + self.push(NodePar { + dirs: group.dirs, + align: group.align, + // FIXME: This is a hack and should be superseded by something + // better. + cross_expansion: if self.groups.len() <= 1 { + Expansion::Fill + } else { + Expansion::Fit + }, + line_spacing: group.line_spacing, + children, + }); + } + } + + /// Start a layouting group. + /// + /// All further calls to [`push`](Self::push) will collect nodes for this group. + /// The given metadata will be returned alongside the collected nodes + /// in a matching call to [`end_group`](Self::end_group). + fn start_group(&mut self, meta: T) { + self.groups.push((Box::new(meta), std::mem::take(&mut self.inner))); + } + + /// End a layouting group started with [`start_group`](Self::start_group). + /// + /// This returns the stored metadata and the collected nodes. + #[track_caller] + fn end_group(&mut self) -> (T, Vec) { + if let Some(&Node::Spacing(spacing)) = self.inner.last() { + if spacing.softness == Softness::Soft { + self.inner.pop(); + } + } + + let (any, outer) = self.groups.pop().expect("no pushed group"); + let group = *any.downcast::().expect("bad group type"); + (group, std::mem::replace(&mut self.inner, outer)) + } + + /// Set the directions if they would apply to different axes, producing an + /// appropriate error otherwise. + pub fn set_dirs(&mut self, new: Gen>>) { + let dirs = Gen::new( + new.main.map(|s| s.v).unwrap_or(self.state.dirs.main), + new.cross.map(|s| s.v).unwrap_or(self.state.dirs.cross), + ); + + if dirs.main.axis() != dirs.cross.axis() { + self.state.dirs = dirs; + } else { + for dir in new.main.iter().chain(new.cross.iter()) { + self.diag(error!(dir.span, "aligned axis")); + } + } + } + + /// Apply a forced line break. + pub fn apply_linebreak(&mut self) { + self.end_par_group(); + self.start_par_group(); + } + + /// Apply a forced paragraph break. + pub fn apply_parbreak(&mut self) { + self.end_par_group(); + let em = self.state.font.font_size(); + self.push(NodeSpacing { + amount: self.state.par.par_spacing.resolve(em), + softness: Softness::Soft, + }); + self.start_par_group(); + } + + /// Construct a text node from the given string based on the active text + /// state. + pub fn make_text_node(&self, text: String) -> NodeText { + let mut variant = self.state.font.variant; + + if self.state.font.strong { + variant.weight = variant.weight.thicken(300); + } + + if self.state.font.emph { + variant.style = match variant.style { + FontStyle::Normal => FontStyle::Italic, + FontStyle::Italic => FontStyle::Normal, + FontStyle::Oblique => FontStyle::Normal, + } + } + + NodeText { + text, + align: self.state.align, + dir: self.state.dirs.cross, + font_size: self.state.font.font_size(), + families: Rc::clone(&self.state.font.families), + variant, + } + } +} + +/// Defines how an item interacts with surrounding items. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum Softness { + /// A soft item can be skipped in some circumstances. + Soft, + /// A hard item is always retained. + Hard, +} + +/// A group for a page run. +#[derive(Debug)] +struct PageGroup { + size: Size, + padding: Sides, + dirs: LayoutDirs, + align: ChildAlign, + softness: Softness, +} + +/// A group for generic content. +#[derive(Debug)] +struct ContentGroup; + +/// A group for a paragraph. +#[derive(Debug)] +struct ParGroup { + dirs: LayoutDirs, + align: ChildAlign, + line_spacing: Length, +} diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 62bd444c5..20d32e846 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -1,36 +1,32 @@ -//! Evaluation of syntax trees. +//! Evaluation of syntax trees into layout trees. #[macro_use] mod value; -mod args; +mod call; +mod context; mod scope; mod state; -pub use args::*; +pub use call::*; +pub use context::*; pub use scope::*; pub use state::*; pub use value::*; -use std::any::Any; use std::rc::Rc; -use fontdock::FontStyle; - use crate::color::Color; -use crate::diag::Diag; -use crate::diag::{Deco, Feedback, Pass}; +use crate::diag::Pass; use crate::env::SharedEnv; -use crate::geom::{BoxAlign, Dir, Flow, Gen, Length, Linear, Relative, Sides, Size}; -use crate::layout::{ - Document, Expansion, LayoutNode, Pad, Pages, Par, Spacing, Stack, Text, -}; +use crate::geom::{Gen, Length, Relative}; +use crate::layout::{self, Expansion, NodeSpacing, NodeStack}; use crate::syntax::*; -/// Evaluate a syntax tree into a document. +/// Evaluate a syntax tree into a layout tree. /// /// The given `state` is the base state that may be updated over the course of /// evaluation. -pub fn eval(tree: &SynTree, env: SharedEnv, state: State) -> Pass { +pub fn eval(tree: &Tree, env: SharedEnv, state: State) -> Pass { let mut ctx = EvalContext::new(env, state); ctx.start_page_group(Softness::Hard); tree.eval(&mut ctx); @@ -38,285 +34,6 @@ pub fn eval(tree: &SynTree, env: SharedEnv, state: State) -> Pass { ctx.finish() } -/// The context for evaluation. -#[derive(Debug)] -pub struct EvalContext { - /// The environment from which resources are gathered. - pub env: SharedEnv, - /// The active evaluation state. - pub state: State, - /// The accumulated feedback. - feedback: Feedback, - /// The finished page runs. - runs: Vec, - /// The stack of logical groups (paragraphs and such). - /// - /// Each entry contains metadata about the group and nodes that are at the - /// same level as the group, which will return to `inner` once the group is - /// finished. - groups: Vec<(Box, Vec)>, - /// The nodes in the current innermost group - /// (whose metadata is in `groups.last()`). - inner: Vec, -} - -impl EvalContext { - /// Create a new evaluation context with a base state. - pub fn new(env: SharedEnv, state: State) -> Self { - Self { - env, - state, - groups: vec![], - inner: vec![], - runs: vec![], - feedback: Feedback::new(), - } - } - - /// Finish evaluation and return the created document. - pub fn finish(self) -> Pass { - assert!(self.groups.is_empty(), "unfinished group"); - Pass::new(Document { runs: self.runs }, self.feedback) - } - - /// Add a diagnostic to the feedback. - pub fn diag(&mut self, diag: Spanned) { - self.feedback.diags.push(diag); - } - - /// Add a decoration to the feedback. - pub fn deco(&mut self, deco: Spanned) { - self.feedback.decos.push(deco); - } - - /// Push a layout node to the active group. - /// - /// Spacing nodes will be handled according to their [`Softness`]. - pub fn push(&mut self, node: impl Into) { - let node = node.into(); - - if let LayoutNode::Spacing(this) = node { - if this.softness == Softness::Soft && self.inner.is_empty() { - return; - } - - if let Some(&LayoutNode::Spacing(other)) = self.inner.last() { - if this.softness > other.softness { - self.inner.pop(); - } else if this.softness == Softness::Soft { - return; - } - } - } - - self.inner.push(node); - } - - /// Start a page group based on the active page state. - /// - /// The `softness` is a hint on whether empty pages should be kept in the - /// output. - /// - /// This also starts an inner paragraph. - pub fn start_page_group(&mut self, softness: Softness) { - self.start_group(PageGroup { - size: self.state.page.size, - padding: self.state.page.margins(), - flow: self.state.flow, - align: self.state.align, - softness, - }); - self.start_par_group(); - } - - /// End a page group, returning its [`Softness`]. - /// - /// Whether the page is kept when it's empty is decided by `keep_empty` - /// based on its softness. If kept, the page is pushed to the finished page - /// runs. - /// - /// This also ends an inner paragraph. - pub fn end_page_group( - &mut self, - keep_empty: impl FnOnce(Softness) -> bool, - ) -> Softness { - self.end_par_group(); - let (group, children) = self.end_group::(); - if !children.is_empty() || keep_empty(group.softness) { - self.runs.push(Pages { - size: group.size, - child: LayoutNode::dynamic(Pad { - padding: group.padding, - child: LayoutNode::dynamic(Stack { - flow: group.flow, - align: group.align, - expansion: Gen::uniform(Expansion::Fill), - children, - }), - }), - }) - } - group.softness - } - - /// Start a content group. - /// - /// This also starts an inner paragraph. - pub fn start_content_group(&mut self) { - self.start_group(ContentGroup); - self.start_par_group(); - } - - /// End a content group and return the resulting nodes. - /// - /// This also ends an inner paragraph. - pub fn end_content_group(&mut self) -> Vec { - self.end_par_group(); - self.end_group::().1 - } - - /// Start a paragraph group based on the active text state. - pub fn start_par_group(&mut self) { - let em = self.state.font.font_size(); - self.start_group(ParGroup { - flow: self.state.flow, - align: self.state.align, - line_spacing: self.state.par.line_spacing.resolve(em), - }); - } - - /// End a paragraph group and push it to its parent group if it's not empty. - pub fn end_par_group(&mut self) { - let (group, children) = self.end_group::(); - if !children.is_empty() { - // FIXME: This is a hack and should be superseded by something - // better. - let cross_expansion = Expansion::fill_if(self.groups.len() <= 1); - self.push(Par { - flow: group.flow, - align: group.align, - cross_expansion, - line_spacing: group.line_spacing, - children, - }); - } - } - - /// Start a layouting group. - /// - /// All further calls to [`push`](Self::push) will collect nodes for this group. - /// The given metadata will be returned alongside the collected nodes - /// in a matching call to [`end_group`](Self::end_group). - fn start_group(&mut self, meta: T) { - self.groups.push((Box::new(meta), std::mem::take(&mut self.inner))); - } - - /// End a layouting group started with [`start_group`](Self::start_group). - /// - /// This returns the stored metadata and the collected nodes. - #[track_caller] - fn end_group(&mut self) -> (T, Vec) { - if let Some(&LayoutNode::Spacing(spacing)) = self.inner.last() { - if spacing.softness == Softness::Soft { - self.inner.pop(); - } - } - - let (any, outer) = self.groups.pop().expect("no pushed group"); - let group = *any.downcast::().expect("bad group type"); - (group, std::mem::replace(&mut self.inner, outer)) - } - - /// Updates the flow directions if the resulting main and cross directions - /// apply to different axes. Generates an appropriate error, otherwise. - pub fn set_flow(&mut self, new: Gen>>) { - let flow = Gen::new( - new.main.map(|s| s.v).unwrap_or(self.state.flow.main), - new.cross.map(|s| s.v).unwrap_or(self.state.flow.cross), - ); - - if flow.main.axis() != flow.cross.axis() { - self.state.flow = flow; - } else { - for dir in new.main.iter().chain(new.cross.iter()) { - self.diag(error!(dir.span, "aligned axis")); - } - } - } - - /// Apply a forced line break. - pub fn apply_linebreak(&mut self) { - self.end_par_group(); - self.start_par_group(); - } - - /// Apply a forced paragraph break. - pub fn apply_parbreak(&mut self) { - self.end_par_group(); - let em = self.state.font.font_size(); - self.push(Spacing { - amount: self.state.par.par_spacing.resolve(em), - softness: Softness::Soft, - }); - self.start_par_group(); - } - - /// Construct a text node from the given string based on the active text - /// state. - pub fn make_text_node(&self, text: String) -> Text { - let mut variant = self.state.font.variant; - - if self.state.font.strong { - variant.weight = variant.weight.thicken(300); - } - - if self.state.font.emph { - variant.style = match variant.style { - FontStyle::Normal => FontStyle::Italic, - FontStyle::Italic => FontStyle::Normal, - FontStyle::Oblique => FontStyle::Normal, - } - } - - Text { - text, - align: self.state.align, - dir: self.state.flow.cross, - font_size: self.state.font.font_size(), - families: Rc::clone(&self.state.font.families), - variant, - } - } -} - -/// A group for page runs. -struct PageGroup { - size: Size, - padding: Sides, - flow: Flow, - align: BoxAlign, - softness: Softness, -} - -/// A group for generic content. -struct ContentGroup; - -/// A group for paragraphs. -struct ParGroup { - flow: Flow, - align: BoxAlign, - line_spacing: Length, -} - -/// Defines how an item interact with surrounding items. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub enum Softness { - /// A soft item can be skipped in some circumstances. - Soft, - /// A hard item is always retained. - Hard, -} - /// Evaluate an item. /// /// _Note_: Evaluation is not necessarily pure, it may change the active state. @@ -339,7 +56,7 @@ where } } -impl Eval for &[Spanned] { +impl Eval for &[Spanned] { type Output = (); fn eval(self, ctx: &mut EvalContext) -> Self::Output { @@ -349,33 +66,33 @@ impl Eval for &[Spanned] { } } -impl Eval for Spanned<&SynNode> { +impl Eval for Spanned<&Node> { type Output = (); fn eval(self, ctx: &mut EvalContext) -> Self::Output { match self.v { - SynNode::Text(text) => { + Node::Text(text) => { let node = ctx.make_text_node(text.clone()); ctx.push(node); } - SynNode::Space => { + Node::Space => { let em = ctx.state.font.font_size(); - ctx.push(Spacing { + ctx.push(NodeSpacing { amount: ctx.state.par.word_spacing.resolve(em), softness: Softness::Soft, }); } - SynNode::Linebreak => ctx.apply_linebreak(), - SynNode::Parbreak => ctx.apply_parbreak(), + Node::Linebreak => ctx.apply_linebreak(), + Node::Parbreak => ctx.apply_parbreak(), - SynNode::Strong => ctx.state.font.strong ^= true, - SynNode::Emph => ctx.state.font.emph ^= true, + Node::Strong => ctx.state.font.strong ^= true, + Node::Emph => ctx.state.font.emph ^= true, - SynNode::Heading(heading) => heading.with_span(self.span).eval(ctx), - SynNode::Raw(raw) => raw.with_span(self.span).eval(ctx), + Node::Heading(heading) => heading.with_span(self.span).eval(ctx), + Node::Raw(raw) => raw.with_span(self.span).eval(ctx), - SynNode::Expr(expr) => { + Node::Expr(expr) => { let value = expr.with_span(self.span).eval(ctx); value.eval(ctx) } @@ -413,15 +130,15 @@ impl Eval for Spanned<&NodeRaw> { let mut children = vec![]; for line in &self.v.lines { - children.push(LayoutNode::Text(ctx.make_text_node(line.clone()))); - children.push(LayoutNode::Spacing(Spacing { + children.push(layout::Node::Text(ctx.make_text_node(line.clone()))); + children.push(layout::Node::Spacing(NodeSpacing { amount: line_spacing, softness: Softness::Hard, })); } - ctx.push(Stack { - flow: ctx.state.flow, + ctx.push(NodeStack { + dirs: ctx.state.dirs, align: ctx.state.align, expansion: Gen::uniform(Expansion::Fit), children, @@ -436,10 +153,13 @@ impl Eval for Spanned<&Expr> { fn eval(self, ctx: &mut EvalContext) -> Self::Output { match self.v { - Expr::Lit(lit) => lit.with_span(self.span).eval(ctx), - Expr::Call(call) => call.with_span(self.span).eval(ctx), - Expr::Unary(unary) => unary.with_span(self.span).eval(ctx), - Expr::Binary(binary) => binary.with_span(self.span).eval(ctx), + Expr::Lit(v) => v.with_span(self.span).eval(ctx), + Expr::Call(v) => v.with_span(self.span).eval(ctx), + Expr::Unary(v) => v.with_span(self.span).eval(ctx), + Expr::Binary(v) => v.with_span(self.span).eval(ctx), + Expr::Array(v) => Value::Array(v.with_span(self.span).eval(ctx)), + Expr::Dict(v) => Value::Dict(v.with_span(self.span).eval(ctx)), + Expr::Content(v) => Value::Content(v.clone()), } } } @@ -463,14 +183,11 @@ impl Eval for Spanned<&Lit> { Lit::Percent(v) => Value::Relative(Relative::new(v / 100.0)), Lit::Color(v) => Value::Color(Color::Rgba(v)), Lit::Str(ref v) => Value::Str(v.clone()), - Lit::Array(ref v) => Value::Array(v.with_span(self.span).eval(ctx)), - Lit::Dict(ref v) => Value::Dict(v.with_span(self.span).eval(ctx)), - Lit::Content(ref v) => Value::Content(v.clone()), } } } -impl Eval for Spanned<&Array> { +impl Eval for Spanned<&ExprArray> { type Output = ValueArray; fn eval(self, ctx: &mut EvalContext) -> Self::Output { @@ -478,7 +195,7 @@ impl Eval for Spanned<&Array> { } } -impl Eval for Spanned<&Dict> { +impl Eval for Spanned<&ExprDict> { type Output = ValueDict; fn eval(self, ctx: &mut EvalContext) -> Self::Output { @@ -489,36 +206,6 @@ impl Eval for Spanned<&Dict> { } } -impl Eval for Spanned<&ExprCall> { - type Output = Value; - - fn eval(self, ctx: &mut EvalContext) -> Self::Output { - let name = &self.v.name.v; - let span = self.v.name.span; - - if let Some(value) = ctx.state.scope.get(name) { - if let Value::Func(func) = value { - let func = func.clone(); - ctx.feedback.decos.push(Deco::Resolved.with_span(span)); - - let mut args = self.v.args.as_ref().eval(ctx); - let returned = func(ctx, &mut args); - args.finish(ctx); - - return returned; - } else { - let ty = value.type_name(); - ctx.diag(error!(span, "a value of type {} is not callable", ty)); - } - } else if !name.is_empty() { - ctx.diag(error!(span, "unknown function")); - } - - ctx.feedback.decos.push(Deco::Unresolved.with_span(span)); - Value::Error - } -} - impl Eval for Spanned<&ExprUnary> { type Output = Value; diff --git a/src/eval/scope.rs b/src/eval/scope.rs index c9ce1423e..dd7cc1da1 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -1,11 +1,9 @@ -//! Mapping from identifiers to functions. - use std::collections::HashMap; use std::fmt::{self, Debug, Formatter}; use super::Value; -/// A map from identifiers to functions. +/// A map from identifiers to values. #[derive(Default, Clone, PartialEq)] pub struct Scope { values: HashMap, diff --git a/src/eval/state.rs b/src/eval/state.rs index 3f3ac8e4c..9cdafaf2e 100644 --- a/src/eval/state.rs +++ b/src/eval/state.rs @@ -1,46 +1,46 @@ -//! Evaluation state. - use std::rc::Rc; use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, FontWeight}; use super::Scope; -use crate::geom::{Align, BoxAlign, Dir, Flow, Length, Linear, Relative, Sides, Size}; +use crate::geom::{ + Align, ChildAlign, Dir, LayoutDirs, Length, Linear, Relative, Sides, Size, +}; use crate::paper::{Paper, PaperClass, PAPER_A4}; -/// The active evaluation state. +/// The evaluation state. #[derive(Debug, Clone, PartialEq)] pub struct State { - /// The scope that contains function definitions. + /// The scope that contains variable definitions. pub scope: Scope, - /// The page state. - pub page: PageState, - /// The paragraph state. - pub par: ParState, - /// The font state. - pub font: FontState, - /// The active layouting directions. - pub flow: Flow, - /// The active box alignments. - pub align: BoxAlign, + /// The current page state. + pub page: StatePage, + /// The current paragraph state. + pub par: StatePar, + /// The current font state. + pub font: StateFont, + /// The current directions. + pub dirs: LayoutDirs, + /// The current alignments. + pub align: ChildAlign, } impl Default for State { fn default() -> Self { Self { scope: crate::library::_std(), - page: PageState::default(), - par: ParState::default(), - font: FontState::default(), - flow: Flow::new(Dir::TTB, Dir::LTR), - align: BoxAlign::new(Align::Start, Align::Start), + page: StatePage::default(), + par: StatePar::default(), + font: StateFont::default(), + dirs: LayoutDirs::new(Dir::TTB, Dir::LTR), + align: ChildAlign::new(Align::Start, Align::Start), } } } /// Defines page properties. #[derive(Debug, Copy, Clone, PartialEq)] -pub struct PageState { +pub struct StatePage { /// The class of this page. pub class: PaperClass, /// The width and height of the page. @@ -50,7 +50,7 @@ pub struct PageState { pub margins: Sides>, } -impl PageState { +impl StatePage { /// The default page style for the given paper. pub fn new(paper: Paper) -> Self { Self { @@ -72,7 +72,7 @@ impl PageState { } } -impl Default for PageState { +impl Default for StatePage { fn default() -> Self { Self::new(PAPER_A4) } @@ -80,7 +80,7 @@ impl Default for PageState { /// Defines paragraph properties. #[derive(Debug, Copy, Clone, PartialEq)] -pub struct ParState { +pub struct StatePar { /// The spacing between words (dependent on scaled font size). pub word_spacing: Linear, /// The spacing between lines (dependent on scaled font size). @@ -89,7 +89,7 @@ pub struct ParState { pub par_spacing: Linear, } -impl Default for ParState { +impl Default for StatePar { fn default() -> Self { Self { word_spacing: Relative::new(0.25).into(), @@ -101,7 +101,7 @@ impl Default for ParState { /// Defines font properties. #[derive(Debug, Clone, PartialEq)] -pub struct FontState { +pub struct StateFont { /// A tree of font family names and generic class names. pub families: Rc, /// The selected font variant. @@ -118,14 +118,14 @@ pub struct FontState { pub emph: bool, } -impl FontState { +impl StateFont { /// The absolute font size. pub fn font_size(&self) -> Length { self.scale.resolve(self.size) } } -impl Default for FontState { +impl Default for StateFont { fn default() -> Self { Self { families: Rc::new(default_font_families()), @@ -150,8 +150,6 @@ fn default_font_families() -> FallbackTree { "serif" => ["source serif pro", "noto serif"], "sans-serif" => ["source sans pro", "noto sans"], "monospace" => ["source code pro", "noto sans mono"], - "emoji" => ["segoe ui emoji", "noto emoji"], - "math" => ["latin modern math", "serif"], }, base: [ "source sans pro", diff --git a/src/eval/value.rs b/src/eval/value.rs index d1dcdcfa2..a91ff137a 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -1,5 +1,3 @@ -//! Computational values. - use std::any::Any; use std::collections::HashMap; use std::fmt::{self, Debug, Formatter}; @@ -9,7 +7,7 @@ use std::rc::Rc; use super::{Args, Eval, EvalContext}; use crate::color::Color; use crate::geom::{Length, Linear, Relative}; -use crate::syntax::{Spanned, SynTree, WithSpan}; +use crate::syntax::{Spanned, Tree, WithSpan}; /// A computational value. #[derive(Clone, PartialEq)] @@ -47,6 +45,14 @@ pub enum Value { } impl Value { + /// Create a new dynamic value. + pub fn any(any: T) -> Self + where + T: Type + Debug + Clone + PartialEq + 'static, + { + Self::Any(ValueAny::new(any)) + } + /// Try to cast the value into a specific type. pub fn cast(self) -> CastResult where @@ -130,7 +136,7 @@ pub type ValueArray = Vec; pub type ValueDict = HashMap; /// A content value: `{*Hi* there}`. -pub type ValueContent = SynTree; +pub type ValueContent = Tree; /// A wrapper around a reference-counted executable function. #[derive(Clone)] @@ -197,7 +203,7 @@ impl ValueAny { self.0.as_any().downcast_ref() } - /// The name of the stored object's type. + /// The name of the stored value's type. pub fn type_name(&self) -> &'static str { self.0.dyn_type_name() } @@ -289,7 +295,7 @@ pub enum CastResult { } impl CastResult { - /// Access the conversion resulting, discarding a possibly existing warning. + /// Access the conversion result, discarding a possibly existing warning. pub fn ok(self) -> Option { match self { CastResult::Ok(t) | CastResult::Warn(t, _) => Some(t), @@ -399,7 +405,7 @@ impl From for Value { } } -/// Make a type usable with [`ValueAny`]. +/// Make a type usable as a [`Value`]. /// /// Given a type `T`, this implements the following traits: /// - [`Type`] for `T`, @@ -419,7 +425,7 @@ macro_rules! impl_type { impl From<$type> for $crate::eval::Value { fn from(any: $type) -> Self { - $crate::eval::Value::Any($crate::eval::ValueAny::new(any)) + $crate::eval::Value::any(any) } } diff --git a/src/export/pdf.rs b/src/export/pdf.rs index 5a2aa0ccf..03c9ae950 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -15,22 +15,22 @@ use ttf_parser::{name_id, GlyphId}; use crate::env::{Env, ImageResource, ResourceId}; use crate::geom::Length; -use crate::layout::{BoxLayout, LayoutElement}; +use crate::layout::{Element, Frame}; -/// Export a list of layouts into a _PDF_ document. +/// Export a collection of frames into a _PDF_ document. /// -/// This creates one page per layout. Additionally to the layouts, you need to -/// pass in the font loader used for typesetting such that the fonts can be -/// included in the _PDF_. +/// This creates one page per frame. In addition to the frames, you need to pass +/// in the environment used for typesetting such that things like fonts and +/// images can be included in the _PDF_. /// /// Returns the raw bytes making up the _PDF_ document. -pub fn export(layouts: &[BoxLayout], env: &Env) -> Vec { - PdfExporter::new(layouts, env).write() +pub fn export(frames: &[Frame], env: &Env) -> Vec { + PdfExporter::new(frames, env).write() } struct PdfExporter<'a> { writer: PdfWriter, - layouts: &'a [BoxLayout], + frames: &'a [Frame], env: &'a Env, refs: Refs, fonts: Remapper, @@ -38,7 +38,7 @@ struct PdfExporter<'a> { } impl<'a> PdfExporter<'a> { - fn new(layouts: &'a [BoxLayout], env: &'a Env) -> Self { + fn new(frames: &'a [Frame], env: &'a Env) -> Self { let mut writer = PdfWriter::new(1, 7); writer.set_indent(2); @@ -46,11 +46,11 @@ impl<'a> PdfExporter<'a> { let mut images = Remapper::new(); let mut alpha_masks = 0; - for layout in layouts { - for (_, element) in &layout.elements { + for frame in frames { + for (_, element) in &frame.elements { match element { - LayoutElement::Text(shaped) => fonts.insert(shaped.face), - LayoutElement::Image(image) => { + Element::Text(shaped) => fonts.insert(shaped.face), + Element::Image(image) => { let img = env.resources.loaded::(image.res); if img.buf.color().has_alpha() { alpha_masks += 1; @@ -61,16 +61,9 @@ impl<'a> PdfExporter<'a> { } } - let refs = Refs::new(layouts.len(), fonts.len(), images.len(), alpha_masks); + let refs = Refs::new(frames.len(), fonts.len(), images.len(), alpha_masks); - Self { - writer, - layouts, - env, - refs, - fonts, - images, - } + Self { writer, frames, env, refs, fonts, images } } fn write(mut self) -> Vec { @@ -110,7 +103,7 @@ impl<'a> PdfExporter<'a> { // The page objects (non-root nodes in the page tree). for ((page_id, content_id), page) in - self.refs.pages().zip(self.refs.contents()).zip(self.layouts) + self.refs.pages().zip(self.refs.contents()).zip(self.frames) { self.writer .page(page_id) @@ -126,12 +119,12 @@ impl<'a> PdfExporter<'a> { } fn write_pages(&mut self) { - for (id, page) in self.refs.contents().zip(self.layouts) { + for (id, page) in self.refs.contents().zip(self.frames) { self.write_page(id, &page); } } - fn write_page(&mut self, id: Ref, page: &'a BoxLayout) { + fn write_page(&mut self, id: Ref, page: &'a Frame) { let mut content = Content::new(); // We only write font switching actions when the used face changes. To @@ -141,7 +134,7 @@ impl<'a> PdfExporter<'a> { let mut text = content.text(); for (pos, element) in &page.elements { - if let LayoutElement::Text(shaped) = element { + if let Element::Text(shaped) = element { // Check if we need to issue a font switching action. if shaped.face != face || shaped.font_size != size { face = shaped.face; @@ -161,7 +154,7 @@ impl<'a> PdfExporter<'a> { drop(text); for (pos, element) in &page.elements { - if let LayoutElement::Image(image) = element { + if let Element::Image(image) = element { let name = format!("Im{}", self.images.map(image.res)); let size = image.size; let x = pos.x.to_pt() as f32; @@ -359,12 +352,12 @@ struct FontRefs { impl Refs { const OBJECTS_PER_FONT: usize = 5; - fn new(layouts: usize, fonts: usize, images: usize, alpha_masks: usize) -> Self { + fn new(frames: usize, fonts: usize, images: usize, alpha_masks: usize) -> Self { let catalog = 1; let page_tree = catalog + 1; let pages_start = page_tree + 1; - let contents_start = pages_start + layouts as i32; - let fonts_start = contents_start + layouts as i32; + let contents_start = pages_start + frames as i32; + let fonts_start = contents_start + frames as i32; let images_start = fonts_start + (Self::OBJECTS_PER_FONT * fonts) as i32; let alpha_masks_start = images_start + images as i32; let end = alpha_masks_start + alpha_masks as i32; diff --git a/src/font.rs b/src/font.rs index 68a2db677..40ec59186 100644 --- a/src/font.rs +++ b/src/font.rs @@ -3,19 +3,16 @@ use fontdock::{ContainsChar, FaceFromVec, FontSource}; use ttf_parser::Face; -/// A font loader backed by a dynamic source. -pub type FontLoader = fontdock::FontLoader>; - -/// The dynamic font source. -pub type DynSource = dyn FontSource; +/// A font loader that is backed by a dynamic source. +pub type FontLoader = fontdock::FontLoader>>; /// An owned font face. -pub struct OwnedFace { +pub struct FaceBuf { data: Box<[u8]>, face: Face<'static>, } -impl OwnedFace { +impl FaceBuf { /// Get a reference to the underlying face. pub fn get(&self) -> &Face<'_> { // We can't implement Deref because that would leak the internal 'static @@ -29,7 +26,7 @@ impl OwnedFace { } } -impl FaceFromVec for OwnedFace { +impl FaceFromVec for FaceBuf { fn from_vec(vec: Vec, i: u32) -> Option { let data = vec.into_boxed_slice(); @@ -45,7 +42,7 @@ impl FaceFromVec for OwnedFace { } } -impl ContainsChar for OwnedFace { +impl ContainsChar for FaceBuf { fn contains_char(&self, c: char) -> bool { self.get().glyph_index(c).is_some() } diff --git a/src/geom/align.rs b/src/geom/align.rs index 7c4d965f4..8f02a4eac 100644 --- a/src/geom/align.rs +++ b/src/geom/align.rs @@ -1,7 +1,7 @@ use super::*; -/// The alignment of a box in a container. -pub type BoxAlign = Gen; +/// The alignment of a child in a container. +pub type ChildAlign = Gen; /// Where to align something along a directed axis. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] diff --git a/src/geom/dir.rs b/src/geom/dir.rs index f7ffa3e28..c5eaa3a60 100644 --- a/src/geom/dir.rs +++ b/src/geom/dir.rs @@ -1,7 +1,7 @@ use super::*; -/// The directions along which content flows in a container. -pub type Flow = Gen; +/// The directions along which nodes are layouted. +pub type LayoutDirs = Gen; /// The four directions into which content can be laid out. #[derive(Debug, Copy, Clone, Eq, PartialEq)] diff --git a/src/geom/gen.rs b/src/geom/gen.rs index 11b117eab..8723ea99c 100644 --- a/src/geom/gen.rs +++ b/src/geom/gen.rs @@ -50,8 +50,8 @@ impl Get for Gen { impl Switch for Gen { type Other = Spec; - fn switch(self, flow: Flow) -> Self::Other { - match flow.main.axis() { + fn switch(self, dirs: LayoutDirs) -> Self::Other { + match dirs.main.axis() { SpecAxis::Horizontal => Spec::new(self.main, self.cross), SpecAxis::Vertical => Spec::new(self.cross, self.main), } @@ -80,10 +80,10 @@ impl GenAxis { impl Switch for GenAxis { type Other = SpecAxis; - fn switch(self, flow: Flow) -> Self::Other { + fn switch(self, dirs: LayoutDirs) -> Self::Other { match self { - Self::Main => flow.main.axis(), - Self::Cross => flow.cross.axis(), + Self::Main => dirs.main.axis(), + Self::Cross => dirs.cross.axis(), } } } diff --git a/src/geom/mod.rs b/src/geom/mod.rs index 0589346eb..69bb08983 100644 --- a/src/geom/mod.rs +++ b/src/geom/mod.rs @@ -50,5 +50,5 @@ pub trait Switch { /// The other version of this type based on the current layouting /// directions. - fn switch(self, flow: Flow) -> Self::Other; + fn switch(self, dirs: LayoutDirs) -> Self::Other; } diff --git a/src/geom/point.rs b/src/geom/point.rs index 4523a861a..0faa781c8 100644 --- a/src/geom/point.rs +++ b/src/geom/point.rs @@ -45,8 +45,8 @@ impl Get for Point { impl Switch for Point { type Other = Gen; - fn switch(self, flow: Flow) -> Self::Other { - match flow.main.axis() { + fn switch(self, dirs: LayoutDirs) -> Self::Other { + match dirs.main.axis() { SpecAxis::Horizontal => Gen::new(self.x, self.y), SpecAxis::Vertical => Gen::new(self.y, self.x), } diff --git a/src/geom/size.rs b/src/geom/size.rs index 289846591..bc233e5cc 100644 --- a/src/geom/size.rs +++ b/src/geom/size.rs @@ -53,8 +53,8 @@ impl Get for Size { impl Switch for Size { type Other = Gen; - fn switch(self, flow: Flow) -> Self::Other { - match flow.main.axis() { + fn switch(self, dirs: LayoutDirs) -> Self::Other { + match dirs.main.axis() { SpecAxis::Horizontal => Gen::new(self.width, self.height), SpecAxis::Vertical => Gen::new(self.height, self.width), } diff --git a/src/geom/spec.rs b/src/geom/spec.rs index f259ce25a..c61f9526c 100644 --- a/src/geom/spec.rs +++ b/src/geom/spec.rs @@ -66,8 +66,8 @@ impl Get for Spec { impl Switch for Spec { type Other = Gen; - fn switch(self, flow: Flow) -> Self::Other { - match flow.main.axis() { + fn switch(self, dirs: LayoutDirs) -> Self::Other { + match dirs.main.axis() { SpecAxis::Horizontal => Gen::new(self.horizontal, self.vertical), SpecAxis::Vertical => Gen::new(self.vertical, self.horizontal), } @@ -96,11 +96,11 @@ impl SpecAxis { impl Switch for SpecAxis { type Other = GenAxis; - fn switch(self, flow: Flow) -> Self::Other { - if self == flow.main.axis() { + fn switch(self, dirs: LayoutDirs) -> Self::Other { + if self == dirs.main.axis() { GenAxis::Main } else { - debug_assert_eq!(self, flow.cross.axis()); + debug_assert_eq!(self, dirs.cross.axis()); GenAxis::Cross } } diff --git a/src/layout/fixed.rs b/src/layout/fixed.rs index d0daa2ca0..2ec46df37 100644 --- a/src/layout/fixed.rs +++ b/src/layout/fixed.rs @@ -3,16 +3,16 @@ use crate::geom::Linear; /// A node that can fix its child's width and height. #[derive(Debug, Clone, PartialEq)] -pub struct Fixed { +pub struct NodeFixed { /// The fixed width, if any. pub width: Option, /// The fixed height, if any. pub height: Option, /// The child node whose size to fix. - pub child: LayoutNode, + pub child: Node, } -impl Layout for Fixed { +impl Layout for NodeFixed { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted { let Area { rem, full } = areas.current; let size = Size::new( @@ -25,8 +25,8 @@ impl Layout for Fixed { } } -impl From for LayoutNode { - fn from(fixed: Fixed) -> Self { - Self::dynamic(fixed) +impl From for Node { + fn from(fixed: NodeFixed) -> Self { + Self::any(fixed) } } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 44a2c2fa2..d09566e35 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,4 +1,4 @@ -//! Layouting of documents. +//! Layouting. mod fixed; mod node; @@ -20,10 +20,48 @@ pub use spacing::*; pub use stack::*; pub use text::*; -/// Layout a document and return the produced layouts. -pub fn layout(document: &Document, env: SharedEnv) -> Vec { - let mut ctx = LayoutContext { env }; - document.layout(&mut ctx) +/// Layout a tree into a collection of frames. +pub fn layout(tree: &Tree, env: SharedEnv) -> Vec { + tree.layout(&mut LayoutContext { env }) +} + +/// A tree of layout nodes. +#[derive(Debug, Clone, PartialEq)] +pub struct Tree { + /// Runs of pages with the same properties. + pub runs: Vec, +} + +impl Tree { + /// Layout the tree into a collection of frames. + pub fn layout(&self, ctx: &mut LayoutContext) -> Vec { + self.runs.iter().flat_map(|run| run.layout(ctx)).collect() + } +} + +/// A run of pages that all have the same properties. +#[derive(Debug, Clone, PartialEq)] +pub struct NodePages { + /// The size of each page. + pub size: Size, + /// The layout node that produces the actual pages (typically a + /// [`NodeStack`]). + pub child: Node, +} + +impl NodePages { + /// Layout the page run. + pub fn layout(&self, ctx: &mut LayoutContext) -> Vec { + let areas = Areas::repeat(self.size); + let layouted = self.child.layout(ctx, &areas); + layouted.frames() + } +} + +/// Layout a node. +pub trait Layout { + /// Layout the node into the given areas. + fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted; } /// The context for layouting. @@ -33,22 +71,30 @@ pub struct LayoutContext { pub env: SharedEnv, } -/// Layout a node. -pub trait Layout { - /// Layout the node into the given areas. - fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted; +/// An area into which content can be laid out. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Area { + /// The remaining size of this area. + pub rem: Size, + /// The full size this area once had (used for relative sizing). + pub full: Size, } -/// A sequence of areas to layout into. +impl Area { + /// Create a new area. + pub fn new(size: Size) -> Self { + Self { rem: size, full: size } + } +} + +/// A collection of areas to layout into. #[derive(Debug, Clone, PartialEq)] pub struct Areas { /// The current area. pub current: Area, - /// The backlog of followup areas. - /// - /// _Note_: This works stack-like and not queue-like! + /// A stack of followup areas (the next area is the last element). pub backlog: Vec, - /// The last area that is repeated when the backlog is empty. + /// The final area that is repeated when the backlog is empty. pub last: Option, } @@ -86,23 +132,30 @@ impl Areas { } } -/// The area into which content can be laid out. -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct Area { - /// The remaining size of this area. - pub rem: Size, - /// The full size this area once had (used for relative sizing). - pub full: Size, +/// The result of layouting a node. +#[derive(Debug, Clone, PartialEq)] +pub enum Layouted { + /// Spacing that should be added to the parent. + Spacing(Length), + /// A layout that should be added to and aligned in the parent. + Frame(Frame, ChildAlign), + /// Multiple layouts. + Frames(Vec, ChildAlign), } -impl Area { - /// Create a new area. - pub fn new(size: Size) -> Self { - Self { rem: size, full: size } +impl Layouted { + /// Return all frames contained in this variant (zero, one or arbitrarily + /// many). + pub fn frames(self) -> Vec { + match self { + Self::Spacing(_) => vec![], + Self::Frame(frame, _) => vec![frame], + Self::Frames(frames, _) => frames, + } } } -/// How to determine a container's size along an axis. +/// Whether to expand or shrink a node along an axis. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Expansion { /// Fit the content. @@ -111,111 +164,49 @@ pub enum Expansion { Fill, } -impl Expansion { - /// Returns `Fill` if the condition is true and `Fit` otherwise. - pub fn fill_if(condition: bool) -> Self { - if condition { Self::Fill } else { Self::Fit } - } -} - -/// The result of [layouting](Layout::layout) a node. +/// A finished layout with elements at fixed positions. #[derive(Debug, Clone, PartialEq)] -pub enum Layouted { - /// Spacing that should be added to the parent. - Spacing(Length), - /// A layout that should be added to and aligned in the parent. - Layout(BoxLayout, BoxAlign), - /// Multiple layouts. - Layouts(Vec, BoxAlign), -} - -impl Layouted { - /// Return all layouts contained in this variant (zero, one or arbitrarily - /// many). - pub fn into_layouts(self) -> Vec { - match self { - Self::Spacing(_) => vec![], - Self::Layout(layout, _) => vec![layout], - Self::Layouts(layouts, _) => layouts, - } - } -} - -/// A finished box with content at fixed positions. -#[derive(Debug, Clone, PartialEq)] -pub struct BoxLayout { - /// The size of the box. +pub struct Frame { + /// The size of the frame. pub size: Size, /// The elements composing this layout. - pub elements: Vec<(Point, LayoutElement)>, + pub elements: Vec<(Point, Element)>, } -impl BoxLayout { - /// Create a new empty collection. +impl Frame { + /// Create a new, empty frame. pub fn new(size: Size) -> Self { Self { size, elements: vec![] } } /// Add an element at a position. - pub fn push(&mut self, pos: Point, element: LayoutElement) { + pub fn push(&mut self, pos: Point, element: Element) { self.elements.push((pos, element)); } - /// Add all elements of another collection, placing them relative to the - /// given position. - pub fn push_layout(&mut self, pos: Point, more: Self) { - for (subpos, element) in more.elements { + /// Add all elements of another frame, placing them relative to the given + /// position. + pub fn push_frame(&mut self, pos: Point, subframe: Self) { + for (subpos, element) in subframe.elements { self.push(pos + subpos, element); } } } -/// A layout element, the basic building block layouts are composed of. +/// The building block frames are composed of. #[derive(Debug, Clone, PartialEq)] -pub enum LayoutElement { +pub enum Element { /// Shaped text. Text(Shaped), /// An image. - Image(ImageElement), + Image(Image), } -/// An image. +/// An image element. #[derive(Debug, Clone, PartialEq)] -pub struct ImageElement { - /// The image. +pub struct Image { + /// The image resource. pub res: ResourceId, - /// The document size of the image. + /// The size of the image in the document. pub size: Size, } - -/// The top-level layout node. -#[derive(Debug, Clone, PartialEq)] -pub struct Document { - /// The runs of pages with same properties. - pub runs: Vec, -} - -impl Document { - /// Layout the document. - pub fn layout(&self, ctx: &mut LayoutContext) -> Vec { - self.runs.iter().flat_map(|run| run.layout(ctx)).collect() - } -} - -/// A variable-length run of pages that all have the same properties. -#[derive(Debug, Clone, PartialEq)] -pub struct Pages { - /// The size of each page. - pub size: Size, - /// The layout node that produces the actual pages (typically a [`Stack`]). - pub child: LayoutNode, -} - -impl Pages { - /// Layout the page run. - pub fn layout(&self, ctx: &mut LayoutContext) -> Vec { - let areas = Areas::repeat(self.size); - let layouted = self.child.layout(ctx, &areas); - layouted.into_layouts() - } -} diff --git a/src/layout/node.rs b/src/layout/node.rs index 447678989..c945ee19a 100644 --- a/src/layout/node.rs +++ b/src/layout/node.rs @@ -1,91 +1,89 @@ -//! Layout nodes. - use std::any::Any; use std::fmt::{self, Debug, Formatter}; use super::*; -/// A self-contained, styled layout node. +/// A self-contained layout node. #[derive(Clone, PartialEq)] -pub enum LayoutNode { - /// A spacing node. - Spacing(Spacing), +pub enum Node { /// A text node. - Text(Text), - /// A dynamic that can implement custom layouting behaviour. - Dyn(Dynamic), + Text(NodeText), + /// A spacing node. + Spacing(NodeSpacing), + /// A dynamic node that can implement custom layouting behaviour. + Any(NodeAny), } -impl LayoutNode { +impl Node { /// Create a new dynamic node. - pub fn dynamic(inner: T) -> Self + pub fn any(any: T) -> Self where T: Layout + Debug + Clone + PartialEq + 'static, { - Self::Dyn(Dynamic::new(inner)) + Self::Any(NodeAny::new(any)) } } -impl Layout for LayoutNode { +impl Layout for Node { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted { match self { Self::Spacing(spacing) => spacing.layout(ctx, areas), Self::Text(text) => text.layout(ctx, areas), - Self::Dyn(dynamic) => dynamic.layout(ctx, areas), + Self::Any(any) => any.layout(ctx, areas), } } } -impl Debug for LayoutNode { +impl Debug for Node { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::Spacing(spacing) => spacing.fmt(f), Self::Text(text) => text.fmt(f), - Self::Dyn(dynamic) => dynamic.fmt(f), + Self::Any(any) => any.fmt(f), } } } /// A wrapper around a dynamic layouting node. -pub struct Dynamic(Box); +pub struct NodeAny(Box); -impl Dynamic { +impl NodeAny { /// Create a new instance from any node that satisifies the required bounds. - pub fn new(inner: T) -> Self + pub fn new(any: T) -> Self where T: Layout + Debug + Clone + PartialEq + 'static, { - Self(Box::new(inner)) + Self(Box::new(any)) } } -impl Layout for Dynamic { +impl Layout for NodeAny { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted { self.0.layout(ctx, areas) } } -impl Clone for Dynamic { +impl Clone for NodeAny { fn clone(&self) -> Self { Self(self.0.dyn_clone()) } } -impl PartialEq for Dynamic { +impl PartialEq for NodeAny { fn eq(&self, other: &Self) -> bool { self.0.dyn_eq(other.0.as_ref()) } } -impl Debug for Dynamic { +impl Debug for NodeAny { fn fmt(&self, f: &mut Formatter) -> fmt::Result { self.0.fmt(f) } } -impl From for LayoutNode { - fn from(dynamic: Dynamic) -> Self { - Self::Dyn(dynamic) +impl From for Node { + fn from(dynamic: NodeAny) -> Self { + Self::Any(dynamic) } } diff --git a/src/layout/pad.rs b/src/layout/pad.rs index 00830a070..f947a7f5c 100644 --- a/src/layout/pad.rs +++ b/src/layout/pad.rs @@ -1,26 +1,26 @@ use super::*; use crate::geom::Linear; -/// A node that pads its child at the sides. +/// A node that adds padding to its child. #[derive(Debug, Clone, PartialEq)] -pub struct Pad { +pub struct NodePad { /// The amount of padding. pub padding: Sides, /// The child node whose sides to pad. - pub child: LayoutNode, + pub child: Node, } -impl Layout for Pad { +impl Layout for NodePad { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted { - let areas = shrink_areas(areas, self.padding); + let areas = shrink(areas, self.padding); let mut layouted = self.child.layout(ctx, &areas); match &mut layouted { Layouted::Spacing(_) => {} - Layouted::Layout(layout, _) => pad_layout(layout, self.padding), - Layouted::Layouts(layouts, _) => { - for layout in layouts { - pad_layout(layout, self.padding); + Layouted::Frame(frame, _) => pad(frame, self.padding), + Layouted::Frames(frames, _) => { + for frame in frames { + pad(frame, self.padding); } } } @@ -29,14 +29,14 @@ impl Layout for Pad { } } -impl From for LayoutNode { - fn from(pad: Pad) -> Self { - Self::dynamic(pad) +impl From for Node { + fn from(pad: NodePad) -> Self { + Self::any(pad) } } /// Shrink all areas by the padding. -fn shrink_areas(areas: &Areas, padding: Sides) -> Areas { +fn shrink(areas: &Areas, padding: Sides) -> Areas { let shrink = |size| size - padding.resolve(size).size(); Areas { current: Area { @@ -49,12 +49,12 @@ fn shrink_areas(areas: &Areas, padding: Sides) -> Areas { } /// Enlarge the box and move all elements inwards. -fn pad_layout(layout: &mut BoxLayout, padding: Sides) { - let padding = padding.resolve(layout.size); +fn pad(frame: &mut Frame, padding: Sides) { + let padding = padding.resolve(frame.size); let origin = Point::new(padding.left, padding.top); - layout.size += padding.size(); - for (point, _) in &mut layout.elements { + frame.size += padding.size(); + for (point, _) in &mut frame.elements { *point += origin; } } diff --git a/src/layout/par.rs b/src/layout/par.rs index 723f27cb7..3a8e39697 100644 --- a/src/layout/par.rs +++ b/src/layout/par.rs @@ -2,69 +2,67 @@ use super::*; /// A node that arranges its children into a paragraph. #[derive(Debug, Clone, PartialEq)] -pub struct Par { +pub struct NodePar { /// The `main` and `cross` directions of this paragraph. /// /// The children are placed in lines along the `cross` direction. The lines /// are stacked along the `main` direction. - pub flow: Flow, + pub dirs: LayoutDirs, /// Whether to expand the cross axis to fill the area or to fit the content. pub cross_expansion: Expansion, /// The spacing to insert after each line. pub line_spacing: Length, /// The nodes to be arranged in a paragraph. - pub children: Vec, + pub children: Vec, /// How to align this paragraph in _its_ parent. - pub align: BoxAlign, + pub align: ChildAlign, } -impl Layout for Par { +impl Layout for NodePar { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted { let mut layouter = ParLayouter::new(self, areas.clone()); for child in &self.children { match child.layout(ctx, &layouter.areas) { Layouted::Spacing(spacing) => layouter.push_spacing(spacing), - Layouted::Layout(layout, align) => { - layouter.push_layout(layout, align.cross) - } - Layouted::Layouts(layouts, align) => { - for layout in layouts { - layouter.push_layout(layout, align.cross); + Layouted::Frame(frame, align) => layouter.push_frame(frame, align.cross), + Layouted::Frames(frames, align) => { + for frame in frames { + layouter.push_frame(frame, align.cross); } } } } - Layouted::Layouts(layouter.finish(), self.align) + Layouted::Frames(layouter.finish(), self.align) } } -impl From for LayoutNode { - fn from(par: Par) -> Self { - Self::dynamic(par) +impl From for Node { + fn from(par: NodePar) -> Self { + Self::any(par) } } struct ParLayouter<'a> { - par: &'a Par, + par: &'a NodePar, main: SpecAxis, cross: SpecAxis, - flow: Flow, + dirs: LayoutDirs, areas: Areas, - finished: Vec, - lines: Vec<(Length, BoxLayout, Align)>, + finished: Vec, + lines: Vec<(Length, Frame, Align)>, lines_size: Gen, - run: Vec<(Length, BoxLayout, Align)>, + run: Vec<(Length, Frame, Align)>, run_size: Gen, run_ruler: Align, } impl<'a> ParLayouter<'a> { - fn new(par: &'a Par, areas: Areas) -> Self { + fn new(par: &'a NodePar, areas: Areas) -> Self { Self { par, - main: par.flow.main.axis(), - cross: par.flow.cross.axis(), - flow: par.flow, + main: par.dirs.main.axis(), + cross: par.dirs.cross.axis(), + dirs: par.dirs, areas, finished: vec![], lines: vec![], @@ -80,7 +78,7 @@ impl<'a> ParLayouter<'a> { self.run_size.cross = (self.run_size.cross + amount).min(cross_max); } - fn push_layout(&mut self, layout: BoxLayout, align: Align) { + fn push_frame(&mut self, frame: Frame, align: Align) { if self.run_ruler > align { self.finish_run(); } @@ -88,16 +86,16 @@ impl<'a> ParLayouter<'a> { let fits = { let mut usable = self.areas.current.rem; *usable.get_mut(self.cross) -= self.run_size.cross; - usable.fits(layout.size) + usable.fits(frame.size) }; if !fits { self.finish_run(); - while !self.areas.current.rem.fits(layout.size) { + while !self.areas.current.rem.fits(frame.size) { if self.areas.in_full_last() { // TODO: Diagnose once the necessary spans exist. - let _ = warning!("cannot fit box into any area"); + let _ = warning!("cannot fit frame into any area"); break; } else { self.finish_area(); @@ -105,8 +103,8 @@ impl<'a> ParLayouter<'a> { } } - let size = layout.size.switch(self.flow); - self.run.push((self.run_size.cross, layout, align)); + let size = frame.size.switch(self.dirs); + self.run.push((self.run_size.cross, frame, align)); self.run_size.cross += size.cross; self.run_size.main = self.run_size.main.max(size.main); @@ -119,13 +117,13 @@ impl<'a> ParLayouter<'a> { Expansion::Fit => self.run_size.cross, }); - let mut output = BoxLayout::new(full_size.switch(self.flow).to_size()); + let mut output = Frame::new(full_size.switch(self.dirs).to_size()); - for (before, layout, align) in std::mem::take(&mut self.run) { - let child_cross_size = layout.size.get(self.cross); + for (before, frame, align) in std::mem::take(&mut self.run) { + let child_cross_size = frame.size.get(self.cross); // Position along the cross axis. - let cross = align.resolve(if self.flow.cross.is_positive() { + let cross = align.resolve(if self.dirs.cross.is_positive() { let after_with_self = self.run_size.cross - before; before .. full_size.cross - after_with_self } else { @@ -134,8 +132,8 @@ impl<'a> ParLayouter<'a> { full_size.cross - before_with_self .. after }); - let pos = Gen::new(Length::ZERO, cross).switch(self.flow).to_point(); - output.push_layout(pos, layout); + let pos = Gen::new(Length::ZERO, cross).switch(self.dirs).to_point(); + output.push_frame(pos, frame); } self.lines.push((self.lines_size.main, output, self.run_ruler)); @@ -151,27 +149,27 @@ impl<'a> ParLayouter<'a> { fn finish_area(&mut self) { let size = self.lines_size; - let mut output = BoxLayout::new(size.switch(self.flow).to_size()); + let mut output = Frame::new(size.switch(self.dirs).to_size()); for (before, run, cross_align) in std::mem::take(&mut self.lines) { - let child_size = run.size.switch(self.flow); + let child_size = run.size.switch(self.dirs); // Position along the main axis. - let main = if self.flow.main.is_positive() { + let main = if self.dirs.main.is_positive() { before } else { size.main - (before + child_size.main) }; // Align along the cross axis. - let cross = cross_align.resolve(if self.flow.cross.is_positive() { + let cross = cross_align.resolve(if self.dirs.cross.is_positive() { Length::ZERO .. size.cross - child_size.cross } else { size.cross - child_size.cross .. Length::ZERO }); - let pos = Gen::new(main, cross).switch(self.flow).to_point(); - output.push_layout(pos, run); + let pos = Gen::new(main, cross).switch(self.dirs).to_point(); + output.push_frame(pos, run); } self.finished.push(output); @@ -180,7 +178,7 @@ impl<'a> ParLayouter<'a> { self.lines_size = Gen::ZERO; } - fn finish(mut self) -> Vec { + fn finish(mut self) -> Vec { self.finish_run(); self.finish_area(); self.finished diff --git a/src/layout/spacing.rs b/src/layout/spacing.rs index c9a9c233e..f0024fabd 100644 --- a/src/layout/spacing.rs +++ b/src/layout/spacing.rs @@ -5,7 +5,7 @@ use crate::eval::Softness; /// A spacing node. #[derive(Copy, Clone, PartialEq)] -pub struct Spacing { +pub struct NodeSpacing { /// The amount of spacing to insert. pub amount: Length, /// Defines how spacing interacts with surrounding spacing. @@ -19,13 +19,13 @@ pub struct Spacing { pub softness: Softness, } -impl Layout for Spacing { +impl Layout for NodeSpacing { fn layout(&self, _: &mut LayoutContext, _: &Areas) -> Layouted { Layouted::Spacing(self.amount) } } -impl Debug for Spacing { +impl Debug for NodeSpacing { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self.softness { Softness::Soft => write!(f, "Soft({})", self.amount), @@ -34,8 +34,8 @@ impl Debug for Spacing { } } -impl From for LayoutNode { - fn from(spacing: Spacing) -> Self { +impl From for Node { + fn from(spacing: NodeSpacing) -> Self { Self::Spacing(spacing) } } diff --git a/src/layout/stack.rs b/src/layout/stack.rs index 9d2540e90..e98be7edf 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -1,65 +1,65 @@ use super::*; -/// A node that stacks and align its children. +/// A node that stacks its children. #[derive(Debug, Clone, PartialEq)] -pub struct Stack { +pub struct NodeStack { /// The `main` and `cross` directions of this stack. /// /// The children are stacked along the `main` direction. The `cross` /// direction is required for aligning the children. - pub flow: Flow, + pub dirs: LayoutDirs, /// How to align this stack in _its_ parent. - pub align: BoxAlign, + pub align: ChildAlign, /// Whether to expand the axes to fill the area or to fit the content. pub expansion: Gen, /// The nodes to be stacked. - pub children: Vec, + pub children: Vec, } -impl Layout for Stack { +impl Layout for NodeStack { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted { let mut layouter = StackLayouter::new(self, areas.clone()); for child in &self.children { match child.layout(ctx, &layouter.areas) { Layouted::Spacing(spacing) => layouter.push_spacing(spacing), - Layouted::Layout(layout, align) => layouter.push_layout(layout, align), - Layouted::Layouts(layouts, align) => { - for layout in layouts { - layouter.push_layout(layout, align); + Layouted::Frame(frame, align) => layouter.push_frame(frame, align), + Layouted::Frames(frames, align) => { + for frame in frames { + layouter.push_frame(frame, align); } } } } - Layouted::Layouts(layouter.finish(), self.align) + Layouted::Frames(layouter.finish(), self.align) } } -impl From for LayoutNode { - fn from(stack: Stack) -> Self { - Self::dynamic(stack) +impl From for Node { + fn from(stack: NodeStack) -> Self { + Self::any(stack) } } struct StackLayouter<'a> { - stack: &'a Stack, + stack: &'a NodeStack, main: SpecAxis, - flow: Flow, + dirs: LayoutDirs, areas: Areas, - finished: Vec, - layouts: Vec<(Length, BoxLayout, BoxAlign)>, + finished: Vec, + frames: Vec<(Length, Frame, ChildAlign)>, used: Gen, ruler: Align, } impl<'a> StackLayouter<'a> { - fn new(stack: &'a Stack, areas: Areas) -> Self { + fn new(stack: &'a NodeStack, areas: Areas) -> Self { Self { stack, - main: stack.flow.main.axis(), - flow: stack.flow, + main: stack.dirs.main.axis(), + dirs: stack.dirs, areas, finished: vec![], - layouts: vec![], + frames: vec![], used: Gen::ZERO, ruler: Align::Start, } @@ -72,23 +72,23 @@ impl<'a> StackLayouter<'a> { self.used.main += capped; } - fn push_layout(&mut self, layout: BoxLayout, align: BoxAlign) { + fn push_frame(&mut self, frame: Frame, align: ChildAlign) { if self.ruler > align.main { self.finish_area(); } - while !self.areas.current.rem.fits(layout.size) { + while !self.areas.current.rem.fits(frame.size) { if self.areas.in_full_last() { // TODO: Diagnose once the necessary spans exist. - let _ = warning!("cannot fit box into any area"); + let _ = warning!("cannot fit frame into any area"); break; } else { self.finish_area(); } } - let size = layout.size.switch(self.flow); - self.layouts.push((self.used.main, layout, align)); + let size = frame.size.switch(self.dirs); + self.frames.push((self.used.main, frame, align)); *self.areas.current.rem.get_mut(self.main) -= size.main; self.used.main += size.main; @@ -98,7 +98,7 @@ impl<'a> StackLayouter<'a> { fn finish_area(&mut self) { let full_size = { - let full = self.areas.current.full.switch(self.flow); + let full = self.areas.current.full.switch(self.dirs); Gen::new( match self.stack.expansion.main { Expansion::Fill => full.main, @@ -111,13 +111,13 @@ impl<'a> StackLayouter<'a> { ) }; - let mut output = BoxLayout::new(full_size.switch(self.flow).to_size()); + let mut output = Frame::new(full_size.switch(self.dirs).to_size()); - for (before, layout, align) in std::mem::take(&mut self.layouts) { - let child_size = layout.size.switch(self.flow); + for (before, frame, align) in std::mem::take(&mut self.frames) { + let child_size = frame.size.switch(self.dirs); // Align along the main axis. - let main = align.main.resolve(if self.flow.main.is_positive() { + let main = align.main.resolve(if self.dirs.main.is_positive() { let after_with_self = self.used.main - before; before .. full_size.main - after_with_self } else { @@ -127,14 +127,14 @@ impl<'a> StackLayouter<'a> { }); // Align along the cross axis. - let cross = align.cross.resolve(if self.flow.cross.is_positive() { + let cross = align.cross.resolve(if self.dirs.cross.is_positive() { Length::ZERO .. full_size.cross - child_size.cross } else { full_size.cross - child_size.cross .. Length::ZERO }); - let pos = Gen::new(main, cross).switch(self.flow).to_point(); - output.push_layout(pos, layout); + let pos = Gen::new(main, cross).switch(self.dirs).to_point(); + output.push_frame(pos, frame); } self.finished.push(output); @@ -144,7 +144,7 @@ impl<'a> StackLayouter<'a> { self.ruler = Align::Start; } - fn finish(mut self) -> Vec { + fn finish(mut self) -> Vec { self.finish_area(); self.finished } diff --git a/src/layout/text.rs b/src/layout/text.rs index 56b2328e0..cfd833727 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -8,11 +8,11 @@ use crate::shaping; /// A text node. #[derive(Clone, PartialEq)] -pub struct Text { +pub struct NodeText { /// The text. pub text: String, /// How to align this text node in its parent. - pub align: BoxAlign, + pub align: ChildAlign, /// The text direction. pub dir: Dir, /// The font size. @@ -23,15 +23,15 @@ pub struct Text { pub variant: FontVariant, } -impl Layout for Text { +impl Layout for NodeText { fn layout(&self, ctx: &mut LayoutContext, _: &Areas) -> Layouted { let mut env = ctx.env.borrow_mut(); - Layouted::Layout( + Layouted::Frame( shaping::shape( - &mut env.fonts, &self.text, self.dir, self.font_size, + &mut env.fonts, &self.families, self.variant, ), @@ -40,14 +40,14 @@ impl Layout for Text { } } -impl Debug for Text { +impl Debug for NodeText { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "Text({})", self.text) } } -impl From for LayoutNode { - fn from(text: Text) -> Self { +impl From for Node { + fn from(text: NodeText) -> Self { Self::Text(text) } } diff --git a/src/lib.rs b/src/lib.rs index d471b09d5..15d688034 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,23 +5,23 @@ //! [iterator of tokens][tokens]. This token stream is [parsed] into a [syntax //! tree]. The structures describing the tree can be found in the [syntax] //! module. -//! - **Evaluation:** The next step is to [evaluate] the parsed "script" to a -//! [document], a high-level, fully styled representation. The nodes of the -//! document tree are fully self-contained and order-independent and thus much +//! - **Evaluation:** The next step is to [evaluate] the parsed "script" into a +//! [layout tree], a high-level, fully styled representation. The nodes of +//! this tree are fully self-contained and order-independent and thus much //! better suited for layouting than the syntax tree. -//! - **Layouting:** The next step is to [layout] the document into a portable -//! version of the typeset document. The output of this is a vector of -//! [`BoxLayout`]s (corresponding to pages), ready for exporting. +//! - **Layouting:** Next, the tree is to [layouted] into a portable version of +//! the typeset document. The output of this is a vector of [`Frame`]s +//! (corresponding to pages), ready for exporting. //! - **Exporting:** The finished layout can be exported into a supported //! format. Submodules for these formats are located in the [export] module. //! Currently, the only supported output format is [_PDF_]. //! //! [tokens]: parse::Tokens //! [parsed]: parse::parse -//! [syntax tree]: syntax::SynTree +//! [syntax tree]: syntax::Tree //! [evaluate]: eval::eval -//! [document]: layout::Document -//! [layout]: layout::layout +//! [layout tree]: layout::Tree +//! [layouted]: layout::layout //! [_PDF_]: export::pdf #[macro_use] @@ -46,13 +46,13 @@ use std::rc::Rc; use crate::diag::{Feedback, Pass}; use crate::env::SharedEnv; use crate::eval::State; -use crate::layout::BoxLayout; +use crate::layout::Frame; -/// Process _Typst_ source code directly into a collection of layouts. -pub fn typeset(src: &str, env: SharedEnv, state: State) -> Pass> { - let Pass { output: tree, feedback: f1 } = parse::parse(src); - let Pass { output: document, feedback: f2 } = - eval::eval(&tree, Rc::clone(&env), state); - let layouts = layout::layout(&document, env); - Pass::new(layouts, Feedback::join(f1, f2)) +/// Process _Typst_ source code directly into a collection of frames. +pub fn typeset(src: &str, env: SharedEnv, state: State) -> Pass> { + let Pass { output: syntax_tree, feedback: f1 } = parse::parse(src); + let Pass { output: layout_tree, feedback: f2 } = + eval::eval(&syntax_tree, Rc::clone(&env), state); + let frames = layout::layout(&layout_tree, env); + Pass::new(frames, Feedback::join(f1, f2)) } diff --git a/src/library/insert.rs b/src/library/insert.rs index 587f96dc7..7d95afe38 100644 --- a/src/library/insert.rs +++ b/src/library/insert.rs @@ -22,7 +22,7 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> Value { if let Some((res, img)) = loaded { let dimensions = img.buf.dimensions(); drop(env); - ctx.push(Image { + ctx.push(NodeImage { res, dimensions, width, @@ -40,7 +40,7 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> Value { /// An image node. #[derive(Debug, Clone, PartialEq)] -struct Image { +struct NodeImage { /// The resource id of the image file. res: ResourceId, /// The pixel dimensions of the image. @@ -50,10 +50,10 @@ struct Image { /// The fixed height, if any. height: Option, /// How to align this image node in its parent. - align: BoxAlign, + align: ChildAlign, } -impl Layout for Image { +impl Layout for NodeImage { fn layout(&self, _: &mut LayoutContext, areas: &Areas) -> Layouted { let Area { rem, full } = areas.current; let pixel_ratio = (self.dimensions.0 as f64) / (self.dimensions.1 as f64); @@ -76,18 +76,15 @@ impl Layout for Image { } }; - let mut boxed = BoxLayout::new(size); - boxed.push( - Point::ZERO, - LayoutElement::Image(ImageElement { res: self.res, size }), - ); + let mut frame = Frame::new(size); + frame.push(Point::ZERO, Element::Image(Image { res: self.res, size })); - Layouted::Layout(boxed, self.align) + Layouted::Frame(frame, self.align) } } -impl From for LayoutNode { - fn from(image: Image) -> Self { - Self::dynamic(image) +impl From for Node { + fn from(image: NodeImage) -> Self { + Self::any(image) } } diff --git a/src/library/layout.rs b/src/library/layout.rs index ac152d075..e469c9be6 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -1,5 +1,5 @@ use crate::eval::Softness; -use crate::layout::{Expansion, Fixed, Spacing, Stack}; +use crate::layout::{Expansion, NodeFixed, NodeSpacing, NodeStack}; use crate::paper::{Paper, PaperClass}; use crate::prelude::*; @@ -45,8 +45,8 @@ pub fn align(ctx: &mut EvalContext, args: &mut Args) -> Value { // Check whether we know which axis this alignment belongs to. if let Some(axis) = axis { // We know the axis. - let gen_axis = axis.switch(ctx.state.flow); - let gen_align = arg.switch(ctx.state.flow); + let gen_axis = axis.switch(ctx.state.dirs); + let gen_align = arg.switch(ctx.state.dirs); if arg.axis().map_or(false, |a| a != axis) { ctx.diag(error!(span, "invalid alignment for {} axis", axis)); @@ -132,7 +132,7 @@ impl Alignment { impl Switch for Alignment { type Other = Align; - fn switch(self, flow: Flow) -> Self::Other { + fn switch(self, dirs: LayoutDirs) -> Self::Other { let get = |dir: Dir, at_positive_start| { if dir.is_positive() == at_positive_start { Align::Start @@ -141,12 +141,12 @@ impl Switch for Alignment { } }; - let flow = flow.switch(flow); + let dirs = dirs.switch(dirs); match self { - Self::Left => get(flow.horizontal, true), - Self::Right => get(flow.horizontal, false), - Self::Top => get(flow.vertical, true), - Self::Bottom => get(flow.vertical, false), + Self::Left => get(dirs.horizontal, true), + Self::Right => get(dirs.horizontal, false), + Self::Top => get(dirs.vertical, true), + Self::Bottom => get(dirs.vertical, false), Self::Center => Align::Center, } } @@ -169,9 +169,9 @@ pub fn boxed(ctx: &mut EvalContext, args: &mut Args) -> Value { let main = args.get(ctx, "main-dir"); let cross = args.get(ctx, "cross-dir"); - ctx.set_flow(Gen::new(main, cross)); + ctx.set_dirs(Gen::new(main, cross)); - let flow = ctx.state.flow; + let dirs = ctx.state.dirs; let align = ctx.state.align; ctx.start_content_group(); @@ -182,19 +182,14 @@ pub fn boxed(ctx: &mut EvalContext, args: &mut Args) -> Value { let children = ctx.end_content_group(); - ctx.push(Fixed { + let fill_if = |c| if c { Expansion::Fill } else { Expansion::Fit }; + let expansion = + Spec::new(fill_if(width.is_some()), fill_if(height.is_some())).switch(dirs); + + ctx.push(NodeFixed { width, height, - child: LayoutNode::dynamic(Stack { - flow, - align, - expansion: Spec::new( - Expansion::fill_if(width.is_some()), - Expansion::fill_if(height.is_some()), - ) - .switch(flow), - children, - }), + child: Node::any(NodeStack { dirs, align, expansion, children }), }); ctx.state = snapshot; @@ -227,8 +222,8 @@ fn spacing(ctx: &mut EvalContext, args: &mut Args, axis: SpecAxis) -> Value { if let Some(linear) = spacing { let amount = linear.resolve(ctx.state.font.font_size()); - let spacing = Spacing { amount, softness: Softness::Hard }; - if axis == ctx.state.flow.main.axis() { + let spacing = NodeSpacing { amount, softness: Softness::Hard }; + if axis == ctx.state.dirs.main.axis() { ctx.end_par_group(); ctx.push(spacing); ctx.start_par_group(); @@ -305,7 +300,7 @@ pub fn page(ctx: &mut EvalContext, args: &mut Args) -> Value { let main = args.get(ctx, "main-dir"); let cross = args.get(ctx, "cross-dir"); - ctx.set_flow(Gen::new(main, cross)); + ctx.set_dirs(Gen::new(main, cross)); let mut softness = ctx.end_page_group(|_| false); if let Some(body) = args.find::(ctx) { diff --git a/src/main.rs b/src/main.rs index 213734d77..4746bc0c5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,7 +49,7 @@ fn main() -> anyhow::Result<()> { let state = State::default(); let Pass { - output: layouts, + output: frames, feedback: Feedback { mut diags, .. }, } = typeset(&src, Rc::clone(&env), state); @@ -72,7 +72,7 @@ fn main() -> anyhow::Result<()> { } } - let pdf_data = pdf::export(&layouts, &env.borrow()); + let pdf_data = pdf::export(&frames, &env.borrow()); fs::write(&dest_path, pdf_data).context("Failed to write PDF file.")?; Ok(()) diff --git a/src/paper.rs b/src/paper.rs index 21f547568..bbe078031 100644 --- a/src/paper.rs +++ b/src/paper.rs @@ -5,7 +5,7 @@ use crate::geom::{Length, Linear, Relative, Sides, Size}; /// Specification of a paper. #[derive(Debug, Copy, Clone, PartialEq)] pub struct Paper { - /// The kind of paper, which defines the default margins. + /// The broad class this paper belongs to. pub class: PaperClass, /// The width of the paper in millimeters. pub width: f64, @@ -25,7 +25,7 @@ impl Paper { } } -/// Paper classes define default margins for a class of related papers. +/// Defines default margins for a class of related papers. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum PaperClass { Custom, diff --git a/src/parse/collection.rs b/src/parse/collection.rs index db267dbed..889cfb0fb 100644 --- a/src/parse/collection.rs +++ b/src/parse/collection.rs @@ -2,7 +2,7 @@ use super::*; use crate::diag::Deco; /// Parse the arguments to a function call. -pub fn arguments(p: &mut Parser) -> Arguments { +pub fn arguments(p: &mut Parser) -> ExprArgs { collection(p, vec![]) } @@ -74,7 +74,7 @@ trait Collection { fn push_comma(&mut self) {} } -impl Collection for Arguments { +impl Collection for ExprArgs { fn push_arg(&mut self, _: &mut Parser, arg: Spanned) { self.push(arg.v); } @@ -85,17 +85,17 @@ impl Collection for Arguments { enum State { Unknown, Expr(Spanned), - Array(Array), - Dict(Dict), + Array(ExprArray), + Dict(ExprDict), } impl State { fn into_expr(self) -> Expr { match self { - Self::Unknown => Expr::Lit(Lit::Array(vec![])), + Self::Unknown => Expr::Array(vec![]), Self::Expr(expr) => expr.v, - Self::Array(array) => Expr::Lit(Lit::Array(array)), - Self::Dict(dict) => Expr::Lit(Lit::Dict(dict)), + Self::Array(array) => Expr::Array(array), + Self::Dict(dict) => Expr::Dict(dict), } } } diff --git a/src/parse/lines.rs b/src/parse/lines.rs index be120d8aa..d1a6c781f 100644 --- a/src/parse/lines.rs +++ b/src/parse/lines.rs @@ -1,5 +1,3 @@ -//! Conversion of byte positions to line/column locations. - use super::Scanner; use crate::syntax::{Location, Offset, Pos}; diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 912a34d07..e6cac17f1 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -22,13 +22,13 @@ use crate::syntax::*; use collection::{arguments, parenthesized}; /// Parse a string of source code. -pub fn parse(src: &str) -> Pass { +pub fn parse(src: &str) -> Pass { let mut p = Parser::new(src); Pass::new(tree(&mut p), p.finish()) } /// Parse a syntax tree. -fn tree(p: &mut Parser) -> SynTree { +fn tree(p: &mut Parser) -> Tree { // We keep track of whether we are at the start of a block or paragraph // to know whether headings are allowed. let mut at_start = true; @@ -36,8 +36,8 @@ fn tree(p: &mut Parser) -> SynTree { while !p.eof() { if let Some(node) = p.span_if(|p| node(p, at_start)) { match node.v { - SynNode::Parbreak => at_start = true, - SynNode::Space => {} + Node::Parbreak => at_start = true, + Node::Space => {} _ => at_start = false, } tree.push(node); @@ -47,42 +47,42 @@ fn tree(p: &mut Parser) -> SynTree { } /// Parse a syntax node. -fn node(p: &mut Parser, at_start: bool) -> Option { +fn node(p: &mut Parser, at_start: bool) -> Option { let node = match p.peek()? { Token::Space(newlines) => { if newlines < 2 { - SynNode::Space + Node::Space } else { - SynNode::Parbreak + Node::Parbreak } } - Token::Text(text) => SynNode::Text(text.into()), + Token::Text(text) => Node::Text(text.into()), Token::LineComment(_) | Token::BlockComment(_) => { p.eat(); return None; } - Token::Star => SynNode::Strong, - Token::Underscore => SynNode::Emph, - Token::Tilde => SynNode::Text("\u{00A0}".into()), - Token::Backslash => SynNode::Linebreak, + Token::Star => Node::Strong, + Token::Underscore => Node::Emph, + Token::Tilde => Node::Text("\u{00A0}".into()), + Token::Backslash => Node::Linebreak, Token::Hashtag => { if at_start { - return Some(SynNode::Heading(heading(p))); + return Some(Node::Heading(heading(p))); } else { - SynNode::Text(p.get(p.peek_span()).into()) + Node::Text(p.get(p.peek_span()).into()) } } - Token::Raw(t) => SynNode::Raw(raw(p, t)), - Token::UnicodeEscape(t) => SynNode::Text(unicode_escape(p, t)), + Token::Raw(t) => Node::Raw(raw(p, t)), + Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)), Token::LeftBracket => { - return Some(SynNode::Expr(Expr::Call(bracket_call(p)))); + return Some(Node::Expr(Expr::Call(bracket_call(p)))); } Token::LeftBrace => { - return Some(SynNode::Expr(block_expr(p)?)); + return Some(Node::Expr(block_expr(p)?)); } _ => { @@ -189,15 +189,15 @@ fn bracket_call(p: &mut Parser) -> ExprCall { p.end_group(); if p.peek() == Some(Token::LeftBracket) { - let body = p.span(|p| Expr::Lit(Lit::Content(bracket_body(p)))); + let body = p.span(|p| Expr::Content(bracket_body(p))); inner.span.expand(body.span); inner.v.args.v.push(Argument::Pos(body)); } while let Some(mut top) = outer.pop() { let span = inner.span; - let node = inner.map(|c| SynNode::Expr(Expr::Call(c))); - let expr = Expr::Lit(Lit::Content(vec![node])).with_span(span); + let node = inner.map(|c| Node::Expr(Expr::Call(c))); + let expr = Expr::Content(vec![node]).with_span(span); top.v.args.v.push(Argument::Pos(expr)); inner = top; } @@ -227,7 +227,7 @@ fn bracket_subheader(p: &mut Parser) -> ExprCall { } /// Parse the body of a bracketed function call. -fn bracket_body(p: &mut Parser) -> SynTree { +fn bracket_body(p: &mut Parser) -> Tree { p.push_mode(TokenMode::Body); p.start_group(Group::Bracket); let tree = tree(p); @@ -299,13 +299,13 @@ fn value(p: &mut Parser) -> Option { let expr = match p.peek() { // Bracketed function call. Some(Token::LeftBracket) => { - let node = p.span(|p| SynNode::Expr(Expr::Call(bracket_call(p)))); - return Some(Expr::Lit(Lit::Content(vec![node]))); + let node = p.span(|p| Node::Expr(Expr::Call(bracket_call(p)))); + return Some(Expr::Content(vec![node])); } // Content expression. Some(Token::LeftBrace) => { - return Some(Expr::Lit(Lit::Content(content(p)))); + return Some(Expr::Content(content(p))); } // Dictionary or just a parenthesized expression. @@ -345,7 +345,7 @@ fn value(p: &mut Parser) -> Option { } // Parse a content value: `{...}`. -fn content(p: &mut Parser) -> SynTree { +fn content(p: &mut Parser) -> Tree { p.push_mode(TokenMode::Body); p.start_group(Group::Brace); let tree = tree(p); diff --git a/src/parse/resolve.rs b/src/parse/resolve.rs index 051bc7d54..d6c6d8a42 100644 --- a/src/parse/resolve.rs +++ b/src/parse/resolve.rs @@ -1,5 +1,3 @@ -//! Resolve strings and raw blocks. - use super::{is_newline, Scanner}; use crate::syntax::{Ident, NodeRaw}; diff --git a/src/parse/scanner.rs b/src/parse/scanner.rs index 69ad21389..cc23a612a 100644 --- a/src/parse/scanner.rs +++ b/src/parse/scanner.rs @@ -1,9 +1,7 @@ -//! Low-level char-based scanner. - use std::fmt::{self, Debug, Formatter}; use std::slice::SliceIndex; -/// A low-level featureful char-based scanner. +/// A featureful char-based scanner. #[derive(Clone)] pub struct Scanner<'s> { src: &'s str, diff --git a/src/parse/tests.rs b/src/parse/tests.rs index 0c8998b5e..d01d09a52 100644 --- a/src/parse/tests.rs +++ b/src/parse/tests.rs @@ -9,7 +9,7 @@ use crate::geom::Unit; use crate::syntax::*; use BinOp::*; -use SynNode::{Emph, Linebreak, Parbreak, Space, Strong}; +use Node::{Emph, Linebreak, Parbreak, Space, Strong}; use UnOp::*; macro_rules! t { @@ -82,16 +82,16 @@ macro_rules! into { }; } -fn Text(text: &str) -> SynNode { - SynNode::Text(text.into()) +fn Text(text: &str) -> Node { + Node::Text(text.into()) } -fn Heading(level: impl Into>, contents: SynTree) -> SynNode { - SynNode::Heading(NodeHeading { level: level.into(), contents }) +fn Heading(level: impl Into>, contents: Tree) -> Node { + Node::Heading(NodeHeading { level: level.into(), contents }) } -fn Raw(lang: Option<&str>, lines: &[&str], inline: bool) -> SynNode { - SynNode::Raw(NodeRaw { +fn Raw(lang: Option<&str>, lines: &[&str], inline: bool) -> Node { + Node::Raw(NodeRaw { lang: lang.map(|id| Ident(id.into())), lines: lines.iter().map(ToString::to_string).collect(), inline, @@ -130,8 +130,8 @@ fn Str(string: &str) -> Expr { Expr::Lit(Lit::Str(string.to_string())) } -fn Block(expr: Expr) -> SynNode { - SynNode::Expr(expr) +fn Block(expr: Expr) -> Node { + Node::Expr(expr) } fn Binary( @@ -157,7 +157,7 @@ macro_rules! Array { (@$($expr:expr),* $(,)?) => { vec![$(into!($expr)),*] }; - ($($tts:tt)*) => (Expr::Lit(Lit::Array(Array![@$($tts)*]))); + ($($tts:tt)*) => (Expr::Array(Array![@$($tts)*])); } macro_rules! Dict { @@ -167,7 +167,7 @@ macro_rules! Dict { expr: into!($expr) }),*] }; - ($($tts:tt)*) => (Expr::Lit(Lit::Dict(Dict![@$($tts)*]))); + ($($tts:tt)*) => (Expr::Dict(Dict![@$($tts)*])); } macro_rules! Args { @@ -187,7 +187,7 @@ macro_rules! Args { macro_rules! Content { (@$($node:expr),* $(,)?) => (vec![$(into!($node)),*]); - ($($tts:tt)*) => (Expr::Lit(Lit::Content(Content![@$($tts)*]))); + ($($tts:tt)*) => (Expr::Content(Content![@$($tts)*])); } macro_rules! Call { @@ -201,7 +201,7 @@ macro_rules! Call { } }; (@$($tts:tt)*) => (Expr::Call(Call!(@@$($tts)*))); - ($($tts:tt)*) => (SynNode::Expr(Call!(@$($tts)*))); + ($($tts:tt)*) => (Node::Expr(Call!(@$($tts)*))); } #[test] diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index a9692a587..ff7f11bd0 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -1,5 +1,3 @@ -//! Tokenization. - use std::fmt::{self, Debug, Formatter}; use super::{is_newline, Scanner}; diff --git a/src/prelude.rs b/src/prelude.rs index 5d446e8cf..1db0d5b00 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -8,7 +8,7 @@ pub use crate::eval::{ }; pub use crate::geom::*; #[doc(no_inline)] -pub use crate::layout::LayoutNode; +pub use crate::layout::Node; #[doc(no_inline)] -pub use crate::syntax::{Span, Spanned, SynTree, WithSpan}; -pub use crate::{error, warning}; +pub use crate::syntax::{Span, Spanned, WithSpan}; +pub use crate::{error, impl_type, warning}; diff --git a/src/shaping.rs b/src/shaping.rs index c42cd0ac4..722d103a4 100644 --- a/src/shaping.rs +++ b/src/shaping.rs @@ -11,7 +11,7 @@ use ttf_parser::{Face, GlyphId}; use crate::font::FontLoader; use crate::geom::{Dir, Length, Point, Size}; -use crate::layout::{BoxLayout, LayoutElement}; +use crate::layout::{Element, Frame}; /// A shaped run of text. #[derive(Clone, PartialEq)] @@ -31,13 +31,13 @@ pub struct Shaped { impl Shaped { /// Create a new shape run with empty `text`, `glyphs` and `offsets`. - pub fn new(face: FaceId, size: Length) -> Self { + pub fn new(face: FaceId, font_size: Length) -> Self { Self { text: String::new(), face, glyphs: vec![], offsets: vec![], - font_size: size, + font_size, } } @@ -58,16 +58,16 @@ impl Debug for Shaped { } } -/// Shape text into a box containing [`Shaped`] runs. +/// Shape text into a frame containing [`Shaped`] runs. pub fn shape( - loader: &mut FontLoader, text: &str, dir: Dir, font_size: Length, + loader: &mut FontLoader, fallback: &FallbackTree, variant: FontVariant, -) -> BoxLayout { - let mut layout = BoxLayout::new(Size::new(Length::ZERO, font_size)); +) -> Frame { + let mut frame = Frame::new(Size::new(Length::ZERO, font_size)); let mut shaped = Shaped::new(FaceId::MAX, font_size); let mut offset = Length::ZERO; @@ -91,9 +91,9 @@ pub fn shape( // Flush the buffer if we change the font face. if shaped.face != id && !shaped.text.is_empty() { - let pos = Point::new(layout.size.width, Length::ZERO); - layout.push(pos, LayoutElement::Text(shaped)); - layout.size.width += offset; + let pos = Point::new(frame.size.width, Length::ZERO); + frame.push(pos, Element::Text(shaped)); + frame.size.width += offset; shaped = Shaped::new(FaceId::MAX, font_size); offset = Length::ZERO; } @@ -108,12 +108,12 @@ pub fn shape( // Flush the last buffered parts of the word. if !shaped.text.is_empty() { - let pos = Point::new(layout.size.width, Length::ZERO); - layout.push(pos, LayoutElement::Text(shaped)); - layout.size.width += offset; + let pos = Point::new(frame.size.width, Length::ZERO); + frame.push(pos, Element::Text(shaped)); + frame.size.width += offset; } - layout + frame } /// Looks up the glyph for `c` and returns its index alongside its width at the diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index 905ade04e..94d07b075 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -1,5 +1,3 @@ -//! Expressions. - use super::*; use crate::color::RgbaColor; use crate::geom::Unit; @@ -7,7 +5,7 @@ use crate::geom::Unit; /// An expression. #[derive(Debug, Clone, PartialEq)] pub enum Expr { - /// A literal: `true`, `1cm`, `"hi"`, `{_Hey!_}`. + /// A literal: `true`, `1cm`, `"hi"`. Lit(Lit), /// An invocation of a function: `[foo ...]`, `foo(...)`. Call(ExprCall), @@ -15,6 +13,12 @@ pub enum Expr { Unary(ExprUnary), /// A binary operation: `a + b`, `a / b`. Binary(ExprBinary), + /// An array expression: `(1, "hi", 12cm)`. + Array(ExprArray), + /// A dictionary expression: `(color: #f79143, pattern: dashed)`. + Dict(ExprDict), + /// A content expression: `{*Hello* there!}`. + Content(ExprContent), } /// An invocation of a function: `[foo ...]`, `foo(...)`. @@ -23,14 +27,14 @@ pub struct ExprCall { /// The name of the function. pub name: Spanned, /// The arguments to the function. - pub args: Spanned, + pub args: Spanned, } /// The arguments to a function: `12, draw: false`. /// /// In case of a bracketed invocation with a body, the body is _not_ /// included in the span for the sake of clearer error messages. -pub type Arguments = Vec; +pub type ExprArgs = Vec; /// An argument to a function call: `12` or `draw: false`. #[derive(Debug, Clone, PartialEq)] @@ -41,6 +45,15 @@ pub enum Argument { Named(Named), } +/// A pair of a name and an expression: `pattern: dashed`. +#[derive(Debug, Clone, PartialEq)] +pub struct Named { + /// The name: `pattern`. + pub name: Spanned, + /// The right-hand side of the pair: `dashed`. + pub expr: Spanned, +} + /// A unary operation: `-x`. #[derive(Debug, Clone, PartialEq)] pub struct ExprUnary { @@ -81,6 +94,15 @@ pub enum BinOp { Div, } +/// An array expression: `(1, "hi", 12cm)`. +pub type ExprArray = SpanVec; + +/// A dictionary expression: `(color: #f79143, pattern: dashed)`. +pub type ExprDict = Vec; + +/// A content expression: `{*Hello* there!}`. +pub type ExprContent = Tree; + /// A literal. #[derive(Debug, Clone, PartialEq)] pub enum Lit { @@ -103,25 +125,4 @@ pub enum Lit { Color(RgbaColor), /// A string literal: `"hello!"`. Str(String), - /// An array literal: `(1, "hi", 12cm)`. - Array(Array), - /// A dictionary literal: `(color: #f79143, pattern: dashed)`. - Dict(Dict), - /// A content literal: `{*Hello* there!}`. - Content(SynTree), -} - -/// An array literal: `(1, "hi", 12cm)`. -pub type Array = SpanVec; - -/// A dictionary literal: `(color: #f79143, pattern: dashed)`. -pub type Dict = Vec; - -/// A pair of a name and an expression: `pattern: dashed`. -#[derive(Debug, Clone, PartialEq)] -pub struct Named { - /// The name: `pattern`. - pub name: Spanned, - /// The right-hand side of the pair: `dashed`. - pub expr: Spanned, } diff --git a/src/syntax/ident.rs b/src/syntax/ident.rs index 4f3668c06..3cb47c47d 100644 --- a/src/syntax/ident.rs +++ b/src/syntax/ident.rs @@ -1,5 +1,3 @@ -//! Unicode identifiers. - use std::ops::Deref; use unicode_xid::UnicodeXID; @@ -44,7 +42,7 @@ impl Deref for Ident { } } -/// Whether the string is a valid identifier. +/// Whether a string is a valid identifier. pub fn is_ident(string: &str) -> bool { let mut chars = string.chars(); chars @@ -52,12 +50,12 @@ pub fn is_ident(string: &str) -> bool { .map_or(false, |c| is_id_start(c) && chars.all(is_id_continue)) } -/// Whether the character can start an identifier. +/// Whether a character can start an identifier. pub fn is_id_start(c: char) -> bool { c.is_xid_start() || c == '_' } -/// Whether the character can continue an identifier. +/// Whether a character can continue an identifier. pub fn is_id_continue(c: char) -> bool { c.is_xid_continue() || c == '_' || c == '-' } diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index 970cd2838..9c78fbc39 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -12,5 +12,5 @@ pub use node::*; pub use span::*; pub use token::*; -/// A collection of nodes which form a tree together with the nodes' children. -pub type SynTree = SpanVec; +/// A collection of nodes which form a tree together with their children. +pub type Tree = SpanVec; diff --git a/src/syntax/node.rs b/src/syntax/node.rs index cee810a2a..d64d4fed0 100644 --- a/src/syntax/node.rs +++ b/src/syntax/node.rs @@ -1,11 +1,8 @@ -//! Syntax tree nodes. - use super::*; -/// A syntax node, which encompasses a single logical entity of parsed source -/// code. +/// A syntax node, encompassing a single logical entity of parsed source code. #[derive(Debug, Clone, PartialEq)] -pub enum SynNode { +pub enum Node { /// Plain text. Text(String), @@ -36,7 +33,7 @@ pub struct NodeHeading { /// The section depth (numer of hashtags minus 1). pub level: Spanned, /// The contents of the heading. - pub contents: SynTree, + pub contents: Tree, } /// A raw block with optional syntax highlighting: `` `raw` ``. diff --git a/src/syntax/span.rs b/src/syntax/span.rs index 200083547..3be770b83 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -1,5 +1,3 @@ -//! Mapping of values to the locations they originate from in source code. - use std::fmt::{self, Debug, Display, Formatter}; use std::ops::Range; @@ -66,12 +64,18 @@ impl Spanned { } /// Map the value using a function while keeping the span. - pub fn map(self, f: impl FnOnce(T) -> U) -> Spanned { + pub fn map(self, f: F) -> Spanned + where + F: FnOnce(T) -> U, + { Spanned { v: f(self.v), span: self.span } } /// Maps the span while keeping the value. - pub fn map_span(mut self, f: impl FnOnce(Span) -> Span) -> Self { + pub fn map_span(mut self, f: F) -> Self + where + F: FnOnce(Span) -> Span, + { self.span = f(self.span); self } @@ -102,7 +106,7 @@ impl Debug for Spanned { } } -/// Locates a slice of source code. +/// Bounds of a slice of source code. #[derive(Copy, Clone, Ord, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct Span { diff --git a/src/syntax/token.rs b/src/syntax/token.rs index b365d8d3b..ef17fac23 100644 --- a/src/syntax/token.rs +++ b/src/syntax/token.rs @@ -1,5 +1,3 @@ -//! Token definition. - use crate::geom::Unit; /// A minimal semantic entity of source code. diff --git a/tests/typ/example-coma.typ b/tests/typ/example-coma.typ index d559a6fbc..5374af1fa 100644 --- a/tests/typ/example-coma.typ +++ b/tests/typ/example-coma.typ @@ -1,4 +1,4 @@ -// Test integration of syntax, page setup, box layout and alignment. +// Test integration of syntax, library and layouting. [page width: 450pt, height: 300pt, margins: 1cm] diff --git a/tests/typeset.rs b/tests/typeset.rs index 4f76d4675..735a46c0a 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -19,7 +19,7 @@ use typst::eval::State; use typst::export::pdf; use typst::font::FontLoader; use typst::geom::{Length, Point, Sides, Size}; -use typst::layout::{BoxLayout, ImageElement, LayoutElement}; +use typst::layout::{Element, Frame, Image}; use typst::parse::{LineMap, Scanner}; use typst::shaping::Shaped; use typst::syntax::{Location, Pos, SpanVec, Spanned, WithSpan}; @@ -134,16 +134,16 @@ fn test( state.page.margins = Sides::uniform(Some(Length::pt(10.0).into())); let Pass { - output: layouts, + output: frames, feedback: Feedback { mut diags, .. }, } = typeset(&src, Rc::clone(env), state); diags.sort(); let env = env.borrow(); - let canvas = draw(&layouts, &env, 2.0); + let canvas = draw(&frames, &env, 2.0); canvas.pixmap.save_png(png_path).unwrap(); - let pdf_data = pdf::export(&layouts, &env); + let pdf_data = pdf::export(&frames, &env); fs::write(pdf_path, pdf_data).unwrap(); let mut ok = true; @@ -226,12 +226,12 @@ fn print_diag(diag: &Spanned, map: &LineMap) { println!("{}: {}-{}: {}", diag.v.level, start, end, diag.v.message); } -fn draw(layouts: &[BoxLayout], env: &Env, pixel_per_pt: f32) -> Canvas { +fn draw(frames: &[Frame], env: &Env, pixel_per_pt: f32) -> Canvas { let pad = Length::pt(5.0); - let height = pad + layouts.iter().map(|l| l.size.height + pad).sum::(); + let height = pad + frames.iter().map(|l| l.size.height + pad).sum::(); let width = 2.0 * pad - + layouts + + frames .iter() .map(|l| l.size.width) .max_by(|a, b| a.partial_cmp(&b).unwrap()) @@ -244,7 +244,7 @@ fn draw(layouts: &[BoxLayout], env: &Env, pixel_per_pt: f32) -> Canvas { canvas.pixmap.fill(Color::BLACK); let mut origin = Point::new(pad, pad); - for layout in layouts { + for frame in frames { let mut paint = Paint::default(); paint.set_color(Color::WHITE); @@ -252,26 +252,26 @@ fn draw(layouts: &[BoxLayout], env: &Env, pixel_per_pt: f32) -> Canvas { Rect::from_xywh( origin.x.to_pt() as f32, origin.y.to_pt() as f32, - layout.size.width.to_pt() as f32, - layout.size.height.to_pt() as f32, + frame.size.width.to_pt() as f32, + frame.size.height.to_pt() as f32, ) .unwrap(), &paint, ); - for &(pos, ref element) in &layout.elements { + for &(pos, ref element) in &frame.elements { let pos = origin + pos; match element { - LayoutElement::Text(shaped) => { + Element::Text(shaped) => { draw_text(&mut canvas, pos, env, shaped); } - LayoutElement::Image(image) => { + Element::Image(image) => { draw_image(&mut canvas, pos, env, image); } } } - origin.y += layout.size.height + pad; + origin.y += frame.size.height + pad; } canvas @@ -303,7 +303,7 @@ fn draw_text(canvas: &mut Canvas, pos: Point, env: &Env, shaped: &Shaped) { } } -fn draw_image(canvas: &mut Canvas, pos: Point, env: &Env, element: &ImageElement) { +fn draw_image(canvas: &mut Canvas, pos: Point, env: &Env, element: &Image) { let img = &env.resources.loaded::(element.res); let mut pixmap = Pixmap::new(img.buf.width(), img.buf.height()).unwrap();