mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
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:
parent
c53d98a22f
commit
594809e35b
@ -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>> {
|
||||||
|
201
src/eval/mod.rs
201
src/eval/mod.rs
@ -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()),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -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()
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
173
src/exec/mod.rs
173
src/exec/mod.rs
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>;
|
||||||
|
@ -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;
|
||||||
|
33
src/lib.rs
33
src/lib.rs
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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;
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
@ -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`.
|
||||||
|
@ -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),
|
||||||
|
@ -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 |
@ -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(:)
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
---
|
---
|
||||||
// Test template addition.
|
// Test template addition.
|
||||||
// Ref: true
|
// Ref: true
|
||||||
{[*Hello ] + [world!*]}
|
{[*Hello ] + [world!]}
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test math operators.
|
// Test math operators.
|
||||||
|
@ -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"
|
||||||
|
@ -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]
|
||||||
|
@ -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)[
|
||||||
|
@ -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
|
||||||
|
@ -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]
|
||||||
|
@ -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?
|
||||||
]
|
]
|
||||||
|
@ -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[
|
||||||
|
@ -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),
|
||||||
|
@ -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),
|
||||||
|
@ -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),
|
||||||
|
@ -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"))
|
||||||
|
@ -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]
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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 \
|
||||||
Lריווח #h(1cm) R
|
Lריווח #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)
|
||||||
|
@ -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集
|
||||||
由执行制作人戴蒙·林道夫和卡尔顿·库斯编剧,导演则是另一名执行制作人杰克·本德
|
由执行制作人戴蒙·林道夫和卡尔顿·库斯编剧,导演则是另一名执行制作人杰克·本德
|
||||||
|
@ -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.])
|
||||||
|
@ -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")
|
||||||
|
@ -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.
|
||||||
|
@ -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")
|
||||||
ס \ טֶ
|
ס \ טֶ
|
||||||
|
@ -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.
|
||||||
|
@ -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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user