Move and rename many things 🚛

This commit is contained in:
Laurenz 2021-01-03 00:12:09 +01:00
parent 1c40dc42e7
commit aae67bd572
48 changed files with 891 additions and 929 deletions

View File

@ -33,18 +33,18 @@ fn benchmarks(c: &mut Criterion) {
// Prepare intermediate results and run warm. // Prepare intermediate results and run warm.
let state = State::default(); let state = State::default();
let tree = parse(COMA).output; let syntax_tree = parse(COMA).output;
let document = eval(&tree, Rc::clone(&env), state.clone()).output; let layout_tree = eval(&syntax_tree, Rc::clone(&env), state.clone()).output;
let layouts = layout(&document, Rc::clone(&env)); let frames = layout(&layout_tree, Rc::clone(&env));
// Bench! // Bench!
bench!("parse-coma": parse(COMA)); bench!("parse-coma": parse(COMA));
bench!("eval-coma": eval(&tree, Rc::clone(&env), state.clone())); bench!("eval-coma": eval(&syntax_tree, Rc::clone(&env), state.clone()));
bench!("layout-coma": layout(&document, Rc::clone(&env))); bench!("layout-coma": layout(&layout_tree, Rc::clone(&env)));
bench!("typeset-coma": typeset(COMA, Rc::clone(&env), state.clone())); bench!("typeset-coma": typeset(COMA, Rc::clone(&env), state.clone()));
let env = env.borrow(); let env = env.borrow();
bench!("export-pdf-coma": pdf::export(&layouts, &env)); bench!("export-pdf-coma": pdf::export(&frames, &env));
} }
criterion_group!(benches, benchmarks); criterion_group!(benches, benchmarks);

View File

@ -1,8 +1,6 @@
//! Diagnostics and decorations for source code. //! Diagnostics and decorations for source code.
//! //!
//! There are no fatal errors. The document will always compile and yield a //! Errors are never fatal, the document will always compile and yield a layout.
//! layout on a best effort process, but diagnostics are nevertheless generated
//! for incorrect things.
use crate::syntax::SpanVec; use crate::syntax::SpanVec;
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
@ -21,22 +19,9 @@ impl<T> Pass<T> {
pub fn new(output: T, feedback: Feedback) -> Self { pub fn new(output: T, feedback: Feedback) -> Self {
Self { output, feedback } 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<U>(self, f: impl FnOnce(T) -> U) -> Pass<U> {
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)] #[derive(Debug, Default, Clone, Eq, PartialEq)]
pub struct Feedback { pub struct Feedback {
/// Diagnostics about the source code. /// 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)] #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Diag { pub struct Diag {

View File

@ -42,12 +42,13 @@ impl ResourceLoader {
Self { paths: HashMap::new(), entries: vec![] } Self { paths: HashMap::new(), entries: vec![] }
} }
/// Load a resource from a path. /// Load a resource from a path and parse it.
pub fn load<R: 'static>( pub fn load<P, F, R>(&mut self, path: P, parse: F) -> Option<(ResourceId, &R)>
&mut self, where
path: impl AsRef<Path>, P: AsRef<Path>,
parse: impl FnOnce(Vec<u8>) -> Option<R>, F: FnOnce(Vec<u8>) -> Option<R>,
) -> Option<(ResourceId, &R)> { R: 'static,
{
let path = path.as_ref(); let path = path.as_ref();
let id = match self.paths.entry(path.to_owned()) { let id = match self.paths.entry(path.to_owned()) {
Entry::Occupied(entry) => *entry.get(), Entry::Occupied(entry) => *entry.get(),

View File

@ -1,14 +1,37 @@
use super::*; use super::*;
use crate::diag::Deco;
/// Evaluated arguments to a function. impl Eval for Spanned<&ExprCall> {
#[derive(Debug)] type Output = Value;
pub struct Args {
span: Span, fn eval(self, ctx: &mut EvalContext) -> Self::Output {
pos: SpanVec<Value>, let name = &self.v.name.v;
named: Vec<(Spanned<String>, Spanned<Value>)>, 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; type Output = Args;
fn eval(self, ctx: &mut EvalContext) -> Self::Output { 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<Value>,
named: Vec<(Spanned<String>, Spanned<Value>)>,
}
impl Args { impl Args {
/// Find the first convertible positional argument. /// Find the first convertible positional argument.
pub fn find<T>(&mut self, ctx: &mut EvalContext) -> Option<T> pub fn find<T>(&mut self, ctx: &mut EvalContext) -> Option<T>
@ -66,9 +97,8 @@ impl Args {
self.pos.iter_mut().filter_map(move |slot| try_cast(ctx, slot)) self.pos.iter_mut().filter_map(move |slot| try_cast(ctx, slot))
} }
/// Convert the value for the given named argument. /// Convert the value for the given named argument, producing an error if
/// /// the conversion fails.
/// Generates an error if the conversion fails.
pub fn get<'a, T>(&mut self, ctx: &mut EvalContext, name: &str) -> Option<T> pub fn get<'a, T>(&mut self, ctx: &mut EvalContext, name: &str) -> Option<T>
where where
T: Cast<Spanned<Value>>, T: Cast<Spanned<Value>>,
@ -78,7 +108,7 @@ impl Args {
cast(ctx, value) 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) { pub fn finish(self, ctx: &mut EvalContext) {
let a = self.pos.iter().map(|v| v.as_ref()); 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))); let b = self.named.iter().map(|(k, v)| (&v.v).with_span(k.span.join(v.span)));

298
src/eval/context.rs Normal file
View File

@ -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<NodePages>,
/// 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<dyn Any>, Vec<Node>)>,
/// The nodes in the current innermost group
/// (whose metadata is in `groups.last()`).
inner: Vec<Node>,
}
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<Tree> {
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<Diag>) {
self.feedback.diags.push(diag);
}
/// Add a decoration to the feedback.
pub fn deco(&mut self, deco: Spanned<Deco>) {
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<Node>) {
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<F>(&mut self, keep_empty: F) -> Softness
where
F: FnOnce(Softness) -> bool,
{
self.end_par_group();
let (group, children) = self.end_group::<PageGroup>();
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<Node> {
self.end_par_group();
self.end_group::<ContentGroup>().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::<ParGroup>();
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<T: 'static>(&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<T: 'static>(&mut self) -> (T, Vec<Node>) {
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::<T>().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<Option<Spanned<Dir>>>) {
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<Linear>,
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,
}

View File

@ -1,36 +1,32 @@
//! Evaluation of syntax trees. //! Evaluation of syntax trees into layout trees.
#[macro_use] #[macro_use]
mod value; mod value;
mod args; mod call;
mod context;
mod scope; mod scope;
mod state; mod state;
pub use args::*; pub use call::*;
pub use context::*;
pub use scope::*; pub use scope::*;
pub use state::*; pub use state::*;
pub use value::*; pub use value::*;
use std::any::Any;
use std::rc::Rc; use std::rc::Rc;
use fontdock::FontStyle;
use crate::color::Color; use crate::color::Color;
use crate::diag::Diag; use crate::diag::Pass;
use crate::diag::{Deco, Feedback, Pass};
use crate::env::SharedEnv; use crate::env::SharedEnv;
use crate::geom::{BoxAlign, Dir, Flow, Gen, Length, Linear, Relative, Sides, Size}; use crate::geom::{Gen, Length, Relative};
use crate::layout::{ use crate::layout::{self, Expansion, NodeSpacing, NodeStack};
Document, Expansion, LayoutNode, Pad, Pages, Par, Spacing, Stack, Text,
};
use crate::syntax::*; 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 /// The given `state` is the base state that may be updated over the course of
/// evaluation. /// evaluation.
pub fn eval(tree: &SynTree, env: SharedEnv, state: State) -> Pass<Document> { pub fn eval(tree: &Tree, env: SharedEnv, state: State) -> Pass<layout::Tree> {
let mut ctx = EvalContext::new(env, state); let mut ctx = EvalContext::new(env, state);
ctx.start_page_group(Softness::Hard); ctx.start_page_group(Softness::Hard);
tree.eval(&mut ctx); tree.eval(&mut ctx);
@ -38,285 +34,6 @@ pub fn eval(tree: &SynTree, env: SharedEnv, state: State) -> Pass<Document> {
ctx.finish() 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<Pages>,
/// 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<dyn Any>, Vec<LayoutNode>)>,
/// The nodes in the current innermost group
/// (whose metadata is in `groups.last()`).
inner: Vec<LayoutNode>,
}
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<Document> {
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<Diag>) {
self.feedback.diags.push(diag);
}
/// Add a decoration to the feedback.
pub fn deco(&mut self, deco: Spanned<Deco>) {
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<LayoutNode>) {
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::<PageGroup>();
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<LayoutNode> {
self.end_par_group();
self.end_group::<ContentGroup>().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::<ParGroup>();
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<T: 'static>(&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<T: 'static>(&mut self) -> (T, Vec<LayoutNode>) {
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::<T>().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<Option<Spanned<Dir>>>) {
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<Linear>,
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. /// Evaluate an item.
/// ///
/// _Note_: Evaluation is not necessarily pure, it may change the active state. /// _Note_: Evaluation is not necessarily pure, it may change the active state.
@ -339,7 +56,7 @@ where
} }
} }
impl Eval for &[Spanned<SynNode>] { impl Eval for &[Spanned<Node>] {
type Output = (); type Output = ();
fn eval(self, ctx: &mut EvalContext) -> Self::Output { fn eval(self, ctx: &mut EvalContext) -> Self::Output {
@ -349,33 +66,33 @@ impl Eval for &[Spanned<SynNode>] {
} }
} }
impl Eval for Spanned<&SynNode> { impl Eval for Spanned<&Node> {
type Output = (); type Output = ();
fn eval(self, ctx: &mut EvalContext) -> Self::Output { fn eval(self, ctx: &mut EvalContext) -> Self::Output {
match self.v { match self.v {
SynNode::Text(text) => { Node::Text(text) => {
let node = ctx.make_text_node(text.clone()); let node = ctx.make_text_node(text.clone());
ctx.push(node); ctx.push(node);
} }
SynNode::Space => { Node::Space => {
let em = ctx.state.font.font_size(); let em = ctx.state.font.font_size();
ctx.push(Spacing { ctx.push(NodeSpacing {
amount: ctx.state.par.word_spacing.resolve(em), amount: ctx.state.par.word_spacing.resolve(em),
softness: Softness::Soft, softness: Softness::Soft,
}); });
} }
SynNode::Linebreak => ctx.apply_linebreak(), Node::Linebreak => ctx.apply_linebreak(),
SynNode::Parbreak => ctx.apply_parbreak(), Node::Parbreak => ctx.apply_parbreak(),
SynNode::Strong => ctx.state.font.strong ^= true, Node::Strong => ctx.state.font.strong ^= true,
SynNode::Emph => ctx.state.font.emph ^= true, Node::Emph => ctx.state.font.emph ^= true,
SynNode::Heading(heading) => heading.with_span(self.span).eval(ctx), Node::Heading(heading) => heading.with_span(self.span).eval(ctx),
SynNode::Raw(raw) => raw.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); let value = expr.with_span(self.span).eval(ctx);
value.eval(ctx) value.eval(ctx)
} }
@ -413,15 +130,15 @@ impl Eval for Spanned<&NodeRaw> {
let mut children = vec![]; let mut children = vec![];
for line in &self.v.lines { for line in &self.v.lines {
children.push(LayoutNode::Text(ctx.make_text_node(line.clone()))); children.push(layout::Node::Text(ctx.make_text_node(line.clone())));
children.push(LayoutNode::Spacing(Spacing { children.push(layout::Node::Spacing(NodeSpacing {
amount: line_spacing, amount: line_spacing,
softness: Softness::Hard, softness: Softness::Hard,
})); }));
} }
ctx.push(Stack { ctx.push(NodeStack {
flow: ctx.state.flow, dirs: ctx.state.dirs,
align: ctx.state.align, align: ctx.state.align,
expansion: Gen::uniform(Expansion::Fit), expansion: Gen::uniform(Expansion::Fit),
children, children,
@ -436,10 +153,13 @@ impl Eval for Spanned<&Expr> {
fn eval(self, ctx: &mut EvalContext) -> Self::Output { fn eval(self, ctx: &mut EvalContext) -> Self::Output {
match self.v { match self.v {
Expr::Lit(lit) => lit.with_span(self.span).eval(ctx), Expr::Lit(v) => v.with_span(self.span).eval(ctx),
Expr::Call(call) => call.with_span(self.span).eval(ctx), Expr::Call(v) => v.with_span(self.span).eval(ctx),
Expr::Unary(unary) => unary.with_span(self.span).eval(ctx), Expr::Unary(v) => v.with_span(self.span).eval(ctx),
Expr::Binary(binary) => binary.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::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
Lit::Color(v) => Value::Color(Color::Rgba(v)), Lit::Color(v) => Value::Color(Color::Rgba(v)),
Lit::Str(ref v) => Value::Str(v.clone()), 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; type Output = ValueArray;
fn eval(self, ctx: &mut EvalContext) -> Self::Output { 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; type Output = ValueDict;
fn eval(self, ctx: &mut EvalContext) -> Self::Output { 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> { impl Eval for Spanned<&ExprUnary> {
type Output = Value; type Output = Value;

View File

@ -1,11 +1,9 @@
//! Mapping from identifiers to functions.
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use super::Value; use super::Value;
/// A map from identifiers to functions. /// A map from identifiers to values.
#[derive(Default, Clone, PartialEq)] #[derive(Default, Clone, PartialEq)]
pub struct Scope { pub struct Scope {
values: HashMap<String, Value>, values: HashMap<String, Value>,

View File

@ -1,46 +1,46 @@
//! Evaluation state.
use std::rc::Rc; use std::rc::Rc;
use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, FontWeight}; use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, FontWeight};
use super::Scope; 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}; use crate::paper::{Paper, PaperClass, PAPER_A4};
/// The active evaluation state. /// The evaluation state.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct State { pub struct State {
/// The scope that contains function definitions. /// The scope that contains variable definitions.
pub scope: Scope, pub scope: Scope,
/// The page state. /// The current page state.
pub page: PageState, pub page: StatePage,
/// The paragraph state. /// The current paragraph state.
pub par: ParState, pub par: StatePar,
/// The font state. /// The current font state.
pub font: FontState, pub font: StateFont,
/// The active layouting directions. /// The current directions.
pub flow: Flow, pub dirs: LayoutDirs,
/// The active box alignments. /// The current alignments.
pub align: BoxAlign, pub align: ChildAlign,
} }
impl Default for State { impl Default for State {
fn default() -> Self { fn default() -> Self {
Self { Self {
scope: crate::library::_std(), scope: crate::library::_std(),
page: PageState::default(), page: StatePage::default(),
par: ParState::default(), par: StatePar::default(),
font: FontState::default(), font: StateFont::default(),
flow: Flow::new(Dir::TTB, Dir::LTR), dirs: LayoutDirs::new(Dir::TTB, Dir::LTR),
align: BoxAlign::new(Align::Start, Align::Start), align: ChildAlign::new(Align::Start, Align::Start),
} }
} }
} }
/// Defines page properties. /// Defines page properties.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub struct PageState { pub struct StatePage {
/// The class of this page. /// The class of this page.
pub class: PaperClass, pub class: PaperClass,
/// The width and height of the page. /// The width and height of the page.
@ -50,7 +50,7 @@ pub struct PageState {
pub margins: Sides<Option<Linear>>, pub margins: Sides<Option<Linear>>,
} }
impl PageState { impl StatePage {
/// The default page style for the given paper. /// The default page style for the given paper.
pub fn new(paper: Paper) -> Self { pub fn new(paper: Paper) -> Self {
Self { Self {
@ -72,7 +72,7 @@ impl PageState {
} }
} }
impl Default for PageState { impl Default for StatePage {
fn default() -> Self { fn default() -> Self {
Self::new(PAPER_A4) Self::new(PAPER_A4)
} }
@ -80,7 +80,7 @@ impl Default for PageState {
/// Defines paragraph properties. /// Defines paragraph properties.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub struct ParState { pub struct StatePar {
/// The spacing between words (dependent on scaled font size). /// The spacing between words (dependent on scaled font size).
pub word_spacing: Linear, pub word_spacing: Linear,
/// The spacing between lines (dependent on scaled font size). /// The spacing between lines (dependent on scaled font size).
@ -89,7 +89,7 @@ pub struct ParState {
pub par_spacing: Linear, pub par_spacing: Linear,
} }
impl Default for ParState { impl Default for StatePar {
fn default() -> Self { fn default() -> Self {
Self { Self {
word_spacing: Relative::new(0.25).into(), word_spacing: Relative::new(0.25).into(),
@ -101,7 +101,7 @@ impl Default for ParState {
/// Defines font properties. /// Defines font properties.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct FontState { pub struct StateFont {
/// A tree of font family names and generic class names. /// A tree of font family names and generic class names.
pub families: Rc<FallbackTree>, pub families: Rc<FallbackTree>,
/// The selected font variant. /// The selected font variant.
@ -118,14 +118,14 @@ pub struct FontState {
pub emph: bool, pub emph: bool,
} }
impl FontState { impl StateFont {
/// The absolute font size. /// The absolute font size.
pub fn font_size(&self) -> Length { pub fn font_size(&self) -> Length {
self.scale.resolve(self.size) self.scale.resolve(self.size)
} }
} }
impl Default for FontState { impl Default for StateFont {
fn default() -> Self { fn default() -> Self {
Self { Self {
families: Rc::new(default_font_families()), families: Rc::new(default_font_families()),
@ -150,8 +150,6 @@ fn default_font_families() -> FallbackTree {
"serif" => ["source serif pro", "noto serif"], "serif" => ["source serif pro", "noto serif"],
"sans-serif" => ["source sans pro", "noto sans"], "sans-serif" => ["source sans pro", "noto sans"],
"monospace" => ["source code pro", "noto sans mono"], "monospace" => ["source code pro", "noto sans mono"],
"emoji" => ["segoe ui emoji", "noto emoji"],
"math" => ["latin modern math", "serif"],
}, },
base: [ base: [
"source sans pro", "source sans pro",

View File

@ -1,5 +1,3 @@
//! Computational values.
use std::any::Any; use std::any::Any;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
@ -9,7 +7,7 @@ use std::rc::Rc;
use super::{Args, Eval, EvalContext}; use super::{Args, Eval, EvalContext};
use crate::color::Color; use crate::color::Color;
use crate::geom::{Length, Linear, Relative}; use crate::geom::{Length, Linear, Relative};
use crate::syntax::{Spanned, SynTree, WithSpan}; use crate::syntax::{Spanned, Tree, WithSpan};
/// A computational value. /// A computational value.
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
@ -47,6 +45,14 @@ pub enum Value {
} }
impl Value { impl Value {
/// Create a new dynamic value.
pub fn any<T>(any: T) -> Self
where
T: Type + Debug + Clone + PartialEq + 'static,
{
Self::Any(ValueAny::new(any))
}
/// Try to cast the value into a specific type. /// Try to cast the value into a specific type.
pub fn cast<T>(self) -> CastResult<T, Self> pub fn cast<T>(self) -> CastResult<T, Self>
where where
@ -130,7 +136,7 @@ pub type ValueArray = Vec<Value>;
pub type ValueDict = HashMap<String, Value>; pub type ValueDict = HashMap<String, Value>;
/// A content value: `{*Hi* there}`. /// A content value: `{*Hi* there}`.
pub type ValueContent = SynTree; pub type ValueContent = Tree;
/// A wrapper around a reference-counted executable function. /// A wrapper around a reference-counted executable function.
#[derive(Clone)] #[derive(Clone)]
@ -197,7 +203,7 @@ impl ValueAny {
self.0.as_any().downcast_ref() 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 { pub fn type_name(&self) -> &'static str {
self.0.dyn_type_name() self.0.dyn_type_name()
} }
@ -289,7 +295,7 @@ pub enum CastResult<T, V> {
} }
impl<T, V> CastResult<T, V> { impl<T, V> CastResult<T, V> {
/// Access the conversion resulting, discarding a possibly existing warning. /// Access the conversion result, discarding a possibly existing warning.
pub fn ok(self) -> Option<T> { pub fn ok(self) -> Option<T> {
match self { match self {
CastResult::Ok(t) | CastResult::Warn(t, _) => Some(t), CastResult::Ok(t) | CastResult::Warn(t, _) => Some(t),
@ -399,7 +405,7 @@ impl From<ValueAny> for Value {
} }
} }
/// Make a type usable with [`ValueAny`]. /// Make a type usable as a [`Value`].
/// ///
/// Given a type `T`, this implements the following traits: /// Given a type `T`, this implements the following traits:
/// - [`Type`] for `T`, /// - [`Type`] for `T`,
@ -419,7 +425,7 @@ macro_rules! impl_type {
impl From<$type> for $crate::eval::Value { impl From<$type> for $crate::eval::Value {
fn from(any: $type) -> Self { fn from(any: $type) -> Self {
$crate::eval::Value::Any($crate::eval::ValueAny::new(any)) $crate::eval::Value::any(any)
} }
} }

View File

@ -15,22 +15,22 @@ use ttf_parser::{name_id, GlyphId};
use crate::env::{Env, ImageResource, ResourceId}; use crate::env::{Env, ImageResource, ResourceId};
use crate::geom::Length; 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 /// This creates one page per frame. In addition to the frames, you need to pass
/// pass in the font loader used for typesetting such that the fonts can be /// in the environment used for typesetting such that things like fonts and
/// included in the _PDF_. /// images can be included in the _PDF_.
/// ///
/// Returns the raw bytes making up the _PDF_ document. /// Returns the raw bytes making up the _PDF_ document.
pub fn export(layouts: &[BoxLayout], env: &Env) -> Vec<u8> { pub fn export(frames: &[Frame], env: &Env) -> Vec<u8> {
PdfExporter::new(layouts, env).write() PdfExporter::new(frames, env).write()
} }
struct PdfExporter<'a> { struct PdfExporter<'a> {
writer: PdfWriter, writer: PdfWriter,
layouts: &'a [BoxLayout], frames: &'a [Frame],
env: &'a Env, env: &'a Env,
refs: Refs, refs: Refs,
fonts: Remapper<FaceId>, fonts: Remapper<FaceId>,
@ -38,7 +38,7 @@ struct PdfExporter<'a> {
} }
impl<'a> 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); let mut writer = PdfWriter::new(1, 7);
writer.set_indent(2); writer.set_indent(2);
@ -46,11 +46,11 @@ impl<'a> PdfExporter<'a> {
let mut images = Remapper::new(); let mut images = Remapper::new();
let mut alpha_masks = 0; let mut alpha_masks = 0;
for layout in layouts { for frame in frames {
for (_, element) in &layout.elements { for (_, element) in &frame.elements {
match element { match element {
LayoutElement::Text(shaped) => fonts.insert(shaped.face), Element::Text(shaped) => fonts.insert(shaped.face),
LayoutElement::Image(image) => { Element::Image(image) => {
let img = env.resources.loaded::<ImageResource>(image.res); let img = env.resources.loaded::<ImageResource>(image.res);
if img.buf.color().has_alpha() { if img.buf.color().has_alpha() {
alpha_masks += 1; 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 { Self { writer, frames, env, refs, fonts, images }
writer,
layouts,
env,
refs,
fonts,
images,
}
} }
fn write(mut self) -> Vec<u8> { fn write(mut self) -> Vec<u8> {
@ -110,7 +103,7 @@ impl<'a> PdfExporter<'a> {
// The page objects (non-root nodes in the page tree). // The page objects (non-root nodes in the page tree).
for ((page_id, content_id), page) in 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 self.writer
.page(page_id) .page(page_id)
@ -126,12 +119,12 @@ impl<'a> PdfExporter<'a> {
} }
fn write_pages(&mut self) { 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); 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(); let mut content = Content::new();
// We only write font switching actions when the used face changes. To // 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(); let mut text = content.text();
for (pos, element) in &page.elements { 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. // Check if we need to issue a font switching action.
if shaped.face != face || shaped.font_size != size { if shaped.face != face || shaped.font_size != size {
face = shaped.face; face = shaped.face;
@ -161,7 +154,7 @@ impl<'a> PdfExporter<'a> {
drop(text); drop(text);
for (pos, element) in &page.elements { 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 name = format!("Im{}", self.images.map(image.res));
let size = image.size; let size = image.size;
let x = pos.x.to_pt() as f32; let x = pos.x.to_pt() as f32;
@ -359,12 +352,12 @@ struct FontRefs {
impl Refs { impl Refs {
const OBJECTS_PER_FONT: usize = 5; 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 catalog = 1;
let page_tree = catalog + 1; let page_tree = catalog + 1;
let pages_start = page_tree + 1; let pages_start = page_tree + 1;
let contents_start = pages_start + layouts as i32; let contents_start = pages_start + frames as i32;
let fonts_start = contents_start + layouts as i32; let fonts_start = contents_start + frames as i32;
let images_start = fonts_start + (Self::OBJECTS_PER_FONT * fonts) as i32; let images_start = fonts_start + (Self::OBJECTS_PER_FONT * fonts) as i32;
let alpha_masks_start = images_start + images as i32; let alpha_masks_start = images_start + images as i32;
let end = alpha_masks_start + alpha_masks as i32; let end = alpha_masks_start + alpha_masks as i32;

View File

@ -3,19 +3,16 @@
use fontdock::{ContainsChar, FaceFromVec, FontSource}; use fontdock::{ContainsChar, FaceFromVec, FontSource};
use ttf_parser::Face; use ttf_parser::Face;
/// A font loader backed by a dynamic source. /// A font loader that is backed by a dynamic source.
pub type FontLoader = fontdock::FontLoader<Box<DynSource>>; pub type FontLoader = fontdock::FontLoader<Box<dyn FontSource<Face = FaceBuf>>>;
/// The dynamic font source.
pub type DynSource = dyn FontSource<Face = OwnedFace>;
/// An owned font face. /// An owned font face.
pub struct OwnedFace { pub struct FaceBuf {
data: Box<[u8]>, data: Box<[u8]>,
face: Face<'static>, face: Face<'static>,
} }
impl OwnedFace { impl FaceBuf {
/// Get a reference to the underlying face. /// Get a reference to the underlying face.
pub fn get(&self) -> &Face<'_> { pub fn get(&self) -> &Face<'_> {
// We can't implement Deref because that would leak the internal 'static // 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<u8>, i: u32) -> Option<Self> { fn from_vec(vec: Vec<u8>, i: u32) -> Option<Self> {
let data = vec.into_boxed_slice(); 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 { fn contains_char(&self, c: char) -> bool {
self.get().glyph_index(c).is_some() self.get().glyph_index(c).is_some()
} }

View File

@ -1,7 +1,7 @@
use super::*; use super::*;
/// The alignment of a box in a container. /// The alignment of a child in a container.
pub type BoxAlign = Gen<Align>; pub type ChildAlign = Gen<Align>;
/// Where to align something along a directed axis. /// Where to align something along a directed axis.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]

View File

@ -1,7 +1,7 @@
use super::*; use super::*;
/// The directions along which content flows in a container. /// The directions along which nodes are layouted.
pub type Flow = Gen<Dir>; pub type LayoutDirs = Gen<Dir>;
/// The four directions into which content can be laid out. /// The four directions into which content can be laid out.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]

View File

@ -50,8 +50,8 @@ impl<T> Get<GenAxis> for Gen<T> {
impl<T> Switch for Gen<T> { impl<T> Switch for Gen<T> {
type Other = Spec<T>; type Other = Spec<T>;
fn switch(self, flow: Flow) -> Self::Other { fn switch(self, dirs: LayoutDirs) -> Self::Other {
match flow.main.axis() { match dirs.main.axis() {
SpecAxis::Horizontal => Spec::new(self.main, self.cross), SpecAxis::Horizontal => Spec::new(self.main, self.cross),
SpecAxis::Vertical => Spec::new(self.cross, self.main), SpecAxis::Vertical => Spec::new(self.cross, self.main),
} }
@ -80,10 +80,10 @@ impl GenAxis {
impl Switch for GenAxis { impl Switch for GenAxis {
type Other = SpecAxis; type Other = SpecAxis;
fn switch(self, flow: Flow) -> Self::Other { fn switch(self, dirs: LayoutDirs) -> Self::Other {
match self { match self {
Self::Main => flow.main.axis(), Self::Main => dirs.main.axis(),
Self::Cross => flow.cross.axis(), Self::Cross => dirs.cross.axis(),
} }
} }
} }

View File

@ -50,5 +50,5 @@ pub trait Switch {
/// The other version of this type based on the current layouting /// The other version of this type based on the current layouting
/// directions. /// directions.
fn switch(self, flow: Flow) -> Self::Other; fn switch(self, dirs: LayoutDirs) -> Self::Other;
} }

View File

@ -45,8 +45,8 @@ impl Get<SpecAxis> for Point {
impl Switch for Point { impl Switch for Point {
type Other = Gen<Length>; type Other = Gen<Length>;
fn switch(self, flow: Flow) -> Self::Other { fn switch(self, dirs: LayoutDirs) -> Self::Other {
match flow.main.axis() { match dirs.main.axis() {
SpecAxis::Horizontal => Gen::new(self.x, self.y), SpecAxis::Horizontal => Gen::new(self.x, self.y),
SpecAxis::Vertical => Gen::new(self.y, self.x), SpecAxis::Vertical => Gen::new(self.y, self.x),
} }

View File

@ -53,8 +53,8 @@ impl Get<SpecAxis> for Size {
impl Switch for Size { impl Switch for Size {
type Other = Gen<Length>; type Other = Gen<Length>;
fn switch(self, flow: Flow) -> Self::Other { fn switch(self, dirs: LayoutDirs) -> Self::Other {
match flow.main.axis() { match dirs.main.axis() {
SpecAxis::Horizontal => Gen::new(self.width, self.height), SpecAxis::Horizontal => Gen::new(self.width, self.height),
SpecAxis::Vertical => Gen::new(self.height, self.width), SpecAxis::Vertical => Gen::new(self.height, self.width),
} }

View File

@ -66,8 +66,8 @@ impl<T> Get<SpecAxis> for Spec<T> {
impl<T> Switch for Spec<T> { impl<T> Switch for Spec<T> {
type Other = Gen<T>; type Other = Gen<T>;
fn switch(self, flow: Flow) -> Self::Other { fn switch(self, dirs: LayoutDirs) -> Self::Other {
match flow.main.axis() { match dirs.main.axis() {
SpecAxis::Horizontal => Gen::new(self.horizontal, self.vertical), SpecAxis::Horizontal => Gen::new(self.horizontal, self.vertical),
SpecAxis::Vertical => Gen::new(self.vertical, self.horizontal), SpecAxis::Vertical => Gen::new(self.vertical, self.horizontal),
} }
@ -96,11 +96,11 @@ impl SpecAxis {
impl Switch for SpecAxis { impl Switch for SpecAxis {
type Other = GenAxis; type Other = GenAxis;
fn switch(self, flow: Flow) -> Self::Other { fn switch(self, dirs: LayoutDirs) -> Self::Other {
if self == flow.main.axis() { if self == dirs.main.axis() {
GenAxis::Main GenAxis::Main
} else { } else {
debug_assert_eq!(self, flow.cross.axis()); debug_assert_eq!(self, dirs.cross.axis());
GenAxis::Cross GenAxis::Cross
} }
} }

View File

@ -3,16 +3,16 @@ use crate::geom::Linear;
/// A node that can fix its child's width and height. /// A node that can fix its child's width and height.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Fixed { pub struct NodeFixed {
/// The fixed width, if any. /// The fixed width, if any.
pub width: Option<Linear>, pub width: Option<Linear>,
/// The fixed height, if any. /// The fixed height, if any.
pub height: Option<Linear>, pub height: Option<Linear>,
/// The child node whose size to fix. /// 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 { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted {
let Area { rem, full } = areas.current; let Area { rem, full } = areas.current;
let size = Size::new( let size = Size::new(
@ -25,8 +25,8 @@ impl Layout for Fixed {
} }
} }
impl From<Fixed> for LayoutNode { impl From<NodeFixed> for Node {
fn from(fixed: Fixed) -> Self { fn from(fixed: NodeFixed) -> Self {
Self::dynamic(fixed) Self::any(fixed)
} }
} }

View File

@ -1,4 +1,4 @@
//! Layouting of documents. //! Layouting.
mod fixed; mod fixed;
mod node; mod node;
@ -20,10 +20,48 @@ pub use spacing::*;
pub use stack::*; pub use stack::*;
pub use text::*; pub use text::*;
/// Layout a document and return the produced layouts. /// Layout a tree into a collection of frames.
pub fn layout(document: &Document, env: SharedEnv) -> Vec<BoxLayout> { pub fn layout(tree: &Tree, env: SharedEnv) -> Vec<Frame> {
let mut ctx = LayoutContext { env }; tree.layout(&mut LayoutContext { env })
document.layout(&mut ctx) }
/// A tree of layout nodes.
#[derive(Debug, Clone, PartialEq)]
pub struct Tree {
/// Runs of pages with the same properties.
pub runs: Vec<NodePages>,
}
impl Tree {
/// Layout the tree into a collection of frames.
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Frame> {
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<Frame> {
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. /// The context for layouting.
@ -33,22 +71,30 @@ pub struct LayoutContext {
pub env: SharedEnv, pub env: SharedEnv,
} }
/// Layout a node. /// An area into which content can be laid out.
pub trait Layout { #[derive(Debug, Copy, Clone, PartialEq)]
/// Layout the node into the given areas. pub struct Area {
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted; /// 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)] #[derive(Debug, Clone, PartialEq)]
pub struct Areas { pub struct Areas {
/// The current area. /// The current area.
pub current: Area, pub current: Area,
/// The backlog of followup areas. /// A stack of followup areas (the next area is the last element).
///
/// _Note_: This works stack-like and not queue-like!
pub backlog: Vec<Size>, pub backlog: Vec<Size>,
/// 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<Size>, pub last: Option<Size>,
} }
@ -86,23 +132,30 @@ impl Areas {
} }
} }
/// The area into which content can be laid out. /// The result of layouting a node.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Area { pub enum Layouted {
/// The remaining size of this area. /// Spacing that should be added to the parent.
pub rem: Size, Spacing(Length),
/// The full size this area once had (used for relative sizing). /// A layout that should be added to and aligned in the parent.
pub full: Size, Frame(Frame, ChildAlign),
/// Multiple layouts.
Frames(Vec<Frame>, ChildAlign),
} }
impl Area { impl Layouted {
/// Create a new area. /// Return all frames contained in this variant (zero, one or arbitrarily
pub fn new(size: Size) -> Self { /// many).
Self { rem: size, full: size } pub fn frames(self) -> Vec<Frame> {
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)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Expansion { pub enum Expansion {
/// Fit the content. /// Fit the content.
@ -111,111 +164,49 @@ pub enum Expansion {
Fill, Fill,
} }
impl Expansion { /// A finished layout with elements at fixed positions.
/// 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.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Layouted { pub struct Frame {
/// Spacing that should be added to the parent. /// The size of the frame.
Spacing(Length),
/// A layout that should be added to and aligned in the parent.
Layout(BoxLayout, BoxAlign),
/// Multiple layouts.
Layouts(Vec<BoxLayout>, BoxAlign),
}
impl Layouted {
/// Return all layouts contained in this variant (zero, one or arbitrarily
/// many).
pub fn into_layouts(self) -> Vec<BoxLayout> {
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 size: Size, pub size: Size,
/// The elements composing this layout. /// The elements composing this layout.
pub elements: Vec<(Point, LayoutElement)>, pub elements: Vec<(Point, Element)>,
} }
impl BoxLayout { impl Frame {
/// Create a new empty collection. /// Create a new, empty frame.
pub fn new(size: Size) -> Self { pub fn new(size: Size) -> Self {
Self { size, elements: vec![] } Self { size, elements: vec![] }
} }
/// Add an element at a position. /// 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)); self.elements.push((pos, element));
} }
/// Add all elements of another collection, placing them relative to the /// Add all elements of another frame, placing them relative to the given
/// given position. /// position.
pub fn push_layout(&mut self, pos: Point, more: Self) { pub fn push_frame(&mut self, pos: Point, subframe: Self) {
for (subpos, element) in more.elements { for (subpos, element) in subframe.elements {
self.push(pos + subpos, element); 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)] #[derive(Debug, Clone, PartialEq)]
pub enum LayoutElement { pub enum Element {
/// Shaped text. /// Shaped text.
Text(Shaped), Text(Shaped),
/// An image. /// An image.
Image(ImageElement), Image(Image),
} }
/// An image. /// An image element.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct ImageElement { pub struct Image {
/// The image. /// The image resource.
pub res: ResourceId, pub res: ResourceId,
/// The document size of the image. /// The size of the image in the document.
pub size: Size, 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<Pages>,
}
impl Document {
/// Layout the document.
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<BoxLayout> {
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<BoxLayout> {
let areas = Areas::repeat(self.size);
let layouted = self.child.layout(ctx, &areas);
layouted.into_layouts()
}
}

View File

@ -1,91 +1,89 @@
//! Layout nodes.
use std::any::Any; use std::any::Any;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use super::*; use super::*;
/// A self-contained, styled layout node. /// A self-contained layout node.
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub enum LayoutNode { pub enum Node {
/// A spacing node.
Spacing(Spacing),
/// A text node. /// A text node.
Text(Text), Text(NodeText),
/// A dynamic that can implement custom layouting behaviour. /// A spacing node.
Dyn(Dynamic), Spacing(NodeSpacing),
/// A dynamic node that can implement custom layouting behaviour.
Any(NodeAny),
} }
impl LayoutNode { impl Node {
/// Create a new dynamic node. /// Create a new dynamic node.
pub fn dynamic<T>(inner: T) -> Self pub fn any<T>(any: T) -> Self
where where
T: Layout + Debug + Clone + PartialEq + 'static, 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 { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted {
match self { match self {
Self::Spacing(spacing) => spacing.layout(ctx, areas), Self::Spacing(spacing) => spacing.layout(ctx, areas),
Self::Text(text) => text.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 { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {
Self::Spacing(spacing) => spacing.fmt(f), Self::Spacing(spacing) => spacing.fmt(f),
Self::Text(text) => text.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. /// A wrapper around a dynamic layouting node.
pub struct Dynamic(Box<dyn Bounds>); pub struct NodeAny(Box<dyn Bounds>);
impl Dynamic { impl NodeAny {
/// Create a new instance from any node that satisifies the required bounds. /// Create a new instance from any node that satisifies the required bounds.
pub fn new<T>(inner: T) -> Self pub fn new<T>(any: T) -> Self
where where
T: Layout + Debug + Clone + PartialEq + 'static, 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 { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted {
self.0.layout(ctx, areas) self.0.layout(ctx, areas)
} }
} }
impl Clone for Dynamic { impl Clone for NodeAny {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self(self.0.dyn_clone()) Self(self.0.dyn_clone())
} }
} }
impl PartialEq for Dynamic { impl PartialEq for NodeAny {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.0.dyn_eq(other.0.as_ref()) self.0.dyn_eq(other.0.as_ref())
} }
} }
impl Debug for Dynamic { impl Debug for NodeAny {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
self.0.fmt(f) self.0.fmt(f)
} }
} }
impl From<Dynamic> for LayoutNode { impl From<NodeAny> for Node {
fn from(dynamic: Dynamic) -> Self { fn from(dynamic: NodeAny) -> Self {
Self::Dyn(dynamic) Self::Any(dynamic)
} }
} }

View File

@ -1,26 +1,26 @@
use super::*; use super::*;
use crate::geom::Linear; 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)] #[derive(Debug, Clone, PartialEq)]
pub struct Pad { pub struct NodePad {
/// The amount of padding. /// The amount of padding.
pub padding: Sides<Linear>, pub padding: Sides<Linear>,
/// The child node whose sides to pad. /// 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 { 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); let mut layouted = self.child.layout(ctx, &areas);
match &mut layouted { match &mut layouted {
Layouted::Spacing(_) => {} Layouted::Spacing(_) => {}
Layouted::Layout(layout, _) => pad_layout(layout, self.padding), Layouted::Frame(frame, _) => pad(frame, self.padding),
Layouted::Layouts(layouts, _) => { Layouted::Frames(frames, _) => {
for layout in layouts { for frame in frames {
pad_layout(layout, self.padding); pad(frame, self.padding);
} }
} }
} }
@ -29,14 +29,14 @@ impl Layout for Pad {
} }
} }
impl From<Pad> for LayoutNode { impl From<NodePad> for Node {
fn from(pad: Pad) -> Self { fn from(pad: NodePad) -> Self {
Self::dynamic(pad) Self::any(pad)
} }
} }
/// Shrink all areas by the padding. /// Shrink all areas by the padding.
fn shrink_areas(areas: &Areas, padding: Sides<Linear>) -> Areas { fn shrink(areas: &Areas, padding: Sides<Linear>) -> Areas {
let shrink = |size| size - padding.resolve(size).size(); let shrink = |size| size - padding.resolve(size).size();
Areas { Areas {
current: Area { current: Area {
@ -49,12 +49,12 @@ fn shrink_areas(areas: &Areas, padding: Sides<Linear>) -> Areas {
} }
/// Enlarge the box and move all elements inwards. /// Enlarge the box and move all elements inwards.
fn pad_layout(layout: &mut BoxLayout, padding: Sides<Linear>) { fn pad(frame: &mut Frame, padding: Sides<Linear>) {
let padding = padding.resolve(layout.size); let padding = padding.resolve(frame.size);
let origin = Point::new(padding.left, padding.top); let origin = Point::new(padding.left, padding.top);
layout.size += padding.size(); frame.size += padding.size();
for (point, _) in &mut layout.elements { for (point, _) in &mut frame.elements {
*point += origin; *point += origin;
} }
} }

View File

@ -2,69 +2,67 @@ use super::*;
/// A node that arranges its children into a paragraph. /// A node that arranges its children into a paragraph.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Par { pub struct NodePar {
/// The `main` and `cross` directions of this paragraph. /// The `main` and `cross` directions of this paragraph.
/// ///
/// The children are placed in lines along the `cross` direction. The lines /// The children are placed in lines along the `cross` direction. The lines
/// are stacked along the `main` direction. /// 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. /// Whether to expand the cross axis to fill the area or to fit the content.
pub cross_expansion: Expansion, pub cross_expansion: Expansion,
/// The spacing to insert after each line. /// The spacing to insert after each line.
pub line_spacing: Length, pub line_spacing: Length,
/// The nodes to be arranged in a paragraph. /// The nodes to be arranged in a paragraph.
pub children: Vec<LayoutNode>, pub children: Vec<Node>,
/// How to align this paragraph in _its_ parent. /// 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 { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted {
let mut layouter = ParLayouter::new(self, areas.clone()); let mut layouter = ParLayouter::new(self, areas.clone());
for child in &self.children { for child in &self.children {
match child.layout(ctx, &layouter.areas) { match child.layout(ctx, &layouter.areas) {
Layouted::Spacing(spacing) => layouter.push_spacing(spacing), Layouted::Spacing(spacing) => layouter.push_spacing(spacing),
Layouted::Layout(layout, align) => { Layouted::Frame(frame, align) => layouter.push_frame(frame, align.cross),
layouter.push_layout(layout, align.cross) Layouted::Frames(frames, align) => {
} for frame in frames {
Layouted::Layouts(layouts, align) => { layouter.push_frame(frame, align.cross);
for layout in layouts {
layouter.push_layout(layout, align.cross);
} }
} }
} }
} }
Layouted::Layouts(layouter.finish(), self.align) Layouted::Frames(layouter.finish(), self.align)
} }
} }
impl From<Par> for LayoutNode { impl From<NodePar> for Node {
fn from(par: Par) -> Self { fn from(par: NodePar) -> Self {
Self::dynamic(par) Self::any(par)
} }
} }
struct ParLayouter<'a> { struct ParLayouter<'a> {
par: &'a Par, par: &'a NodePar,
main: SpecAxis, main: SpecAxis,
cross: SpecAxis, cross: SpecAxis,
flow: Flow, dirs: LayoutDirs,
areas: Areas, areas: Areas,
finished: Vec<BoxLayout>, finished: Vec<Frame>,
lines: Vec<(Length, BoxLayout, Align)>, lines: Vec<(Length, Frame, Align)>,
lines_size: Gen<Length>, lines_size: Gen<Length>,
run: Vec<(Length, BoxLayout, Align)>, run: Vec<(Length, Frame, Align)>,
run_size: Gen<Length>, run_size: Gen<Length>,
run_ruler: Align, run_ruler: Align,
} }
impl<'a> ParLayouter<'a> { impl<'a> ParLayouter<'a> {
fn new(par: &'a Par, areas: Areas) -> Self { fn new(par: &'a NodePar, areas: Areas) -> Self {
Self { Self {
par, par,
main: par.flow.main.axis(), main: par.dirs.main.axis(),
cross: par.flow.cross.axis(), cross: par.dirs.cross.axis(),
flow: par.flow, dirs: par.dirs,
areas, areas,
finished: vec![], finished: vec![],
lines: vec![], lines: vec![],
@ -80,7 +78,7 @@ impl<'a> ParLayouter<'a> {
self.run_size.cross = (self.run_size.cross + amount).min(cross_max); 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 { if self.run_ruler > align {
self.finish_run(); self.finish_run();
} }
@ -88,16 +86,16 @@ impl<'a> ParLayouter<'a> {
let fits = { let fits = {
let mut usable = self.areas.current.rem; let mut usable = self.areas.current.rem;
*usable.get_mut(self.cross) -= self.run_size.cross; *usable.get_mut(self.cross) -= self.run_size.cross;
usable.fits(layout.size) usable.fits(frame.size)
}; };
if !fits { if !fits {
self.finish_run(); 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() { if self.areas.in_full_last() {
// TODO: Diagnose once the necessary spans exist. // TODO: Diagnose once the necessary spans exist.
let _ = warning!("cannot fit box into any area"); let _ = warning!("cannot fit frame into any area");
break; break;
} else { } else {
self.finish_area(); self.finish_area();
@ -105,8 +103,8 @@ impl<'a> ParLayouter<'a> {
} }
} }
let size = layout.size.switch(self.flow); let size = frame.size.switch(self.dirs);
self.run.push((self.run_size.cross, layout, align)); self.run.push((self.run_size.cross, frame, align));
self.run_size.cross += size.cross; self.run_size.cross += size.cross;
self.run_size.main = self.run_size.main.max(size.main); 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, 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) { for (before, frame, align) in std::mem::take(&mut self.run) {
let child_cross_size = layout.size.get(self.cross); let child_cross_size = frame.size.get(self.cross);
// Position along the cross axis. // 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; let after_with_self = self.run_size.cross - before;
before .. full_size.cross - after_with_self before .. full_size.cross - after_with_self
} else { } else {
@ -134,8 +132,8 @@ impl<'a> ParLayouter<'a> {
full_size.cross - before_with_self .. after full_size.cross - before_with_self .. after
}); });
let pos = Gen::new(Length::ZERO, cross).switch(self.flow).to_point(); let pos = Gen::new(Length::ZERO, cross).switch(self.dirs).to_point();
output.push_layout(pos, layout); output.push_frame(pos, frame);
} }
self.lines.push((self.lines_size.main, output, self.run_ruler)); self.lines.push((self.lines_size.main, output, self.run_ruler));
@ -151,27 +149,27 @@ impl<'a> ParLayouter<'a> {
fn finish_area(&mut self) { fn finish_area(&mut self) {
let size = self.lines_size; 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) { 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. // Position along the main axis.
let main = if self.flow.main.is_positive() { let main = if self.dirs.main.is_positive() {
before before
} else { } else {
size.main - (before + child_size.main) size.main - (before + child_size.main)
}; };
// Align along the cross axis. // 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 Length::ZERO .. size.cross - child_size.cross
} else { } else {
size.cross - child_size.cross .. Length::ZERO size.cross - child_size.cross .. Length::ZERO
}); });
let pos = Gen::new(main, cross).switch(self.flow).to_point(); let pos = Gen::new(main, cross).switch(self.dirs).to_point();
output.push_layout(pos, run); output.push_frame(pos, run);
} }
self.finished.push(output); self.finished.push(output);
@ -180,7 +178,7 @@ impl<'a> ParLayouter<'a> {
self.lines_size = Gen::ZERO; self.lines_size = Gen::ZERO;
} }
fn finish(mut self) -> Vec<BoxLayout> { fn finish(mut self) -> Vec<Frame> {
self.finish_run(); self.finish_run();
self.finish_area(); self.finish_area();
self.finished self.finished

View File

@ -5,7 +5,7 @@ use crate::eval::Softness;
/// A spacing node. /// A spacing node.
#[derive(Copy, Clone, PartialEq)] #[derive(Copy, Clone, PartialEq)]
pub struct Spacing { pub struct NodeSpacing {
/// The amount of spacing to insert. /// The amount of spacing to insert.
pub amount: Length, pub amount: Length,
/// Defines how spacing interacts with surrounding spacing. /// Defines how spacing interacts with surrounding spacing.
@ -19,13 +19,13 @@ pub struct Spacing {
pub softness: Softness, pub softness: Softness,
} }
impl Layout for Spacing { impl Layout for NodeSpacing {
fn layout(&self, _: &mut LayoutContext, _: &Areas) -> Layouted { fn layout(&self, _: &mut LayoutContext, _: &Areas) -> Layouted {
Layouted::Spacing(self.amount) Layouted::Spacing(self.amount)
} }
} }
impl Debug for Spacing { impl Debug for NodeSpacing {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self.softness { match self.softness {
Softness::Soft => write!(f, "Soft({})", self.amount), Softness::Soft => write!(f, "Soft({})", self.amount),
@ -34,8 +34,8 @@ impl Debug for Spacing {
} }
} }
impl From<Spacing> for LayoutNode { impl From<NodeSpacing> for Node {
fn from(spacing: Spacing) -> Self { fn from(spacing: NodeSpacing) -> Self {
Self::Spacing(spacing) Self::Spacing(spacing)
} }
} }

View File

@ -1,65 +1,65 @@
use super::*; use super::*;
/// A node that stacks and align its children. /// A node that stacks its children.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Stack { pub struct NodeStack {
/// The `main` and `cross` directions of this stack. /// The `main` and `cross` directions of this stack.
/// ///
/// The children are stacked along the `main` direction. The `cross` /// The children are stacked along the `main` direction. The `cross`
/// direction is required for aligning the children. /// direction is required for aligning the children.
pub flow: Flow, pub dirs: LayoutDirs,
/// How to align this stack in _its_ parent. /// 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. /// Whether to expand the axes to fill the area or to fit the content.
pub expansion: Gen<Expansion>, pub expansion: Gen<Expansion>,
/// The nodes to be stacked. /// The nodes to be stacked.
pub children: Vec<LayoutNode>, pub children: Vec<Node>,
} }
impl Layout for Stack { impl Layout for NodeStack {
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted {
let mut layouter = StackLayouter::new(self, areas.clone()); let mut layouter = StackLayouter::new(self, areas.clone());
for child in &self.children { for child in &self.children {
match child.layout(ctx, &layouter.areas) { match child.layout(ctx, &layouter.areas) {
Layouted::Spacing(spacing) => layouter.push_spacing(spacing), Layouted::Spacing(spacing) => layouter.push_spacing(spacing),
Layouted::Layout(layout, align) => layouter.push_layout(layout, align), Layouted::Frame(frame, align) => layouter.push_frame(frame, align),
Layouted::Layouts(layouts, align) => { Layouted::Frames(frames, align) => {
for layout in layouts { for frame in frames {
layouter.push_layout(layout, align); layouter.push_frame(frame, align);
} }
} }
} }
} }
Layouted::Layouts(layouter.finish(), self.align) Layouted::Frames(layouter.finish(), self.align)
} }
} }
impl From<Stack> for LayoutNode { impl From<NodeStack> for Node {
fn from(stack: Stack) -> Self { fn from(stack: NodeStack) -> Self {
Self::dynamic(stack) Self::any(stack)
} }
} }
struct StackLayouter<'a> { struct StackLayouter<'a> {
stack: &'a Stack, stack: &'a NodeStack,
main: SpecAxis, main: SpecAxis,
flow: Flow, dirs: LayoutDirs,
areas: Areas, areas: Areas,
finished: Vec<BoxLayout>, finished: Vec<Frame>,
layouts: Vec<(Length, BoxLayout, BoxAlign)>, frames: Vec<(Length, Frame, ChildAlign)>,
used: Gen<Length>, used: Gen<Length>,
ruler: Align, ruler: Align,
} }
impl<'a> StackLayouter<'a> { impl<'a> StackLayouter<'a> {
fn new(stack: &'a Stack, areas: Areas) -> Self { fn new(stack: &'a NodeStack, areas: Areas) -> Self {
Self { Self {
stack, stack,
main: stack.flow.main.axis(), main: stack.dirs.main.axis(),
flow: stack.flow, dirs: stack.dirs,
areas, areas,
finished: vec![], finished: vec![],
layouts: vec![], frames: vec![],
used: Gen::ZERO, used: Gen::ZERO,
ruler: Align::Start, ruler: Align::Start,
} }
@ -72,23 +72,23 @@ impl<'a> StackLayouter<'a> {
self.used.main += capped; 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 { if self.ruler > align.main {
self.finish_area(); 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() { if self.areas.in_full_last() {
// TODO: Diagnose once the necessary spans exist. // TODO: Diagnose once the necessary spans exist.
let _ = warning!("cannot fit box into any area"); let _ = warning!("cannot fit frame into any area");
break; break;
} else { } else {
self.finish_area(); self.finish_area();
} }
} }
let size = layout.size.switch(self.flow); let size = frame.size.switch(self.dirs);
self.layouts.push((self.used.main, layout, align)); self.frames.push((self.used.main, frame, align));
*self.areas.current.rem.get_mut(self.main) -= size.main; *self.areas.current.rem.get_mut(self.main) -= size.main;
self.used.main += size.main; self.used.main += size.main;
@ -98,7 +98,7 @@ impl<'a> StackLayouter<'a> {
fn finish_area(&mut self) { fn finish_area(&mut self) {
let full_size = { let full_size = {
let full = self.areas.current.full.switch(self.flow); let full = self.areas.current.full.switch(self.dirs);
Gen::new( Gen::new(
match self.stack.expansion.main { match self.stack.expansion.main {
Expansion::Fill => full.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) { for (before, frame, align) in std::mem::take(&mut self.frames) {
let child_size = layout.size.switch(self.flow); let child_size = frame.size.switch(self.dirs);
// Align along the main axis. // 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; let after_with_self = self.used.main - before;
before .. full_size.main - after_with_self before .. full_size.main - after_with_self
} else { } else {
@ -127,14 +127,14 @@ impl<'a> StackLayouter<'a> {
}); });
// Align along the cross axis. // 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 Length::ZERO .. full_size.cross - child_size.cross
} else { } else {
full_size.cross - child_size.cross .. Length::ZERO full_size.cross - child_size.cross .. Length::ZERO
}); });
let pos = Gen::new(main, cross).switch(self.flow).to_point(); let pos = Gen::new(main, cross).switch(self.dirs).to_point();
output.push_layout(pos, layout); output.push_frame(pos, frame);
} }
self.finished.push(output); self.finished.push(output);
@ -144,7 +144,7 @@ impl<'a> StackLayouter<'a> {
self.ruler = Align::Start; self.ruler = Align::Start;
} }
fn finish(mut self) -> Vec<BoxLayout> { fn finish(mut self) -> Vec<Frame> {
self.finish_area(); self.finish_area();
self.finished self.finished
} }

View File

@ -8,11 +8,11 @@ use crate::shaping;
/// A text node. /// A text node.
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct Text { pub struct NodeText {
/// The text. /// The text.
pub text: String, pub text: String,
/// How to align this text node in its parent. /// How to align this text node in its parent.
pub align: BoxAlign, pub align: ChildAlign,
/// The text direction. /// The text direction.
pub dir: Dir, pub dir: Dir,
/// The font size. /// The font size.
@ -23,15 +23,15 @@ pub struct Text {
pub variant: FontVariant, pub variant: FontVariant,
} }
impl Layout for Text { impl Layout for NodeText {
fn layout(&self, ctx: &mut LayoutContext, _: &Areas) -> Layouted { fn layout(&self, ctx: &mut LayoutContext, _: &Areas) -> Layouted {
let mut env = ctx.env.borrow_mut(); let mut env = ctx.env.borrow_mut();
Layouted::Layout( Layouted::Frame(
shaping::shape( shaping::shape(
&mut env.fonts,
&self.text, &self.text,
self.dir, self.dir,
self.font_size, self.font_size,
&mut env.fonts,
&self.families, &self.families,
self.variant, 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 { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Text({})", self.text) write!(f, "Text({})", self.text)
} }
} }
impl From<Text> for LayoutNode { impl From<NodeText> for Node {
fn from(text: Text) -> Self { fn from(text: NodeText) -> Self {
Self::Text(text) Self::Text(text)
} }
} }

View File

@ -5,23 +5,23 @@
//! [iterator of tokens][tokens]. This token stream is [parsed] into a [syntax //! [iterator of tokens][tokens]. This token stream is [parsed] into a [syntax
//! tree]. The structures describing the tree can be found in the [syntax] //! tree]. The structures describing the tree can be found in the [syntax]
//! module. //! module.
//! - **Evaluation:** The next step is to [evaluate] the parsed "script" to a //! - **Evaluation:** The next step is to [evaluate] the parsed "script" into a
//! [document], a high-level, fully styled representation. The nodes of the //! [layout tree], a high-level, fully styled representation. The nodes of
//! document tree are fully self-contained and order-independent and thus much //! this tree are fully self-contained and order-independent and thus much
//! better suited for layouting than the syntax tree. //! better suited for layouting than the syntax tree.
//! - **Layouting:** The next step is to [layout] the document into a portable //! - **Layouting:** Next, the tree is to [layouted] into a portable version of
//! version of the typeset document. The output of this is a vector of //! the typeset document. The output of this is a vector of [`Frame`]s
//! [`BoxLayout`]s (corresponding to pages), ready for exporting. //! (corresponding to pages), ready for exporting.
//! - **Exporting:** The finished layout can be exported into a supported //! - **Exporting:** The finished layout can be exported into a supported
//! format. Submodules for these formats are located in the [export] module. //! format. Submodules for these formats are located in the [export] module.
//! Currently, the only supported output format is [_PDF_]. //! Currently, the only supported output format is [_PDF_].
//! //!
//! [tokens]: parse::Tokens //! [tokens]: parse::Tokens
//! [parsed]: parse::parse //! [parsed]: parse::parse
//! [syntax tree]: syntax::SynTree //! [syntax tree]: syntax::Tree
//! [evaluate]: eval::eval //! [evaluate]: eval::eval
//! [document]: layout::Document //! [layout tree]: layout::Tree
//! [layout]: layout::layout //! [layouted]: layout::layout
//! [_PDF_]: export::pdf //! [_PDF_]: export::pdf
#[macro_use] #[macro_use]
@ -46,13 +46,13 @@ use std::rc::Rc;
use crate::diag::{Feedback, Pass}; use crate::diag::{Feedback, Pass};
use crate::env::SharedEnv; use crate::env::SharedEnv;
use crate::eval::State; use crate::eval::State;
use crate::layout::BoxLayout; use crate::layout::Frame;
/// Process _Typst_ source code directly into a collection of layouts. /// Process _Typst_ source code directly into a collection of frames.
pub fn typeset(src: &str, env: SharedEnv, state: State) -> Pass<Vec<BoxLayout>> { pub fn typeset(src: &str, env: SharedEnv, state: State) -> Pass<Vec<Frame>> {
let Pass { output: tree, feedback: f1 } = parse::parse(src); let Pass { output: syntax_tree, feedback: f1 } = parse::parse(src);
let Pass { output: document, feedback: f2 } = let Pass { output: layout_tree, feedback: f2 } =
eval::eval(&tree, Rc::clone(&env), state); eval::eval(&syntax_tree, Rc::clone(&env), state);
let layouts = layout::layout(&document, env); let frames = layout::layout(&layout_tree, env);
Pass::new(layouts, Feedback::join(f1, f2)) Pass::new(frames, Feedback::join(f1, f2))
} }

View File

@ -22,7 +22,7 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> Value {
if let Some((res, img)) = loaded { if let Some((res, img)) = loaded {
let dimensions = img.buf.dimensions(); let dimensions = img.buf.dimensions();
drop(env); drop(env);
ctx.push(Image { ctx.push(NodeImage {
res, res,
dimensions, dimensions,
width, width,
@ -40,7 +40,7 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> Value {
/// An image node. /// An image node.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
struct Image { struct NodeImage {
/// The resource id of the image file. /// The resource id of the image file.
res: ResourceId, res: ResourceId,
/// The pixel dimensions of the image. /// The pixel dimensions of the image.
@ -50,10 +50,10 @@ struct Image {
/// The fixed height, if any. /// The fixed height, if any.
height: Option<Linear>, height: Option<Linear>,
/// How to align this image node in its parent. /// 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 { fn layout(&self, _: &mut LayoutContext, areas: &Areas) -> Layouted {
let Area { rem, full } = areas.current; let Area { rem, full } = areas.current;
let pixel_ratio = (self.dimensions.0 as f64) / (self.dimensions.1 as f64); 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); let mut frame = Frame::new(size);
boxed.push( frame.push(Point::ZERO, Element::Image(Image { res: self.res, size }));
Point::ZERO,
LayoutElement::Image(ImageElement { res: self.res, size }),
);
Layouted::Layout(boxed, self.align) Layouted::Frame(frame, self.align)
} }
} }
impl From<Image> for LayoutNode { impl From<NodeImage> for Node {
fn from(image: Image) -> Self { fn from(image: NodeImage) -> Self {
Self::dynamic(image) Self::any(image)
} }
} }

View File

@ -1,5 +1,5 @@
use crate::eval::Softness; 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::paper::{Paper, PaperClass};
use crate::prelude::*; 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. // Check whether we know which axis this alignment belongs to.
if let Some(axis) = axis { if let Some(axis) = axis {
// We know the axis. // We know the axis.
let gen_axis = axis.switch(ctx.state.flow); let gen_axis = axis.switch(ctx.state.dirs);
let gen_align = arg.switch(ctx.state.flow); let gen_align = arg.switch(ctx.state.dirs);
if arg.axis().map_or(false, |a| a != axis) { if arg.axis().map_or(false, |a| a != axis) {
ctx.diag(error!(span, "invalid alignment for {} axis", axis)); ctx.diag(error!(span, "invalid alignment for {} axis", axis));
@ -132,7 +132,7 @@ impl Alignment {
impl Switch for Alignment { impl Switch for Alignment {
type Other = Align; type Other = Align;
fn switch(self, flow: Flow) -> Self::Other { fn switch(self, dirs: LayoutDirs) -> Self::Other {
let get = |dir: Dir, at_positive_start| { let get = |dir: Dir, at_positive_start| {
if dir.is_positive() == at_positive_start { if dir.is_positive() == at_positive_start {
Align::Start Align::Start
@ -141,12 +141,12 @@ impl Switch for Alignment {
} }
}; };
let flow = flow.switch(flow); let dirs = dirs.switch(dirs);
match self { match self {
Self::Left => get(flow.horizontal, true), Self::Left => get(dirs.horizontal, true),
Self::Right => get(flow.horizontal, false), Self::Right => get(dirs.horizontal, false),
Self::Top => get(flow.vertical, true), Self::Top => get(dirs.vertical, true),
Self::Bottom => get(flow.vertical, false), Self::Bottom => get(dirs.vertical, false),
Self::Center => Align::Center, 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 main = args.get(ctx, "main-dir");
let cross = args.get(ctx, "cross-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; let align = ctx.state.align;
ctx.start_content_group(); ctx.start_content_group();
@ -182,19 +182,14 @@ pub fn boxed(ctx: &mut EvalContext, args: &mut Args) -> Value {
let children = ctx.end_content_group(); 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, width,
height, height,
child: LayoutNode::dynamic(Stack { child: Node::any(NodeStack { dirs, align, expansion, children }),
flow,
align,
expansion: Spec::new(
Expansion::fill_if(width.is_some()),
Expansion::fill_if(height.is_some()),
)
.switch(flow),
children,
}),
}); });
ctx.state = snapshot; ctx.state = snapshot;
@ -227,8 +222,8 @@ fn spacing(ctx: &mut EvalContext, args: &mut Args, axis: SpecAxis) -> Value {
if let Some(linear) = spacing { if let Some(linear) = spacing {
let amount = linear.resolve(ctx.state.font.font_size()); let amount = linear.resolve(ctx.state.font.font_size());
let spacing = Spacing { amount, softness: Softness::Hard }; let spacing = NodeSpacing { amount, softness: Softness::Hard };
if axis == ctx.state.flow.main.axis() { if axis == ctx.state.dirs.main.axis() {
ctx.end_par_group(); ctx.end_par_group();
ctx.push(spacing); ctx.push(spacing);
ctx.start_par_group(); 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 main = args.get(ctx, "main-dir");
let cross = args.get(ctx, "cross-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); let mut softness = ctx.end_page_group(|_| false);
if let Some(body) = args.find::<ValueContent>(ctx) { if let Some(body) = args.find::<ValueContent>(ctx) {

View File

@ -49,7 +49,7 @@ fn main() -> anyhow::Result<()> {
let state = State::default(); let state = State::default();
let Pass { let Pass {
output: layouts, output: frames,
feedback: Feedback { mut diags, .. }, feedback: Feedback { mut diags, .. },
} = typeset(&src, Rc::clone(&env), state); } = 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.")?; fs::write(&dest_path, pdf_data).context("Failed to write PDF file.")?;
Ok(()) Ok(())

View File

@ -5,7 +5,7 @@ use crate::geom::{Length, Linear, Relative, Sides, Size};
/// Specification of a paper. /// Specification of a paper.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub struct Paper { pub struct Paper {
/// The kind of paper, which defines the default margins. /// The broad class this paper belongs to.
pub class: PaperClass, pub class: PaperClass,
/// The width of the paper in millimeters. /// The width of the paper in millimeters.
pub width: f64, 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)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum PaperClass { pub enum PaperClass {
Custom, Custom,

View File

@ -2,7 +2,7 @@ use super::*;
use crate::diag::Deco; use crate::diag::Deco;
/// Parse the arguments to a function call. /// Parse the arguments to a function call.
pub fn arguments(p: &mut Parser) -> Arguments { pub fn arguments(p: &mut Parser) -> ExprArgs {
collection(p, vec![]) collection(p, vec![])
} }
@ -74,7 +74,7 @@ trait Collection {
fn push_comma(&mut self) {} fn push_comma(&mut self) {}
} }
impl Collection for Arguments { impl Collection for ExprArgs {
fn push_arg(&mut self, _: &mut Parser, arg: Spanned<Argument>) { fn push_arg(&mut self, _: &mut Parser, arg: Spanned<Argument>) {
self.push(arg.v); self.push(arg.v);
} }
@ -85,17 +85,17 @@ impl Collection for Arguments {
enum State { enum State {
Unknown, Unknown,
Expr(Spanned<Expr>), Expr(Spanned<Expr>),
Array(Array), Array(ExprArray),
Dict(Dict), Dict(ExprDict),
} }
impl State { impl State {
fn into_expr(self) -> Expr { fn into_expr(self) -> Expr {
match self { match self {
Self::Unknown => Expr::Lit(Lit::Array(vec![])), Self::Unknown => Expr::Array(vec![]),
Self::Expr(expr) => expr.v, Self::Expr(expr) => expr.v,
Self::Array(array) => Expr::Lit(Lit::Array(array)), Self::Array(array) => Expr::Array(array),
Self::Dict(dict) => Expr::Lit(Lit::Dict(dict)), Self::Dict(dict) => Expr::Dict(dict),
} }
} }
} }

View File

@ -1,5 +1,3 @@
//! Conversion of byte positions to line/column locations.
use super::Scanner; use super::Scanner;
use crate::syntax::{Location, Offset, Pos}; use crate::syntax::{Location, Offset, Pos};

View File

@ -22,13 +22,13 @@ use crate::syntax::*;
use collection::{arguments, parenthesized}; use collection::{arguments, parenthesized};
/// Parse a string of source code. /// Parse a string of source code.
pub fn parse(src: &str) -> Pass<SynTree> { pub fn parse(src: &str) -> Pass<Tree> {
let mut p = Parser::new(src); let mut p = Parser::new(src);
Pass::new(tree(&mut p), p.finish()) Pass::new(tree(&mut p), p.finish())
} }
/// Parse a syntax tree. /// 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 // We keep track of whether we are at the start of a block or paragraph
// to know whether headings are allowed. // to know whether headings are allowed.
let mut at_start = true; let mut at_start = true;
@ -36,8 +36,8 @@ fn tree(p: &mut Parser) -> SynTree {
while !p.eof() { while !p.eof() {
if let Some(node) = p.span_if(|p| node(p, at_start)) { if let Some(node) = p.span_if(|p| node(p, at_start)) {
match node.v { match node.v {
SynNode::Parbreak => at_start = true, Node::Parbreak => at_start = true,
SynNode::Space => {} Node::Space => {}
_ => at_start = false, _ => at_start = false,
} }
tree.push(node); tree.push(node);
@ -47,42 +47,42 @@ fn tree(p: &mut Parser) -> SynTree {
} }
/// Parse a syntax node. /// Parse a syntax node.
fn node(p: &mut Parser, at_start: bool) -> Option<SynNode> { fn node(p: &mut Parser, at_start: bool) -> Option<Node> {
let node = match p.peek()? { let node = match p.peek()? {
Token::Space(newlines) => { Token::Space(newlines) => {
if newlines < 2 { if newlines < 2 {
SynNode::Space Node::Space
} else { } else {
SynNode::Parbreak Node::Parbreak
} }
} }
Token::Text(text) => SynNode::Text(text.into()), Token::Text(text) => Node::Text(text.into()),
Token::LineComment(_) | Token::BlockComment(_) => { Token::LineComment(_) | Token::BlockComment(_) => {
p.eat(); p.eat();
return None; return None;
} }
Token::Star => SynNode::Strong, Token::Star => Node::Strong,
Token::Underscore => SynNode::Emph, Token::Underscore => Node::Emph,
Token::Tilde => SynNode::Text("\u{00A0}".into()), Token::Tilde => Node::Text("\u{00A0}".into()),
Token::Backslash => SynNode::Linebreak, Token::Backslash => Node::Linebreak,
Token::Hashtag => { Token::Hashtag => {
if at_start { if at_start {
return Some(SynNode::Heading(heading(p))); return Some(Node::Heading(heading(p)));
} else { } 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::Raw(t) => Node::Raw(raw(p, t)),
Token::UnicodeEscape(t) => SynNode::Text(unicode_escape(p, t)), Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)),
Token::LeftBracket => { Token::LeftBracket => {
return Some(SynNode::Expr(Expr::Call(bracket_call(p)))); return Some(Node::Expr(Expr::Call(bracket_call(p))));
} }
Token::LeftBrace => { 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(); p.end_group();
if p.peek() == Some(Token::LeftBracket) { 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.span.expand(body.span);
inner.v.args.v.push(Argument::Pos(body)); inner.v.args.v.push(Argument::Pos(body));
} }
while let Some(mut top) = outer.pop() { while let Some(mut top) = outer.pop() {
let span = inner.span; let span = inner.span;
let node = inner.map(|c| SynNode::Expr(Expr::Call(c))); let node = inner.map(|c| Node::Expr(Expr::Call(c)));
let expr = Expr::Lit(Lit::Content(vec![node])).with_span(span); let expr = Expr::Content(vec![node]).with_span(span);
top.v.args.v.push(Argument::Pos(expr)); top.v.args.v.push(Argument::Pos(expr));
inner = top; inner = top;
} }
@ -227,7 +227,7 @@ fn bracket_subheader(p: &mut Parser) -> ExprCall {
} }
/// Parse the body of a bracketed function call. /// 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.push_mode(TokenMode::Body);
p.start_group(Group::Bracket); p.start_group(Group::Bracket);
let tree = tree(p); let tree = tree(p);
@ -299,13 +299,13 @@ fn value(p: &mut Parser) -> Option<Expr> {
let expr = match p.peek() { let expr = match p.peek() {
// Bracketed function call. // Bracketed function call.
Some(Token::LeftBracket) => { Some(Token::LeftBracket) => {
let node = p.span(|p| SynNode::Expr(Expr::Call(bracket_call(p)))); let node = p.span(|p| Node::Expr(Expr::Call(bracket_call(p))));
return Some(Expr::Lit(Lit::Content(vec![node]))); return Some(Expr::Content(vec![node]));
} }
// Content expression. // Content expression.
Some(Token::LeftBrace) => { Some(Token::LeftBrace) => {
return Some(Expr::Lit(Lit::Content(content(p)))); return Some(Expr::Content(content(p)));
} }
// Dictionary or just a parenthesized expression. // Dictionary or just a parenthesized expression.
@ -345,7 +345,7 @@ fn value(p: &mut Parser) -> Option<Expr> {
} }
// Parse a content value: `{...}`. // Parse a content value: `{...}`.
fn content(p: &mut Parser) -> SynTree { fn content(p: &mut Parser) -> Tree {
p.push_mode(TokenMode::Body); p.push_mode(TokenMode::Body);
p.start_group(Group::Brace); p.start_group(Group::Brace);
let tree = tree(p); let tree = tree(p);

View File

@ -1,5 +1,3 @@
//! Resolve strings and raw blocks.
use super::{is_newline, Scanner}; use super::{is_newline, Scanner};
use crate::syntax::{Ident, NodeRaw}; use crate::syntax::{Ident, NodeRaw};

View File

@ -1,9 +1,7 @@
//! Low-level char-based scanner.
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::slice::SliceIndex; use std::slice::SliceIndex;
/// A low-level featureful char-based scanner. /// A featureful char-based scanner.
#[derive(Clone)] #[derive(Clone)]
pub struct Scanner<'s> { pub struct Scanner<'s> {
src: &'s str, src: &'s str,

View File

@ -9,7 +9,7 @@ use crate::geom::Unit;
use crate::syntax::*; use crate::syntax::*;
use BinOp::*; use BinOp::*;
use SynNode::{Emph, Linebreak, Parbreak, Space, Strong}; use Node::{Emph, Linebreak, Parbreak, Space, Strong};
use UnOp::*; use UnOp::*;
macro_rules! t { macro_rules! t {
@ -82,16 +82,16 @@ macro_rules! into {
}; };
} }
fn Text(text: &str) -> SynNode { fn Text(text: &str) -> Node {
SynNode::Text(text.into()) Node::Text(text.into())
} }
fn Heading(level: impl Into<Spanned<u8>>, contents: SynTree) -> SynNode { fn Heading(level: impl Into<Spanned<u8>>, contents: Tree) -> Node {
SynNode::Heading(NodeHeading { level: level.into(), contents }) Node::Heading(NodeHeading { level: level.into(), contents })
} }
fn Raw(lang: Option<&str>, lines: &[&str], inline: bool) -> SynNode { fn Raw(lang: Option<&str>, lines: &[&str], inline: bool) -> Node {
SynNode::Raw(NodeRaw { Node::Raw(NodeRaw {
lang: lang.map(|id| Ident(id.into())), lang: lang.map(|id| Ident(id.into())),
lines: lines.iter().map(ToString::to_string).collect(), lines: lines.iter().map(ToString::to_string).collect(),
inline, inline,
@ -130,8 +130,8 @@ fn Str(string: &str) -> Expr {
Expr::Lit(Lit::Str(string.to_string())) Expr::Lit(Lit::Str(string.to_string()))
} }
fn Block(expr: Expr) -> SynNode { fn Block(expr: Expr) -> Node {
SynNode::Expr(expr) Node::Expr(expr)
} }
fn Binary( fn Binary(
@ -157,7 +157,7 @@ macro_rules! Array {
(@$($expr:expr),* $(,)?) => { (@$($expr:expr),* $(,)?) => {
vec![$(into!($expr)),*] vec![$(into!($expr)),*]
}; };
($($tts:tt)*) => (Expr::Lit(Lit::Array(Array![@$($tts)*]))); ($($tts:tt)*) => (Expr::Array(Array![@$($tts)*]));
} }
macro_rules! Dict { macro_rules! Dict {
@ -167,7 +167,7 @@ macro_rules! Dict {
expr: into!($expr) expr: into!($expr)
}),*] }),*]
}; };
($($tts:tt)*) => (Expr::Lit(Lit::Dict(Dict![@$($tts)*]))); ($($tts:tt)*) => (Expr::Dict(Dict![@$($tts)*]));
} }
macro_rules! Args { macro_rules! Args {
@ -187,7 +187,7 @@ macro_rules! Args {
macro_rules! Content { macro_rules! Content {
(@$($node:expr),* $(,)?) => (vec![$(into!($node)),*]); (@$($node:expr),* $(,)?) => (vec![$(into!($node)),*]);
($($tts:tt)*) => (Expr::Lit(Lit::Content(Content![@$($tts)*]))); ($($tts:tt)*) => (Expr::Content(Content![@$($tts)*]));
} }
macro_rules! Call { macro_rules! Call {
@ -201,7 +201,7 @@ macro_rules! Call {
} }
}; };
(@$($tts:tt)*) => (Expr::Call(Call!(@@$($tts)*))); (@$($tts:tt)*) => (Expr::Call(Call!(@@$($tts)*)));
($($tts:tt)*) => (SynNode::Expr(Call!(@$($tts)*))); ($($tts:tt)*) => (Node::Expr(Call!(@$($tts)*)));
} }
#[test] #[test]

View File

@ -1,5 +1,3 @@
//! Tokenization.
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use super::{is_newline, Scanner}; use super::{is_newline, Scanner};

View File

@ -8,7 +8,7 @@ pub use crate::eval::{
}; };
pub use crate::geom::*; pub use crate::geom::*;
#[doc(no_inline)] #[doc(no_inline)]
pub use crate::layout::LayoutNode; pub use crate::layout::Node;
#[doc(no_inline)] #[doc(no_inline)]
pub use crate::syntax::{Span, Spanned, SynTree, WithSpan}; pub use crate::syntax::{Span, Spanned, WithSpan};
pub use crate::{error, warning}; pub use crate::{error, impl_type, warning};

View File

@ -11,7 +11,7 @@ use ttf_parser::{Face, GlyphId};
use crate::font::FontLoader; use crate::font::FontLoader;
use crate::geom::{Dir, Length, Point, Size}; use crate::geom::{Dir, Length, Point, Size};
use crate::layout::{BoxLayout, LayoutElement}; use crate::layout::{Element, Frame};
/// A shaped run of text. /// A shaped run of text.
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
@ -31,13 +31,13 @@ pub struct Shaped {
impl Shaped { impl Shaped {
/// Create a new shape run with empty `text`, `glyphs` and `offsets`. /// 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 { Self {
text: String::new(), text: String::new(),
face, face,
glyphs: vec![], glyphs: vec![],
offsets: 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( pub fn shape(
loader: &mut FontLoader,
text: &str, text: &str,
dir: Dir, dir: Dir,
font_size: Length, font_size: Length,
loader: &mut FontLoader,
fallback: &FallbackTree, fallback: &FallbackTree,
variant: FontVariant, variant: FontVariant,
) -> BoxLayout { ) -> Frame {
let mut layout = BoxLayout::new(Size::new(Length::ZERO, font_size)); let mut frame = Frame::new(Size::new(Length::ZERO, font_size));
let mut shaped = Shaped::new(FaceId::MAX, font_size); let mut shaped = Shaped::new(FaceId::MAX, font_size);
let mut offset = Length::ZERO; let mut offset = Length::ZERO;
@ -91,9 +91,9 @@ pub fn shape(
// Flush the buffer if we change the font face. // Flush the buffer if we change the font face.
if shaped.face != id && !shaped.text.is_empty() { if shaped.face != id && !shaped.text.is_empty() {
let pos = Point::new(layout.size.width, Length::ZERO); let pos = Point::new(frame.size.width, Length::ZERO);
layout.push(pos, LayoutElement::Text(shaped)); frame.push(pos, Element::Text(shaped));
layout.size.width += offset; frame.size.width += offset;
shaped = Shaped::new(FaceId::MAX, font_size); shaped = Shaped::new(FaceId::MAX, font_size);
offset = Length::ZERO; offset = Length::ZERO;
} }
@ -108,12 +108,12 @@ pub fn shape(
// Flush the last buffered parts of the word. // Flush the last buffered parts of the word.
if !shaped.text.is_empty() { if !shaped.text.is_empty() {
let pos = Point::new(layout.size.width, Length::ZERO); let pos = Point::new(frame.size.width, Length::ZERO);
layout.push(pos, LayoutElement::Text(shaped)); frame.push(pos, Element::Text(shaped));
layout.size.width += offset; frame.size.width += offset;
} }
layout frame
} }
/// Looks up the glyph for `c` and returns its index alongside its width at the /// Looks up the glyph for `c` and returns its index alongside its width at the

View File

@ -1,5 +1,3 @@
//! Expressions.
use super::*; use super::*;
use crate::color::RgbaColor; use crate::color::RgbaColor;
use crate::geom::Unit; use crate::geom::Unit;
@ -7,7 +5,7 @@ use crate::geom::Unit;
/// An expression. /// An expression.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Expr { pub enum Expr {
/// A literal: `true`, `1cm`, `"hi"`, `{_Hey!_}`. /// A literal: `true`, `1cm`, `"hi"`.
Lit(Lit), Lit(Lit),
/// An invocation of a function: `[foo ...]`, `foo(...)`. /// An invocation of a function: `[foo ...]`, `foo(...)`.
Call(ExprCall), Call(ExprCall),
@ -15,6 +13,12 @@ pub enum Expr {
Unary(ExprUnary), Unary(ExprUnary),
/// A binary operation: `a + b`, `a / b`. /// A binary operation: `a + b`, `a / b`.
Binary(ExprBinary), 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(...)`. /// An invocation of a function: `[foo ...]`, `foo(...)`.
@ -23,14 +27,14 @@ pub struct ExprCall {
/// The name of the function. /// The name of the function.
pub name: Spanned<Ident>, pub name: Spanned<Ident>,
/// The arguments to the function. /// The arguments to the function.
pub args: Spanned<Arguments>, pub args: Spanned<ExprArgs>,
} }
/// The arguments to a function: `12, draw: false`. /// The arguments to a function: `12, draw: false`.
/// ///
/// In case of a bracketed invocation with a body, the body is _not_ /// In case of a bracketed invocation with a body, the body is _not_
/// included in the span for the sake of clearer error messages. /// included in the span for the sake of clearer error messages.
pub type Arguments = Vec<Argument>; pub type ExprArgs = Vec<Argument>;
/// An argument to a function call: `12` or `draw: false`. /// An argument to a function call: `12` or `draw: false`.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -41,6 +45,15 @@ pub enum Argument {
Named(Named), 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<Ident>,
/// The right-hand side of the pair: `dashed`.
pub expr: Spanned<Expr>,
}
/// A unary operation: `-x`. /// A unary operation: `-x`.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct ExprUnary { pub struct ExprUnary {
@ -81,6 +94,15 @@ pub enum BinOp {
Div, Div,
} }
/// An array expression: `(1, "hi", 12cm)`.
pub type ExprArray = SpanVec<Expr>;
/// A dictionary expression: `(color: #f79143, pattern: dashed)`.
pub type ExprDict = Vec<Named>;
/// A content expression: `{*Hello* there!}`.
pub type ExprContent = Tree;
/// A literal. /// A literal.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Lit { pub enum Lit {
@ -103,25 +125,4 @@ pub enum Lit {
Color(RgbaColor), Color(RgbaColor),
/// A string literal: `"hello!"`. /// A string literal: `"hello!"`.
Str(String), 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<Expr>;
/// A dictionary literal: `(color: #f79143, pattern: dashed)`.
pub type Dict = Vec<Named>;
/// A pair of a name and an expression: `pattern: dashed`.
#[derive(Debug, Clone, PartialEq)]
pub struct Named {
/// The name: `pattern`.
pub name: Spanned<Ident>,
/// The right-hand side of the pair: `dashed`.
pub expr: Spanned<Expr>,
} }

View File

@ -1,5 +1,3 @@
//! Unicode identifiers.
use std::ops::Deref; use std::ops::Deref;
use unicode_xid::UnicodeXID; 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 { pub fn is_ident(string: &str) -> bool {
let mut chars = string.chars(); let mut chars = string.chars();
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)) .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 { pub fn is_id_start(c: char) -> bool {
c.is_xid_start() || c == '_' 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 { pub fn is_id_continue(c: char) -> bool {
c.is_xid_continue() || c == '_' || c == '-' c.is_xid_continue() || c == '_' || c == '-'
} }

View File

@ -12,5 +12,5 @@ pub use node::*;
pub use span::*; pub use span::*;
pub use token::*; pub use token::*;
/// A collection of nodes which form a tree together with the nodes' children. /// A collection of nodes which form a tree together with their children.
pub type SynTree = SpanVec<SynNode>; pub type Tree = SpanVec<Node>;

View File

@ -1,11 +1,8 @@
//! Syntax tree nodes.
use super::*; use super::*;
/// A syntax node, which encompasses a single logical entity of parsed source /// A syntax node, encompassing a single logical entity of parsed source code.
/// code.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum SynNode { pub enum Node {
/// Plain text. /// Plain text.
Text(String), Text(String),
@ -36,7 +33,7 @@ pub struct NodeHeading {
/// The section depth (numer of hashtags minus 1). /// The section depth (numer of hashtags minus 1).
pub level: Spanned<u8>, pub level: Spanned<u8>,
/// The contents of the heading. /// The contents of the heading.
pub contents: SynTree, pub contents: Tree,
} }
/// A raw block with optional syntax highlighting: `` `raw` ``. /// A raw block with optional syntax highlighting: `` `raw` ``.

View File

@ -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::fmt::{self, Debug, Display, Formatter};
use std::ops::Range; use std::ops::Range;
@ -66,12 +64,18 @@ impl<T> Spanned<T> {
} }
/// Map the value using a function while keeping the span. /// Map the value using a function while keeping the span.
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Spanned<U> { pub fn map<F, U>(self, f: F) -> Spanned<U>
where
F: FnOnce(T) -> U,
{
Spanned { v: f(self.v), span: self.span } Spanned { v: f(self.v), span: self.span }
} }
/// Maps the span while keeping the value. /// Maps the span while keeping the value.
pub fn map_span(mut self, f: impl FnOnce(Span) -> Span) -> Self { pub fn map_span<F>(mut self, f: F) -> Self
where
F: FnOnce(Span) -> Span,
{
self.span = f(self.span); self.span = f(self.span);
self self
} }
@ -102,7 +106,7 @@ impl<T: Debug> Debug for Spanned<T> {
} }
} }
/// Locates a slice of source code. /// Bounds of a slice of source code.
#[derive(Copy, Clone, Ord, PartialOrd)] #[derive(Copy, Clone, Ord, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Span { pub struct Span {

View File

@ -1,5 +1,3 @@
//! Token definition.
use crate::geom::Unit; use crate::geom::Unit;
/// A minimal semantic entity of source code. /// A minimal semantic entity of source code.

View File

@ -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] [page width: 450pt, height: 300pt, margins: 1cm]

View File

@ -19,7 +19,7 @@ use typst::eval::State;
use typst::export::pdf; use typst::export::pdf;
use typst::font::FontLoader; use typst::font::FontLoader;
use typst::geom::{Length, Point, Sides, Size}; 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::parse::{LineMap, Scanner};
use typst::shaping::Shaped; use typst::shaping::Shaped;
use typst::syntax::{Location, Pos, SpanVec, Spanned, WithSpan}; 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())); state.page.margins = Sides::uniform(Some(Length::pt(10.0).into()));
let Pass { let Pass {
output: layouts, output: frames,
feedback: Feedback { mut diags, .. }, feedback: Feedback { mut diags, .. },
} = typeset(&src, Rc::clone(env), state); } = typeset(&src, Rc::clone(env), state);
diags.sort(); diags.sort();
let env = env.borrow(); 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(); 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(); fs::write(pdf_path, pdf_data).unwrap();
let mut ok = true; let mut ok = true;
@ -226,12 +226,12 @@ fn print_diag(diag: &Spanned<Diag>, map: &LineMap) {
println!("{}: {}-{}: {}", diag.v.level, start, end, diag.v.message); 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 pad = Length::pt(5.0);
let height = pad + layouts.iter().map(|l| l.size.height + pad).sum::<Length>(); let height = pad + frames.iter().map(|l| l.size.height + pad).sum::<Length>();
let width = 2.0 * pad let width = 2.0 * pad
+ layouts + frames
.iter() .iter()
.map(|l| l.size.width) .map(|l| l.size.width)
.max_by(|a, b| a.partial_cmp(&b).unwrap()) .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); canvas.pixmap.fill(Color::BLACK);
let mut origin = Point::new(pad, pad); let mut origin = Point::new(pad, pad);
for layout in layouts { for frame in frames {
let mut paint = Paint::default(); let mut paint = Paint::default();
paint.set_color(Color::WHITE); paint.set_color(Color::WHITE);
@ -252,26 +252,26 @@ fn draw(layouts: &[BoxLayout], env: &Env, pixel_per_pt: f32) -> Canvas {
Rect::from_xywh( Rect::from_xywh(
origin.x.to_pt() as f32, origin.x.to_pt() as f32,
origin.y.to_pt() as f32, origin.y.to_pt() as f32,
layout.size.width.to_pt() as f32, frame.size.width.to_pt() as f32,
layout.size.height.to_pt() as f32, frame.size.height.to_pt() as f32,
) )
.unwrap(), .unwrap(),
&paint, &paint,
); );
for &(pos, ref element) in &layout.elements { for &(pos, ref element) in &frame.elements {
let pos = origin + pos; let pos = origin + pos;
match element { match element {
LayoutElement::Text(shaped) => { Element::Text(shaped) => {
draw_text(&mut canvas, pos, env, shaped); draw_text(&mut canvas, pos, env, shaped);
} }
LayoutElement::Image(image) => { Element::Image(image) => {
draw_image(&mut canvas, pos, env, image); draw_image(&mut canvas, pos, env, image);
} }
} }
} }
origin.y += layout.size.height + pad; origin.y += frame.size.height + pad;
} }
canvas 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::<ImageResource>(element.res); let img = &env.resources.loaded::<ImageResource>(element.res);
let mut pixmap = Pixmap::new(img.buf.width(), img.buf.height()).unwrap(); let mut pixmap = Pixmap::new(img.buf.width(), img.buf.height()).unwrap();