Library functions behave more imperatively

- Templates scope state changes
- State-modifying function operate in place instead of returning a template
- Internal template representation contains actual owned nodes instead of a pointer to a syntax tree + an expression map
- No more wide calls
This commit is contained in:
Laurenz 2021-08-17 22:04:18 +02:00
parent c53d98a22f
commit 594809e35b
46 changed files with 945 additions and 1033 deletions

View File

@ -5,8 +5,7 @@ use std::rc::Rc;
use criterion::{criterion_group, criterion_main, Criterion}; use criterion::{criterion_group, criterion_main, Criterion};
use typst::diag::TypResult; use typst::diag::TypResult;
use typst::eval::{eval, Module}; use typst::eval::{eval, Module, State};
use typst::exec::exec;
use typst::export::pdf; use typst::export::pdf;
use typst::layout::{layout, Frame, LayoutTree}; use typst::layout::{layout, Frame, LayoutTree};
use typst::loading::FsLoader; use typst::loading::FsLoader;
@ -30,7 +29,7 @@ fn benchmarks(c: &mut Criterion) {
let case = Case::new(ctx.clone(), id); let case = Case::new(ctx.clone(), id);
macro_rules! bench { macro_rules! bench {
($step:literal, setup = |$ctx:ident| $setup:expr, code = $code:expr $(,)?) => { ($step:literal, setup: |$ctx:ident| $setup:expr, code: $code:expr $(,)?) => {
c.bench_function(&format!("{}-{}", $step, name), |b| { c.bench_function(&format!("{}-{}", $step, name), |b| {
b.iter_batched( b.iter_batched(
|| { || {
@ -49,7 +48,7 @@ fn benchmarks(c: &mut Criterion) {
bench!("parse", case.parse()); bench!("parse", case.parse());
bench!("eval", case.eval()); bench!("eval", case.eval());
bench!("exec", case.exec()); bench!("build", case.build());
#[cfg(not(feature = "layout-cache"))] #[cfg(not(feature = "layout-cache"))]
{ {
@ -59,16 +58,8 @@ fn benchmarks(c: &mut Criterion) {
#[cfg(feature = "layout-cache")] #[cfg(feature = "layout-cache")]
{ {
bench!( bench!("layout", setup: |ctx| ctx.layouts.clear(), code: case.layout());
"layout", bench!("typeset", setup: |ctx| ctx.layouts.clear(), code: case.typeset());
setup = |ctx| ctx.layouts.clear(),
code = case.layout(),
);
bench!(
"typeset",
setup = |ctx| ctx.layouts.clear(),
code = case.typeset(),
);
bench!("layout-cached", case.layout()); bench!("layout-cached", case.layout());
bench!("typeset-cached", case.typeset()); bench!("typeset-cached", case.typeset());
} }
@ -80,8 +71,9 @@ fn benchmarks(c: &mut Criterion) {
/// A test case with prepared intermediate results. /// A test case with prepared intermediate results.
struct Case { struct Case {
ctx: Rc<RefCell<Context>>, ctx: Rc<RefCell<Context>>,
state: State,
id: SourceId, id: SourceId,
ast: Rc<SyntaxTree>, ast: SyntaxTree,
module: Module, module: Module,
tree: LayoutTree, tree: LayoutTree,
frames: Vec<Rc<Frame>>, frames: Vec<Rc<Frame>>,
@ -90,13 +82,22 @@ struct Case {
impl Case { impl Case {
fn new(ctx: Rc<RefCell<Context>>, id: SourceId) -> Self { fn new(ctx: Rc<RefCell<Context>>, id: SourceId) -> Self {
let mut borrowed = ctx.borrow_mut(); let mut borrowed = ctx.borrow_mut();
let state = State::default();
let source = borrowed.sources.get(id); let source = borrowed.sources.get(id);
let ast = Rc::new(parse(source).unwrap()); let ast = parse(source).unwrap();
let module = eval(&mut borrowed, id, Rc::clone(&ast)).unwrap(); let module = eval(&mut borrowed, id, &ast).unwrap();
let tree = exec(&mut borrowed, &module.template); let tree = module.template.to_tree(&state);
let frames = layout(&mut borrowed, &tree); let frames = layout(&mut borrowed, &tree);
drop(borrowed); drop(borrowed);
Self { ctx, id, ast, module, tree, frames } Self {
ctx,
state,
id,
ast,
module,
tree,
frames,
}
} }
fn parse(&self) -> SyntaxTree { fn parse(&self) -> SyntaxTree {
@ -104,11 +105,11 @@ impl Case {
} }
fn eval(&self) -> TypResult<Module> { fn eval(&self) -> TypResult<Module> {
eval(&mut self.ctx.borrow_mut(), self.id, Rc::clone(&self.ast)) eval(&mut self.ctx.borrow_mut(), self.id, &self.ast)
} }
fn exec(&self) -> LayoutTree { fn build(&self) -> LayoutTree {
exec(&mut self.ctx.borrow_mut(), &self.module.template) self.module.template.to_tree(&self.state)
} }
fn layout(&self) -> Vec<Rc<Frame>> { fn layout(&self) -> Vec<Rc<Frame>> {

View File

@ -1,4 +1,4 @@
//! Evaluation of syntax trees. //! Evaluation of syntax trees into modules.
#[macro_use] #[macro_use]
mod array; mod array;
@ -10,6 +10,7 @@ mod capture;
mod function; mod function;
mod ops; mod ops;
mod scope; mod scope;
mod state;
mod str; mod str;
mod template; mod template;
@ -19,39 +20,38 @@ pub use capture::*;
pub use dict::*; pub use dict::*;
pub use function::*; pub use function::*;
pub use scope::*; pub use scope::*;
pub use state::*;
pub use template::*; pub use template::*;
pub use value::*; pub use value::*;
use std::cell::RefMut; use std::cell::RefMut;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Write;
use std::io; use std::io;
use std::mem; use std::mem;
use std::path::PathBuf; use std::path::PathBuf;
use std::rc::Rc; use std::rc::Rc;
use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult}; use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult};
use crate::geom::{Angle, Fractional, Length, Relative}; use crate::geom::{Angle, Fractional, Gen, Length, Relative};
use crate::image::ImageStore; use crate::image::ImageStore;
use crate::layout::{ParChild, ParNode, StackChild, StackNode};
use crate::loading::Loader; use crate::loading::Loader;
use crate::parse::parse; use crate::parse::parse;
use crate::source::{SourceId, SourceStore}; use crate::source::{SourceId, SourceStore};
use crate::syntax::visit::Visit; use crate::syntax::visit::Visit;
use crate::syntax::*; use crate::syntax::*;
use crate::util::RefMutExt; use crate::util::{EcoString, RefMutExt};
use crate::Context; use crate::Context;
/// Evaluate a parsed source file into a module. /// Evaluate a parsed source file into a module.
pub fn eval( pub fn eval(ctx: &mut Context, source: SourceId, ast: &SyntaxTree) -> TypResult<Module> {
ctx: &mut Context,
source: SourceId,
ast: Rc<SyntaxTree>,
) -> TypResult<Module> {
let mut ctx = EvalContext::new(ctx, source); let mut ctx = EvalContext::new(ctx, source);
let template = ast.eval(&mut ctx)?; let template = ast.eval(&mut ctx)?;
Ok(Module { scope: ctx.scopes.top, template }) Ok(Module { scope: ctx.scopes.top, template })
} }
/// An evaluated module, ready for importing or execution. /// An evaluated module, ready for importing or instantiation.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Module { pub struct Module {
/// The top-level definitions that were bound in this module. /// The top-level definitions that were bound in this module.
@ -74,8 +74,8 @@ pub struct EvalContext<'a> {
pub modules: HashMap<SourceId, Module>, pub modules: HashMap<SourceId, Module>,
/// The active scopes. /// The active scopes.
pub scopes: Scopes<'a>, pub scopes: Scopes<'a>,
/// The expression map for the currently built template. /// The currently built template.
pub map: ExprMap, pub template: Template,
} }
impl<'a> EvalContext<'a> { impl<'a> EvalContext<'a> {
@ -88,7 +88,7 @@ impl<'a> EvalContext<'a> {
route: vec![source], route: vec![source],
modules: HashMap::new(), modules: HashMap::new(),
scopes: Scopes::new(Some(&ctx.std)), scopes: Scopes::new(Some(&ctx.std)),
map: ExprMap::new(), template: Template::new(),
} }
} }
@ -158,17 +158,17 @@ pub trait Eval {
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output>; fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output>;
} }
impl Eval for Rc<SyntaxTree> { impl Eval for SyntaxTree {
type Output = Template; type Output = Template;
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
let map = { Ok({
let prev = mem::take(&mut ctx.map); let prev = mem::take(&mut ctx.template);
ctx.template.save();
self.walk(ctx)?; self.walk(ctx)?;
mem::replace(&mut ctx.map, prev) ctx.template.restore();
}; mem::replace(&mut ctx.template, prev)
})
Ok(TemplateTree { tree: Rc::clone(self), map }.into())
} }
} }
@ -671,43 +671,6 @@ impl Eval for IncludeExpr {
} }
} }
/// Walk a node in a template, filling the context's expression map.
pub trait Walk {
/// Walk the node.
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()>;
}
impl Walk for SyntaxTree {
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
for node in self.iter() {
node.walk(ctx)?;
}
Ok(())
}
}
impl Walk for SyntaxNode {
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
match self {
Self::Space => {}
Self::Text(_) => {}
Self::Linebreak(_) => {}
Self::Parbreak(_) => {}
Self::Strong(_) => {}
Self::Emph(_) => {}
Self::Raw(_) => {}
Self::Heading(n) => n.body.walk(ctx)?,
Self::List(n) => n.body.walk(ctx)?,
Self::Enum(n) => n.body.walk(ctx)?,
Self::Expr(n) => {
let value = n.eval(ctx)?;
ctx.map.insert(n as *const _, value);
}
}
Ok(())
}
}
/// Try to mutably access the value an expression points to. /// Try to mutably access the value an expression points to.
/// ///
/// This only works if the expression is a valid lvalue. /// This only works if the expression is a valid lvalue.
@ -754,3 +717,129 @@ impl Access for CallExpr {
}) })
} }
} }
/// Walk a syntax node and fill the currently built template.
pub trait Walk {
/// Walk the node.
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()>;
}
impl Walk for SyntaxTree {
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
for node in self.iter() {
node.walk(ctx)?;
}
Ok(())
}
}
impl Walk for SyntaxNode {
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
match self {
Self::Space => ctx.template.space(),
Self::Linebreak(_) => ctx.template.linebreak(),
Self::Parbreak(_) => ctx.template.parbreak(),
Self::Strong(_) => {
ctx.template.modify(|state| state.font_mut().strong ^= true);
}
Self::Emph(_) => {
ctx.template.modify(|state| state.font_mut().emph ^= true);
}
Self::Text(text) => ctx.template.text(text),
Self::Raw(raw) => raw.walk(ctx)?,
Self::Heading(heading) => heading.walk(ctx)?,
Self::List(list) => list.walk(ctx)?,
Self::Enum(enum_) => enum_.walk(ctx)?,
Self::Expr(expr) => match expr.eval(ctx)? {
Value::None => {}
Value::Int(v) => ctx.template.text(v.to_string()),
Value::Float(v) => ctx.template.text(v.to_string()),
Value::Str(v) => ctx.template.text(v),
Value::Template(v) => ctx.template += v,
// For values which can't be shown "naturally", we print the
// representation in monospace.
other => ctx.template.monospace(other.to_string()),
},
}
Ok(())
}
}
impl Walk for RawNode {
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
if self.block {
ctx.template.parbreak();
}
ctx.template.monospace(&self.text);
if self.block {
ctx.template.parbreak();
}
Ok(())
}
}
impl Walk for HeadingNode {
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
let level = self.level;
let body = self.body.eval(ctx)?;
ctx.template.parbreak();
ctx.template.save();
ctx.template.modify(move |state| {
let font = state.font_mut();
let upscale = 1.6 - 0.1 * level as f64;
font.size *= upscale;
font.strong = true;
});
ctx.template += body;
ctx.template.restore();
ctx.template.parbreak();
Ok(())
}
}
impl Walk for ListItem {
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
let body = self.body.eval(ctx)?;
walk_item(ctx, '•'.into(), body);
Ok(())
}
}
impl Walk for EnumItem {
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
let body = self.body.eval(ctx)?;
let mut label = EcoString::new();
write!(&mut label, "{}.", self.number.unwrap_or(1)).unwrap();
walk_item(ctx, label, body);
Ok(())
}
}
/// Walk a list or enum item, converting it into a stack.
fn walk_item(ctx: &mut EvalContext, label: EcoString, body: Template) {
ctx.template += Template::from_block(move |state| {
let label = ParNode {
dir: state.dirs.cross,
line_spacing: state.line_spacing(),
children: vec![ParChild::Text(
label.clone(),
state.aligns.cross,
Rc::clone(&state.font),
)],
};
StackNode {
dirs: Gen::new(state.dirs.main, state.dirs.cross),
aspect: None,
children: vec![
StackChild::Any(label.into(), Gen::default()),
StackChild::Spacing((state.font.size / 2.0).into()),
StackChild::Any(body.to_stack(&state).into(), Gen::default()),
],
}
});
}

View File

@ -8,18 +8,18 @@ use crate::geom::*;
use crate::layout::Paint; use crate::layout::Paint;
use crate::paper::{PaperClass, PAPER_A4}; use crate::paper::{PaperClass, PAPER_A4};
/// The execution state. /// Defines an set of properties a template can be instantiated with.
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct State { pub struct State {
/// The direction for text and other inline objects. /// The direction for text and other inline objects.
pub dirs: Gen<Dir>, pub dirs: Gen<Dir>,
/// The current alignments of layouts in their parents. /// The alignments of layouts in their parents.
pub aligns: Gen<Align>, pub aligns: Gen<Align>,
/// The current page settings. /// The page settings.
pub page: Rc<PageState>, pub page: Rc<PageState>,
/// The current paragraph settings. /// The paragraph settings.
pub par: Rc<ParState>, pub par: Rc<ParState>,
/// The current font settings. /// The font settings.
pub font: Rc<FontState>, pub font: Rc<FontState>,
} }
@ -98,7 +98,7 @@ impl Default for PageState {
} }
} }
/// Style paragraph properties. /// Defines paragraph properties.
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct ParState { pub struct ParState {
/// The spacing between paragraphs (dependent on scaled font size). /// The spacing between paragraphs (dependent on scaled font size).
@ -119,13 +119,12 @@ impl Default for ParState {
/// Defines font properties. /// Defines font properties.
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct FontState { pub struct FontState {
/// Whether the strong toggle is active or inactive. This determines /// Whether 300 extra font weight should be added to what is defined by the
/// whether the next `*` adds or removes font weight. /// `variant`.
pub strong: bool, pub strong: bool,
/// Whether the emphasis toggle is active or inactive. This determines /// Whether the the font style defined by the `variant` should be inverted.
/// whether the next `_` makes italic or non-italic.
pub emph: bool, pub emph: bool,
/// Whether the monospace toggle is active or inactive. /// Whether a monospace font should be preferred.
pub monospace: bool, pub monospace: bool,
/// The font size. /// The font size.
pub size: Length, pub size: Length,
@ -176,11 +175,13 @@ impl FontState {
.then(|| self.families.monospace.as_slice()) .then(|| self.families.monospace.as_slice())
.unwrap_or_default(); .unwrap_or_default();
let core = self.families.list.iter().flat_map(move |family| match family { let core = self.families.list.iter().flat_map(move |family| {
FontFamily::Named(name) => std::slice::from_ref(name), match family {
FontFamily::Serif => &self.families.serif, FontFamily::Named(name) => std::slice::from_ref(name),
FontFamily::SansSerif => &self.families.sans_serif, FontFamily::Serif => &self.families.serif,
FontFamily::Monospace => &self.families.monospace, FontFamily::SansSerif => &self.families.sans_serif,
FontFamily::Monospace => &self.families.monospace,
}
}); });
head.iter() head.iter()

View File

@ -1,28 +1,145 @@
use std::collections::HashMap;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::fmt::{self, Debug, Display, Formatter}; use std::fmt::{self, Debug, Display, Formatter};
use std::ops::{Add, AddAssign, Deref}; use std::mem;
use std::ops::{Add, AddAssign};
use std::rc::Rc; use std::rc::Rc;
use super::{Str, Value}; use super::{State, Str};
use crate::diag::StrResult; use crate::diag::StrResult;
use crate::exec::ExecContext; use crate::geom::{Align, Dir, Gen, GenAxis, Length, Linear, Sides, Size};
use crate::syntax::{Expr, SyntaxTree}; use crate::layout::{
LayoutNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild, StackNode,
};
use crate::util::EcoString; use crate::util::EcoString;
/// A template value: `[*Hi* there]`. /// A template value: `[*Hi* there]`.
#[derive(Debug, Default, Clone)] #[derive(Default, Clone)]
pub struct Template(Rc<Vec<TemplateNode>>); pub struct Template(Rc<Vec<TemplateNode>>);
/// One node in a template.
#[derive(Clone)]
enum TemplateNode {
/// A word space.
Space,
/// A line break.
Linebreak,
/// A paragraph break.
Parbreak,
/// A page break.
Pagebreak(bool),
/// Plain text.
Text(EcoString),
/// Spacing.
Spacing(GenAxis, Linear),
/// An inline node builder.
Inline(Rc<dyn Fn(&State) -> LayoutNode>),
/// An block node builder.
Block(Rc<dyn Fn(&State) -> LayoutNode>),
/// Save the current state.
Save,
/// Restore the last saved state.
Restore,
/// A function that can modify the current state.
Modify(Rc<dyn Fn(&mut State)>),
}
impl Template { impl Template {
/// Create a new template from a vector of nodes. /// Create a new, empty template.
pub fn new(nodes: Vec<TemplateNode>) -> Self { pub fn new() -> Self {
Self(Rc::new(nodes)) Self(Rc::new(vec![]))
} }
/// Iterate over the contained template nodes. /// Create a template from a builder for an inline-level node.
pub fn iter(&self) -> std::slice::Iter<TemplateNode> { pub fn from_inline<F, T>(f: F) -> Self
self.0.iter() where
F: Fn(&State) -> T + 'static,
T: Into<LayoutNode>,
{
let node = TemplateNode::Inline(Rc::new(move |s| f(s).into()));
Self(Rc::new(vec![node]))
}
/// Create a template from a builder for a block-level node.
pub fn from_block<F, T>(f: F) -> Self
where
F: Fn(&State) -> T + 'static,
T: Into<LayoutNode>,
{
let node = TemplateNode::Block(Rc::new(move |s| f(s).into()));
Self(Rc::new(vec![node]))
}
/// Add a word space to the template.
pub fn space(&mut self) {
self.make_mut().push(TemplateNode::Space);
}
/// Add a line break to the template.
pub fn linebreak(&mut self) {
self.make_mut().push(TemplateNode::Linebreak);
}
/// Add a paragraph break to the template.
pub fn parbreak(&mut self) {
self.make_mut().push(TemplateNode::Parbreak);
}
/// Add a page break to the template.
pub fn pagebreak(&mut self, keep: bool) {
self.make_mut().push(TemplateNode::Pagebreak(keep));
}
/// Add text to the template.
pub fn text(&mut self, text: impl Into<EcoString>) {
self.make_mut().push(TemplateNode::Text(text.into()));
}
/// Add text, but in monospace.
pub fn monospace(&mut self, text: impl Into<EcoString>) {
self.save();
self.modify(|state| state.font_mut().monospace = true);
self.text(text);
self.restore();
}
/// Add spacing along an axis.
pub fn spacing(&mut self, axis: GenAxis, spacing: Linear) {
self.make_mut().push(TemplateNode::Spacing(axis, spacing));
}
/// Register a restorable snapshot.
pub fn save(&mut self) {
self.make_mut().push(TemplateNode::Save);
}
/// Ensure that later nodes are untouched by state modifications made since
/// the last snapshot.
pub fn restore(&mut self) {
self.make_mut().push(TemplateNode::Restore);
}
/// Modify the state.
pub fn modify<F>(&mut self, f: F)
where
F: Fn(&mut State) + 'static,
{
self.make_mut().push(TemplateNode::Modify(Rc::new(f)));
}
/// Build the stack node resulting from instantiating the template in the
/// given state.
pub fn to_stack(&self, state: &State) -> StackNode {
let mut builder = Builder::new(state, false);
builder.template(self);
builder.build_stack()
}
/// Build the layout tree resulting from instantiating the template in the
/// given state.
pub fn to_tree(&self, state: &State) -> LayoutTree {
let mut builder = Builder::new(state, true);
builder.template(self);
builder.build_tree()
} }
/// Repeat this template `n` times. /// Repeat this template `n` times.
@ -33,9 +150,14 @@ impl Template {
.ok_or_else(|| format!("cannot repeat this template {} times", n))?; .ok_or_else(|| format!("cannot repeat this template {} times", n))?;
Ok(Self(Rc::new( Ok(Self(Rc::new(
self.iter().cloned().cycle().take(count).collect(), self.0.iter().cloned().cycle().take(count).collect(),
))) )))
} }
/// Return a mutable reference to the inner vector.
fn make_mut(&mut self) -> &mut Vec<TemplateNode> {
Rc::make_mut(&mut self.0)
}
} }
impl Display for Template { impl Display for Template {
@ -44,6 +166,12 @@ impl Display for Template {
} }
} }
impl Debug for Template {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad("Template { .. }")
}
}
impl PartialEq for Template { impl PartialEq for Template {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0, &other.0) Rc::ptr_eq(&self.0, &other.0)
@ -73,7 +201,7 @@ impl Add<Str> for Template {
type Output = Self; type Output = Self;
fn add(mut self, rhs: Str) -> Self::Output { fn add(mut self, rhs: Str) -> Self::Output {
Rc::make_mut(&mut self.0).push(TemplateNode::Str(rhs.into())); Rc::make_mut(&mut self.0).push(TemplateNode::Text(rhs.into()));
self self
} }
} }
@ -82,86 +210,306 @@ impl Add<Template> for Str {
type Output = Template; type Output = Template;
fn add(self, mut rhs: Template) -> Self::Output { fn add(self, mut rhs: Template) -> Self::Output {
Rc::make_mut(&mut rhs.0).insert(0, TemplateNode::Str(self.into())); Rc::make_mut(&mut rhs.0).insert(0, TemplateNode::Text(self.into()));
rhs rhs
} }
} }
impl From<TemplateTree> for Template { /// Transforms from template to layout representation.
fn from(tree: TemplateTree) -> Self { struct Builder {
Self::new(vec![TemplateNode::Tree(tree)]) /// The active state.
state: State,
/// Snapshots of the state.
snapshots: Vec<State>,
/// The tree of finished page runs.
tree: LayoutTree,
/// When we are building the top-level layout trees, this contains metrics
/// of the page. While building a stack, this is `None`.
page: Option<PageBuilder>,
/// The currently built stack of paragraphs.
stack: StackBuilder,
}
impl Builder {
/// Create a new builder with a base state.
fn new(state: &State, pages: bool) -> Self {
Self {
state: state.clone(),
snapshots: vec![],
tree: LayoutTree { runs: vec![] },
page: pages.then(|| PageBuilder::new(state, true)),
stack: StackBuilder::new(state),
}
}
/// Build a template.
fn template(&mut self, template: &Template) {
for node in template.0.iter() {
self.node(node);
}
}
/// Build a template node.
fn node(&mut self, node: &TemplateNode) {
match node {
TemplateNode::Save => self.snapshots.push(self.state.clone()),
TemplateNode::Restore => {
let state = self.snapshots.pop().unwrap();
let newpage = state.page != self.state.page;
self.state = state;
if newpage {
self.pagebreak(true, false);
}
}
TemplateNode::Space => self.space(),
TemplateNode::Linebreak => self.linebreak(),
TemplateNode::Parbreak => self.parbreak(),
TemplateNode::Pagebreak(keep) => self.pagebreak(*keep, true),
TemplateNode::Text(text) => self.text(text),
TemplateNode::Spacing(axis, amount) => self.spacing(*axis, *amount),
TemplateNode::Inline(f) => self.inline(f(&self.state)),
TemplateNode::Block(f) => self.block(f(&self.state)),
TemplateNode::Modify(f) => f(&mut self.state),
}
}
/// Push a word space into the active paragraph.
fn space(&mut self) {
self.stack.par.push_soft(self.make_text_node(' '));
}
/// Apply a forced line break.
fn linebreak(&mut self) {
self.stack.par.push_hard(self.make_text_node('\n'));
}
/// Apply a forced paragraph break.
fn parbreak(&mut self) {
let amount = self.state.par_spacing();
self.stack.finish_par(&self.state);
self.stack.push_soft(StackChild::Spacing(amount.into()));
}
/// Apply a forced page break.
fn pagebreak(&mut self, keep: bool, hard: bool) {
if let Some(builder) = &mut self.page {
let page = mem::replace(builder, PageBuilder::new(&self.state, hard));
let stack = mem::replace(&mut self.stack, StackBuilder::new(&self.state));
self.tree.runs.extend(page.build(stack.build(), keep));
}
}
/// Push text into the active paragraph.
///
/// The text is split into lines at newlines.
fn text(&mut self, text: impl Into<EcoString>) {
self.stack.par.push(self.make_text_node(text));
}
/// Push an inline node into the active paragraph.
fn inline(&mut self, node: impl Into<LayoutNode>) {
let align = self.state.aligns.cross;
self.stack.par.push(ParChild::Any(node.into(), align));
}
/// Push a block node into the active stack, finishing the active paragraph.
fn block(&mut self, node: impl Into<LayoutNode>) {
self.parbreak();
let aligns = self.state.aligns;
self.stack.push(StackChild::Any(node.into(), aligns));
self.parbreak();
}
/// Push spacing into the active paragraph or stack depending on the `axis`.
fn spacing(&mut self, axis: GenAxis, amount: Linear) {
match axis {
GenAxis::Main => {
self.stack.finish_par(&self.state);
self.stack.push_hard(StackChild::Spacing(amount));
}
GenAxis::Cross => {
self.stack.par.push_hard(ParChild::Spacing(amount));
}
}
}
/// Finish building and return the created stack.
fn build_stack(self) -> StackNode {
assert!(self.page.is_none());
self.stack.build()
}
/// Finish building and return the created layout tree.
fn build_tree(mut self) -> LayoutTree {
assert!(self.page.is_some());
self.pagebreak(true, false);
self.tree
}
/// Construct a text node with the given text and settings from the active
/// state.
fn make_text_node(&self, text: impl Into<EcoString>) -> ParChild {
ParChild::Text(
text.into(),
self.state.aligns.cross,
Rc::clone(&self.state.font),
)
} }
} }
impl From<TemplateFunc> for Template { struct PageBuilder {
fn from(func: TemplateFunc) -> Self { size: Size,
Self::new(vec![TemplateNode::Func(func)]) padding: Sides<Linear>,
hard: bool,
}
impl PageBuilder {
fn new(state: &State, hard: bool) -> Self {
Self {
size: state.page.size,
padding: state.page.margins(),
hard,
}
}
fn build(self, child: StackNode, keep: bool) -> Option<PageRun> {
let Self { size, padding, hard } = self;
(!child.children.is_empty() || (keep && hard)).then(|| PageRun {
size,
child: PadNode { padding, child: child.into() }.into(),
})
} }
} }
impl From<Str> for Template { struct StackBuilder {
fn from(string: Str) -> Self { dirs: Gen<Dir>,
Self::new(vec![TemplateNode::Str(string.into())]) children: Vec<StackChild>,
last: Last<StackChild>,
par: ParBuilder,
}
impl StackBuilder {
fn new(state: &State) -> Self {
Self {
dirs: state.dirs,
children: vec![],
last: Last::None,
par: ParBuilder::new(state),
}
}
fn push(&mut self, child: StackChild) {
self.children.extend(self.last.any());
self.children.push(child);
}
fn push_soft(&mut self, child: StackChild) {
self.last.soft(child);
}
fn push_hard(&mut self, child: StackChild) {
self.last.hard();
self.children.push(child);
}
fn finish_par(&mut self, state: &State) {
let par = mem::replace(&mut self.par, ParBuilder::new(state));
if let Some(par) = par.build() {
self.push(par);
}
}
fn build(self) -> StackNode {
let Self { dirs, mut children, par, mut last } = self;
if let Some(par) = par.build() {
children.extend(last.any());
children.push(par);
}
StackNode { dirs, aspect: None, children }
} }
} }
/// One node of a template. struct ParBuilder {
/// aligns: Gen<Align>,
/// Evaluating a template expression creates only a single node. Adding multiple dir: Dir,
/// templates can yield multi-node templates. line_spacing: Length,
#[derive(Debug, Clone)] children: Vec<ParChild>,
pub enum TemplateNode { last: Last<ParChild>,
/// A template that was evaluated from a template expression.
Tree(TemplateTree),
/// A function template that can implement custom behaviour.
Func(TemplateFunc),
/// A template that was converted from a string.
Str(EcoString),
} }
/// A template that consists of a syntax tree plus already evaluated impl ParBuilder {
/// expressions. fn new(state: &State) -> Self {
#[derive(Debug, Clone)] Self {
pub struct TemplateTree { aligns: state.aligns,
/// The syntax tree of the corresponding template expression. dir: state.dirs.cross,
pub tree: Rc<SyntaxTree>, line_spacing: state.line_spacing(),
/// The evaluated expressions in the syntax tree. children: vec![],
pub map: ExprMap, last: Last::None,
} }
}
/// A map from expressions to the values they evaluated to. fn push(&mut self, child: ParChild) {
/// if let Some(soft) = self.last.any() {
/// The raw pointers point into the expressions contained in some self.push_inner(soft);
/// [`SyntaxTree`]. Since the lifetime is erased, the tree could go out of scope }
/// while the hash map still lives. Although this could lead to lookup panics, self.push_inner(child);
/// it is safe since the pointers are never dereferenced. }
pub type ExprMap = HashMap<*const Expr, Value>;
/// A reference-counted dynamic template node that can implement custom fn push_soft(&mut self, child: ParChild) {
/// behaviour. self.last.soft(child);
#[derive(Clone)] }
pub struct TemplateFunc(Rc<dyn Fn(&mut ExecContext)>);
impl TemplateFunc { fn push_hard(&mut self, child: ParChild) {
/// Create a new function template from a rust function or closure. self.last.hard();
pub fn new<F>(f: F) -> Self self.push_inner(child);
where }
F: Fn(&mut ExecContext) + 'static,
{ fn push_inner(&mut self, child: ParChild) {
Self(Rc::new(f)) if let ParChild::Text(curr_text, curr_props, curr_align) = &child {
if let Some(ParChild::Text(prev_text, prev_props, prev_align)) =
self.children.last_mut()
{
if prev_align == curr_align && prev_props == curr_props {
prev_text.push_str(&curr_text);
return;
}
}
}
self.children.push(child);
}
fn build(self) -> Option<StackChild> {
let Self { aligns, dir, line_spacing, children, .. } = self;
(!children.is_empty()).then(|| {
let node = ParNode { dir, line_spacing, children };
StackChild::Any(node.into(), aligns)
})
} }
} }
impl Debug for TemplateFunc { /// Finite state machine for spacing coalescing.
fn fmt(&self, f: &mut Formatter) -> fmt::Result { enum Last<N> {
f.debug_struct("TemplateFunc").finish() None,
} Any,
Soft(N),
} }
impl Deref for TemplateFunc { impl<N> Last<N> {
type Target = dyn Fn(&mut ExecContext); fn any(&mut self) -> Option<N> {
match mem::replace(self, Self::Any) {
Self::Soft(soft) => Some(soft),
_ => None,
}
}
fn deref(&self) -> &Self::Target { fn soft(&mut self, soft: N) {
self.0.as_ref() if let Self::Any = self {
*self = Self::Soft(soft);
}
}
fn hard(&mut self) {
*self = Self::None;
} }
} }

View File

@ -3,10 +3,9 @@ use std::cmp::Ordering;
use std::fmt::{self, Debug, Display, Formatter}; use std::fmt::{self, Debug, Display, Formatter};
use std::rc::Rc; use std::rc::Rc;
use super::{ops, Array, Dict, Function, Str, Template, TemplateFunc}; use super::{ops, Array, Dict, Function, Str, Template};
use crate::color::{Color, RgbaColor}; use crate::color::{Color, RgbaColor};
use crate::diag::StrResult; use crate::diag::StrResult;
use crate::exec::ExecContext;
use crate::geom::{Angle, Fractional, Length, Linear, Relative}; use crate::geom::{Angle, Fractional, Length, Linear, Relative};
use crate::syntax::Spanned; use crate::syntax::Spanned;
use crate::util::EcoString; use crate::util::EcoString;
@ -51,14 +50,6 @@ pub enum Value {
} }
impl Value { impl Value {
/// Create a new template consisting of a single function node.
pub fn template<F>(f: F) -> Self
where
F: Fn(&mut ExecContext) + 'static,
{
Self::Template(TemplateFunc::new(f).into())
}
/// The name of the stored value's type. /// The name of the stored value's type.
pub fn type_name(&self) -> &'static str { pub fn type_name(&self) -> &'static str {
match self { match self {

View File

@ -1,302 +0,0 @@
use std::mem;
use std::rc::Rc;
use super::{Exec, ExecWithMap, State};
use crate::eval::{ExprMap, Template};
use crate::geom::{Align, Dir, Gen, GenAxis, Length, Linear, Sides, Size};
use crate::layout::{
LayoutNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild, StackNode,
};
use crate::syntax::SyntaxTree;
use crate::util::EcoString;
use crate::Context;
/// The context for execution.
pub struct ExecContext {
/// The active execution state.
pub state: State,
/// The tree of finished page runs.
tree: LayoutTree,
/// When we are building the top-level stack, this contains metrics of the
/// page. While building a group stack through `exec_group`, this is `None`.
page: Option<PageBuilder>,
/// The currently built stack of paragraphs.
stack: StackBuilder,
}
impl ExecContext {
/// Create a new execution context with a base state.
pub fn new(ctx: &mut Context) -> Self {
Self {
state: ctx.state.clone(),
tree: LayoutTree { runs: vec![] },
page: Some(PageBuilder::new(&ctx.state, true)),
stack: StackBuilder::new(&ctx.state),
}
}
/// Push a word space into the active paragraph.
pub fn space(&mut self) {
self.stack.par.push_soft(self.make_text_node(' '));
}
/// Apply a forced line break.
pub fn linebreak(&mut self) {
self.stack.par.push_hard(self.make_text_node('\n'));
}
/// Apply a forced paragraph break.
pub fn parbreak(&mut self) {
let amount = self.state.par_spacing();
self.stack.finish_par(&self.state);
self.stack.push_soft(StackChild::Spacing(amount.into()));
}
/// Apply a forced page break.
pub fn pagebreak(&mut self, keep: bool, hard: bool) {
if let Some(builder) = &mut self.page {
let page = mem::replace(builder, PageBuilder::new(&self.state, hard));
let stack = mem::replace(&mut self.stack, StackBuilder::new(&self.state));
self.tree.runs.extend(page.build(stack.build(), keep));
}
}
/// Push text into the active paragraph.
///
/// The text is split into lines at newlines.
pub fn text(&mut self, text: impl Into<EcoString>) {
self.stack.par.push(self.make_text_node(text));
}
/// Push text, but in monospace.
pub fn text_mono(&mut self, text: impl Into<EcoString>) {
let prev = Rc::clone(&self.state.font);
self.state.font_mut().monospace = true;
self.text(text);
self.state.font = prev;
}
/// Push an inline node into the active paragraph.
pub fn inline(&mut self, node: impl Into<LayoutNode>) {
let align = self.state.aligns.cross;
self.stack.par.push(ParChild::Any(node.into(), align));
}
/// Push a block node into the active stack, finishing the active paragraph.
pub fn block(&mut self, node: impl Into<LayoutNode>) {
self.parbreak();
let aligns = self.state.aligns;
self.stack.push(StackChild::Any(node.into(), aligns));
self.parbreak();
}
/// Push spacing into the active paragraph or stack depending on the `axis`.
pub fn spacing(&mut self, axis: GenAxis, amount: Linear) {
match axis {
GenAxis::Main => {
self.stack.finish_par(&self.state);
self.stack.push_hard(StackChild::Spacing(amount));
}
GenAxis::Cross => {
self.stack.par.push_hard(ParChild::Spacing(amount));
}
}
}
/// Execute a template and return the result as a stack node.
pub fn exec_template(&mut self, template: &Template) -> StackNode {
self.exec_to_stack(|ctx| template.exec(ctx))
}
/// Execute a syntax tree with a map and return the result as a stack node.
pub fn exec_tree(&mut self, tree: &SyntaxTree, map: &ExprMap) -> StackNode {
self.exec_to_stack(|ctx| tree.exec_with_map(ctx, map))
}
/// Execute something and return the result as a stack node.
pub fn exec_to_stack(&mut self, f: impl FnOnce(&mut Self)) -> StackNode {
let snapshot = self.state.clone();
let page = self.page.take();
let stack = mem::replace(&mut self.stack, StackBuilder::new(&self.state));
f(self);
self.state = snapshot;
self.page = page;
mem::replace(&mut self.stack, stack).build()
}
/// Finish execution and return the created layout tree.
pub fn finish(mut self) -> LayoutTree {
assert!(self.page.is_some());
self.pagebreak(true, false);
self.tree
}
/// Construct a text node with the given text and settings from the active
/// state.
fn make_text_node(&self, text: impl Into<EcoString>) -> ParChild {
ParChild::Text(
text.into(),
self.state.aligns.cross,
Rc::clone(&self.state.font),
)
}
}
struct PageBuilder {
size: Size,
padding: Sides<Linear>,
hard: bool,
}
impl PageBuilder {
fn new(state: &State, hard: bool) -> Self {
Self {
size: state.page.size,
padding: state.page.margins(),
hard,
}
}
fn build(self, child: StackNode, keep: bool) -> Option<PageRun> {
let Self { size, padding, hard } = self;
(!child.children.is_empty() || (keep && hard)).then(|| PageRun {
size,
child: PadNode { padding, child: child.into() }.into(),
})
}
}
struct StackBuilder {
dirs: Gen<Dir>,
children: Vec<StackChild>,
last: Last<StackChild>,
par: ParBuilder,
}
impl StackBuilder {
fn new(state: &State) -> Self {
Self {
dirs: state.dirs,
children: vec![],
last: Last::None,
par: ParBuilder::new(state),
}
}
fn push(&mut self, child: StackChild) {
self.children.extend(self.last.any());
self.children.push(child);
}
fn push_soft(&mut self, child: StackChild) {
self.last.soft(child);
}
fn push_hard(&mut self, child: StackChild) {
self.last.hard();
self.children.push(child);
}
fn finish_par(&mut self, state: &State) {
let par = mem::replace(&mut self.par, ParBuilder::new(state));
if let Some(par) = par.build() {
self.push(par);
}
}
fn build(self) -> StackNode {
let Self { dirs, mut children, par, mut last } = self;
if let Some(par) = par.build() {
children.extend(last.any());
children.push(par);
}
StackNode { dirs, aspect: None, children }
}
}
struct ParBuilder {
aligns: Gen<Align>,
dir: Dir,
line_spacing: Length,
children: Vec<ParChild>,
last: Last<ParChild>,
}
impl ParBuilder {
fn new(state: &State) -> Self {
Self {
aligns: state.aligns,
dir: state.dirs.cross,
line_spacing: state.line_spacing(),
children: vec![],
last: Last::None,
}
}
fn push(&mut self, child: ParChild) {
if let Some(soft) = self.last.any() {
self.push_inner(soft);
}
self.push_inner(child);
}
fn push_soft(&mut self, child: ParChild) {
self.last.soft(child);
}
fn push_hard(&mut self, child: ParChild) {
self.last.hard();
self.push_inner(child);
}
fn push_inner(&mut self, child: ParChild) {
if let ParChild::Text(curr_text, curr_props, curr_align) = &child {
if let Some(ParChild::Text(prev_text, prev_props, prev_align)) =
self.children.last_mut()
{
if prev_align == curr_align && prev_props == curr_props {
prev_text.push_str(&curr_text);
return;
}
}
}
self.children.push(child);
}
fn build(self) -> Option<StackChild> {
let Self { aligns, dir, line_spacing, children, .. } = self;
(!children.is_empty()).then(|| {
let node = ParNode { dir, line_spacing, children };
StackChild::Any(node.into(), aligns)
})
}
}
/// Finite state machine for spacing coalescing.
enum Last<N> {
None,
Any,
Soft(N),
}
impl<N> Last<N> {
fn any(&mut self) -> Option<N> {
match mem::replace(self, Self::Any) {
Self::Soft(soft) => Some(soft),
_ => None,
}
}
fn soft(&mut self, soft: N) {
if let Self::Any = self {
*self = Self::Soft(soft);
}
}
fn hard(&mut self) {
*self = Self::None;
}
}

View File

@ -1,173 +0,0 @@
//! Execution of syntax trees.
mod context;
mod state;
pub use context::*;
pub use state::*;
use std::fmt::Write;
use crate::eval::{ExprMap, Template, TemplateFunc, TemplateNode, TemplateTree, Value};
use crate::geom::Gen;
use crate::layout::{LayoutTree, StackChild, StackNode};
use crate::syntax::*;
use crate::util::EcoString;
use crate::Context;
/// Execute a template to produce a layout tree.
pub fn exec(ctx: &mut Context, template: &Template) -> LayoutTree {
let mut ctx = ExecContext::new(ctx);
template.exec(&mut ctx);
ctx.finish()
}
/// Execute a node.
///
/// This manipulates active styling and document state and produces layout
/// nodes. Because syntax nodes and layout nodes do not correspond one-to-one,
/// constructed layout nodes are pushed into the context instead of returned.
/// The context takes care of reshaping the nodes into the correct tree
/// structure.
pub trait Exec {
/// Execute the node.
fn exec(&self, ctx: &mut ExecContext);
}
/// Execute a node with an expression map that applies to it.
pub trait ExecWithMap {
/// Execute the node.
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap);
}
impl ExecWithMap for SyntaxTree {
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) {
for node in self {
node.exec_with_map(ctx, map);
}
}
}
impl ExecWithMap for SyntaxNode {
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) {
match self {
Self::Space => ctx.space(),
Self::Text(text) => ctx.text(text),
Self::Linebreak(_) => ctx.linebreak(),
Self::Parbreak(_) => ctx.parbreak(),
Self::Strong(_) => ctx.state.font_mut().strong ^= true,
Self::Emph(_) => ctx.state.font_mut().emph ^= true,
Self::Raw(n) => n.exec(ctx),
Self::Heading(n) => n.exec_with_map(ctx, map),
Self::List(n) => n.exec_with_map(ctx, map),
Self::Enum(n) => n.exec_with_map(ctx, map),
Self::Expr(n) => map[&(n as *const _)].exec(ctx),
}
}
}
impl Exec for RawNode {
fn exec(&self, ctx: &mut ExecContext) {
if self.block {
ctx.parbreak();
}
ctx.text_mono(&self.text);
if self.block {
ctx.parbreak();
}
}
}
impl ExecWithMap for HeadingNode {
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) {
ctx.parbreak();
let snapshot = ctx.state.clone();
let font = ctx.state.font_mut();
let upscale = 1.6 - 0.1 * self.level as f64;
font.size *= upscale;
font.strong = true;
self.body.exec_with_map(ctx, map);
ctx.state = snapshot;
ctx.parbreak();
}
}
impl ExecWithMap for ListItem {
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) {
exec_item(ctx, '•'.into(), &self.body, map);
}
}
impl ExecWithMap for EnumItem {
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) {
let mut label = EcoString::new();
write!(&mut label, "{}.", self.number.unwrap_or(1)).unwrap();
exec_item(ctx, label, &self.body, map);
}
}
fn exec_item(ctx: &mut ExecContext, label: EcoString, body: &SyntaxTree, map: &ExprMap) {
let label = ctx.exec_to_stack(|ctx| ctx.text(label));
let body = ctx.exec_tree(body, map);
ctx.block(StackNode {
dirs: Gen::new(ctx.state.dirs.main, ctx.state.dirs.cross),
aspect: None,
children: vec![
StackChild::Any(label.into(), Gen::default()),
StackChild::Spacing((ctx.state.font.size / 2.0).into()),
StackChild::Any(body.into(), Gen::default()),
],
});
}
impl Exec for Value {
fn exec(&self, ctx: &mut ExecContext) {
match self {
Value::None => {}
Value::Int(v) => ctx.text(v.to_string()),
Value::Float(v) => ctx.text(v.to_string()),
Value::Str(v) => ctx.text(v),
Value::Template(v) => v.exec(ctx),
// For values which can't be shown "naturally", we print the
// representation in monospace.
other => ctx.text_mono(other.to_string()),
}
}
}
impl Exec for Template {
fn exec(&self, ctx: &mut ExecContext) {
for node in self.iter() {
node.exec(ctx);
}
}
}
impl Exec for TemplateNode {
fn exec(&self, ctx: &mut ExecContext) {
match self {
Self::Tree(v) => v.exec(ctx),
Self::Func(v) => v.exec(ctx),
Self::Str(v) => ctx.text(v),
}
}
}
impl Exec for TemplateTree {
fn exec(&self, ctx: &mut ExecContext) {
self.tree.exec_with_map(ctx, &self.map)
}
}
impl Exec for TemplateFunc {
fn exec(&self, ctx: &mut ExecContext) {
let snapshot = ctx.state.clone();
self(ctx);
ctx.state = snapshot;
}
}

View File

@ -5,7 +5,7 @@ use unicode_bidi::{BidiInfo, Level};
use xi_unicode::LineBreakIterator; use xi_unicode::LineBreakIterator;
use super::*; use super::*;
use crate::exec::FontState; use crate::eval::FontState;
use crate::util::{EcoString, RangeExt, SliceExt}; use crate::util::{EcoString, RangeExt, SliceExt};
type Range = std::ops::Range<usize>; type Range = std::ops::Range<usize>;

View File

@ -5,7 +5,7 @@ use std::ops::Range;
use rustybuzz::UnicodeBuffer; use rustybuzz::UnicodeBuffer;
use super::{Element, Frame, Glyph, LayoutContext, Text}; use super::{Element, Frame, Glyph, LayoutContext, Text};
use crate::exec::{FontState, LineState}; use crate::eval::{FontState, LineState};
use crate::font::{Face, FaceId, FontVariant, LineMetrics}; use crate::font::{Face, FaceId, FontVariant, LineMetrics};
use crate::geom::{Dir, Length, Point, Size}; use crate::geom::{Dir, Length, Point, Size};
use crate::layout::Geometry; use crate::layout::Geometry;

View File

@ -6,12 +6,12 @@
//! 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 syntax tree. This //! - **Evaluation:** The next step is to [evaluate] the syntax tree. This
//! computes the value of each node in the document and produces a [module]. //! produces a [module], consisting of a scope of values that were exported by
//! - **Execution:** Now, we can [execute] the parsed and evaluated module. This //! the module and a template with the contents of the module. This template
//! results in a [layout tree], a high-level, fully styled representation of //! can be [instantiated] in a state to produce a layout tree, a high-level,
//! the document. The nodes of this tree are self-contained and //! fully styled representation of the document. The nodes of this tree are
//! order-independent and thus much better suited for layouting than the //! self-contained and order-independent and thus much better suited for
//! syntax tree. //! layouting than a syntax tree.
//! - **Layouting:** Next, the tree is [layouted] into a portable version of the //! - **Layouting:** Next, the tree is [layouted] into a portable version of the
//! typeset document. The output of this is a collection of [`Frame`]s (one //! typeset document. The output of this is a collection of [`Frame`]s (one
//! per page), ready for exporting. //! per page), ready for exporting.
@ -23,7 +23,7 @@
//! [syntax tree]: syntax::SyntaxTree //! [syntax tree]: syntax::SyntaxTree
//! [evaluate]: eval::eval //! [evaluate]: eval::eval
//! [module]: eval::Module //! [module]: eval::Module
//! [execute]: exec::exec //! [instantiated]: eval::Template::to_tree
//! [layout tree]: layout::LayoutTree //! [layout tree]: layout::LayoutTree
//! [layouted]: layout::layout //! [layouted]: layout::layout
//! [PDF]: export::pdf //! [PDF]: export::pdf
@ -33,7 +33,6 @@ pub mod diag;
#[macro_use] #[macro_use]
pub mod eval; pub mod eval;
pub mod color; pub mod color;
pub mod exec;
pub mod export; pub mod export;
pub mod font; pub mod font;
pub mod geom; pub mod geom;
@ -50,13 +49,12 @@ pub mod util;
use std::rc::Rc; use std::rc::Rc;
use crate::diag::TypResult; use crate::diag::TypResult;
use crate::eval::Scope; use crate::eval::{Scope, State};
use crate::exec::State;
use crate::font::FontStore; use crate::font::FontStore;
use crate::image::ImageStore; use crate::image::ImageStore;
use crate::layout::Frame;
#[cfg(feature = "layout-cache")] #[cfg(feature = "layout-cache")]
use crate::layout::LayoutCache; use crate::layout::LayoutCache;
use crate::layout::{Frame, LayoutTree};
use crate::loading::Loader; use crate::loading::Loader;
use crate::source::{SourceId, SourceStore}; use crate::source::{SourceId, SourceStore};
@ -90,16 +88,21 @@ impl Context {
ContextBuilder::default() ContextBuilder::default()
} }
/// Execute a source file and produce the resulting layout tree.
pub fn execute(&mut self, id: SourceId) -> TypResult<LayoutTree> {
let source = self.sources.get(id);
let ast = parse::parse(source)?;
let module = eval::eval(self, id, &ast)?;
Ok(module.template.to_tree(&self.state))
}
/// Typeset a source file into a collection of layouted frames. /// Typeset a source file into a collection of layouted frames.
/// ///
/// Returns either a vector of frames representing individual pages or /// Returns either a vector of frames representing individual pages or
/// diagnostics in the form of a vector of error message with file and span /// diagnostics in the form of a vector of error message with file and span
/// information. /// information.
pub fn typeset(&mut self, id: SourceId) -> TypResult<Vec<Rc<Frame>>> { pub fn typeset(&mut self, id: SourceId) -> TypResult<Vec<Rc<Frame>>> {
let source = self.sources.get(id); let tree = self.execute(id)?;
let ast = parse::parse(source)?;
let module = eval::eval(self, id, Rc::new(ast))?;
let tree = exec::exec(self, &module.template);
let frames = layout::layout(self, &tree); let frames = layout::layout(self, &tree);
Ok(frames) Ok(frames)
} }

View File

@ -23,9 +23,11 @@ pub fn image(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
}) })
})?; })?;
Ok(Value::template(move |ctx| { Ok(Value::Template(Template::from_inline(move |_| ImageNode {
ctx.inline(ImageNode { id, width, height }) id,
})) width,
height,
})))
} }
/// `rect`: A rectangle with optional content. /// `rect`: A rectangle with optional content.
@ -61,22 +63,23 @@ fn rect_impl(
fill: Option<Color>, fill: Option<Color>,
body: Template, body: Template,
) -> Value { ) -> Value {
Value::template(move |ctx| { Value::Template(Template::from_inline(move |state| {
let mut stack = ctx.exec_template(&body); let mut stack = body.to_stack(state);
stack.aspect = aspect; stack.aspect = aspect;
let fixed = FixedNode { width, height, child: stack.into() }; let mut node = FixedNode { width, height, child: stack.into() }.into();
if let Some(fill) = fill { if let Some(fill) = fill {
ctx.inline(BackgroundNode { node = BackgroundNode {
shape: BackgroundShape::Rect, shape: BackgroundShape::Rect,
fill: Paint::Color(fill), fill: Paint::Color(fill),
child: fixed.into(), child: node,
}); }
} else { .into();
ctx.inline(fixed);
} }
})
node
}))
} }
/// `ellipse`: An ellipse with optional content. /// `ellipse`: An ellipse with optional content.
@ -112,15 +115,15 @@ fn ellipse_impl(
fill: Option<Color>, fill: Option<Color>,
body: Template, body: Template,
) -> Value { ) -> Value {
Value::template(move |ctx| { Value::Template(Template::from_inline(move |state| {
// This padding ratio ensures that the rectangular padded region fits // This padding ratio ensures that the rectangular padded region fits
// perfectly into the ellipse. // perfectly into the ellipse.
const PAD: f64 = 0.5 - SQRT_2 / 4.0; const PAD: f64 = 0.5 - SQRT_2 / 4.0;
let mut stack = ctx.exec_template(&body); let mut stack = body.to_stack(state);
stack.aspect = aspect; stack.aspect = aspect;
let fixed = FixedNode { let mut node = FixedNode {
width, width,
height, height,
child: PadNode { child: PadNode {
@ -128,16 +131,18 @@ fn ellipse_impl(
child: stack.into(), child: stack.into(),
} }
.into(), .into(),
}; }
.into();
if let Some(fill) = fill { if let Some(fill) = fill {
ctx.inline(BackgroundNode { node = BackgroundNode {
shape: BackgroundShape::Ellipse, shape: BackgroundShape::Ellipse,
fill: Paint::Color(fill), fill: Paint::Color(fill),
child: fixed.into(), child: node,
}); }
} else { .into();
ctx.inline(fixed);
} }
})
node
}))
} }

View File

@ -3,7 +3,7 @@ use crate::layout::{FixedNode, GridNode, PadNode, StackChild, StackNode, TrackSi
use crate::paper::{Paper, PaperClass}; use crate::paper::{Paper, PaperClass};
/// `page`: Configure pages. /// `page`: Configure pages.
pub fn page(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { pub fn page(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let paper = match args.eat::<Spanned<Str>>() { let paper = match args.eat::<Spanned<Str>>() {
Some(name) => match Paper::from_name(&name.v) { Some(name) => match Paper::from_name(&name.v) {
None => bail!(name.span, "invalid paper name"), None => bail!(name.span, "invalid paper name"),
@ -20,87 +20,83 @@ pub fn page(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let right = args.named("right")?; let right = args.named("right")?;
let bottom = args.named("bottom")?; let bottom = args.named("bottom")?;
let flip = args.named("flip")?; let flip = args.named("flip")?;
let body = args.expect::<Template>("body")?;
Ok(Value::template(move |ctx| { ctx.template.modify(move |state| {
let snapshot = ctx.state.clone(); let page = state.page_mut();
let state = ctx.state.page_mut();
if let Some(paper) = paper { if let Some(paper) = paper {
state.class = paper.class(); page.class = paper.class();
state.size = paper.size(); page.size = paper.size();
} }
if let Some(width) = width { if let Some(width) = width {
state.class = PaperClass::Custom; page.class = PaperClass::Custom;
state.size.width = width; page.size.width = width;
} }
if let Some(height) = height { if let Some(height) = height {
state.class = PaperClass::Custom; page.class = PaperClass::Custom;
state.size.height = height; page.size.height = height;
} }
if let Some(margins) = margins { if let Some(margins) = margins {
state.margins = Sides::splat(Some(margins)); page.margins = Sides::splat(Some(margins));
} }
if let Some(left) = left { if let Some(left) = left {
state.margins.left = Some(left); page.margins.left = Some(left);
} }
if let Some(top) = top { if let Some(top) = top {
state.margins.top = Some(top); page.margins.top = Some(top);
} }
if let Some(right) = right { if let Some(right) = right {
state.margins.right = Some(right); page.margins.right = Some(right);
} }
if let Some(bottom) = bottom { if let Some(bottom) = bottom {
state.margins.bottom = Some(bottom); page.margins.bottom = Some(bottom);
} }
if flip.unwrap_or(false) { if flip.unwrap_or(false) {
std::mem::swap(&mut state.size.width, &mut state.size.height); std::mem::swap(&mut page.size.width, &mut page.size.height);
} }
});
ctx.pagebreak(false, true); ctx.template.pagebreak(false);
body.exec(ctx);
ctx.state = snapshot; Ok(Value::None)
ctx.pagebreak(true, false);
}))
} }
/// `pagebreak`: Start a new page. /// `pagebreak`: Start a new page.
pub fn pagebreak(_: &mut EvalContext, _: &mut Arguments) -> TypResult<Value> { pub fn pagebreak(ctx: &mut EvalContext, _: &mut Arguments) -> TypResult<Value> {
Ok(Value::template(move |ctx| ctx.pagebreak(true, true))) ctx.template.pagebreak(true);
Ok(Value::None)
} }
/// `h`: Horizontal spacing. /// `h`: Horizontal spacing.
pub fn h(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { pub fn h(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let spacing = args.expect("spacing")?; let spacing = args.expect("spacing")?;
Ok(Value::template(move |ctx| { ctx.template.spacing(GenAxis::Cross, spacing);
ctx.spacing(GenAxis::Cross, spacing); Ok(Value::None)
}))
} }
/// `v`: Vertical spacing. /// `v`: Vertical spacing.
pub fn v(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { pub fn v(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let spacing = args.expect("spacing")?; let spacing = args.expect("spacing")?;
Ok(Value::template(move |ctx| { ctx.template.spacing(GenAxis::Main, spacing);
ctx.spacing(GenAxis::Main, spacing); Ok(Value::None)
}))
} }
/// `align`: Configure the alignment along the layouting axes. /// `align`: Configure the alignment along the layouting axes.
pub fn align(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { pub fn align(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let mut horizontal = args.named("horizontal")?;
let mut vertical = args.named("vertical")?;
let first = args.eat::<Align>(); let first = args.eat::<Align>();
let second = args.eat::<Align>(); let second = args.eat::<Align>();
let body = args.expect::<Template>("body")?; let body = args.eat::<Template>();
let mut horizontal = args.named("horizontal")?;
let mut vertical = args.named("vertical")?;
for value in first.into_iter().chain(second) { for value in first.into_iter().chain(second) {
match value.axis() { match value.axis() {
@ -114,38 +110,52 @@ pub fn align(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
} }
} }
Ok(Value::template(move |ctx| { let realign = |template: &mut Template| {
if let Some(horizontal) = horizontal { template.modify(move |state| {
ctx.state.aligns.cross = horizontal; if let Some(horizontal) = horizontal {
} state.aligns.cross = horizontal;
}
if let Some(vertical) = vertical { if let Some(vertical) = vertical {
ctx.state.aligns.main = vertical; state.aligns.main = vertical;
ctx.parbreak(); }
} });
body.exec(ctx); if vertical.is_some() {
})) template.parbreak();
}
};
if let Some(body) = body {
let mut template = Template::new();
template.save();
realign(&mut template);
template += body;
template.restore();
Ok(Value::Template(template))
} else {
realign(&mut ctx.template);
Ok(Value::None)
}
} }
/// `box`: Place content in a rectangular box. /// `box`: Place content in a rectangular box.
pub fn boxed(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { pub fn boxed(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let width = args.named("width")?; let width = args.named("width")?;
let height = args.named("height")?; let height = args.named("height")?;
let body = args.eat().unwrap_or_default(); let body: Template = args.eat().unwrap_or_default();
Ok(Value::template(move |ctx| { Ok(Value::Template(Template::from_inline(move |state| {
let child = ctx.exec_template(&body).into(); let child = body.to_stack(state).into();
ctx.inline(FixedNode { width, height, child }); FixedNode { width, height, child }
})) })))
} }
/// `block`: Place content in a block. /// `block`: Place content in a block.
pub fn block(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { pub fn block(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let body = args.expect("body")?; let body: Template = args.expect("body")?;
Ok(Value::template(move |ctx| { Ok(Value::Template(Template::from_block(move |state| {
let block = ctx.exec_template(&body); body.to_stack(state)
ctx.block(block); })))
}))
} }
/// `pad`: Pad content at the sides. /// `pad`: Pad content at the sides.
@ -155,7 +165,7 @@ pub fn pad(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let top = args.named("top")?; let top = args.named("top")?;
let right = args.named("right")?; let right = args.named("right")?;
let bottom = args.named("bottom")?; let bottom = args.named("bottom")?;
let body = args.expect("body")?; let body: Template = args.expect("body")?;
let padding = Sides::new( let padding = Sides::new(
left.or(all).unwrap_or_default(), left.or(all).unwrap_or_default(),
@ -164,36 +174,38 @@ pub fn pad(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
bottom.or(all).unwrap_or_default(), bottom.or(all).unwrap_or_default(),
); );
Ok(Value::template(move |ctx| { Ok(Value::Template(Template::from_block(move |state| {
let child = ctx.exec_template(&body).into(); PadNode {
ctx.block(PadNode { padding, child }); padding,
})) child: body.to_stack(&state).into(),
}
})))
} }
/// `stack`: Stack children along an axis. /// `stack`: Stack children along an axis.
pub fn stack(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { pub fn stack(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let dir = args.named("dir")?; let dir = args.named("dir")?;
let children: Vec<_> = args.all().collect(); let children: Vec<Template> = args.all().collect();
Ok(Value::template(move |ctx| { Ok(Value::Template(Template::from_block(move |state| {
let children = children let children = children
.iter() .iter()
.map(|child| { .map(|child| {
let child = ctx.exec_template(child).into(); let child = child.to_stack(state).into();
StackChild::Any(child, ctx.state.aligns) StackChild::Any(child, state.aligns)
}) })
.collect(); .collect();
let mut dirs = Gen::new(None, dir).unwrap_or(ctx.state.dirs); let mut dirs = Gen::new(None, dir).unwrap_or(state.dirs);
// If the directions become aligned, fix up the cross direction since // If the directions become aligned, fix up the cross direction since
// that's the one that is not user-defined. // that's the one that is not user-defined.
if dirs.main.axis() == dirs.cross.axis() { if dirs.main.axis() == dirs.cross.axis() {
dirs.cross = ctx.state.dirs.main; dirs.cross = state.dirs.main;
} }
ctx.block(StackNode { dirs, aspect: None, children }); StackNode { dirs, aspect: None, children }
})) })))
} }
/// `grid`: Arrange children into a grid. /// `grid`: Arrange children into a grid.
@ -211,7 +223,7 @@ pub fn grid(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let column_dir = args.named("column-dir")?; let column_dir = args.named("column-dir")?;
let row_dir = args.named("row-dir")?; let row_dir = args.named("row-dir")?;
let children: Vec<_> = args.all().collect(); let children: Vec<Template> = args.all().collect();
let tracks = Gen::new(columns, rows); let tracks = Gen::new(columns, rows);
let gutter = Gen::new( let gutter = Gen::new(
@ -219,14 +231,13 @@ pub fn grid(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
gutter_rows.unwrap_or(default), gutter_rows.unwrap_or(default),
); );
Ok(Value::template(move |ctx| { Ok(Value::Template(Template::from_block(move |state| {
let children = let children =
children.iter().map(|child| ctx.exec_template(child).into()).collect(); children.iter().map(|child| child.to_stack(&state).into()).collect();
let mut dirs = Gen::new(column_dir, row_dir).unwrap_or(ctx.state.dirs);
// If the directions become aligned, try to fix up the direction which // If the directions become aligned, try to fix up the direction which
// is not user-defined. // is not user-defined.
let mut dirs = Gen::new(column_dir, row_dir).unwrap_or(state.dirs);
if dirs.main.axis() == dirs.cross.axis() { if dirs.main.axis() == dirs.cross.axis() {
let target = if column_dir.is_some() { let target = if column_dir.is_some() {
&mut dirs.main &mut dirs.main
@ -234,20 +245,20 @@ pub fn grid(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
&mut dirs.cross &mut dirs.cross
}; };
*target = if target.axis() == ctx.state.dirs.cross.axis() { *target = if target.axis() == state.dirs.cross.axis() {
ctx.state.dirs.main state.dirs.main
} else { } else {
ctx.state.dirs.cross state.dirs.cross
}; };
} }
ctx.block(GridNode { GridNode {
dirs, dirs,
tracks: tracks.clone(), tracks: tracks.clone(),
gutter: gutter.clone(), gutter: gutter.clone(),
children, children,
}) }
})) })))
} }
/// Defines size of rows and columns in a grid. /// Defines size of rows and columns in a grid.

View File

@ -19,7 +19,6 @@ use std::rc::Rc;
use crate::color::{Color, RgbaColor}; use crate::color::{Color, RgbaColor};
use crate::diag::TypResult; use crate::diag::TypResult;
use crate::eval::{Arguments, EvalContext, Scope, Str, Template, Value}; use crate::eval::{Arguments, EvalContext, Scope, Str, Template, Value};
use crate::exec::Exec;
use crate::font::{FontFamily, FontStretch, FontStyle, FontWeight, VerticalFontMetric}; use crate::font::{FontFamily, FontStretch, FontStyle, FontWeight, VerticalFontMetric};
use crate::geom::*; use crate::geom::*;
use crate::syntax::Spanned; use crate::syntax::Spanned;

View File

@ -1,10 +1,15 @@
use crate::exec::{FontState, LineState}; use crate::eval::{FontState, LineState};
use crate::layout::Paint; use crate::layout::Paint;
use super::*; use super::*;
/// `font`: Configure the font. /// `font`: Configure the font.
pub fn font(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { pub fn font(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let list = args.named("family")?.or_else(|| {
let families: Vec<_> = args.all().collect();
(!families.is_empty()).then(|| FontDef(Rc::new(families)))
});
let size = args.named::<Linear>("size")?.or_else(|| args.eat()); let size = args.named::<Linear>("size")?.or_else(|| args.eat());
let style = args.named("style")?; let style = args.named("style")?;
let weight = args.named("weight")?; let weight = args.named("weight")?;
@ -12,67 +17,59 @@ pub fn font(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let top_edge = args.named("top-edge")?; let top_edge = args.named("top-edge")?;
let bottom_edge = args.named("bottom-edge")?; let bottom_edge = args.named("bottom-edge")?;
let fill = args.named("fill")?; let fill = args.named("fill")?;
let list = args.named("family")?.or_else(|| {
let families: Vec<_> = args.all().collect();
(!families.is_empty()).then(|| FontDef(Rc::new(families)))
});
let serif = args.named("serif")?; let serif = args.named("serif")?;
let sans_serif = args.named("sans-serif")?; let sans_serif = args.named("sans-serif")?;
let monospace = args.named("monospace")?; let monospace = args.named("monospace")?;
let body = args.expect::<Template>("body")?; ctx.template.modify(move |state| {
let font = state.font_mut();
Ok(Value::template(move |ctx| {
let state = ctx.state.font_mut();
if let Some(size) = size { if let Some(size) = size {
state.size = size.resolve(state.size); font.size = size.resolve(font.size);
} }
if let Some(style) = style { if let Some(style) = style {
state.variant.style = style; font.variant.style = style;
} }
if let Some(weight) = weight { if let Some(weight) = weight {
state.variant.weight = weight; font.variant.weight = weight;
} }
if let Some(stretch) = stretch { if let Some(stretch) = stretch {
state.variant.stretch = stretch; font.variant.stretch = stretch;
} }
if let Some(top_edge) = top_edge { if let Some(top_edge) = top_edge {
state.top_edge = top_edge; font.top_edge = top_edge;
} }
if let Some(bottom_edge) = bottom_edge { if let Some(bottom_edge) = bottom_edge {
state.bottom_edge = bottom_edge; font.bottom_edge = bottom_edge;
} }
if let Some(fill) = fill { if let Some(fill) = fill {
state.fill = Paint::Color(fill); font.fill = Paint::Color(fill);
} }
if let Some(FontDef(list)) = &list { if let Some(FontDef(list)) = &list {
state.families_mut().list = list.clone(); font.families_mut().list = list.clone();
} }
if let Some(FamilyDef(serif)) = &serif { if let Some(FamilyDef(serif)) = &serif {
state.families_mut().serif = serif.clone(); font.families_mut().serif = serif.clone();
} }
if let Some(FamilyDef(sans_serif)) = &sans_serif { if let Some(FamilyDef(sans_serif)) = &sans_serif {
state.families_mut().sans_serif = sans_serif.clone(); font.families_mut().sans_serif = sans_serif.clone();
} }
if let Some(FamilyDef(monospace)) = &monospace { if let Some(FamilyDef(monospace)) = &monospace {
state.families_mut().monospace = monospace.clone(); font.families_mut().monospace = monospace.clone();
} }
});
body.exec(ctx); Ok(Value::None)
}))
} }
struct FontDef(Rc<Vec<FontFamily>>); struct FontDef(Rc<Vec<FontFamily>>);
@ -104,29 +101,29 @@ castable! {
} }
/// `par`: Configure paragraphs. /// `par`: Configure paragraphs.
pub fn par(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { pub fn par(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let par_spacing = args.named("spacing")?; let par_spacing = args.named("spacing")?;
let line_spacing = args.named("leading")?; let line_spacing = args.named("leading")?;
let body = args.expect::<Template>("body")?;
Ok(Value::template(move |ctx| { ctx.template.modify(move |state| {
let state = ctx.state.par_mut(); let par = state.par_mut();
if let Some(par_spacing) = par_spacing { if let Some(par_spacing) = par_spacing {
state.par_spacing = par_spacing; par.par_spacing = par_spacing;
} }
if let Some(line_spacing) = line_spacing { if let Some(line_spacing) = line_spacing {
state.line_spacing = line_spacing; par.line_spacing = line_spacing;
} }
});
ctx.parbreak(); ctx.template.parbreak();
body.exec(ctx);
})) Ok(Value::None)
} }
/// `lang`: Configure the language. /// `lang`: Configure the language.
pub fn lang(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { pub fn lang(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
let iso = args.eat::<Str>(); let iso = args.eat::<Str>();
let dir = if let Some(dir) = args.named::<Spanned<Dir>>("dir")? { let dir = if let Some(dir) = args.named::<Spanned<Dir>>("dir")? {
if dir.v.axis() == SpecAxis::Horizontal { if dir.v.axis() == SpecAxis::Horizontal {
@ -138,16 +135,13 @@ pub fn lang(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
iso.as_deref().map(lang_dir) iso.as_deref().map(lang_dir)
}; };
let body = args.expect::<Template>("body")?; if let Some(dir) = dir {
ctx.template.modify(move |state| state.dirs.cross = dir);
}
Ok(Value::template(move |ctx| { ctx.template.parbreak();
if let Some(dir) = dir {
ctx.state.dirs.cross = dir;
}
ctx.parbreak(); Ok(Value::None)
body.exec(ctx);
}))
} }
/// The default direction for the language identified by the given `iso` code. /// The default direction for the language identified by the given `iso` code.
@ -159,22 +153,23 @@ fn lang_dir(iso: &str) -> Dir {
} }
} }
/// `strike`: Enable striken-through text. /// `strike`: Set striken-through text.
pub fn strike(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { pub fn strike(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
line_impl(args, |font| &mut font.strikethrough) line_impl(ctx, args, |font| &mut font.strikethrough)
} }
/// `underline`: Enable underlined text. /// `underline`: Set underlined text.
pub fn underline(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { pub fn underline(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
line_impl(args, |font| &mut font.underline) line_impl(ctx, args, |font| &mut font.underline)
} }
/// `overline`: Add an overline above text. /// `overline`: Set text with an overline.
pub fn overline(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> { pub fn overline(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
line_impl(args, |font| &mut font.overline) line_impl(ctx, args, |font| &mut font.overline)
} }
fn line_impl( fn line_impl(
_: &mut EvalContext,
args: &mut Arguments, args: &mut Arguments,
substate: fn(&mut FontState) -> &mut Option<Rc<LineState>>, substate: fn(&mut FontState) -> &mut Option<Rc<LineState>>,
) -> TypResult<Value> { ) -> TypResult<Value> {
@ -182,10 +177,10 @@ fn line_impl(
let thickness = args.named::<Linear>("thickness")?.or_else(|| args.eat()); let thickness = args.named::<Linear>("thickness")?.or_else(|| args.eat());
let offset = args.named("offset")?; let offset = args.named("offset")?;
let extent = args.named("extent")?.unwrap_or_default(); let extent = args.named("extent")?.unwrap_or_default();
let body = args.expect::<Template>("body")?; let body = args.expect("body")?;
// Suppress any existing strikethrough if strength is explicitly zero. // Suppress any existing strikethrough if strength is explicitly zero.
let state = thickness.map_or(true, |s| !s.is_zero()).then(|| { let line = thickness.map_or(true, |s| !s.is_zero()).then(|| {
Rc::new(LineState { Rc::new(LineState {
stroke: stroke.map(Paint::Color), stroke: stroke.map(Paint::Color),
thickness, thickness,
@ -194,8 +189,11 @@ fn line_impl(
}) })
}); });
Ok(Value::template(move |ctx| { let mut template = Template::new();
*substate(ctx.state.font_mut()) = state.clone(); template.save();
body.exec(ctx); template.modify(move |state| *substate(state.font_mut()) = line.clone());
})) template += body;
template.restore();
Ok(Value::Template(template))
} }

View File

@ -57,23 +57,8 @@ where
// or template to know whether things like headings are allowed. // or template to know whether things like headings are allowed.
let mut tree = vec![]; let mut tree = vec![];
while !p.eof() && f(p) { while !p.eof() && f(p) {
if let Some(mut node) = node(p, &mut at_start) { if let Some(node) = node(p, &mut at_start) {
at_start &= matches!(node, SyntaxNode::Space | SyntaxNode::Parbreak(_)); at_start &= matches!(node, SyntaxNode::Space | SyntaxNode::Parbreak(_));
// Look for wide call.
if let SyntaxNode::Expr(Expr::Call(call)) = &mut node {
if call.wide {
let start = p.next_start();
let tree = tree_while(p, true, f);
call.args.items.push(CallArg::Pos(Expr::Template(Box::new(
TemplateExpr {
span: p.span_from(start),
tree: Rc::new(tree),
},
))));
}
}
tree.push(node); tree.push(node);
} }
} }
@ -538,7 +523,7 @@ fn idents(p: &mut Parser, items: Vec<CallArg>) -> Vec<Ident> {
// Parse a template value: `[...]`. // Parse a template value: `[...]`.
fn template(p: &mut Parser) -> Expr { fn template(p: &mut Parser) -> Expr {
p.start_group(Group::Bracket, TokenMode::Markup); p.start_group(Group::Bracket, TokenMode::Markup);
let tree = Rc::new(tree(p)); let tree = tree(p);
let span = p.end_group(); let span = p.end_group();
Expr::Template(Box::new(TemplateExpr { span, tree })) Expr::Template(Box::new(TemplateExpr { span, tree }))
} }
@ -566,13 +551,6 @@ fn block(p: &mut Parser, scoping: bool) -> Expr {
/// Parse a function call. /// Parse a function call.
fn call(p: &mut Parser, callee: Expr) -> Option<Expr> { fn call(p: &mut Parser, callee: Expr) -> Option<Expr> {
let mut wide = p.eat_if(Token::Excl);
if wide && p.outer_mode() == TokenMode::Code {
let span = p.span_from(callee.span().start);
p.error(span, "wide calls are only allowed directly in templates");
wide = false;
}
let mut args = match p.peek_direct() { let mut args = match p.peek_direct() {
Some(Token::LeftParen) => args(p), Some(Token::LeftParen) => args(p),
Some(Token::LeftBracket) => CallArgs { Some(Token::LeftBracket) => CallArgs {
@ -593,7 +571,6 @@ fn call(p: &mut Parser, callee: Expr) -> Option<Expr> {
Some(Expr::Call(Box::new(CallExpr { Some(Expr::Call(Box::new(CallExpr {
span: p.span_from(callee.span().start), span: p.span_from(callee.span().start),
callee, callee,
wide,
args, args,
}))) })))
} }

View File

@ -170,7 +170,7 @@ pub struct TemplateExpr {
/// The source code location. /// The source code location.
pub span: Span, pub span: Span,
/// The contents of the template. /// The contents of the template.
pub tree: Rc<SyntaxTree>, pub tree: SyntaxTree,
} }
/// A grouped expression: `(1 + 2)`. /// A grouped expression: `(1 + 2)`.
@ -406,8 +406,6 @@ pub struct CallExpr {
pub span: Span, pub span: Span,
/// The function to call. /// The function to call.
pub callee: Expr, pub callee: Expr,
/// Whether the call is wide, that is, capturing the template behind it.
pub wide: bool,
/// The arguments to the function. /// The arguments to the function.
pub args: CallArgs, pub args: CallArgs,
} }

View File

@ -5,8 +5,6 @@ use super::*;
pub enum SyntaxNode { pub enum SyntaxNode {
/// Whitespace containing less than two newlines. /// Whitespace containing less than two newlines.
Space, Space,
/// Plain text.
Text(EcoString),
/// A forced line break: `\`. /// A forced line break: `\`.
Linebreak(Span), Linebreak(Span),
/// A paragraph break: Two or more newlines. /// A paragraph break: Two or more newlines.
@ -15,6 +13,8 @@ pub enum SyntaxNode {
Strong(Span), Strong(Span),
/// Emphasized text was enabled / disabled: `_`. /// Emphasized text was enabled / disabled: `_`.
Emph(Span), Emph(Span),
/// Plain text.
Text(EcoString),
/// A raw block with optional syntax highlighting: `` `...` ``. /// A raw block with optional syntax highlighting: `` `...` ``.
Raw(Box<RawNode>), Raw(Box<RawNode>),
/// A section heading: `= Introduction`. /// A section heading: `= Introduction`.

View File

@ -88,11 +88,11 @@ impl Pretty for SyntaxNode {
match self { match self {
// TODO: Handle escaping. // TODO: Handle escaping.
Self::Space => p.push(' '), Self::Space => p.push(' '),
Self::Text(text) => p.push_str(text),
Self::Linebreak(_) => p.push_str(r"\"), Self::Linebreak(_) => p.push_str(r"\"),
Self::Parbreak(_) => p.push_str("\n\n"), Self::Parbreak(_) => p.push_str("\n\n"),
Self::Strong(_) => p.push('*'), Self::Strong(_) => p.push('*'),
Self::Emph(_) => p.push('_'), Self::Emph(_) => p.push('_'),
Self::Text(text) => p.push_str(text),
Self::Raw(raw) => raw.pretty(p), Self::Raw(raw) => raw.pretty(p),
Self::Heading(n) => n.pretty(p), Self::Heading(n) => n.pretty(p),
Self::List(n) => n.pretty(p), Self::List(n) => n.pretty(p),

View File

@ -87,11 +87,11 @@ impl_visitors! {
visit_node(v, node: SyntaxNode) { visit_node(v, node: SyntaxNode) {
match node { match node {
SyntaxNode::Space => {} SyntaxNode::Space => {}
SyntaxNode::Text(_) => {}
SyntaxNode::Linebreak(_) => {} SyntaxNode::Linebreak(_) => {}
SyntaxNode::Parbreak(_) => {} SyntaxNode::Parbreak(_) => {}
SyntaxNode::Strong(_) => {} SyntaxNode::Strong(_) => {}
SyntaxNode::Emph(_) => {} SyntaxNode::Emph(_) => {}
SyntaxNode::Text(_) => {}
SyntaxNode::Raw(_) => {} SyntaxNode::Raw(_) => {}
SyntaxNode::Heading(n) => v.visit_heading(n), SyntaxNode::Heading(n) => v.visit_heading(n),
SyntaxNode::List(n) => v.visit_list(n), SyntaxNode::List(n) => v.visit_list(n),
@ -149,7 +149,7 @@ impl_visitors! {
visit_template(v, template: TemplateExpr) { visit_template(v, template: TemplateExpr) {
v.visit_enter(); v.visit_enter();
v.visit_tree(r!(rc: template.tree)); v.visit_tree(r!(template.tree));
v.visit_exit(); v.visit_exit();
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -5,7 +5,7 @@
// Ref: true // Ref: true
// Ommitted space. // Ommitted space.
#font(weight:bold)[Bold] [#font(weight:bold)Bold]
// Call return value of function with body. // Call return value of function with body.
#let f(x, body) = (y) => [#x] + body + [#y] #let f(x, body) = (y) => [#x] + body + [#y]
@ -20,35 +20,6 @@
#f()[A] #f()[A]
#f([A]) #f([A])
---
// Ref: true
// Test multiple wide calls in separate expressions inside a template.
[
#font!(fill: eastern) - First
#font!(fill: forest) - Second
]
// Test wide call in heading.
= A #align!(right) B
C
---
// Test wide call in expression.
// Error: 2-4 wide calls are only allowed directly in templates
{f!()}
// Error: 5-7 wide calls are only allowed directly in templates
#g!(f!())
---
// Test wide call evaluation semantics.
#let x = 1
#let f(x, body) = test(x, 1)
#f!(x)
{ x = 2 }
--- ---
// Trailing comma. // Trailing comma.
#test(1 + 1, 2,) #test(1 + 1, 2,)
@ -90,9 +61,6 @@ C
#f[1](2) #f[1](2)
--- ---
// Error: 7 expected argument list
#func!
// Error: 7-8 expected expression, found colon // Error: 7-8 expected expression, found colon
#func(:) #func(:)

View File

@ -4,7 +4,7 @@
--- ---
// Test template addition. // Test template addition.
// Ref: true // Ref: true
{[*Hello ] + [world!*]} {[*Hello ] + [world!]}
--- ---
// Test math operators. // Test math operators.

View File

@ -1,5 +1,5 @@
// Configuration with `page` and `font` functions. // Configuration with `page` and `font` functions.
#page!(width: 450pt, margins: 1cm) #page(width: 450pt, margins: 1cm)
// There are variables and they can take normal values like strings, ... // There are variables and they can take normal values like strings, ...
#let city = "Berlin" #let city = "Berlin"

View File

@ -5,23 +5,24 @@
Auto-sized circle. \ Auto-sized circle. \
#circle(fill: rgb("eb5278"))[ #circle(fill: rgb("eb5278"))[
#align!(center, center) #align(center, center)
But, soft! But, soft!
] ]
Center-aligned rect in auto-sized circle. Center-aligned rect in auto-sized circle.
#circle(fill: forest)[ #circle(fill: forest)[
#align!(center, center) #align(center, center)
#rect!(fill: conifer) #rect(fill: conifer, pad(5pt)[
#pad!(5pt) But, soft!
But, soft! ])
] ]
100%-width rect in auto-sized circle. \ 100%-width rect in auto-sized circle. \
#circle(fill: forest)[ #circle(fill: forest,
#rect!(width: 100%, fill: conifer) rect(width: 100%, fill: conifer)[
But, soft! what light through yonder window breaks? But, soft! what light through yonder window breaks?
] ]
)
Expanded by height. Expanded by height.
#circle(fill: conifer)[A \ B \ C] #circle(fill: conifer)[A \ B \ C]
@ -29,8 +30,8 @@ Expanded by height.
--- ---
// Test relative sizing. // Test relative sizing.
#rect(width: 100%, height: 50pt, fill: rgb("aaa"))[ #rect(width: 100%, height: 50pt, fill: rgb("aaa"))[
#align!(center, center) #align(center, center)
#font!(fill: white) #font(fill: white)
#circle(radius: 10pt, fill: eastern)[A] #circle(radius: 10pt, fill: eastern)[A]
#circle(height: 60%, fill: eastern)[B] #circle(height: 60%, fill: eastern)[B]
#circle(width: 20% + 20pt, fill: eastern)[C] #circle(width: 20% + 20pt, fill: eastern)[C]

View File

@ -2,12 +2,14 @@
--- ---
100% rect in 100% ellipse in fixed rect. \ 100% rect in 100% ellipse in fixed rect. \
#rect(width: 3cm, height: 2cm, fill: rgb("2a631a"))[ #rect(width: 3cm, height: 2cm, fill: rgb("2a631a"),
#ellipse!(width: 100%, height: 100%, fill: forest) ellipse(width: 100%, height: 100%, fill: forest,
#rect!(width: 100%, height: 100%, fill: conifer) rect(width: 100%, height: 100%, fill: conifer)[
#align!(center, center) #align(center, center)
Stuff inside an ellipse! Stuff inside an ellipse!
] ]
)
)
Auto-sized ellipse. \ Auto-sized ellipse. \
#ellipse(fill: conifer)[ #ellipse(fill: conifer)[

View File

@ -17,7 +17,10 @@
#image("../../res/rhino.png") #image("../../res/rhino.png")
// Fit to height of page. // Fit to height of page.
#page(height: 40pt, image("../../res/rhino.png")) [
#page(height: 40pt)
#image("../../res/rhino.png")
]
// Set width explicitly. // Set width explicitly.
#image("../../res/rhino.png", width: 50pt) #image("../../res/rhino.png", width: 50pt)
@ -29,7 +32,8 @@
#image("../../res/rhino.png", width: 25pt, height: 50pt) #image("../../res/rhino.png", width: 25pt, height: 50pt)
// Make sure the bounding-box of the image is correct. // Make sure the bounding-box of the image is correct.
#align(bottom, right, image("../../res/tiger.jpg", width: 60pt)) #align(bottom, right)
#image("../../res/tiger.jpg", width: 60pt)
--- ---
// Error: 8-29 file not found // Error: 8-29 file not found

View File

@ -3,7 +3,7 @@
--- ---
// Test the `rect` function. // Test the `rect` function.
#page!(width: 150pt) #page(width: 150pt)
// Fit to text. // Fit to text.
#rect(fill: conifer)[Textbox] #rect(fill: conifer)[Textbox]

View File

@ -3,22 +3,21 @@
--- ---
Auto-sized square. \ Auto-sized square. \
#square(fill: eastern)[ #square(fill: eastern)[
#align!(center) #font(fill: white, weight: bold)
#pad!(5pt) #align(center)
#font!(fill: white, weight: bold) #pad(5pt)[Typst]
Typst
] ]
--- ---
// Test height overflow. // Test height overflow.
#page!(width: 75pt, height: 100pt) #page(width: 75pt, height: 100pt)
#square(fill: conifer)[ #square(fill: conifer)[
But, soft! what light through yonder window breaks? But, soft! what light through yonder window breaks?
] ]
--- ---
// Test width overflow. // Test width overflow.
#page!(width: 100pt, height: 75pt) #page(width: 100pt, height: 75pt)
#square(fill: conifer)[ #square(fill: conifer)[
But, soft! what light through yonder window breaks? But, soft! what light through yonder window breaks?
] ]

View File

@ -12,7 +12,7 @@ Apart
--- ---
// Test block over multiple pages. // Test block over multiple pages.
#page!(height: 60pt) #page(height: 60pt)
First! First!
#block[ #block[

View File

@ -3,7 +3,7 @@
--- ---
#let rect(width, fill) = rect(width: width, height: 2cm, fill: fill) #let rect(width, fill) = rect(width: width, height: 2cm, fill: fill)
#page!(width: 100pt, height: 140pt) #page(width: 100pt, height: 140pt)
#grid( #grid(
columns: (auto, 1fr, 3fr, 0.25cm, 3%, 2mm + 10%), columns: (auto, 1fr, 3fr, 0.25cm, 3%, 2mm + 10%),
rect(0.5cm, rgb("2a631a")), rect(0.5cm, rgb("2a631a")),
@ -33,7 +33,7 @@
) )
--- ---
#page!(height: 3cm, width: 2cm) #page(height: 3cm, width: 2cm)
#grid( #grid(
columns: (1fr, 1cm, 1fr, 1fr), columns: (1fr, 1cm, 1fr, 1fr),
column-dir: ttb, column-dir: ttb,
@ -46,8 +46,8 @@
) )
--- ---
#page!(height: 3cm, margins: 0pt) #page(height: 3cm, margins: 0pt)
#align!(center) #align(center)
#grid( #grid(
columns: (1fr,), columns: (1fr,),
rows: (1fr, auto, 2fr), rows: (1fr, auto, 2fr),

View File

@ -1,7 +1,7 @@
// Test using the `grid` function to create a finance table. // Test using the `grid` function to create a finance table.
--- ---
#page!(width: 12cm, height: 2.5cm) #page(width: 12cm, height: 2.5cm)
#grid( #grid(
columns: 5, columns: 5,
gutter-columns: (2fr, 1fr, 1fr), gutter-columns: (2fr, 1fr, 1fr),

View File

@ -1,7 +1,7 @@
// Test grid cells that overflow to the next region. // Test grid cells that overflow to the next region.
--- ---
#page!(width: 5cm, height: 3cm) #page(width: 5cm, height: 3cm)
#grid( #grid(
columns: 2, columns: 2,
gutter-rows: 3 * (8pt,), gutter-rows: 3 * (8pt,),
@ -18,7 +18,7 @@
--- ---
// Test a column that starts overflowing right after another row/column did // Test a column that starts overflowing right after another row/column did
// that. // that.
#page!(width: 5cm, height: 2cm) #page(width: 5cm, height: 2cm)
#grid( #grid(
columns: 4 * (1fr,), columns: 4 * (1fr,),
gutter-rows: (10pt,), gutter-rows: (10pt,),
@ -32,7 +32,7 @@
--- ---
// Test two columns in the same row overflowing by a different amount. // Test two columns in the same row overflowing by a different amount.
#page!(width: 5cm, height: 2cm) #page(width: 5cm, height: 2cm)
#grid( #grid(
columns: 3 * (1fr,), columns: 3 * (1fr,),
gutter-rows: (8pt,), gutter-rows: (8pt,),
@ -48,7 +48,7 @@
--- ---
// Test grid within a grid, overflowing. // Test grid within a grid, overflowing.
#page!(width: 5cm, height: 2.25cm) #page(width: 5cm, height: 2.25cm)
#grid( #grid(
columns: 4 * (1fr,), columns: 4 * (1fr,),
gutter-rows: (10pt,), gutter-rows: (10pt,),
@ -62,7 +62,7 @@
--- ---
// Test partition of `fr` units before and after multi-region layout. // Test partition of `fr` units before and after multi-region layout.
#page!(width: 5cm, height: 4cm) #page(width: 5cm, height: 4cm)
#grid( #grid(
columns: 2 * (1fr,), columns: 2 * (1fr,),
rows: (1fr, 2fr, auto, 1fr, 1cm), rows: (1fr, 2fr, auto, 1fr, 1cm),

View File

@ -5,10 +5,11 @@
#pad(left: 10pt, [Indented!]) #pad(left: 10pt, [Indented!])
// All sides together. // All sides together.
#rect(fill: conifer)[ #rect(fill: conifer,
#pad!(10pt, right: 20pt) pad(10pt, right: 20pt,
#rect(width: 20pt, height: 20pt, fill: rgb("eb5278")) rect(width: 20pt, height: 20pt, fill: rgb("eb5278"))
] )
)
Hi #box(pad(left: 10pt)[]) there Hi #box(pad(left: 10pt)[]) there
@ -27,7 +28,7 @@ Hi #box(pad(left: 10pt)[]) there
--- ---
// Test that the pad node doesn't consume the whole region. // Test that the pad node doesn't consume the whole region.
#page!(height: 6cm) #page(height: 6cm)
#align(left)[Before] #align(left)[Before]
#pad(10pt, image("../../res/tiger.jpg")) #pad(10pt, image("../../res/tiger.jpg"))

View File

@ -2,44 +2,45 @@
--- ---
// Set width and height. // Set width and height.
#page!(width: 120pt, height: 120pt) #page(width: 120pt, height: 120pt)
#page(width: 40pt)[High] [#page(width: 40pt) High]
#page(height: 40pt)[Wide] [#page(height: 40pt) Wide]
// Set all margins at once. // Set all margins at once.
#page(margins: 30pt)[ [
#page(margins: 30pt)
#align(top, left)[TL] #align(top, left)[TL]
#align(bottom, right)[BR] #align(bottom, right)[BR]
] ]
// Set individual margins. // Set individual margins.
#page!(height: 40pt) #page(height: 40pt)
#page(left: 0pt, align(left)[Left]) [#page(left: 0pt) #align(left) Left]
#page(right: 0pt, align(right)[Right]) [#page(right: 0pt) #align(right) Right]
#page(top: 0pt, align(top)[Top]) [#page(top: 0pt) #align(top) Top]
#page(bottom: 0pt, align(bottom)[Bottom]) [#page(bottom: 0pt) #align(bottom) Bottom]
// Ensure that specific margins override general margins. // Ensure that specific margins override general margins.
#page(margins: 0pt, left: 20pt)[Overriden] [#page(margins: 0pt, left: 20pt) Overriden]
// Flipped predefined paper. // Flipped predefined paper.
#page("a11", flip: true)[Flipped A11] [#page("a11", flip: true) Flipped A11]
// Flipped custom page size. // Flipped custom page size.
#page!(width: 40pt, height: 120pt) #page(width: 40pt, height: 120pt)
#page!(flip: true) #page(flip: true)
Wide Wide
--- ---
// Test a combination of pages with bodies and normal content. // Test a combination of pages with bodies and normal content.
#page!(height: 50pt) #page(height: 50pt)
#page[First] [#page() First]
#page[Second] [#page() Second]
#pagebreak() #pagebreak()
#pagebreak() #pagebreak()
Fourth Fourth
#page[] [#page(height: 25pt)]
Sixth Sixth
#page[Seventh and last] [#page() Seventh and last]

View File

@ -3,7 +3,7 @@
--- ---
First of two First of two
#pagebreak() #pagebreak()
#page!(height: 40pt) #page(height: 40pt)
--- ---
// Make sure that you can't do page related stuff in a container. // Make sure that you can't do page related stuff in a container.
@ -11,7 +11,7 @@ A
#box[ #box[
B B
#pagebreak() #pagebreak()
#page("a4")[] #page("a4")
] ]
C C

View File

@ -1,7 +1,7 @@
// Test simple text. // Test simple text.
--- ---
#page!(width: 250pt, height: 110pt) #page(width: 250pt, height: 110pt)
But, soft! what light through yonder window breaks? It is the east, and Juliet But, soft! what light through yonder window breaks? It is the east, and Juliet
is the sun. Arise, fair sun, and kill the envious moon, Who is already sick and is the sun. Arise, fair sun, and kill the envious moon, Who is already sick and

View File

@ -3,54 +3,54 @@
--- ---
// Test reordering with different top-level paragraph directions. // Test reordering with different top-level paragraph directions.
#let text = [Text טֶקסט] #let text = [Text טֶקסט]
#font!("EB Garamond", "Noto Serif Hebrew") #font("EB Garamond", "Noto Serif Hebrew")
#lang!("he") {text} #lang("he") {text}
#lang!("de") {text} #lang("de") {text}
--- ---
// Test that consecutive, embedded LTR runs stay LTR. // Test that consecutive, embedded LTR runs stay LTR.
// Here, we have two runs: "A" and italic "B". // Here, we have two runs: "A" and italic "B".
#let text = [أنت A_B_مطرC] #let text = [أنت A_B_مطرC]
#font!("EB Garamond", "Noto Sans Arabic") #font("EB Garamond", "Noto Sans Arabic")
#lang!("ar") {text} #lang("ar") {text}
#lang!("de") {text} #lang("de") {text}
--- ---
// Test that consecutive, embedded RTL runs stay RTL. // Test that consecutive, embedded RTL runs stay RTL.
// Here, we have three runs: "גֶ", bold "שֶׁ", and "ם". // Here, we have three runs: "גֶ", bold "שֶׁ", and "ם".
#let text = [Aגֶ*שֶׁ*םB] #let text = [Aגֶ*שֶׁ*םB]
#font!("EB Garamond", "Noto Serif Hebrew") #font("EB Garamond", "Noto Serif Hebrew")
#lang!("he") {text} #lang("he") {text}
#lang!("de") {text} #lang("de") {text}
--- ---
// Test embedding up to level 4 with isolates. // Test embedding up to level 4 with isolates.
#font!("EB Garamond", "Noto Serif Hebrew", "Twitter Color Emoji") #font("EB Garamond", "Noto Serif Hebrew", "Twitter Color Emoji")
#lang!(dir: rtl) #lang(dir: rtl)
א\u{2066}A\u{2067}Bב\u{2069}? א\u{2066}A\u{2067}Bב\u{2069}?
--- ---
// Test hard line break (leads to two paragraphs in unicode-bidi). // Test hard line break (leads to two paragraphs in unicode-bidi).
#font!("Noto Sans Arabic", "EB Garamond") #font("Noto Sans Arabic", "EB Garamond")
#lang!("ar") #lang("ar")
Life المطر هو الحياة \ Life المطر هو الحياة \
الحياة تمطر is rain. الحياة تمطر is rain.
--- ---
// Test spacing. // Test spacing.
#font!("EB Garamond", "Noto Serif Hebrew") #font("EB Garamond", "Noto Serif Hebrew")
L #h(1cm) ריווחR \ L #h(1cm) ריווחR \
יווח #h(1cm) R יווח #h(1cm) R
--- ---
// Test inline object. // Test inline object.
#font!("Noto Serif Hebrew", "EB Garamond") #font("Noto Serif Hebrew", "EB Garamond")
#lang!("he") #lang("he")
קרנפיםRh#image("../../res/rhino.png", height: 11pt)inoחיים קרנפיםRh#image("../../res/rhino.png", height: 11pt)inoחיים
--- ---
// Test the `lang` function. // Test the `lang` function.
// Ref: false // Ref: false
// Error: 13-16 must be horizontal // Error: 12-15 must be horizontal
#lang!(dir: ttb) #lang(dir: ttb)

View File

@ -1,7 +1,7 @@
// Test chinese text from Wikipedia. // Test chinese text from Wikipedia.
--- ---
#font!("Noto Serif CJK SC") #font("Noto Serif CJK SC")
是美国广播公司电视剧《迷失》第3季的第22和23集也是全剧的第71集和72集 是美国广播公司电视剧《迷失》第3季的第22和23集也是全剧的第71集和72集
由执行制作人戴蒙·林道夫和卡尔顿·库斯编剧,导演则是另一名执行制作人杰克·本德 由执行制作人戴蒙·林道夫和卡尔顿·库斯编剧,导演则是另一名执行制作人杰克·本德

View File

@ -1,19 +1,19 @@
// Test text decorations. // Test text decorations.
--- ---
#let red = rgb("fc0030")
// Basic strikethrough. // Basic strikethrough.
#strike[ #strike[Statements dreamt up by the utterly deranged.]
Statements dreamt up by the utterly deranged.
]
// Move underline down. // Move underline down.
#underline(offset: 5pt)[Further below.] #underline(offset: 5pt)[Further below.]
// Different color. // Different color.
#underline(rgb("fc0030"))[Critical information is conveyed here.] #underline(red)[Critical information is conveyed here.]
// Inherits font color. // Inherits font color.
#font(fill: rgb("fc0030"), underline[Change with the wind.]) [#font(fill: red) #underline[Change with the wind.]]
// Both over- and underline. // Both over- and underline.
#overline(underline[Running amongst the wolves.]) #overline(underline[Running amongst the wolves.])

View File

@ -2,42 +2,43 @@
--- ---
// Set same font size in three different ways. // Set same font size in three different ways.
#font(22pt)[A] [#font(22pt) A]
#font(200%)[A] [#font(200%) A]
#font(size: 16.5pt + 50%)[A] [#font(size: 16.5pt + 50%) A]
// Do nothing. // Do nothing.
#font[Normal] [#font() Normal]
// Set style (is available). // Set style (is available).
#font(style: italic)[Italic] [#font(style: italic) Italic]
// Set weight (is available). // Set weight (is available).
#font(weight: bold)[Bold] [#font(weight: bold) Bold]
// Set stretch (not available, matching closest). // Set stretch (not available, matching closest).
#font(stretch: 50%)[Condensed] [#font(stretch: 50%) Condensed]
// Set family. // Set family.
#font(family: "PT Sans")[Sans serif] [#font(family: "PT Sans") Sans serif]
// Emoji. // Emoji.
Emoji: 🐪, 🌋, 🏞 Emoji: 🐪, 🌋, 🏞
// Math. // Math.
#font("Latin Modern Math")[ [#font("Latin Modern Math") 𝛼 + 3𝛽 d𝑡]
𝛼 + 3𝛽 d𝑡
]
// Colors. // Colors.
#font(fill: eastern)[This is #font(fill: rgb("FA644B"))[way more] colorful.] [
#font(fill: eastern)
This is [#font(fill: rgb("FA644B")) way more] colorful.
]
--- ---
// Test top and bottom edge. // Test top and bottom edge.
#page!(width: 170pt) #page(width: 170pt)
#let try(top, bottom) = rect(fill: conifer)[ #let try(top, bottom) = rect(fill: conifer)[
#font!(top-edge: top, bottom-edge: bottom) #font(top-edge: top, bottom-edge: bottom)
`From `#top` to `#bottom `From `#top` to `#bottom
] ]
@ -48,31 +49,31 @@ Emoji: 🐪, 🌋, 🏞
--- ---
// Test class definitions. // Test class definitions.
#font!(sans-serif: "PT Sans") #font(sans-serif: "PT Sans")
#font(family: sans-serif)[Sans-serif.] \ [#font(family: sans-serif) Sans-serif.] \
#font(monospace)[Monospace.] \ [#font(monospace) Monospace.] \
#font(monospace, monospace: ("Nope", "Latin Modern Math"))[Math.] [#font(monospace, monospace: ("Nope", "Latin Modern Math")) Math.]
--- ---
// Error: 7-12 unexpected argument // Error: 7-12 unexpected argument
#font(false)[] #font(false)
--- ---
// Error: 14-18 expected font style, found font weight // Error: 14-18 expected font style, found font weight
#font(style: bold, weight: "thin")[] #font(style: bold, weight: "thin")
--- ---
// Error: 14-15 expected string or array of strings, found integer // Error: 14-15 expected string or array of strings, found integer
#font(serif: 0)[] #font(serif: 0)
--- ---
// Error: 19-23 unexpected argument // Error: 19-23 unexpected argument
#font(size: 10pt, 12pt)[] #font(size: 10pt, 12pt)
--- ---
// Error: 28-35 unexpected argument // Error: 28-35 unexpected argument
#font(family: "Helvetica", "Arial")[] #font(family: "Helvetica", "Arial")
--- ---
// Error: 7-27 unexpected argument // Error: 7-27 unexpected argument
#font(something: "invalid")[] #font(something: "invalid")

View File

@ -1,7 +1,7 @@
// Test configuring paragraph properties. // Test configuring paragraph properties.
--- ---
#par!(spacing: 10pt, leading: 25%) #par(spacing: 10pt, leading: 25%)
But, soft! what light through yonder window breaks? It is the east, and Juliet But, soft! what light through yonder window breaks? It is the east, and Juliet
is the sun. is the sun.

View File

@ -7,11 +7,11 @@
Le fira Le fira
// This should just shape nicely. // This should just shape nicely.
#font!("Noto Sans Arabic") #font("Noto Sans Arabic")
دع النص يمطر عليك دع النص يمطر عليك
// This should form a three-member family. // This should form a three-member family.
#font!("Twitter Color Emoji") #font("Twitter Color Emoji")
👩‍👩‍👦 🤚🏿 👩‍👩‍👦 🤚🏿
// These two shouldn't be affected by a zero-width joiner. // These two shouldn't be affected by a zero-width joiner.
@ -20,7 +20,7 @@ Le fira
--- ---
// Test font fallback. // Test font fallback.
#font!("EB Garamond", "Noto Sans Arabic", "Twitter Color Emoji") #font("EB Garamond", "Noto Sans Arabic", "Twitter Color Emoji")
// Font fallback for emoji. // Font fallback for emoji.
A😀B A😀B
@ -40,6 +40,6 @@ A🐈中文B
--- ---
// Test reshaping. // Test reshaping.
#font!("Noto Serif Hebrew") #font("Noto Serif Hebrew")
#lang!("he") #lang("he")
ס \ טֶ ס \ טֶ

View File

@ -30,11 +30,11 @@ A #for _ in (none,) {"B"}C
--- ---
// Test that a run consisting only of whitespace isn't trimmed. // Test that a run consisting only of whitespace isn't trimmed.
A#font("PT Sans")[ ]B A[#font("PT Sans") ]B
--- ---
// Test font change after space. // Test font change after space.
Left #font("PT Sans")[Right]. Left [#font("PT Sans")Right].
--- ---
// Test that space at start of line is not trimmed. // Test that space at start of line is not trimmed.

View File

@ -10,15 +10,14 @@ use ttf_parser::{GlyphId, OutlineBuilder};
use walkdir::WalkDir; use walkdir::WalkDir;
use typst::color::Color; use typst::color::Color;
use typst::diag::{Error, TypResult}; use typst::diag::Error;
use typst::eval::{eval, Value}; use typst::eval::{State, Value};
use typst::exec::{exec, State};
use typst::geom::{self, Length, PathElement, Point, Sides, Size}; use typst::geom::{self, Length, PathElement, Point, Sides, Size};
use typst::image::ImageId; use typst::image::ImageId;
use typst::layout::{layout, Element, Frame, Geometry, LayoutTree, Paint, Text}; use typst::layout::{layout, Element, Frame, Geometry, LayoutTree, Paint, Text};
use typst::loading::FsLoader; use typst::loading::FsLoader;
use typst::parse::{parse, Scanner}; use typst::parse::Scanner;
use typst::source::{SourceFile, SourceId}; use typst::source::SourceFile;
use typst::syntax::{Pos, Span}; use typst::syntax::{Pos, Span};
use typst::Context; use typst::Context;
@ -225,11 +224,10 @@ fn test_part(
let compare_ref = local_compare_ref.unwrap_or(compare_ref); let compare_ref = local_compare_ref.unwrap_or(compare_ref);
let mut ok = true; let mut ok = true;
let (frames, mut errors) = match ctx.execute(id) {
Ok(tree) => {
let mut frames = layout(ctx, &tree);
let result = typeset(ctx, id);
let (frames, mut errors) = match result {
#[allow(unused_variables)]
Ok((tree, mut frames)) => {
#[cfg(feature = "layout-cache")] #[cfg(feature = "layout-cache")]
(ok &= test_incremental(ctx, i, &tree, &frames)); (ok &= test_incremental(ctx, i, &tree, &frames));
@ -274,15 +272,6 @@ fn test_part(
(ok, compare_ref, frames) (ok, compare_ref, frames)
} }
fn typeset(ctx: &mut Context, id: SourceId) -> TypResult<(LayoutTree, Vec<Rc<Frame>>)> {
let source = ctx.sources.get(id);
let ast = parse(source)?;
let module = eval(ctx, id, Rc::new(ast))?;
let tree = exec(ctx, &module.template);
let frames = layout(ctx, &tree);
Ok((tree, frames))
}
#[cfg(feature = "layout-cache")] #[cfg(feature = "layout-cache")]
fn test_incremental( fn test_incremental(
ctx: &mut Context, ctx: &mut Context,