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.
let state = State::default();
let tree = parse(COMA).output;
let document = eval(&tree, Rc::clone(&env), state.clone()).output;
let layouts = layout(&document, Rc::clone(&env));
let syntax_tree = parse(COMA).output;
let layout_tree = eval(&syntax_tree, Rc::clone(&env), state.clone()).output;
let frames = layout(&layout_tree, Rc::clone(&env));
// Bench!
bench!("parse-coma": parse(COMA));
bench!("eval-coma": eval(&tree, Rc::clone(&env), state.clone()));
bench!("layout-coma": layout(&document, Rc::clone(&env)));
bench!("eval-coma": eval(&syntax_tree, Rc::clone(&env), state.clone()));
bench!("layout-coma": layout(&layout_tree, Rc::clone(&env)));
bench!("typeset-coma": typeset(COMA, Rc::clone(&env), state.clone()));
let env = env.borrow();
bench!("export-pdf-coma": pdf::export(&layouts, &env));
bench!("export-pdf-coma": pdf::export(&frames, &env));
}
criterion_group!(benches, benchmarks);

View File

@ -1,8 +1,6 @@
//! Diagnostics and decorations for source code.
//!
//! There are no fatal errors. The document will always compile and yield a
//! layout on a best effort process, but diagnostics are nevertheless generated
//! for incorrect things.
//! Errors are never fatal, the document will always compile and yield a layout.
use crate::syntax::SpanVec;
use std::fmt::{self, Display, Formatter};
@ -21,22 +19,9 @@ impl<T> Pass<T> {
pub fn new(output: T, feedback: Feedback) -> Self {
Self { output, feedback }
}
/// Create a new pass with empty feedback.
pub fn okay(output: T) -> Self {
Self { output, feedback: Feedback::new() }
}
/// Map the output type and keep the feedback data.
pub fn map<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)]
pub struct Feedback {
/// Diagnostics about the source code.
@ -64,7 +49,7 @@ impl Feedback {
}
}
/// A diagnostic that arose in parsing or layouting.
/// A diagnostic with severity level and message.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Diag {

View File

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

View File

@ -1,14 +1,37 @@
use super::*;
use crate::diag::Deco;
/// Evaluated arguments to a function.
#[derive(Debug)]
pub struct Args {
span: Span,
pos: SpanVec<Value>,
named: Vec<(Spanned<String>, Spanned<Value>)>,
impl Eval for Spanned<&ExprCall> {
type Output = Value;
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
let name = &self.v.name.v;
let span = self.v.name.span;
if let Some(value) = ctx.state.scope.get(name) {
if let Value::Func(func) = value {
let func = func.clone();
ctx.deco(Deco::Resolved.with_span(span));
let mut args = self.v.args.as_ref().eval(ctx);
let returned = func(ctx, &mut args);
args.finish(ctx);
return returned;
} else {
let ty = value.type_name();
ctx.diag(error!(span, "a value of type {} is not callable", ty));
}
} else if !name.is_empty() {
ctx.diag(error!(span, "unknown function"));
}
ctx.deco(Deco::Unresolved.with_span(span));
Value::Error
}
}
impl Eval for Spanned<&Arguments> {
impl Eval for Spanned<&ExprArgs> {
type Output = Args;
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
@ -33,6 +56,14 @@ impl Eval for Spanned<&Arguments> {
}
}
/// Evaluated arguments to a function.
#[derive(Debug)]
pub struct Args {
span: Span,
pos: SpanVec<Value>,
named: Vec<(Spanned<String>, Spanned<Value>)>,
}
impl Args {
/// Find the first convertible positional argument.
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))
}
/// Convert the value for the given named argument.
///
/// Generates an error if the conversion fails.
/// Convert the value for the given named argument, producing an error if
/// the conversion fails.
pub fn get<'a, T>(&mut self, ctx: &mut EvalContext, name: &str) -> Option<T>
where
T: Cast<Spanned<Value>>,
@ -78,7 +108,7 @@ impl Args {
cast(ctx, value)
}
/// Generate "unexpected argument" errors for all remaining arguments.
/// Produce "unexpected argument" errors for all remaining arguments.
pub fn finish(self, ctx: &mut EvalContext) {
let a = self.pos.iter().map(|v| v.as_ref());
let b = self.named.iter().map(|(k, v)| (&v.v).with_span(k.span.join(v.span)));

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]
mod value;
mod args;
mod call;
mod context;
mod scope;
mod state;
pub use args::*;
pub use call::*;
pub use context::*;
pub use scope::*;
pub use state::*;
pub use value::*;
use std::any::Any;
use std::rc::Rc;
use fontdock::FontStyle;
use crate::color::Color;
use crate::diag::Diag;
use crate::diag::{Deco, Feedback, Pass};
use crate::diag::Pass;
use crate::env::SharedEnv;
use crate::geom::{BoxAlign, Dir, Flow, Gen, Length, Linear, Relative, Sides, Size};
use crate::layout::{
Document, Expansion, LayoutNode, Pad, Pages, Par, Spacing, Stack, Text,
};
use crate::geom::{Gen, Length, Relative};
use crate::layout::{self, Expansion, NodeSpacing, NodeStack};
use crate::syntax::*;
/// Evaluate a syntax tree into a document.
/// Evaluate a syntax tree into a layout tree.
///
/// The given `state` is the base state that may be updated over the course of
/// evaluation.
pub fn eval(tree: &SynTree, env: SharedEnv, state: State) -> Pass<Document> {
pub fn eval(tree: &Tree, env: SharedEnv, state: State) -> Pass<layout::Tree> {
let mut ctx = EvalContext::new(env, state);
ctx.start_page_group(Softness::Hard);
tree.eval(&mut ctx);
@ -38,285 +34,6 @@ pub fn eval(tree: &SynTree, env: SharedEnv, state: State) -> Pass<Document> {
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.
///
/// _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 = ();
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 = ();
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
match self.v {
SynNode::Text(text) => {
Node::Text(text) => {
let node = ctx.make_text_node(text.clone());
ctx.push(node);
}
SynNode::Space => {
Node::Space => {
let em = ctx.state.font.font_size();
ctx.push(Spacing {
ctx.push(NodeSpacing {
amount: ctx.state.par.word_spacing.resolve(em),
softness: Softness::Soft,
});
}
SynNode::Linebreak => ctx.apply_linebreak(),
SynNode::Parbreak => ctx.apply_parbreak(),
Node::Linebreak => ctx.apply_linebreak(),
Node::Parbreak => ctx.apply_parbreak(),
SynNode::Strong => ctx.state.font.strong ^= true,
SynNode::Emph => ctx.state.font.emph ^= true,
Node::Strong => ctx.state.font.strong ^= true,
Node::Emph => ctx.state.font.emph ^= true,
SynNode::Heading(heading) => heading.with_span(self.span).eval(ctx),
SynNode::Raw(raw) => raw.with_span(self.span).eval(ctx),
Node::Heading(heading) => heading.with_span(self.span).eval(ctx),
Node::Raw(raw) => raw.with_span(self.span).eval(ctx),
SynNode::Expr(expr) => {
Node::Expr(expr) => {
let value = expr.with_span(self.span).eval(ctx);
value.eval(ctx)
}
@ -413,15 +130,15 @@ impl Eval for Spanned<&NodeRaw> {
let mut children = vec![];
for line in &self.v.lines {
children.push(LayoutNode::Text(ctx.make_text_node(line.clone())));
children.push(LayoutNode::Spacing(Spacing {
children.push(layout::Node::Text(ctx.make_text_node(line.clone())));
children.push(layout::Node::Spacing(NodeSpacing {
amount: line_spacing,
softness: Softness::Hard,
}));
}
ctx.push(Stack {
flow: ctx.state.flow,
ctx.push(NodeStack {
dirs: ctx.state.dirs,
align: ctx.state.align,
expansion: Gen::uniform(Expansion::Fit),
children,
@ -436,10 +153,13 @@ impl Eval for Spanned<&Expr> {
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
match self.v {
Expr::Lit(lit) => lit.with_span(self.span).eval(ctx),
Expr::Call(call) => call.with_span(self.span).eval(ctx),
Expr::Unary(unary) => unary.with_span(self.span).eval(ctx),
Expr::Binary(binary) => binary.with_span(self.span).eval(ctx),
Expr::Lit(v) => v.with_span(self.span).eval(ctx),
Expr::Call(v) => v.with_span(self.span).eval(ctx),
Expr::Unary(v) => v.with_span(self.span).eval(ctx),
Expr::Binary(v) => v.with_span(self.span).eval(ctx),
Expr::Array(v) => Value::Array(v.with_span(self.span).eval(ctx)),
Expr::Dict(v) => Value::Dict(v.with_span(self.span).eval(ctx)),
Expr::Content(v) => Value::Content(v.clone()),
}
}
}
@ -463,14 +183,11 @@ impl Eval for Spanned<&Lit> {
Lit::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
Lit::Color(v) => Value::Color(Color::Rgba(v)),
Lit::Str(ref v) => Value::Str(v.clone()),
Lit::Array(ref v) => Value::Array(v.with_span(self.span).eval(ctx)),
Lit::Dict(ref v) => Value::Dict(v.with_span(self.span).eval(ctx)),
Lit::Content(ref v) => Value::Content(v.clone()),
}
}
}
impl Eval for Spanned<&Array> {
impl Eval for Spanned<&ExprArray> {
type Output = ValueArray;
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
@ -478,7 +195,7 @@ impl Eval for Spanned<&Array> {
}
}
impl Eval for Spanned<&Dict> {
impl Eval for Spanned<&ExprDict> {
type Output = ValueDict;
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
@ -489,36 +206,6 @@ impl Eval for Spanned<&Dict> {
}
}
impl Eval for Spanned<&ExprCall> {
type Output = Value;
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
let name = &self.v.name.v;
let span = self.v.name.span;
if let Some(value) = ctx.state.scope.get(name) {
if let Value::Func(func) = value {
let func = func.clone();
ctx.feedback.decos.push(Deco::Resolved.with_span(span));
let mut args = self.v.args.as_ref().eval(ctx);
let returned = func(ctx, &mut args);
args.finish(ctx);
return returned;
} else {
let ty = value.type_name();
ctx.diag(error!(span, "a value of type {} is not callable", ty));
}
} else if !name.is_empty() {
ctx.diag(error!(span, "unknown function"));
}
ctx.feedback.decos.push(Deco::Unresolved.with_span(span));
Value::Error
}
}
impl Eval for Spanned<&ExprUnary> {
type Output = Value;

View File

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

View File

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

View File

@ -1,5 +1,3 @@
//! Computational values.
use std::any::Any;
use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter};
@ -9,7 +7,7 @@ use std::rc::Rc;
use super::{Args, Eval, EvalContext};
use crate::color::Color;
use crate::geom::{Length, Linear, Relative};
use crate::syntax::{Spanned, SynTree, WithSpan};
use crate::syntax::{Spanned, Tree, WithSpan};
/// A computational value.
#[derive(Clone, PartialEq)]
@ -47,6 +45,14 @@ pub enum Value {
}
impl Value {
/// Create a new dynamic value.
pub fn any<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.
pub fn cast<T>(self) -> CastResult<T, Self>
where
@ -130,7 +136,7 @@ pub type ValueArray = Vec<Value>;
pub type ValueDict = HashMap<String, Value>;
/// A content value: `{*Hi* there}`.
pub type ValueContent = SynTree;
pub type ValueContent = Tree;
/// A wrapper around a reference-counted executable function.
#[derive(Clone)]
@ -197,7 +203,7 @@ impl ValueAny {
self.0.as_any().downcast_ref()
}
/// The name of the stored object's type.
/// The name of the stored value's type.
pub fn type_name(&self) -> &'static str {
self.0.dyn_type_name()
}
@ -289,7 +295,7 @@ pub enum CastResult<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> {
match self {
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:
/// - [`Type`] for `T`,
@ -419,7 +425,7 @@ macro_rules! impl_type {
impl From<$type> for $crate::eval::Value {
fn from(any: $type) -> Self {
$crate::eval::Value::Any($crate::eval::ValueAny::new(any))
$crate::eval::Value::any(any)
}
}

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
use super::*;
/// The directions along which content flows in a container.
pub type Flow = Gen<Dir>;
/// The directions along which nodes are layouted.
pub type LayoutDirs = Gen<Dir>;
/// The four directions into which content can be laid out.
#[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> {
type Other = Spec<T>;
fn switch(self, flow: Flow) -> Self::Other {
match flow.main.axis() {
fn switch(self, dirs: LayoutDirs) -> Self::Other {
match dirs.main.axis() {
SpecAxis::Horizontal => Spec::new(self.main, self.cross),
SpecAxis::Vertical => Spec::new(self.cross, self.main),
}
@ -80,10 +80,10 @@ impl GenAxis {
impl Switch for GenAxis {
type Other = SpecAxis;
fn switch(self, flow: Flow) -> Self::Other {
fn switch(self, dirs: LayoutDirs) -> Self::Other {
match self {
Self::Main => flow.main.axis(),
Self::Cross => flow.cross.axis(),
Self::Main => dirs.main.axis(),
Self::Cross => dirs.cross.axis(),
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
//! Layouting of documents.
//! Layouting.
mod fixed;
mod node;
@ -20,10 +20,48 @@ pub use spacing::*;
pub use stack::*;
pub use text::*;
/// Layout a document and return the produced layouts.
pub fn layout(document: &Document, env: SharedEnv) -> Vec<BoxLayout> {
let mut ctx = LayoutContext { env };
document.layout(&mut ctx)
/// Layout a tree into a collection of frames.
pub fn layout(tree: &Tree, env: SharedEnv) -> Vec<Frame> {
tree.layout(&mut LayoutContext { env })
}
/// A tree of layout nodes.
#[derive(Debug, Clone, PartialEq)]
pub struct Tree {
/// Runs of pages with the same properties.
pub runs: Vec<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.
@ -33,22 +71,30 @@ pub struct LayoutContext {
pub env: SharedEnv,
}
/// Layout a node.
pub trait Layout {
/// Layout the node into the given areas.
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted;
/// An area into which content can be laid out.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Area {
/// The remaining size of this area.
pub rem: Size,
/// The full size this area once had (used for relative sizing).
pub full: Size,
}
/// A sequence of areas to layout into.
impl Area {
/// Create a new area.
pub fn new(size: Size) -> Self {
Self { rem: size, full: size }
}
}
/// A collection of areas to layout into.
#[derive(Debug, Clone, PartialEq)]
pub struct Areas {
/// The current area.
pub current: Area,
/// The backlog of followup areas.
///
/// _Note_: This works stack-like and not queue-like!
/// A stack of followup areas (the next area is the last element).
pub backlog: Vec<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>,
}
@ -86,23 +132,30 @@ impl Areas {
}
}
/// The area into which content can be laid out.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Area {
/// The remaining size of this area.
pub rem: Size,
/// The full size this area once had (used for relative sizing).
pub full: Size,
/// The result of layouting a node.
#[derive(Debug, Clone, PartialEq)]
pub enum Layouted {
/// Spacing that should be added to the parent.
Spacing(Length),
/// A layout that should be added to and aligned in the parent.
Frame(Frame, ChildAlign),
/// Multiple layouts.
Frames(Vec<Frame>, ChildAlign),
}
impl Area {
/// Create a new area.
pub fn new(size: Size) -> Self {
Self { rem: size, full: size }
impl Layouted {
/// Return all frames contained in this variant (zero, one or arbitrarily
/// many).
pub fn frames(self) -> Vec<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)]
pub enum Expansion {
/// Fit the content.
@ -111,111 +164,49 @@ pub enum Expansion {
Fill,
}
impl Expansion {
/// Returns `Fill` if the condition is true and `Fit` otherwise.
pub fn fill_if(condition: bool) -> Self {
if condition { Self::Fill } else { Self::Fit }
}
}
/// The result of [layouting](Layout::layout) a node.
/// A finished layout with elements at fixed positions.
#[derive(Debug, Clone, PartialEq)]
pub enum Layouted {
/// Spacing that should be added to the parent.
Spacing(Length),
/// A layout that should be added to and aligned in the parent.
Layout(BoxLayout, BoxAlign),
/// Multiple layouts.
Layouts(Vec<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 struct Frame {
/// The size of the frame.
pub size: Size,
/// The elements composing this layout.
pub elements: Vec<(Point, LayoutElement)>,
pub elements: Vec<(Point, Element)>,
}
impl BoxLayout {
/// Create a new empty collection.
impl Frame {
/// Create a new, empty frame.
pub fn new(size: Size) -> Self {
Self { size, elements: vec![] }
}
/// Add an element at a position.
pub fn push(&mut self, pos: Point, element: LayoutElement) {
pub fn push(&mut self, pos: Point, element: Element) {
self.elements.push((pos, element));
}
/// Add all elements of another collection, placing them relative to the
/// given position.
pub fn push_layout(&mut self, pos: Point, more: Self) {
for (subpos, element) in more.elements {
/// Add all elements of another frame, placing them relative to the given
/// position.
pub fn push_frame(&mut self, pos: Point, subframe: Self) {
for (subpos, element) in subframe.elements {
self.push(pos + subpos, element);
}
}
}
/// A layout element, the basic building block layouts are composed of.
/// The building block frames are composed of.
#[derive(Debug, Clone, PartialEq)]
pub enum LayoutElement {
pub enum Element {
/// Shaped text.
Text(Shaped),
/// An image.
Image(ImageElement),
Image(Image),
}
/// An image.
/// An image element.
#[derive(Debug, Clone, PartialEq)]
pub struct ImageElement {
/// The image.
pub struct Image {
/// The image resource.
pub res: ResourceId,
/// The document size of the image.
/// The size of the image in the document.
pub size: Size,
}
/// The top-level layout node.
#[derive(Debug, Clone, PartialEq)]
pub struct Document {
/// The runs of pages with same properties.
pub runs: Vec<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::fmt::{self, Debug, Formatter};
use super::*;
/// A self-contained, styled layout node.
/// A self-contained layout node.
#[derive(Clone, PartialEq)]
pub enum LayoutNode {
/// A spacing node.
Spacing(Spacing),
pub enum Node {
/// A text node.
Text(Text),
/// A dynamic that can implement custom layouting behaviour.
Dyn(Dynamic),
Text(NodeText),
/// A spacing node.
Spacing(NodeSpacing),
/// A dynamic node that can implement custom layouting behaviour.
Any(NodeAny),
}
impl LayoutNode {
impl Node {
/// Create a new dynamic node.
pub fn dynamic<T>(inner: T) -> Self
pub fn any<T>(any: T) -> Self
where
T: Layout + Debug + Clone + PartialEq + 'static,
{
Self::Dyn(Dynamic::new(inner))
Self::Any(NodeAny::new(any))
}
}
impl Layout for LayoutNode {
impl Layout for Node {
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted {
match self {
Self::Spacing(spacing) => spacing.layout(ctx, areas),
Self::Text(text) => text.layout(ctx, areas),
Self::Dyn(dynamic) => dynamic.layout(ctx, areas),
Self::Any(any) => any.layout(ctx, areas),
}
}
}
impl Debug for LayoutNode {
impl Debug for Node {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Spacing(spacing) => spacing.fmt(f),
Self::Text(text) => text.fmt(f),
Self::Dyn(dynamic) => dynamic.fmt(f),
Self::Any(any) => any.fmt(f),
}
}
}
/// A wrapper around a dynamic layouting node.
pub struct Dynamic(Box<dyn Bounds>);
pub struct NodeAny(Box<dyn Bounds>);
impl Dynamic {
impl NodeAny {
/// 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
T: Layout + Debug + Clone + PartialEq + 'static,
{
Self(Box::new(inner))
Self(Box::new(any))
}
}
impl Layout for Dynamic {
impl Layout for NodeAny {
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted {
self.0.layout(ctx, areas)
}
}
impl Clone for Dynamic {
impl Clone for NodeAny {
fn clone(&self) -> Self {
Self(self.0.dyn_clone())
}
}
impl PartialEq for Dynamic {
impl PartialEq for NodeAny {
fn eq(&self, other: &Self) -> bool {
self.0.dyn_eq(other.0.as_ref())
}
}
impl Debug for Dynamic {
impl Debug for NodeAny {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl From<Dynamic> for LayoutNode {
fn from(dynamic: Dynamic) -> Self {
Self::Dyn(dynamic)
impl From<NodeAny> for Node {
fn from(dynamic: NodeAny) -> Self {
Self::Any(dynamic)
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,8 @@
//! Syntax tree nodes.
use super::*;
/// A syntax node, which encompasses a single logical entity of parsed source
/// code.
/// A syntax node, encompassing a single logical entity of parsed source code.
#[derive(Debug, Clone, PartialEq)]
pub enum SynNode {
pub enum Node {
/// Plain text.
Text(String),
@ -36,7 +33,7 @@ pub struct NodeHeading {
/// The section depth (numer of hashtags minus 1).
pub level: Spanned<u8>,
/// The contents of the heading.
pub contents: SynTree,
pub contents: Tree,
}
/// 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::ops::Range;
@ -66,12 +64,18 @@ impl<T> Spanned<T> {
}
/// 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 }
}
/// 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
}
@ -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)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Span {

View File

@ -1,5 +1,3 @@
//! Token definition.
use crate::geom::Unit;
/// 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]

View File

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