From 757a701c1aa2a6fb80033c7e75666661818da6f9 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 20 Sep 2022 13:05:55 +0200 Subject: [PATCH] A New World --- Cargo.lock | 17 ++ Cargo.toml | 25 ++- benches/oneshot.rs | 81 ++++---- src/diag.rs | 10 +- src/eval/array.rs | 14 +- src/eval/capture.rs | 4 +- src/eval/dict.rs | 4 +- src/eval/func.rs | 21 +- src/eval/methods.rs | 4 +- src/eval/mod.rs | 153 +++++++-------- src/eval/scope.rs | 4 +- src/eval/{machine.rs => vm.rs} | 26 +-- src/export/pdf/image.rs | 66 +++---- src/export/render.rs | 4 +- src/font/book.rs | 4 +- src/font/mod.rs | 2 +- src/image.rs | 2 +- src/lib.rs | 128 +++++------- src/library/graphics/hide.rs | 6 +- src/library/graphics/image.rs | 7 +- src/library/graphics/line.rs | 4 +- src/library/graphics/shape.rs | 8 +- src/library/graphics/transform.rs | 12 +- src/library/layout/align.rs | 6 +- src/library/layout/columns.rs | 10 +- src/library/layout/container.rs | 4 +- src/library/layout/flow.rs | 10 +- src/library/layout/grid.rs | 20 +- src/library/layout/pad.rs | 6 +- src/library/layout/page.rs | 16 +- src/library/layout/place.rs | 6 +- src/library/layout/spacing.rs | 4 +- src/library/layout/stack.rs | 10 +- src/library/math/mod.rs | 6 +- src/library/math/rex.rs | 7 +- src/library/prelude.rs | 7 +- src/library/structure/doc.rs | 4 +- src/library/structure/heading.rs | 12 +- src/library/structure/list.rs | 12 +- src/library/structure/reference.rs | 4 +- src/library/structure/table.rs | 12 +- src/library/text/deco.rs | 4 +- src/library/text/link.rs | 6 +- src/library/text/mod.rs | 16 +- src/library/text/par.rs | 66 +++---- src/library/text/raw.rs | 6 +- src/library/text/repeat.rs | 6 +- src/library/text/shaping.rs | 32 +-- src/library/text/shift.rs | 14 +- src/library/utility/color.rs | 6 +- src/library/utility/data.rs | 4 +- src/library/utility/math.rs | 18 +- src/library/utility/mod.rs | 19 +- src/library/utility/string.rs | 14 +- src/loading.rs | 239 ----------------------- src/main.rs | 301 ++++++++++++++++++++++++++--- src/model/content.rs | 30 +-- src/model/layout.rs | 22 +-- src/model/recipe.rs | 12 +- src/model/show.rs | 14 +- src/model/styles.rs | 12 +- src/parse/incremental.rs | 22 +-- src/parse/mod.rs | 24 +-- src/parse/parser.rs | 8 +- src/parse/resolve.rs | 4 +- src/parse/tokens.rs | 24 +-- src/source.rs | 278 ++++++-------------------- src/syntax/highlight.rs | 6 +- src/syntax/mod.rs | 4 +- src/syntax/span.rs | 4 +- src/util/buffer.rs | 59 ++++++ src/util/mod.rs | 2 + tests/typ/coma.typ | 2 +- tests/typeset.rs | 275 ++++++++++++++++++-------- 74 files changed, 1133 insertions(+), 1182 deletions(-) rename src/eval/{machine.rs => vm.rs} (75%) delete mode 100644 src/loading.rs create mode 100644 src/util/buffer.rs diff --git a/Cargo.lock b/Cargo.lock index 5287d230a..407ff66ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,6 +189,15 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +[[package]] +name = "elsa" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b4b5d23ed6b6948d68240aafa4ac98e568c9a020efd9d4201a6288bc3006e09" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "fancy-regex" version = "0.7.1" @@ -710,6 +719,12 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" @@ -838,6 +853,7 @@ dependencies = [ "codespan-reporting", "csv", "dirs", + "elsa", "flate2", "fxhash", "hypher", @@ -858,6 +874,7 @@ dependencies = [ "rustybuzz", "same-file", "serde", + "siphasher", "subsetter", "svg2pdf", "syntect", diff --git a/Cargo.toml b/Cargo.toml index b2d9d5576..b56e8991c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,9 +5,18 @@ authors = ["The Typst Project Developers"] edition = "2021" [features] -default = ["fs"] -cli = ["fs", "pico-args", "codespan-reporting", "same-file"] -fs = ["dirs", "memmap2", "walkdir", "same-file"] +default = ["tests"] +tests = ["same-file", "walkdir", "elsa", "siphasher"] +cli = [ + "pico-args", + "codespan-reporting", + "dirs", + "memmap2", + "same-file", + "walkdir", + "elsa", + "siphasher", +] [dependencies] # Workspace @@ -57,15 +66,15 @@ resvg = { version = "0.22", default-features = false } roxmltree = "0.14" flate2 = "1" -# Command line interface +# Command line interface / tests pico-args = { version = "0.4", optional = true } codespan-reporting = { version = "0.11", optional = true } same-file = { version = "1", optional = true } - -# File system loading +walkdir = { version = "2", optional = true } +elsa = { version = "1.7", optional = true } dirs = { version = "4", optional = true } memmap2 = { version = "0.5", optional = true } -walkdir = { version = "2", optional = true } +siphasher = { version = "0.3", optional = true } [dev-dependencies] iai = { git = "https://github.com/reknih/iai" } @@ -85,7 +94,7 @@ required-features = ["cli"] [[test]] name = "typeset" -required-features = ["fs"] +required-features = ["tests"] harness = false [[bench]] diff --git a/benches/oneshot.rs b/benches/oneshot.rs index c224d1bf8..b1ed6f3f9 100644 --- a/benches/oneshot.rs +++ b/benches/oneshot.rs @@ -1,26 +1,18 @@ use std::io; use std::path::Path; -use std::sync::Arc; use iai::{black_box, main, Iai}; use unscanny::Scanner; use typst::font::{Font, FontBook}; -use typst::loading::{Buffer, FileHash, Loader}; use typst::parse::{TokenMode, Tokens}; -use typst::source::SourceId; -use typst::{Config, Context}; +use typst::source::{Source, SourceId}; +use typst::util::Buffer; +use typst::{Config, World}; -const SRC: &str = include_str!("bench.typ"); +const TEXT: &str = include_str!("bench.typ"); const FONT: &[u8] = include_bytes!("../fonts/IBMPlexSans-Regular.ttf"); -fn context() -> (Context, SourceId) { - let loader = BenchLoader::new(); - let mut ctx = Context::new(Arc::new(loader), Config::default()); - let id = ctx.sources.provide(Path::new("src.typ"), SRC.to_string()); - (ctx, id) -} - main!( bench_decode, bench_scan, @@ -38,7 +30,7 @@ fn bench_decode(iai: &mut Iai) { // We don't use chars().count() because that has a special // superfast implementation. let mut count = 0; - let mut chars = black_box(SRC).chars(); + let mut chars = black_box(TEXT).chars(); while let Some(_) = chars.next() { count += 1; } @@ -49,7 +41,7 @@ fn bench_decode(iai: &mut Iai) { fn bench_scan(iai: &mut Iai) { iai.run(|| { let mut count = 0; - let mut scanner = Scanner::new(black_box(SRC)); + let mut scanner = Scanner::new(black_box(TEXT)); while let Some(_) = scanner.eat() { count += 1; } @@ -58,21 +50,20 @@ fn bench_scan(iai: &mut Iai) { } fn bench_tokenize(iai: &mut Iai) { - iai.run(|| Tokens::new(black_box(SRC), black_box(TokenMode::Markup)).count()); + iai.run(|| Tokens::new(black_box(TEXT), black_box(TokenMode::Markup)).count()); } fn bench_parse(iai: &mut Iai) { - iai.run(|| typst::parse::parse(SRC)); + iai.run(|| typst::parse::parse(TEXT)); } fn bench_edit(iai: &mut Iai) { - let (mut ctx, id) = context(); - iai.run(|| black_box(ctx.sources.edit(id, 1168 .. 1171, "_Uhr_"))); + let mut source = Source::detached(TEXT); + iai.run(|| black_box(source.edit(1168 .. 1171, "_Uhr_"))); } fn bench_highlight(iai: &mut Iai) { - let (ctx, id) = context(); - let source = ctx.sources.get(id); + let source = Source::detached(TEXT); iai.run(|| { typst::syntax::highlight_node( source.root(), @@ -83,36 +74,60 @@ fn bench_highlight(iai: &mut Iai) { } fn bench_eval(iai: &mut Iai) { - let (mut ctx, id) = context(); - iai.run(|| typst::eval::evaluate(&mut ctx, id, vec![]).unwrap()); + let world = BenchWorld::new(); + let id = world.source.id(); + iai.run(|| typst::eval::evaluate(&world, id, vec![]).unwrap()); } fn bench_layout(iai: &mut Iai) { - let (mut ctx, id) = context(); - let module = typst::eval::evaluate(&mut ctx, id, vec![]).unwrap(); - iai.run(|| typst::model::layout(&mut ctx, &module.content)); + let world = BenchWorld::new(); + let id = world.source.id(); + let module = typst::eval::evaluate(&world, id, vec![]).unwrap(); + iai.run(|| typst::model::layout(&world, &module.content)); } fn bench_render(iai: &mut Iai) { - let (mut ctx, id) = context(); - let frames = typst::typeset(&mut ctx, id).unwrap(); + let world = BenchWorld::new(); + let id = world.source.id(); + let frames = typst::typeset(&world, id).unwrap(); iai.run(|| typst::export::render(&frames[0], 1.0)) } -struct BenchLoader { +struct BenchWorld { + config: Config, book: FontBook, font: Font, + source: Source, } -impl BenchLoader { +impl BenchWorld { fn new() -> Self { let font = Font::new(FONT.into(), 0).unwrap(); let book = FontBook::from_fonts([&font]); - Self { book, font } + let id = SourceId::from_raw(0); + let source = Source::new(id, Path::new("bench.typ"), TEXT.into()); + Self { + config: Config::default(), + book, + font, + source, + } } } -impl Loader for BenchLoader { +impl World for BenchWorld { + fn config(&self) -> &Config { + &self.config + } + + fn resolve(&self, _: &Path) -> io::Result { + Err(io::ErrorKind::NotFound.into()) + } + + fn source(&self, _: SourceId) -> &Source { + &self.source + } + fn book(&self) -> &FontBook { &self.book } @@ -121,10 +136,6 @@ impl Loader for BenchLoader { Ok(self.font.clone()) } - fn resolve(&self, _: &Path) -> io::Result { - Err(io::ErrorKind::NotFound.into()) - } - fn file(&self, _: &Path) -> io::Result { Err(io::ErrorKind::NotFound.into()) } diff --git a/src/diag.rs b/src/diag.rs index c44383072..e96dfb004 100644 --- a/src/diag.rs +++ b/src/diag.rs @@ -5,7 +5,7 @@ use std::io; use std::path::Path; use crate::syntax::{Span, Spanned}; -use crate::Context; +use crate::World; /// Early-return with a [`TypError`]. #[macro_export] @@ -101,21 +101,21 @@ where /// Enrich a [`TypResult`] with a tracepoint. pub trait Trace { /// Add the tracepoint to all errors that lie outside the `span`. - fn trace(self, ctx: &Context, make_point: F, span: Span) -> Self + fn trace(self, world: &dyn World, make_point: F, span: Span) -> Self where F: Fn() -> Tracepoint; } impl Trace for TypResult { - fn trace(self, ctx: &Context, make_point: F, span: Span) -> Self + fn trace(self, world: &dyn World, make_point: F, span: Span) -> Self where F: Fn() -> Tracepoint, { self.map_err(|mut errors| { - let range = ctx.sources.range(span); + let range = world.source(span.source()).range(span); for error in errors.iter_mut() { // Skip traces that surround the error. - let error_range = ctx.sources.range(error.span); + let error_range = world.source(error.span.source()).range(error.span); if range.start <= error_range.start && range.end >= error_range.end { continue; } diff --git a/src/eval/array.rs b/src/eval/array.rs index 43261a22e..6d558393a 100644 --- a/src/eval/array.rs +++ b/src/eval/array.rs @@ -3,7 +3,7 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::ops::{Add, AddAssign}; use std::sync::Arc; -use super::{ops, Args, Func, Machine, Value}; +use super::{ops, Args, Func, Value, Vm}; use crate::diag::{At, StrResult, TypResult}; use crate::syntax::Spanned; use crate::util::ArcExt; @@ -124,7 +124,7 @@ impl Array { } /// Return the first matching element. - pub fn find(&self, vm: &mut Machine, f: Spanned) -> TypResult> { + pub fn find(&self, vm: &mut Vm, f: Spanned) -> TypResult> { for item in self.iter() { let args = Args::new(f.span, [item.clone()]); if f.v.call(vm, args)?.cast::().at(f.span)? { @@ -136,7 +136,7 @@ impl Array { } /// Return the index of the first matching element. - pub fn position(&self, vm: &mut Machine, f: Spanned) -> TypResult> { + pub fn position(&self, vm: &mut Vm, f: Spanned) -> TypResult> { for (i, item) in self.iter().enumerate() { let args = Args::new(f.span, [item.clone()]); if f.v.call(vm, args)?.cast::().at(f.span)? { @@ -149,7 +149,7 @@ impl Array { /// Return a new array with only those elements for which the function /// returns true. - pub fn filter(&self, vm: &mut Machine, f: Spanned) -> TypResult { + pub fn filter(&self, vm: &mut Vm, f: Spanned) -> TypResult { let mut kept = vec![]; for item in self.iter() { let args = Args::new(f.span, [item.clone()]); @@ -161,7 +161,7 @@ impl Array { } /// Transform each item in the array with a function. - pub fn map(&self, vm: &mut Machine, f: Spanned) -> TypResult { + pub fn map(&self, vm: &mut Vm, f: Spanned) -> TypResult { let enumerate = f.v.argc() == Some(2); Ok(self .iter() @@ -178,7 +178,7 @@ impl Array { } /// Whether any element matches. - pub fn any(&self, vm: &mut Machine, f: Spanned) -> TypResult { + pub fn any(&self, vm: &mut Vm, f: Spanned) -> TypResult { for item in self.iter() { let args = Args::new(f.span, [item.clone()]); if f.v.call(vm, args)?.cast::().at(f.span)? { @@ -190,7 +190,7 @@ impl Array { } /// Whether all elements match. - pub fn all(&self, vm: &mut Machine, f: Spanned) -> TypResult { + pub fn all(&self, vm: &mut Vm, f: Spanned) -> TypResult { for item in self.iter() { let args = Args::new(f.span, [item.clone()]); if !f.v.call(vm, args)?.cast::().at(f.span)? { diff --git a/src/eval/capture.rs b/src/eval/capture.rs index bbda96adb..b7570fe9d 100644 --- a/src/eval/capture.rs +++ b/src/eval/capture.rs @@ -136,14 +136,14 @@ mod tests { use crate::parse::parse; #[track_caller] - fn test(src: &str, result: &[&str]) { + fn test(text: &str, result: &[&str]) { let mut scopes = Scopes::new(None); scopes.top.define("x", 0); scopes.top.define("y", 0); scopes.top.define("z", 0); let mut visitor = CapturesVisitor::new(&scopes); - let root = parse(src); + let root = parse(text); visitor.visit(&root); let captures = visitor.finish(); diff --git a/src/eval/dict.rs b/src/eval/dict.rs index 837933b1a..3ba72c8ae 100644 --- a/src/eval/dict.rs +++ b/src/eval/dict.rs @@ -3,7 +3,7 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::ops::{Add, AddAssign}; use std::sync::Arc; -use super::{Args, Array, Func, Machine, Str, Value}; +use super::{Args, Array, Func, Str, Value, Vm}; use crate::diag::{StrResult, TypResult}; use crate::parse::is_ident; use crate::syntax::Spanned; @@ -101,7 +101,7 @@ impl Dict { } /// Transform each pair in the array with a function. - pub fn map(&self, vm: &mut Machine, f: Spanned) -> TypResult { + pub fn map(&self, vm: &mut Vm, f: Spanned) -> TypResult { Ok(self .iter() .map(|(key, value)| { diff --git a/src/eval/func.rs b/src/eval/func.rs index bd312d664..d6b5252ab 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -2,13 +2,13 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::sync::Arc; -use super::{Args, Eval, Flow, Machine, Scope, Scopes, Value}; +use super::{Args, Eval, Flow, Scope, Scopes, Value, Vm}; use crate::diag::{StrResult, TypResult}; use crate::model::{Content, NodeId, StyleMap}; use crate::source::SourceId; use crate::syntax::ast::Expr; use crate::util::EcoString; -use crate::Context; +use crate::World; /// An evaluatable function. #[derive(Clone, Hash)] @@ -29,7 +29,7 @@ impl Func { /// Create a new function from a native rust function. pub fn from_fn( name: &'static str, - func: fn(&mut Machine, &mut Args) -> TypResult, + func: fn(&mut Vm, &mut Args) -> TypResult, ) -> Self { Self(Arc::new(Repr::Native(Native { name, @@ -86,7 +86,7 @@ impl Func { } /// Call the function with the given arguments. - pub fn call(&self, vm: &mut Machine, mut args: Args) -> TypResult { + pub fn call(&self, vm: &mut Vm, mut args: Args) -> TypResult { let value = match self.0.as_ref() { Repr::Native(native) => (native.func)(vm, &mut args)?, Repr::Closure(closure) => closure.call(vm, &mut args)?, @@ -100,8 +100,8 @@ impl Func { } /// Call the function without an existing virtual machine. - pub fn call_detached(&self, ctx: &mut Context, args: Args) -> TypResult { - let mut vm = Machine::new(ctx, vec![], Scopes::new(None)); + pub fn call_detached(&self, world: &dyn World, args: Args) -> TypResult { + let mut vm = Vm::new(world, vec![], Scopes::new(None)); self.call(&mut vm, args) } @@ -144,7 +144,7 @@ struct Native { /// The name of the function. pub name: &'static str, /// The function pointer. - pub func: fn(&mut Machine, &mut Args) -> TypResult, + pub func: fn(&mut Vm, &mut Args) -> TypResult, /// The set rule. pub set: Option TypResult>, /// The id of the node to customize with this function's show rule. @@ -169,7 +169,7 @@ pub trait Node: 'static { /// /// This is passed only the arguments that remain after execution of the /// node's set rule. - fn construct(vm: &mut Machine, args: &mut Args) -> TypResult; + fn construct(vm: &mut Vm, args: &mut Args) -> TypResult; /// Parse relevant arguments into style properties for this node. /// @@ -198,7 +198,7 @@ pub struct Closure { impl Closure { /// Call the function in the context with the arguments. - pub fn call(&self, vm: &mut Machine, args: &mut Args) -> TypResult { + pub fn call(&self, vm: &mut Vm, args: &mut Args) -> TypResult { // Don't leak the scopes from the call site. Instead, we use the scope // of captured variables we collected earlier. let mut scopes = Scopes::new(None); @@ -228,9 +228,8 @@ impl Closure { }; // Evaluate the body. - let mut sub = Machine::new(vm.ctx, route, scopes); + let mut sub = Vm::new(vm.world, route, scopes); let result = self.body.eval(&mut sub); - vm.deps.extend(sub.deps); // Handle control flow. match sub.flow { diff --git a/src/eval/methods.rs b/src/eval/methods.rs index 5072e6882..08d7dd3fa 100644 --- a/src/eval/methods.rs +++ b/src/eval/methods.rs @@ -1,13 +1,13 @@ //! Methods on values. -use super::{Args, Machine, Value}; +use super::{Args, Value, Vm}; use crate::diag::{At, TypResult}; use crate::syntax::Span; use crate::util::EcoString; /// Call a method on a value. pub fn call( - vm: &mut Machine, + vm: &mut Vm, value: Value, method: &str, mut args: Args, diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 2ab94785b..bae9ac940 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -13,11 +13,11 @@ mod value; mod args; mod capture; mod func; -mod machine; pub mod methods; pub mod ops; mod raw; mod scope; +mod vm; pub use self::str::*; pub use args::*; @@ -26,25 +26,25 @@ pub use capture::*; pub use cast::*; pub use dict::*; pub use func::*; -pub use machine::*; pub use raw::*; pub use scope::*; pub use typst_macros::node; pub use value::*; +pub use vm::*; use std::collections::BTreeMap; use unicode_segmentation::UnicodeSegmentation; -use crate::diag::{At, StrResult, Trace, Tracepoint, TypResult}; +use crate::diag::{failed_to_load, At, StrResult, Trace, Tracepoint, TypResult}; use crate::geom::{Angle, Em, Fraction, Length, Ratio}; use crate::library; use crate::model::{Content, Pattern, Recipe, StyleEntry, StyleMap}; -use crate::source::{SourceId, SourceStore}; +use crate::source::SourceId; use crate::syntax::ast::*; use crate::syntax::{Span, Spanned}; use crate::util::EcoString; -use crate::Context; +use crate::World; /// Evaluate a source file and return the resulting module. /// @@ -52,29 +52,24 @@ use crate::Context; /// layoutable contents or diagnostics in the form of a vector of error /// messages with file and span information. pub fn evaluate( - ctx: &mut Context, + world: &dyn World, id: SourceId, mut route: Vec, ) -> TypResult { // Prevent cyclic evaluation. if route.contains(&id) { - let path = ctx.sources.get(id).path().display(); + let path = world.source(id).path().display(); panic!("Tried to cyclicly evaluate {}", path); } route.push(id); - // Parse the file. - let source = ctx.sources.get(id); - let ast = source.ast()?; - let rev = source.rev(); - // Evaluate the module. - let std = ctx.config.std.clone(); - let scopes = Scopes::new(Some(&std)); - let mut vm = Machine::new(ctx, route, scopes); + let ast = world.source(id).ast()?; + let std = &world.config().std; + let scopes = Scopes::new(Some(std)); + let mut vm = Vm::new(world, route, scopes); let result = ast.eval(&mut vm); - vm.deps.push((id, rev)); // Handle control flow. if let Some(flow) = vm.flow { @@ -82,11 +77,7 @@ pub fn evaluate( } // Assemble the module. - Ok(Module { - scope: vm.scopes.top, - content: result?, - deps: vm.deps, - }) + Ok(Module { scope: vm.scopes.top, content: result? }) } /// An evaluated module, ready for importing or layouting. @@ -96,15 +87,6 @@ pub struct Module { pub scope: Scope, /// The module's layoutable contents. pub content: Content, - /// The source file revisions this module depends on. - pub deps: Vec<(SourceId, usize)>, -} - -impl Module { - /// Whether the module is still valid for the given sources. - pub fn valid(&self, sources: &SourceStore) -> bool { - self.deps.iter().all(|&(id, rev)| rev == sources.get(id).rev()) - } } /// Evaluate an expression. @@ -113,20 +95,20 @@ pub trait Eval { type Output; /// Evaluate the expression to the output value. - fn eval(&self, vm: &mut Machine) -> TypResult; + fn eval(&self, vm: &mut Vm) -> TypResult; } impl Eval for Markup { type Output = Content; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { eval_markup(vm, &mut self.nodes()) } } /// Evaluate a stream of markup nodes. fn eval_markup( - vm: &mut Machine, + vm: &mut Vm, nodes: &mut impl Iterator, ) -> TypResult { let flow = vm.flow.take(); @@ -175,7 +157,7 @@ fn eval_markup( impl Eval for MarkupNode { type Output = Content; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { Ok(match self { Self::Space => Content::Space, Self::Parbreak => Content::Parbreak, @@ -199,7 +181,7 @@ impl Eval for MarkupNode { impl Eval for StrongNode { type Output = Content; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { Ok(Content::show(library::text::StrongNode( self.body().eval(vm)?, ))) @@ -209,7 +191,7 @@ impl Eval for StrongNode { impl Eval for EmphNode { type Output = Content; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { Ok(Content::show(library::text::EmphNode( self.body().eval(vm)?, ))) @@ -219,7 +201,7 @@ impl Eval for EmphNode { impl Eval for RawNode { type Output = Content; - fn eval(&self, _: &mut Machine) -> TypResult { + fn eval(&self, _: &mut Vm) -> TypResult { let content = Content::show(library::text::RawNode { text: self.text.clone(), block: self.block, @@ -234,7 +216,7 @@ impl Eval for RawNode { impl Eval for Spanned { type Output = Content; - fn eval(&self, _: &mut Machine) -> TypResult { + fn eval(&self, _: &mut Vm) -> TypResult { Ok(Content::show(library::math::MathNode { formula: self.clone().map(|math| math.formula), display: self.v.display, @@ -245,7 +227,7 @@ impl Eval for Spanned { impl Eval for HeadingNode { type Output = Content; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { Ok(Content::show(library::structure::HeadingNode { body: self.body().eval(vm)?, level: self.level(), @@ -256,7 +238,7 @@ impl Eval for HeadingNode { impl Eval for ListNode { type Output = Content; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { Ok(Content::Item(library::structure::ListItem { kind: library::structure::UNORDERED, number: None, @@ -268,7 +250,7 @@ impl Eval for ListNode { impl Eval for EnumNode { type Output = Content; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { Ok(Content::Item(library::structure::ListItem { kind: library::structure::ORDERED, number: self.number(), @@ -280,7 +262,7 @@ impl Eval for EnumNode { impl Eval for Expr { type Output = Value; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { let forbidden = |name| { error!( self.span(), @@ -321,7 +303,7 @@ impl Eval for Expr { impl Eval for Lit { type Output = Value; - fn eval(&self, _: &mut Machine) -> TypResult { + fn eval(&self, _: &mut Vm) -> TypResult { Ok(match self.kind() { LitKind::None => Value::None, LitKind::Auto => Value::Auto, @@ -343,7 +325,7 @@ impl Eval for Lit { impl Eval for Ident { type Output = Value; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { vm.scopes.get(self).cloned().at(self.span()) } } @@ -351,7 +333,7 @@ impl Eval for Ident { impl Eval for CodeBlock { type Output = Value; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { vm.scopes.enter(); let output = eval_code(vm, &mut self.exprs())?; vm.scopes.exit(); @@ -360,10 +342,7 @@ impl Eval for CodeBlock { } /// Evaluate a stream of expressions. -fn eval_code( - vm: &mut Machine, - exprs: &mut impl Iterator, -) -> TypResult { +fn eval_code(vm: &mut Vm, exprs: &mut impl Iterator) -> TypResult { let flow = vm.flow.take(); let mut output = Value::None; @@ -415,7 +394,7 @@ fn eval_code( impl Eval for ContentBlock { type Output = Content; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { vm.scopes.enter(); let content = self.body().eval(vm)?; vm.scopes.exit(); @@ -426,7 +405,7 @@ impl Eval for ContentBlock { impl Eval for GroupExpr { type Output = Value; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { self.expr().eval(vm) } } @@ -434,7 +413,7 @@ impl Eval for GroupExpr { impl Eval for ArrayExpr { type Output = Array; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { let items = self.items(); let mut vec = Vec::with_capacity(items.size_hint().0); @@ -456,7 +435,7 @@ impl Eval for ArrayExpr { impl Eval for DictExpr { type Output = Dict; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { let mut map = BTreeMap::new(); for item in self.items() { @@ -486,7 +465,7 @@ impl Eval for DictExpr { impl Eval for UnaryExpr { type Output = Value; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { let value = self.expr().eval(vm)?; let result = match self.op() { UnOp::Pos => ops::pos(value), @@ -500,7 +479,7 @@ impl Eval for UnaryExpr { impl Eval for BinaryExpr { type Output = Value; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { match self.op() { BinOp::Add => self.apply(vm, ops::add), BinOp::Sub => self.apply(vm, ops::sub), @@ -529,7 +508,7 @@ impl BinaryExpr { /// Apply a basic binary operation. fn apply( &self, - vm: &mut Machine, + vm: &mut Vm, op: fn(Value, Value) -> StrResult, ) -> TypResult { let lhs = self.lhs().eval(vm)?; @@ -548,7 +527,7 @@ impl BinaryExpr { /// Apply an assignment operation. fn assign( &self, - vm: &mut Machine, + vm: &mut Vm, op: fn(Value, Value) -> StrResult, ) -> TypResult { let rhs = self.rhs().eval(vm)?; @@ -562,7 +541,7 @@ impl BinaryExpr { impl Eval for FieldAccess { type Output = Value; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { let object = self.object().eval(vm)?; let span = self.field().span(); let field = self.field().take(); @@ -588,7 +567,7 @@ impl Eval for FieldAccess { impl Eval for FuncCall { type Output = Value; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { let callee = self.callee().eval(vm)?; let args = self.args().eval(vm)?; @@ -597,7 +576,7 @@ impl Eval for FuncCall { Value::Dict(dict) => dict.get(&args.into_key()?).at(self.span())?.clone(), Value::Func(func) => { let point = || Tracepoint::Call(func.name().map(ToString::to_string)); - func.call(vm, args).trace(vm.ctx, point, self.span())? + func.call(vm, args).trace(vm.world, point, self.span())? } v => bail!( @@ -612,7 +591,7 @@ impl Eval for FuncCall { impl Eval for MethodCall { type Output = Value; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { let span = self.span(); let method = self.method(); let point = || Tracepoint::Call(Some(method.to_string())); @@ -621,12 +600,12 @@ impl Eval for MethodCall { let args = self.args().eval(vm)?; let mut value = self.receiver().access(vm)?; methods::call_mut(&mut value, &method, args, span) - .trace(vm.ctx, point, span)?; + .trace(vm.world, point, span)?; Value::None } else { let value = self.receiver().eval(vm)?; let args = self.args().eval(vm)?; - methods::call(vm, value, &method, args, span).trace(vm.ctx, point, span)? + methods::call(vm, value, &method, args, span).trace(vm.world, point, span)? }) } } @@ -634,7 +613,7 @@ impl Eval for MethodCall { impl Eval for CallArgs { type Output = Args; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { let mut items = Vec::new(); for arg in self.items() { @@ -683,7 +662,7 @@ impl Eval for CallArgs { impl Eval for ClosureExpr { type Output = Value; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { // The closure's name is defined by its let binding if there's one. let name = self.name().map(Ident::take); @@ -730,7 +709,7 @@ impl Eval for ClosureExpr { impl Eval for LetExpr { type Output = Value; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { let value = match self.init() { Some(expr) => expr.eval(vm)?, None => Value::None, @@ -743,7 +722,7 @@ impl Eval for LetExpr { impl Eval for SetExpr { type Output = StyleMap; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { let target = self.target(); let target = target.eval(vm)?.cast::().at(target.span())?; let args = self.args().eval(vm)?; @@ -754,7 +733,7 @@ impl Eval for SetExpr { impl Eval for ShowExpr { type Output = Recipe; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { // Evaluate the target function. let pattern = self.pattern(); let pattern = pattern.eval(vm)?.cast::().at(pattern.span())?; @@ -791,7 +770,7 @@ impl Eval for ShowExpr { impl Eval for IfExpr { type Output = Value; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { let condition = self.condition(); if condition.eval(vm)?.cast::().at(condition.span())? { self.if_body().eval(vm) @@ -806,7 +785,7 @@ impl Eval for IfExpr { impl Eval for WhileExpr { type Output = Value; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { let flow = vm.flow.take(); let mut output = Value::None; @@ -838,7 +817,7 @@ impl Eval for WhileExpr { impl Eval for ForExpr { type Output = Value; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { let flow = vm.flow.take(); let mut output = Value::None; vm.scopes.enter(); @@ -917,7 +896,7 @@ impl Eval for ForExpr { impl Eval for ImportExpr { type Output = Value; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { let span = self.path().span(); let path = self.path().eval(vm)?.cast::().at(span)?; let module = import(vm, &path, span)?; @@ -946,7 +925,7 @@ impl Eval for ImportExpr { impl Eval for IncludeExpr { type Output = Content; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { let span = self.path().span(); let path = self.path().eval(vm)?.cast::().at(span)?; let module = import(vm, &path, span)?; @@ -955,10 +934,14 @@ impl Eval for IncludeExpr { } /// Process an import of a module relative to the current location. -fn import(vm: &mut Machine, path: &str, span: Span) -> TypResult { +fn import(vm: &mut Vm, path: &str, span: Span) -> TypResult { // Load the source file. let full = vm.locate(&path).at(span)?; - let id = vm.ctx.sources.load(&full).at(span)?; + let id = vm + .world + .resolve(&full) + .map_err(|err| failed_to_load("source file", &full, err)) + .at(span)?; // Prevent cyclic importing. if vm.route.contains(&id) { @@ -968,9 +951,7 @@ fn import(vm: &mut Machine, path: &str, span: Span) -> TypResult { // Evaluate the file. let route = vm.route.clone(); let module = - evaluate(vm.ctx, id, route).trace(vm.ctx, || Tracepoint::Import, span)?; - - vm.deps.extend(module.deps.iter().cloned()); + evaluate(vm.world, id, route).trace(vm.world, || Tracepoint::Import, span)?; Ok(module) } @@ -978,7 +959,7 @@ fn import(vm: &mut Machine, path: &str, span: Span) -> TypResult { impl Eval for BreakExpr { type Output = Value; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { if vm.flow.is_none() { vm.flow = Some(Flow::Break(self.span())); } @@ -989,7 +970,7 @@ impl Eval for BreakExpr { impl Eval for ContinueExpr { type Output = Value; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { if vm.flow.is_none() { vm.flow = Some(Flow::Continue(self.span())); } @@ -1000,7 +981,7 @@ impl Eval for ContinueExpr { impl Eval for ReturnExpr { type Output = Value; - fn eval(&self, vm: &mut Machine) -> TypResult { + fn eval(&self, vm: &mut Vm) -> TypResult { let value = self.body().map(|body| body.eval(vm)).transpose()?; if vm.flow.is_none() { vm.flow = Some(Flow::Return(self.span(), value)); @@ -1012,11 +993,11 @@ impl Eval for ReturnExpr { /// Access an expression mutably. pub trait Access { /// Access the value. - fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value>; + fn access<'a>(&self, vm: &'a mut Vm) -> TypResult<&'a mut Value>; } impl Access for Expr { - fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value> { + fn access<'a>(&self, vm: &'a mut Vm) -> TypResult<&'a mut Value> { match self { Expr::Ident(v) => v.access(vm), Expr::FieldAccess(v) => v.access(vm), @@ -1027,13 +1008,13 @@ impl Access for Expr { } impl Access for Ident { - fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value> { + fn access<'a>(&self, vm: &'a mut Vm) -> TypResult<&'a mut Value> { vm.scopes.get_mut(self).at(self.span()) } } impl Access for FieldAccess { - fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value> { + fn access<'a>(&self, vm: &'a mut Vm) -> TypResult<&'a mut Value> { Ok(match self.object().access(vm)? { Value::Dict(dict) => dict.get_mut(self.field().take().into()), v => bail!( @@ -1046,7 +1027,7 @@ impl Access for FieldAccess { } impl Access for FuncCall { - fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value> { + fn access<'a>(&self, vm: &'a mut Vm) -> TypResult<&'a mut Value> { let args = self.args().eval(vm)?; Ok(match self.callee().access(vm)? { Value::Array(array) => array.get_mut(args.into_index()?).at(self.span())?, diff --git a/src/eval/scope.rs b/src/eval/scope.rs index 7c624de0f..1b11e6ea5 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use std::fmt::{self, Debug, Formatter}; use std::hash::Hash; -use super::{Args, Func, Machine, Node, Value}; +use super::{Args, Func, Node, Value, Vm}; use crate::diag::{StrResult, TypResult}; use crate::util::EcoString; @@ -78,7 +78,7 @@ impl Scope { pub fn def_fn( &mut self, name: &'static str, - func: fn(&mut Machine, &mut Args) -> TypResult, + func: fn(&mut Vm, &mut Args) -> TypResult, ) { self.define(name, Func::from_fn(name, func)); } diff --git a/src/eval/machine.rs b/src/eval/vm.rs similarity index 75% rename from src/eval/machine.rs rename to src/eval/vm.rs index 9c58c659f..937152cfe 100644 --- a/src/eval/machine.rs +++ b/src/eval/vm.rs @@ -5,32 +5,24 @@ use crate::diag::{StrResult, TypError}; use crate::source::SourceId; use crate::syntax::Span; use crate::util::PathExt; -use crate::Context; +use crate::World; /// A virtual machine. -pub struct Machine<'a> { +pub struct Vm<'w> { /// The core context. - pub ctx: &'a mut Context, + pub world: &'w dyn World, /// The route of source ids the machine took to reach its current location. pub route: Vec, - /// The dependencies of the current evaluation process. - pub deps: Vec<(SourceId, usize)>, /// The stack of scopes. - pub scopes: Scopes<'a>, + pub scopes: Scopes<'w>, /// A control flow event that is currently happening. pub flow: Option, } -impl<'a> Machine<'a> { +impl<'w> Vm<'w> { /// Create a new virtual machine. - pub fn new(ctx: &'a mut Context, route: Vec, scopes: Scopes<'a>) -> Self { - Self { - ctx, - route, - deps: vec![], - scopes, - flow: None, - } + pub fn new(ctx: &'w dyn World, route: Vec, scopes: Scopes<'w>) -> Self { + Self { world: ctx, route, scopes, flow: None } } /// Resolve a user-entered path to be relative to the compilation @@ -38,10 +30,10 @@ impl<'a> Machine<'a> { pub fn locate(&self, path: &str) -> StrResult { if let Some(&id) = self.route.last() { if let Some(path) = path.strip_prefix('/') { - return Ok(self.ctx.config.root.join(path).normalize()); + return Ok(self.world.config().root.join(path).normalize()); } - if let Some(dir) = self.ctx.sources.get(id).path().parent() { + if let Some(dir) = self.world.source(id).path().parent() { return Ok(dir.join(path).normalize()); } } diff --git a/src/export/pdf/image.rs b/src/export/pdf/image.rs index 7886524c3..f148bb7d0 100644 --- a/src/export/pdf/image.rs +++ b/src/export/pdf/image.rs @@ -16,48 +16,40 @@ pub fn write_images(ctx: &mut PdfContext) { let height = image.height(); // Add the primary image. + // TODO: Error if image could not be encoded. match image.decode().unwrap() { DecodedImage::Raster(dynamic) => { - if let Ok((data, filter, has_color)) = - encode_image(image.format(), &dynamic) - { - let mut image = ctx.writer.image_xobject(image_ref, &data); - image.filter(filter); - image.width(width as i32); - image.height(height as i32); - image.bits_per_component(8); + // TODO: Error if image could not be encoded. + let (data, filter, has_color) = + encode_image(image.format(), &dynamic).unwrap(); - let space = image.color_space(); - if has_color { - space.device_rgb(); - } else { - space.device_gray(); - } + let mut image = ctx.writer.image_xobject(image_ref, &data); + image.filter(filter); + image.width(width as i32); + image.height(height as i32); + image.bits_per_component(8); - // Add a second gray-scale image containing the alpha values if - // this image has an alpha channel. - if dynamic.color().has_alpha() { - let (alpha_data, alpha_filter) = encode_alpha(&dynamic); - let mask_ref = ctx.alloc.bump(); - image.s_mask(mask_ref); - image.finish(); - - let mut mask = ctx.writer.image_xobject(mask_ref, &alpha_data); - mask.filter(alpha_filter); - mask.width(width as i32); - mask.height(height as i32); - mask.color_space().device_gray(); - mask.bits_per_component(8); - } + let space = image.color_space(); + if has_color { + space.device_rgb(); } else { - // TODO: Warn that image could not be encoded. - ctx.writer - .image_xobject(image_ref, &[]) - .width(0) - .height(0) - .bits_per_component(1) - .color_space() - .device_gray(); + space.device_gray(); + } + + // Add a second gray-scale image containing the alpha values if + // this image has an alpha channel. + if dynamic.color().has_alpha() { + let (alpha_data, alpha_filter) = encode_alpha(&dynamic); + let mask_ref = ctx.alloc.bump(); + image.s_mask(mask_ref); + image.finish(); + + let mut mask = ctx.writer.image_xobject(mask_ref, &alpha_data); + mask.filter(alpha_filter); + mask.width(width as i32); + mask.height(height as i32); + mask.color_space().device_gray(); + mask.bits_per_component(8); } } DecodedImage::Svg(svg) => { diff --git a/src/export/render.rs b/src/export/render.rs index bf735ded8..3fb57ad69 100644 --- a/src/export/render.rs +++ b/src/export/render.rs @@ -147,8 +147,8 @@ fn render_svg_glyph( } // Parse XML. - let src = std::str::from_utf8(data).ok()?; - let document = roxmltree::Document::parse(src).ok()?; + let xml = std::str::from_utf8(data).ok()?; + let document = roxmltree::Document::parse(xml).ok()?; let root = document.root_element(); // Parse SVG. diff --git a/src/font/book.rs b/src/font/book.rs index 8f19faf78..323eb84f7 100644 --- a/src/font/book.rs +++ b/src/font/book.rs @@ -39,8 +39,8 @@ impl FontBook { self.infos.push(info); } - /// An ordered iterator over all font families this loader knows and details - /// about the faces that are part of them. + /// An ordered iterator over all font families this book knows and details + /// about the fonts that are part of them. pub fn families( &self, ) -> impl Iterator)> + '_ { diff --git a/src/font/mod.rs b/src/font/mod.rs index 917fea620..2404ad326 100644 --- a/src/font/mod.rs +++ b/src/font/mod.rs @@ -15,7 +15,7 @@ use rex::font::MathHeader; use ttf_parser::{GlyphId, Tag}; use crate::geom::Em; -use crate::loading::Buffer; +use crate::util::Buffer; /// An OpenType font. #[derive(Clone)] diff --git a/src/image.rs b/src/image.rs index c2631477f..db10a1627 100644 --- a/src/image.rs +++ b/src/image.rs @@ -2,7 +2,7 @@ use std::io; -use crate::loading::Buffer; +use crate::util::Buffer; /// A raster or vector image. /// diff --git a/src/lib.rs b/src/lib.rs index c26ef8092..84d91d445 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,117 +44,75 @@ pub mod font; pub mod frame; pub mod image; pub mod library; -pub mod loading; pub mod model; pub mod parse; pub mod source; pub mod syntax; -use std::path::PathBuf; -use std::sync::Arc; +use std::io; +use std::path::{Path, PathBuf}; use crate::diag::TypResult; use crate::eval::Scope; +use crate::font::{Font, FontBook}; use crate::frame::Frame; -use crate::loading::Loader; use crate::model::StyleMap; -use crate::source::{SourceId, SourceStore}; +use crate::source::{Source, SourceId}; +use crate::util::Buffer; /// Typeset a source file into a collection of layouted frames. /// /// Returns either a vector of frames representing individual pages or /// diagnostics in the form of a vector of error message with file and span /// information. -pub fn typeset(ctx: &mut Context, id: SourceId) -> TypResult> { - let module = eval::evaluate(ctx, id, vec![])?; - model::layout(ctx, &module.content) +pub fn typeset(world: &dyn World, main: SourceId) -> TypResult> { + let module = eval::evaluate(world, main, vec![])?; + model::layout(world, &module.content) } -/// The core context which holds the configuration and stores. -pub struct Context { - /// The loader for fonts and files. - pub loader: Arc, - /// Stores loaded source files. - pub sources: SourceStore, - /// The context's configuration. - config: Config, +/// The environment in which typesetting occurs. +pub trait World { + /// Access the global configuration. + fn config(&self) -> &Config; + + /// Resolve the unique id of a source file. + fn resolve(&self, path: &Path) -> io::Result; + + /// Access a source file by id. + fn source(&self, id: SourceId) -> &Source; + + /// Metadata about all known fonts. + fn book(&self) -> &FontBook; + + /// Access the font with the given id. + fn font(&self, id: usize) -> io::Result; + + /// Access a file at a path. + fn file(&self, path: &Path) -> io::Result; } -impl Context { - /// Create a new context. - pub fn new(loader: Arc, config: Config) -> Self { - Self { - loader: Arc::clone(&loader), - sources: SourceStore::new(Arc::clone(&loader)), - config, - } - } -} - -/// Compilation configuration. +/// The global configuration for typesetting. pub struct Config { - /// The compilation root. + /// The compilation root, relative to which absolute paths are. + /// + /// Default: Empty path. pub root: PathBuf, - /// The standard library scope. - pub std: Arc, - /// The default styles. - pub styles: Arc, -} - -impl Config { - /// Create a new configuration builder. - pub fn builder() -> ConfigBuilder { - ConfigBuilder::default() - } + /// The scope containing definitions that are available everywhere. + /// + /// Default: Typst's standard library. + pub std: Scope, + /// The default properties for page size, font selection and so on. + /// + /// Default: Empty style map. + pub styles: StyleMap, } impl Default for Config { fn default() -> Self { - Self::builder().build() - } -} - -/// A builder for a [`Config`]. -/// -/// This struct is created by [`Config::builder`]. -#[derive(Debug, Default, Clone)] -pub struct ConfigBuilder { - root: PathBuf, - std: Option>, - styles: Option>, -} - -impl ConfigBuilder { - /// The compilation root, relative to which absolute paths are. - /// - /// Default: Empty path. - pub fn root(&mut self, root: impl Into) -> &mut Self { - self.root = root.into(); - self - } - - /// The scope containing definitions that are available everywhere. - /// - /// Default: Typst's standard library. - pub fn std(&mut self, std: impl Into>) -> &mut Self { - self.std = Some(std.into()); - self - } - - /// The default properties for page size, font selection and so on. - /// - /// Default: Empty style map. - pub fn styles(&mut self, styles: impl Into>) -> &mut Self { - self.styles = Some(styles.into()); - self - } - - /// Finish building the configuration. - pub fn build(&self) -> Config { - Config { - root: self.root.clone(), - std: self.std.clone().unwrap_or_else(|| Arc::new(library::new())), - styles: self.styles.clone().unwrap_or_default(), + Self { + root: PathBuf::new(), + std: library::new(), + styles: StyleMap::new(), } } } diff --git a/src/library/graphics/hide.rs b/src/library/graphics/hide.rs index f40635a01..f2a423ce0 100644 --- a/src/library/graphics/hide.rs +++ b/src/library/graphics/hide.rs @@ -6,7 +6,7 @@ pub struct HideNode(pub LayoutNode); #[node] impl HideNode { - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { Ok(Content::inline(Self(args.expect("body")?))) } } @@ -14,11 +14,11 @@ impl HideNode { impl Layout for HideNode { fn layout( &self, - ctx: &mut Context, + world: &dyn World, regions: &Regions, styles: StyleChain, ) -> TypResult> { - let mut frames = self.0.layout(ctx, regions, styles)?; + let mut frames = self.0.layout(world, regions, styles)?; for frame in &mut frames { frame.clear(); } diff --git a/src/library/graphics/image.rs b/src/library/graphics/image.rs index f8cdc4cde..1642c7b0b 100644 --- a/src/library/graphics/image.rs +++ b/src/library/graphics/image.rs @@ -13,15 +13,14 @@ impl ImageNode { /// How the image should adjust itself to a given area. pub const FIT: ImageFit = ImageFit::Cover; - fn construct(vm: &mut Machine, args: &mut Args) -> TypResult { + fn construct(vm: &mut Vm, args: &mut Args) -> TypResult { let Spanned { v: path, span } = args.expect::>("path to image file")?; let full = vm.locate(&path).at(span)?; let ext = full.extension().and_then(OsStr::to_str).unwrap_or_default(); let image = vm - .ctx - .loader + .world .file(&full) .and_then(|buffer| Image::new(buffer, ext)) .map_err(|err| failed_to_load("image", &full, err)) @@ -39,7 +38,7 @@ impl ImageNode { impl Layout for ImageNode { fn layout( &self, - _: &mut Context, + _: &dyn World, regions: &Regions, styles: StyleChain, ) -> TypResult> { diff --git a/src/library/graphics/line.rs b/src/library/graphics/line.rs index 15c54f460..95c3a7099 100644 --- a/src/library/graphics/line.rs +++ b/src/library/graphics/line.rs @@ -15,7 +15,7 @@ impl LineNode { #[property(resolve, fold)] pub const STROKE: RawStroke = RawStroke::default(); - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { let origin = args.named("origin")?.unwrap_or_default(); let delta = match args.named::>>("to")? { @@ -40,7 +40,7 @@ impl LineNode { impl Layout for LineNode { fn layout( &self, - _: &mut Context, + _: &dyn World, regions: &Regions, styles: StyleChain, ) -> TypResult> { diff --git a/src/library/graphics/shape.rs b/src/library/graphics/shape.rs index 5cc5a76d2..ee5e43e8e 100644 --- a/src/library/graphics/shape.rs +++ b/src/library/graphics/shape.rs @@ -39,7 +39,7 @@ impl ShapeNode { pub const RADIUS: Corners>> = Corners::splat(Relative::zero()); - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { let size = match S { SQUARE => args.named::("size")?.map(Relative::from), CIRCLE => args.named::("radius")?.map(|r| 2.0 * Relative::from(r)), @@ -78,7 +78,7 @@ impl ShapeNode { impl Layout for ShapeNode { fn layout( &self, - ctx: &mut Context, + world: &dyn World, regions: &Regions, styles: StyleChain, ) -> TypResult> { @@ -93,7 +93,7 @@ impl Layout for ShapeNode { let child = child.clone().padded(inset.map(|side| side.map(RawLength::from))); let mut pod = Regions::one(regions.first, regions.base, regions.expand); - frames = child.layout(ctx, &pod, styles)?; + frames = child.layout(world, &pod, styles)?; for frame in frames.iter_mut() { frame.apply_role(Role::GenericBlock); @@ -113,7 +113,7 @@ impl Layout for ShapeNode { pod.first = Size::splat(length); pod.expand = Spec::splat(true); - frames = child.layout(ctx, &pod, styles)?; + frames = child.layout(world, &pod, styles)?; } } else { // The default size that a shape takes on if it has no child and diff --git a/src/library/graphics/transform.rs b/src/library/graphics/transform.rs index 48cadb1b7..0a4f5b5f9 100644 --- a/src/library/graphics/transform.rs +++ b/src/library/graphics/transform.rs @@ -12,7 +12,7 @@ pub struct MoveNode { #[node] impl MoveNode { - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { let dx = args.named("dx")?.unwrap_or_default(); let dy = args.named("dy")?.unwrap_or_default(); Ok(Content::inline(Self { @@ -25,11 +25,11 @@ impl MoveNode { impl Layout for MoveNode { fn layout( &self, - ctx: &mut Context, + world: &dyn World, regions: &Regions, styles: StyleChain, ) -> TypResult> { - let mut frames = self.child.layout(ctx, regions, styles)?; + let mut frames = self.child.layout(world, regions, styles)?; let delta = self.delta.resolve(styles); for frame in &mut frames { @@ -62,7 +62,7 @@ impl TransformNode { #[property(resolve)] pub const ORIGIN: Spec> = Spec::default(); - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { let transform = match T { ROTATE => { let angle = args.named_or_find("angle")?.unwrap_or_default(); @@ -86,12 +86,12 @@ impl TransformNode { impl Layout for TransformNode { fn layout( &self, - ctx: &mut Context, + world: &dyn World, regions: &Regions, styles: StyleChain, ) -> TypResult> { let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON); - let mut frames = self.child.layout(ctx, regions, styles)?; + let mut frames = self.child.layout(world, regions, styles)?; for frame in &mut frames { let Spec { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s)); diff --git a/src/library/layout/align.rs b/src/library/layout/align.rs index e7a27c045..3b1a4aaf8 100644 --- a/src/library/layout/align.rs +++ b/src/library/layout/align.rs @@ -12,7 +12,7 @@ pub struct AlignNode { #[node] impl AlignNode { - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { let aligns: Spec> = args.find()?.unwrap_or_default(); let body: Content = args.expect("body")?; Ok(match (body, aligns) { @@ -28,7 +28,7 @@ impl AlignNode { impl Layout for AlignNode { fn layout( &self, - ctx: &mut Context, + world: &dyn World, regions: &Regions, styles: StyleChain, ) -> TypResult> { @@ -43,7 +43,7 @@ impl Layout for AlignNode { } // Layout the child. - let mut frames = self.child.layout(ctx, &pod, passed.chain(&styles))?; + let mut frames = self.child.layout(world, &pod, passed.chain(&styles))?; for (region, frame) in regions.iter().zip(&mut frames) { // Align in the target size. The target size depends on whether we // should expand. diff --git a/src/library/layout/columns.rs b/src/library/layout/columns.rs index efc435aad..bfbbfd8d9 100644 --- a/src/library/layout/columns.rs +++ b/src/library/layout/columns.rs @@ -17,7 +17,7 @@ impl ColumnsNode { #[property(resolve)] pub const GUTTER: Relative = Ratio::new(0.04).into(); - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { Ok(Content::block(Self { columns: args.expect("column count")?, child: args.expect("body")?, @@ -28,14 +28,14 @@ impl ColumnsNode { impl Layout for ColumnsNode { fn layout( &self, - ctx: &mut Context, + world: &dyn World, regions: &Regions, styles: StyleChain, ) -> TypResult> { // Separating the infinite space into infinite columns does not make // much sense. if !regions.first.x.is_finite() { - return self.child.layout(ctx, regions, styles); + return self.child.layout(world, regions, styles); } // Determine the width of the gutter and each column. @@ -57,7 +57,7 @@ impl Layout for ColumnsNode { }; // Layout the children. - let mut frames = self.child.layout(ctx, &pod, styles)?.into_iter(); + let mut frames = self.child.layout(world, &pod, styles)?.into_iter(); let mut finished = vec![]; let dir = styles.get(TextNode::DIR); @@ -106,7 +106,7 @@ pub struct ColbreakNode; #[node] impl ColbreakNode { - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { let weak = args.named("weak")?.unwrap_or(false); Ok(Content::Colbreak { weak }) } diff --git a/src/library/layout/container.rs b/src/library/layout/container.rs index df03ac91e..66a437517 100644 --- a/src/library/layout/container.rs +++ b/src/library/layout/container.rs @@ -5,7 +5,7 @@ pub struct BoxNode; #[node] impl BoxNode { - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { let width = args.named("width")?; let height = args.named("height")?; let body: LayoutNode = args.eat()?.unwrap_or_default(); @@ -18,7 +18,7 @@ pub struct BlockNode; #[node] impl BlockNode { - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { Ok(Content::Block(args.eat()?.unwrap_or_default())) } } diff --git a/src/library/layout/flow.rs b/src/library/layout/flow.rs index c6d2999e5..841b80aae 100644 --- a/src/library/layout/flow.rs +++ b/src/library/layout/flow.rs @@ -25,7 +25,7 @@ pub enum FlowChild { impl Layout for FlowNode { fn layout( &self, - ctx: &mut Context, + world: &dyn World, regions: &Regions, styles: StyleChain, ) -> TypResult> { @@ -38,7 +38,7 @@ impl Layout for FlowNode { layouter.layout_spacing(*kind, styles); } FlowChild::Node(ref node) => { - layouter.layout_node(ctx, node, styles)?; + layouter.layout_node(world, node, styles)?; } FlowChild::Colbreak => { layouter.finish_region(); @@ -149,7 +149,7 @@ impl FlowLayouter { /// Layout a node. pub fn layout_node( &mut self, - ctx: &mut Context, + world: &dyn World, node: &LayoutNode, styles: StyleChain, ) -> TypResult<()> { @@ -162,7 +162,7 @@ impl FlowLayouter { // aligned later. if let Some(placed) = node.downcast::() { if placed.out_of_flow() { - let frame = node.layout(ctx, &self.regions, styles)?.remove(0); + let frame = node.layout(world, &self.regions, styles)?.remove(0); self.items.push(FlowItem::Placed(frame)); return Ok(()); } @@ -180,7 +180,7 @@ impl FlowLayouter { .unwrap_or(Align::Top), ); - let frames = node.layout(ctx, &self.regions, styles)?; + let frames = node.layout(world, &self.regions, styles)?; let len = frames.len(); for (i, mut frame) in frames.into_iter().enumerate() { // Set the generic block role. diff --git a/src/library/layout/grid.rs b/src/library/layout/grid.rs index 93dbf97e6..3fde9c10c 100644 --- a/src/library/layout/grid.rs +++ b/src/library/layout/grid.rs @@ -13,7 +13,7 @@ pub struct GridNode { #[node] impl GridNode { - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { let columns = args.named("columns")?.unwrap_or_default(); let rows = args.named("rows")?.unwrap_or_default(); let base_gutter: Vec = args.named("gutter")?.unwrap_or_default(); @@ -33,13 +33,13 @@ impl GridNode { impl Layout for GridNode { fn layout( &self, - ctx: &mut Context, + world: &dyn World, regions: &Regions, styles: StyleChain, ) -> TypResult> { // Prepare grid layout by unifying content and gutter tracks. let layouter = GridLayouter::new( - ctx, + world, self.tracks.as_deref(), self.gutter.as_deref(), &self.cells, @@ -93,7 +93,7 @@ castable! { /// Performs grid layout. pub struct GridLayouter<'a> { /// The core context. - ctx: &'a mut Context, + world: &'a dyn World, /// The grid cells. cells: &'a [LayoutNode], /// The column tracks including gutter tracks. @@ -133,7 +133,7 @@ impl<'a> GridLayouter<'a> { /// /// This prepares grid layout by unifying content and gutter tracks. pub fn new( - ctx: &'a mut Context, + world: &'a dyn World, tracks: Spec<&[TrackSizing]>, gutter: Spec<&[TrackSizing]>, cells: &'a [LayoutNode], @@ -187,7 +187,7 @@ impl<'a> GridLayouter<'a> { regions.expand = Spec::new(true, false); Self { - ctx, + world, cells, cols, rows, @@ -301,7 +301,7 @@ impl<'a> GridLayouter<'a> { v.resolve(self.styles).relative_to(self.regions.base.y); } - let frame = node.layout(self.ctx, &pod, self.styles)?.remove(0); + let frame = node.layout(self.world, &pod, self.styles)?.remove(0); resolved.set_max(frame.width()); } } @@ -371,7 +371,7 @@ impl<'a> GridLayouter<'a> { } let mut sizes = node - .layout(self.ctx, &pod, self.styles)? + .layout(self.world, &pod, self.styles)? .into_iter() .map(|frame| frame.height()); @@ -460,7 +460,7 @@ impl<'a> GridLayouter<'a> { .select(self.regions.base, size); let pod = Regions::one(size, base, Spec::splat(true)); - let frame = node.layout(self.ctx, &pod, self.styles)?.remove(0); + let frame = node.layout(self.world, &pod, self.styles)?.remove(0); match frame.role() { Some(Role::ListLabel | Role::ListItemBody) => { output.apply_role(Role::ListItem) @@ -508,7 +508,7 @@ impl<'a> GridLayouter<'a> { } // Push the layouted frames into the individual output frames. - let frames = node.layout(self.ctx, &pod, self.styles)?; + let frames = node.layout(self.world, &pod, self.styles)?; for (output, frame) in outputs.iter_mut().zip(frames) { match frame.role() { Some(Role::ListLabel | Role::ListItemBody) => { diff --git a/src/library/layout/pad.rs b/src/library/layout/pad.rs index 9d91c6412..72235ccd2 100644 --- a/src/library/layout/pad.rs +++ b/src/library/layout/pad.rs @@ -11,7 +11,7 @@ pub struct PadNode { #[node] impl PadNode { - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { let all = args.named("rest")?.or(args.find()?); let x = args.named("x")?; let y = args.named("y")?; @@ -28,14 +28,14 @@ impl PadNode { impl Layout for PadNode { fn layout( &self, - ctx: &mut Context, + world: &dyn World, regions: &Regions, styles: StyleChain, ) -> TypResult> { // Layout child into padded regions. let padding = self.padding.resolve(styles); let pod = regions.map(|size| shrink(size, padding)); - let mut frames = self.child.layout(ctx, &pod, styles)?; + let mut frames = self.child.layout(world, &pod, styles)?; for frame in &mut frames { // Apply the padding inversely such that the grown size padded diff --git a/src/library/layout/page.rs b/src/library/layout/page.rs index afcc48552..6e43c4efc 100644 --- a/src/library/layout/page.rs +++ b/src/library/layout/page.rs @@ -41,7 +41,7 @@ impl PageNode { #[property(referenced)] pub const FOREGROUND: Marginal = Marginal::None; - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { Ok(Content::Page(Self(args.expect("body")?))) } @@ -57,7 +57,7 @@ impl PageNode { /// Layout the page run into a sequence of frames, one per page. pub fn layout( &self, - ctx: &mut Context, + world: &dyn World, mut page: usize, styles: StyleChain, ) -> TypResult> { @@ -97,7 +97,7 @@ impl PageNode { // Layout the child. let regions = Regions::repeat(size, size, size.map(Length::is_finite)); - let mut frames = child.layout(ctx, ®ions, styles)?; + let mut frames = child.layout(world, ®ions, styles)?; let header = styles.get(Self::HEADER); let footer = styles.get(Self::FOOTER); @@ -126,9 +126,9 @@ impl PageNode { (Role::Foreground, foreground, Point::zero(), size), (Role::Background, background, Point::zero(), size), ] { - if let Some(content) = marginal.resolve(ctx, page)? { + if let Some(content) = marginal.resolve(world, page)? { let pod = Regions::one(area, area, Spec::splat(true)); - let mut sub = content.layout(ctx, &pod, styles)?.remove(0); + let mut sub = content.layout(world, &pod, styles)?.remove(0); sub.apply_role(role); if role == Role::Background { @@ -159,7 +159,7 @@ pub struct PagebreakNode; #[node] impl PagebreakNode { - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { let weak = args.named("weak")?.unwrap_or(false); Ok(Content::Pagebreak { weak }) } @@ -178,13 +178,13 @@ pub enum Marginal { impl Marginal { /// Resolve the marginal based on the page number. - pub fn resolve(&self, ctx: &mut Context, page: usize) -> TypResult> { + pub fn resolve(&self, world: &dyn World, page: usize) -> TypResult> { Ok(match self { Self::None => None, Self::Content(content) => Some(content.clone()), Self::Func(func, span) => { let args = Args::new(*span, [Value::Int(page as i64)]); - Some(func.call_detached(ctx, args)?.display()) + Some(func.call_detached(world, args)?.display()) } }) } diff --git a/src/library/layout/place.rs b/src/library/layout/place.rs index 4c6d00624..bb3aac2dd 100644 --- a/src/library/layout/place.rs +++ b/src/library/layout/place.rs @@ -7,7 +7,7 @@ pub struct PlaceNode(pub LayoutNode); #[node] impl PlaceNode { - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { let aligns = args.find()?.unwrap_or(Spec::with_x(Some(RawAlign::Start))); let dx = args.named("dx")?.unwrap_or_default(); let dy = args.named("dy")?.unwrap_or_default(); @@ -21,7 +21,7 @@ impl PlaceNode { impl Layout for PlaceNode { fn layout( &self, - ctx: &mut Context, + world: &dyn World, regions: &Regions, styles: StyleChain, ) -> TypResult> { @@ -35,7 +35,7 @@ impl Layout for PlaceNode { Regions::one(regions.base, regions.base, expand) }; - let mut frames = self.0.layout(ctx, &pod, styles)?; + let mut frames = self.0.layout(world, &pod, styles)?; // If expansion is off, zero all sizes so that we don't take up any // space in our parent. Otherwise, respect the expand settings. diff --git a/src/library/layout/spacing.rs b/src/library/layout/spacing.rs index da4a96b60..e435e60cf 100644 --- a/src/library/layout/spacing.rs +++ b/src/library/layout/spacing.rs @@ -8,7 +8,7 @@ pub struct HNode; #[node] impl HNode { - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { let amount = args.expect("spacing")?; let weak = args.named("weak")?.unwrap_or(false); Ok(Content::Horizontal { amount, weak }) @@ -20,7 +20,7 @@ pub struct VNode; #[node] impl VNode { - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { let amount = args.expect("spacing")?; let weak = args.named("weak")?.unwrap_or(false); Ok(Content::Vertical { amount, weak, generated: false }) diff --git a/src/library/layout/stack.rs b/src/library/layout/stack.rs index d8dc0e1a0..d07dc35e0 100644 --- a/src/library/layout/stack.rs +++ b/src/library/layout/stack.rs @@ -15,7 +15,7 @@ pub struct StackNode { #[node] impl StackNode { - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { Ok(Content::block(Self { dir: args.named("dir")?.unwrap_or(Dir::TTB), spacing: args.named("spacing")?, @@ -27,7 +27,7 @@ impl StackNode { impl Layout for StackNode { fn layout( &self, - ctx: &mut Context, + world: &dyn World, regions: &Regions, styles: StyleChain, ) -> TypResult> { @@ -47,7 +47,7 @@ impl Layout for StackNode { layouter.layout_spacing(kind); } - layouter.layout_node(ctx, node, styles)?; + layouter.layout_node(world, node, styles)?; deferred = self.spacing; } } @@ -168,7 +168,7 @@ impl<'a> StackLayouter<'a> { /// Layout an arbitrary node. pub fn layout_node( &mut self, - ctx: &mut Context, + world: &dyn World, node: &LayoutNode, styles: StyleChain, ) -> TypResult<()> { @@ -193,7 +193,7 @@ impl<'a> StackLayouter<'a> { self.dir.start().into() }); - let frames = node.layout(ctx, &self.regions, styles)?; + let frames = node.layout(world, &self.regions, styles)?; let len = frames.len(); for (i, mut frame) in frames.into_iter().enumerate() { // Set the generic block role. diff --git a/src/library/math/mod.rs b/src/library/math/mod.rs index 81593c4fc..ed98ab1cc 100644 --- a/src/library/math/mod.rs +++ b/src/library/math/mod.rs @@ -28,7 +28,7 @@ impl MathNode { #[property(resolve, shorthand(around))] pub const BELOW: Option = Some(Ratio::one().into()); - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { Ok(Content::show(Self { formula: args.expect("formula")?, display: args.named("display")?.unwrap_or(false), @@ -48,7 +48,7 @@ impl Show for MathNode { } } - fn realize(&self, _: &mut Context, styles: StyleChain) -> TypResult { + fn realize(&self, _: &dyn World, styles: StyleChain) -> TypResult { let node = self::rex::RexNode { tex: self.formula.clone(), display: self.display, @@ -64,7 +64,7 @@ impl Show for MathNode { fn finalize( &self, - _: &mut Context, + _: &dyn World, styles: StyleChain, mut realized: Content, ) -> TypResult { diff --git a/src/library/math/rex.rs b/src/library/math/rex.rs index 0116b4b26..7bfdeb7a2 100644 --- a/src/library/math/rex.rs +++ b/src/library/math/rex.rs @@ -22,17 +22,16 @@ pub struct RexNode { impl Layout for RexNode { fn layout( &self, - ctx: &mut Context, + world: &dyn World, _: &Regions, styles: StyleChain, ) -> TypResult> { // Load the font. let span = self.tex.span; - let font = ctx - .loader + let font = world .book() .select(self.family.as_str(), variant(styles)) - .and_then(|id| ctx.loader.font(id).ok()) + .and_then(|id| world.font(id).ok()) .ok_or("failed to find math font") .at(span)?; diff --git a/src/library/prelude.rs b/src/library/prelude.rs index 8bd3b8159..a69d9791d 100644 --- a/src/library/prelude.rs +++ b/src/library/prelude.rs @@ -12,16 +12,15 @@ pub use crate::diag::{ failed_to_load, with_alternative, At, Error, StrResult, TypError, TypResult, }; pub use crate::eval::{ - Arg, Args, Array, Cast, Dict, Dynamic, Func, Machine, Node, RawAlign, RawLength, - RawStroke, Scope, Smart, Value, + Arg, Args, Array, Cast, Dict, Dynamic, Func, Node, RawAlign, RawLength, RawStroke, + Scope, Smart, Value, Vm, }; pub use crate::frame::*; pub use crate::geom::*; -pub use crate::loading::Loader; pub use crate::model::{ Content, Fold, Key, Layout, LayoutNode, Regions, Resolve, Selector, Show, ShowNode, StyleChain, StyleMap, StyleVec, }; pub use crate::syntax::{Span, Spanned}; pub use crate::util::EcoString; -pub use crate::Context; +pub use crate::World; diff --git a/src/library/structure/doc.rs b/src/library/structure/doc.rs index 80472e243..cabdb4dc7 100644 --- a/src/library/structure/doc.rs +++ b/src/library/structure/doc.rs @@ -7,11 +7,11 @@ pub struct DocNode(pub StyleVec); impl DocNode { /// Layout the document into a sequence of frames, one per page. - pub fn layout(&self, ctx: &mut Context, styles: StyleChain) -> TypResult> { + pub fn layout(&self, world: &dyn World, styles: StyleChain) -> TypResult> { let mut frames = vec![]; for (page, map) in self.0.iter() { let number = 1 + frames.len(); - frames.extend(page.layout(ctx, number, map.chain(&styles))?); + frames.extend(page.layout(world, number, map.chain(&styles))?); } Ok(frames) } diff --git a/src/library/structure/heading.rs b/src/library/structure/heading.rs index 24054f810..c177481f9 100644 --- a/src/library/structure/heading.rs +++ b/src/library/structure/heading.rs @@ -60,7 +60,7 @@ impl HeadingNode { /// Whether the heading is numbered. pub const NUMBERED: bool = true; - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { Ok(Content::show(Self { body: args.expect("body")?, level: args.named("level")?.unwrap_or(NonZeroUsize::new(1).unwrap()), @@ -82,19 +82,19 @@ impl Show for HeadingNode { } } - fn realize(&self, _: &mut Context, _: StyleChain) -> TypResult { + fn realize(&self, _: &dyn World, _: StyleChain) -> TypResult { Ok(Content::block(self.body.clone())) } fn finalize( &self, - ctx: &mut Context, + world: &dyn World, styles: StyleChain, mut realized: Content, ) -> TypResult { macro_rules! resolve { ($key:expr) => { - styles.get($key).resolve(ctx, self.level)? + styles.get($key).resolve(world, self.level)? }; } @@ -149,13 +149,13 @@ pub enum Leveled { impl Leveled { /// Resolve the value based on the level. - pub fn resolve(&self, ctx: &mut Context, level: NonZeroUsize) -> TypResult { + pub fn resolve(&self, world: &dyn World, level: NonZeroUsize) -> TypResult { Ok(match self { Self::Value(value) => value.clone(), Self::Mapping(mapping) => mapping(level), Self::Func(func, span) => { let args = Args::new(*span, [Value::Int(level.get() as i64)]); - func.call_detached(ctx, args)?.cast().at(*span)? + func.call_detached(world, args)?.cast().at(*span)? } }) } diff --git a/src/library/structure/list.rs b/src/library/structure/list.rs index c4167cf9a..e9365cd6e 100644 --- a/src/library/structure/list.rs +++ b/src/library/structure/list.rs @@ -56,7 +56,7 @@ impl ListNode { #[property(resolve)] pub const SPACING: BlockSpacing = Ratio::one().into(); - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { Ok(Content::show(Self { start: args.named("start")?.unwrap_or(1), tight: args.named("tight")?.unwrap_or(true), @@ -100,7 +100,7 @@ impl Show for ListNode { } } - fn realize(&self, ctx: &mut Context, styles: StyleChain) -> TypResult { + fn realize(&self, world: &dyn World, styles: StyleChain) -> TypResult { let mut cells = vec![]; let mut number = self.start; @@ -112,7 +112,7 @@ impl Show for ListNode { cells.push(LayoutNode::default()); cells.push( label - .resolve(ctx, L, number)? + .resolve(world, L, number)? .styled_with_map(map.clone()) .role(Role::ListLabel) .pack(), @@ -145,7 +145,7 @@ impl Show for ListNode { fn finalize( &self, - _: &mut Context, + _: &dyn World, styles: StyleChain, realized: Content, ) -> TypResult { @@ -208,7 +208,7 @@ impl Label { /// Resolve the value based on the level. pub fn resolve( &self, - ctx: &mut Context, + world: &dyn World, kind: ListKind, number: usize, ) -> TypResult { @@ -225,7 +225,7 @@ impl Label { Self::Content(content) => content.clone(), Self::Func(func, span) => { let args = Args::new(*span, [Value::Int(number as i64)]); - func.call_detached(ctx, args)?.display() + func.call_detached(world, args)?.display() } }) } diff --git a/src/library/structure/reference.rs b/src/library/structure/reference.rs index 0eeb4bf57..22dbec016 100644 --- a/src/library/structure/reference.rs +++ b/src/library/structure/reference.rs @@ -6,7 +6,7 @@ pub struct RefNode(pub EcoString); #[node(showable)] impl RefNode { - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { Ok(Content::show(Self(args.expect("label")?))) } } @@ -22,7 +22,7 @@ impl Show for RefNode { } } - fn realize(&self, _: &mut Context, _: StyleChain) -> TypResult { + fn realize(&self, _: &dyn World, _: StyleChain) -> TypResult { Ok(Content::Text(format_eco!("@{}", self.0))) } } diff --git a/src/library/structure/table.rs b/src/library/structure/table.rs index 0f74fc967..08fa53864 100644 --- a/src/library/structure/table.rs +++ b/src/library/structure/table.rs @@ -30,7 +30,7 @@ impl TableNode { #[property(resolve, shorthand(around))] pub const BELOW: Option = Some(Ratio::one().into()); - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { let columns = args.named("columns")?.unwrap_or_default(); let rows = args.named("rows")?.unwrap_or_default(); let base_gutter: Vec = args.named("gutter")?.unwrap_or_default(); @@ -72,7 +72,7 @@ impl Show for TableNode { } } - fn realize(&self, ctx: &mut Context, styles: StyleChain) -> TypResult { + fn realize(&self, world: &dyn World, styles: StyleChain) -> TypResult { let fill = styles.get(Self::FILL); let stroke = styles.get(Self::STROKE).map(RawStroke::unwrap_or_default); let padding = styles.get(Self::PADDING); @@ -92,7 +92,7 @@ impl Show for TableNode { let x = i % cols; let y = i / cols; - if let Some(fill) = fill.resolve(ctx, x, y)? { + if let Some(fill) = fill.resolve(world, x, y)? { child = child.filled(fill); } @@ -110,7 +110,7 @@ impl Show for TableNode { fn finalize( &self, - _: &mut Context, + _: &dyn World, styles: StyleChain, realized: Content, ) -> TypResult { @@ -129,12 +129,12 @@ pub enum Celled { impl Celled { /// Resolve the value based on the cell position. - pub fn resolve(&self, ctx: &mut Context, x: usize, y: usize) -> TypResult { + pub fn resolve(&self, world: &dyn World, x: usize, y: usize) -> TypResult { Ok(match self { Self::Value(value) => value.clone(), Self::Func(func, span) => { let args = Args::new(*span, [Value::Int(x as i64), Value::Int(y as i64)]); - func.call_detached(ctx, args)?.cast().at(*span)? + func.call_detached(world, args)?.cast().at(*span)? } }) } diff --git a/src/library/text/deco.rs b/src/library/text/deco.rs index 8295f1a2e..c58148b4c 100644 --- a/src/library/text/deco.rs +++ b/src/library/text/deco.rs @@ -34,7 +34,7 @@ impl DecoNode { /// with the glyphs. Does not apply to strikethrough. pub const EVADE: bool = true; - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { Ok(Content::show(Self(args.expect("body")?))) } } @@ -48,7 +48,7 @@ impl Show for DecoNode { dict! { "body" => Value::Content(self.0.clone()) } } - fn realize(&self, _: &mut Context, styles: StyleChain) -> TypResult { + fn realize(&self, _: &dyn World, styles: StyleChain) -> TypResult { Ok(self.0.clone().styled(TextNode::DECO, Decoration { line: L, stroke: styles.get(Self::STROKE).unwrap_or_default(), diff --git a/src/library/text/link.rs b/src/library/text/link.rs index c4898eb05..7d5c31094 100644 --- a/src/library/text/link.rs +++ b/src/library/text/link.rs @@ -18,7 +18,7 @@ impl LinkNode { /// Whether to underline the link. pub const UNDERLINE: Smart = Smart::Auto; - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { Ok(Content::show({ let dest = args.expect::("destination")?; let body = match dest { @@ -64,7 +64,7 @@ impl Show for LinkNode { } } - fn realize(&self, _: &mut Context, _: StyleChain) -> TypResult { + fn realize(&self, _: &dyn World, _: StyleChain) -> TypResult { Ok(self.body.clone().unwrap_or_else(|| match &self.dest { Destination::Url(url) => { let mut text = url.as_str(); @@ -80,7 +80,7 @@ impl Show for LinkNode { fn finalize( &self, - _: &mut Context, + _: &dyn World, styles: StyleChain, mut realized: Content, ) -> TypResult { diff --git a/src/library/text/mod.rs b/src/library/text/mod.rs index 357801861..be586874e 100644 --- a/src/library/text/mod.rs +++ b/src/library/text/mod.rs @@ -128,7 +128,7 @@ impl TextNode { #[property(skip, fold)] pub const DECO: Decoration = vec![]; - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { // The text constructor is special: It doesn't create a text node. // Instead, it leaves the passed argument structurally unchanged, but // styles all text in it. @@ -422,12 +422,12 @@ impl Fold for Vec<(Tag, u32)> { } /// Convert a string or content to lowercase. -pub fn lower(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn lower(_: &mut Vm, args: &mut Args) -> TypResult { case(Case::Lower, args) } /// Convert a string or content to uppercase. -pub fn upper(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn upper(_: &mut Vm, args: &mut Args) -> TypResult { case(Case::Upper, args) } @@ -461,7 +461,7 @@ impl Case { } /// Display text in small capitals. -pub fn smallcaps(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn smallcaps(_: &mut Vm, args: &mut Args) -> TypResult { let body: Content = args.expect("content")?; Ok(Value::Content(body.styled(TextNode::SMALLCAPS, true))) } @@ -493,7 +493,7 @@ pub struct StrongNode(pub Content); #[node(showable)] impl StrongNode { - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { Ok(Content::show(Self(args.expect("body")?))) } } @@ -507,7 +507,7 @@ impl Show for StrongNode { dict! { "body" => Value::Content(self.0.clone()) } } - fn realize(&self, _: &mut Context, _: StyleChain) -> TypResult { + fn realize(&self, _: &dyn World, _: StyleChain) -> TypResult { Ok(self.0.clone().styled(TextNode::BOLD, Toggle)) } } @@ -518,7 +518,7 @@ pub struct EmphNode(pub Content); #[node(showable)] impl EmphNode { - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { Ok(Content::show(Self(args.expect("body")?))) } } @@ -532,7 +532,7 @@ impl Show for EmphNode { dict! { "body" => Value::Content(self.0.clone()) } } - fn realize(&self, _: &mut Context, _: StyleChain) -> TypResult { + fn realize(&self, _: &dyn World, _: StyleChain) -> TypResult { Ok(self.0.clone().styled(TextNode::ITALIC, Toggle)) } } diff --git a/src/library/text/par.rs b/src/library/text/par.rs index 8309bcc8b..e8282ef1a 100644 --- a/src/library/text/par.rs +++ b/src/library/text/par.rs @@ -49,7 +49,7 @@ impl ParNode { #[property(resolve)] pub const LINEBREAKS: Smart = Smart::Auto; - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { // The paragraph constructor is special: It doesn't create a paragraph // node. Instead, it just ensures that the passed content lives is in a // separate paragraph and styles it. @@ -64,7 +64,7 @@ impl ParNode { impl Layout for ParNode { fn layout( &self, - ctx: &mut Context, + world: &dyn World, regions: &Regions, styles: StyleChain, ) -> TypResult> { @@ -74,13 +74,13 @@ impl Layout for ParNode { // Perform BiDi analysis and then prepare paragraph layout by building a // representation on which we can do line breaking without layouting // each and every line from scratch. - let p = prepare(ctx, self, &text, segments, regions, styles)?; + let p = prepare(world, self, &text, segments, regions, styles)?; // Break the paragraph into lines. - let lines = linebreak(&p, ctx.loader.as_ref(), regions.first.x); + let lines = linebreak(&p, world, regions.first.x); // Stack the lines into one frame per region. - stack(&p, ctx, &lines, regions) + stack(&p, world, &lines, regions) } } @@ -170,7 +170,7 @@ pub struct ParbreakNode; #[node] impl ParbreakNode { - fn construct(_: &mut Machine, _: &mut Args) -> TypResult { + fn construct(_: &mut Vm, _: &mut Args) -> TypResult { Ok(Content::Parbreak) } } @@ -180,7 +180,7 @@ pub struct LinebreakNode; #[node] impl LinebreakNode { - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { let justified = args.named("justified")?.unwrap_or(false); Ok(Content::Linebreak { justified }) } @@ -496,7 +496,7 @@ fn collect<'a>( /// Prepare paragraph layout by shaping the whole paragraph and layouting all /// contained inline-level nodes. fn prepare<'a>( - ctx: &mut Context, + world: &dyn World, par: &'a ParNode, text: &'a str, segments: Vec<(Segment<'a>, StyleChain<'a>)>, @@ -517,13 +517,7 @@ fn prepare<'a>( let end = cursor + segment.len(); match segment { Segment::Text(_) => { - shape_range( - &mut items, - ctx.loader.as_ref(), - &bidi, - cursor .. end, - styles, - ); + shape_range(&mut items, world, &bidi, cursor .. end, styles); } Segment::Spacing(spacing) => match spacing { Spacing::Relative(v) => { @@ -540,7 +534,7 @@ fn prepare<'a>( } else { let size = Size::new(regions.first.x, regions.base.y); let pod = Regions::one(size, regions.base, Spec::splat(false)); - let mut frame = node.layout(ctx, &pod, styles)?.remove(0); + let mut frame = node.layout(world, &pod, styles)?.remove(0); frame.translate(Point::with_y(styles.get(TextNode::BASELINE))); frame.apply_role(Role::GenericInline); items.push(Item::Frame(frame)); @@ -567,14 +561,14 @@ fn prepare<'a>( /// items for them. fn shape_range<'a>( items: &mut Vec>, - loader: &dyn Loader, + world: &dyn World, bidi: &BidiInfo<'a>, range: Range, styles: StyleChain<'a>, ) { let mut process = |text, level: Level| { let dir = if level.is_ltr() { Dir::LTR } else { Dir::RTL }; - let shaped = shape(loader, text, styles, dir); + let shaped = shape(world, text, styles, dir); items.push(Item::Text(shaped)); }; @@ -633,12 +627,12 @@ fn shared_get<'a, K: Key<'a>>( /// Find suitable linebreaks. fn linebreak<'a>( p: &'a Preparation<'a>, - loader: &dyn Loader, + world: &dyn World, width: Length, ) -> Vec> { match p.styles.get(ParNode::LINEBREAKS) { - Linebreaks::Simple => linebreak_simple(p, loader, width), - Linebreaks::Optimized => linebreak_optimized(p, loader, width), + Linebreaks::Simple => linebreak_simple(p, world, width), + Linebreaks::Optimized => linebreak_optimized(p, world, width), } } @@ -647,7 +641,7 @@ fn linebreak<'a>( /// very unbalanced line, but is fast and simple. fn linebreak_simple<'a>( p: &'a Preparation<'a>, - loader: &dyn Loader, + world: &dyn World, width: Length, ) -> Vec> { let mut lines = vec![]; @@ -656,7 +650,7 @@ fn linebreak_simple<'a>( for (end, mandatory, hyphen) in breakpoints(p) { // Compute the line and its size. - let mut attempt = line(p, loader, start .. end, mandatory, hyphen); + let mut attempt = line(p, world, start .. end, mandatory, hyphen); // If the line doesn't fit anymore, we push the last fitting attempt // into the stack and rebuild the line from the attempt's end. The @@ -665,7 +659,7 @@ fn linebreak_simple<'a>( if let Some((last_attempt, last_end)) = last.take() { lines.push(last_attempt); start = last_end; - attempt = line(p, loader, start .. end, mandatory, hyphen); + attempt = line(p, world, start .. end, mandatory, hyphen); } } @@ -707,7 +701,7 @@ fn linebreak_simple<'a>( /// text. fn linebreak_optimized<'a>( p: &'a Preparation<'a>, - loader: &dyn Loader, + world: &dyn World, width: Length, ) -> Vec> { /// The cost of a line or paragraph layout. @@ -732,7 +726,7 @@ fn linebreak_optimized<'a>( let mut table = vec![Entry { pred: 0, total: 0.0, - line: line(p, loader, 0 .. 0, false, false), + line: line(p, world, 0 .. 0, false, false), }]; let em = p.styles.get(TextNode::SIZE); @@ -746,7 +740,7 @@ fn linebreak_optimized<'a>( for (i, pred) in table.iter_mut().enumerate().skip(active) { // Layout the line. let start = pred.line.end; - let attempt = line(p, loader, start .. end, mandatory, hyphen); + let attempt = line(p, world, start .. end, mandatory, hyphen); // Determine how much the line's spaces would need to be stretched // to make it the desired width. @@ -920,7 +914,7 @@ impl Breakpoints<'_> { /// Create a line which spans the given range. fn line<'a>( p: &'a Preparation, - loader: &dyn Loader, + world: &dyn World, mut range: Range, mandatory: bool, hyphen: bool, @@ -975,9 +969,9 @@ fn line<'a>( if hyphen || start + shaped.text.len() > range.end { if hyphen || start < range.end || before.is_empty() { let shifted = start - base .. range.end - base; - let mut reshaped = shaped.reshape(loader, shifted); + let mut reshaped = shaped.reshape(world, shifted); if hyphen || shy { - reshaped.push_hyphen(loader); + reshaped.push_hyphen(world); } width += reshaped.width; last = Some(Item::Text(reshaped)); @@ -998,7 +992,7 @@ fn line<'a>( if range.start + shaped.text.len() > end { if range.start < end { let shifted = range.start - base .. end - base; - let reshaped = shaped.reshape(loader, shifted); + let reshaped = shaped.reshape(world, shifted); width += reshaped.width; first = Some(Item::Text(reshaped)); } @@ -1028,7 +1022,7 @@ fn line<'a>( /// Combine layouted lines into one frame per region. fn stack( p: &Preparation, - ctx: &mut Context, + world: &dyn World, lines: &[Line], regions: &Regions, ) -> TypResult> { @@ -1048,7 +1042,7 @@ fn stack( // Stack the lines into one frame per region. for line in lines { - let frame = commit(p, ctx, line, ®ions, width)?; + let frame = commit(p, world, line, ®ions, width)?; let height = frame.size().y; while !regions.first.y.fits(height) && !regions.in_last() { @@ -1078,7 +1072,7 @@ fn stack( /// Commit to a line and build its frame. fn commit( p: &Preparation, - ctx: &mut Context, + world: &dyn World, line: &Line, regions: &Regions, width: Length, @@ -1149,7 +1143,7 @@ fn commit( offset += v.share(fr, remaining); } Item::Text(shaped) => { - let frame = shaped.build(ctx.loader.as_ref(), justification); + let frame = shaped.build(world, justification); push(&mut offset, frame); } Item::Frame(frame) => { @@ -1160,7 +1154,7 @@ fn commit( let fill = Fraction::one().share(fr, remaining); let size = Size::new(fill, regions.base.y); let pod = Regions::one(size, regions.base, Spec::new(false, false)); - let frame = node.layout(ctx, &pod, *styles)?.remove(0); + let frame = node.layout(world, &pod, *styles)?.remove(0); let width = frame.width(); let count = (fill / width).floor(); let remaining = fill % width; diff --git a/src/library/text/raw.rs b/src/library/text/raw.rs index a2a57fa1d..c729fa40d 100644 --- a/src/library/text/raw.rs +++ b/src/library/text/raw.rs @@ -35,7 +35,7 @@ impl RawNode { #[property(resolve, shorthand(around))] pub const BELOW: Option = Some(Ratio::one().into()); - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { Ok(Content::show(Self { text: args.expect("text")?, block: args.named("block")?.unwrap_or(false), @@ -59,7 +59,7 @@ impl Show for RawNode { } } - fn realize(&self, _: &mut Context, styles: StyleChain) -> TypResult { + fn realize(&self, _: &dyn World, styles: StyleChain) -> TypResult { let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase()); let foreground = THEME .settings @@ -111,7 +111,7 @@ impl Show for RawNode { fn finalize( &self, - _: &mut Context, + _: &dyn World, styles: StyleChain, mut realized: Content, ) -> TypResult { diff --git a/src/library/text/repeat.rs b/src/library/text/repeat.rs index aca281fc9..c2a0de700 100644 --- a/src/library/text/repeat.rs +++ b/src/library/text/repeat.rs @@ -6,7 +6,7 @@ pub struct RepeatNode(pub LayoutNode); #[node] impl RepeatNode { - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { Ok(Content::inline(Self(args.expect("body")?))) } } @@ -14,11 +14,11 @@ impl RepeatNode { impl Layout for RepeatNode { fn layout( &self, - ctx: &mut Context, + world: &dyn World, regions: &Regions, styles: StyleChain, ) -> TypResult> { // The actual repeating happens directly in the paragraph. - self.0.layout(ctx, regions, styles) + self.0.layout(world, regions, styles) } } diff --git a/src/library/text/shaping.rs b/src/library/text/shaping.rs index 3d905c997..6e5057024 100644 --- a/src/library/text/shaping.rs +++ b/src/library/text/shaping.rs @@ -80,8 +80,8 @@ impl<'a> ShapedText<'a> { /// /// The `justification` defines how much extra advance width each /// [justifiable glyph](ShapedGlyph::is_justifiable) will get. - pub fn build(&self, loader: &dyn Loader, justification: Length) -> Frame { - let (top, bottom) = self.measure(loader); + pub fn build(&self, world: &dyn World, justification: Length) -> Frame { + let (top, bottom) = self.measure(world); let size = Size::new(self.width, top + bottom); let mut offset = Length::zero(); @@ -144,7 +144,7 @@ impl<'a> ShapedText<'a> { } /// Measure the top and bottom extent of this text. - fn measure(&self, loader: &dyn Loader) -> (Length, Length) { + fn measure(&self, world: &dyn World) -> (Length, Length) { let mut top = Length::zero(); let mut bottom = Length::zero(); @@ -162,10 +162,10 @@ impl<'a> ShapedText<'a> { // When there are no glyphs, we just use the vertical metrics of the // first available font. for family in families(self.styles) { - if let Some(font) = loader + if let Some(font) = world .book() .select(family, self.variant) - .and_then(|id| loader.font(id).ok()) + .and_then(|id| world.font(id).ok()) { expand(&font); break; @@ -199,7 +199,7 @@ impl<'a> ShapedText<'a> { /// shaping process if possible. pub fn reshape( &'a self, - loader: &dyn Loader, + world: &dyn World, text_range: Range, ) -> ShapedText<'a> { if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) { @@ -213,17 +213,17 @@ impl<'a> ShapedText<'a> { glyphs: Cow::Borrowed(glyphs), } } else { - shape(loader, &self.text[text_range], self.styles, self.dir) + shape(world, &self.text[text_range], self.styles, self.dir) } } /// Push a hyphen to end of the text. - pub fn push_hyphen(&mut self, loader: &dyn Loader) { + pub fn push_hyphen(&mut self, world: &dyn World) { families(self.styles).find_map(|family| { - let font = loader + let font = world .book() .select(family, self.variant) - .and_then(|id| loader.font(id).ok())?; + .and_then(|id| world.font(id).ok())?; let ttf = font.ttf(); let glyph_id = ttf.glyph_index('-')?; let x_advance = font.to_em(ttf.glyph_hor_advance(glyph_id)?); @@ -306,7 +306,7 @@ impl Debug for ShapedText<'_> { /// Holds shaping results and metadata common to all shaped segments. struct ShapingContext<'a> { - loader: &'a dyn Loader, + world: &'a dyn World, glyphs: Vec, used: Vec, styles: StyleChain<'a>, @@ -319,7 +319,7 @@ struct ShapingContext<'a> { /// Shape text into [`ShapedText`]. pub fn shape<'a>( - loader: &dyn Loader, + world: &dyn World, text: &'a str, styles: StyleChain<'a>, dir: Dir, @@ -327,7 +327,7 @@ pub fn shape<'a>( let size = styles.get(TextNode::SIZE); let mut ctx = ShapingContext { - loader, + world, size, glyphs: vec![], used: vec![], @@ -368,10 +368,10 @@ fn shape_segment<'a>( } // Find the next available family. - let book = ctx.loader.book(); + let book = ctx.world.book(); let mut selection = families.find_map(|family| { book.select(family, ctx.variant) - .and_then(|id| ctx.loader.font(id).ok()) + .and_then(|id| ctx.world.font(id).ok()) .filter(|font| !ctx.used.contains(font)) }); @@ -380,7 +380,7 @@ fn shape_segment<'a>( let first = ctx.used.first().map(Font::info); selection = book .select_fallback(first, ctx.variant, text) - .and_then(|id| ctx.loader.font(id).ok()) + .and_then(|id| ctx.world.font(id).ok()) .filter(|font| !ctx.used.contains(font)); } diff --git a/src/library/text/shift.rs b/src/library/text/shift.rs index 75b2a5790..5da36da1c 100644 --- a/src/library/text/shift.rs +++ b/src/library/text/shift.rs @@ -28,7 +28,7 @@ impl ShiftNode { /// The font size for synthetic sub- and superscripts. pub const SIZE: TextSize = TextSize(Em::new(0.6).into()); - fn construct(_: &mut Machine, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> TypResult { Ok(Content::show(Self(args.expect("body")?))) } } @@ -42,11 +42,11 @@ impl Show for ShiftNode { dict! { "body" => Value::Content(self.0.clone()) } } - fn realize(&self, ctx: &mut Context, styles: StyleChain) -> TypResult { + fn realize(&self, world: &dyn World, styles: StyleChain) -> TypResult { let mut transformed = None; if styles.get(Self::TYPOGRAPHIC) { if let Some(text) = search_text(&self.0, S) { - if is_shapable(ctx.loader.as_ref(), &text, styles) { + if is_shapable(world, &text, styles) { transformed = Some(Content::Text(text)); } } @@ -91,12 +91,12 @@ fn search_text(content: &Content, mode: ScriptKind) -> Option { /// Checks whether the first retrievable family contains all code points of the /// given string. -fn is_shapable(loader: &dyn Loader, text: &str, styles: StyleChain) -> bool { - let book = loader.book(); +fn is_shapable(world: &dyn World, text: &str, styles: StyleChain) -> bool { for family in styles.get(TextNode::FAMILY).iter() { - if let Some(font) = book + if let Some(font) = world + .book() .select(family.as_str(), variant(styles)) - .and_then(|id| loader.font(id).ok()) + .and_then(|id| world.font(id).ok()) { return text.chars().all(|c| font.ttf().glyph_index(c).is_some()); } diff --git a/src/library/utility/color.rs b/src/library/utility/color.rs index 7c6ed873f..a7d55d1c4 100644 --- a/src/library/utility/color.rs +++ b/src/library/utility/color.rs @@ -3,13 +3,13 @@ use std::str::FromStr; use crate::library::prelude::*; /// Create a grayscale color. -pub fn luma(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn luma(_: &mut Vm, args: &mut Args) -> TypResult { let Component(luma) = args.expect("gray component")?; Ok(Value::Color(LumaColor::new(luma).into())) } /// Create an RGB(A) color. -pub fn rgb(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn rgb(_: &mut Vm, args: &mut Args) -> TypResult { Ok(Value::Color( if let Some(string) = args.find::>()? { match RgbaColor::from_str(&string.v) { @@ -27,7 +27,7 @@ pub fn rgb(_: &mut Machine, args: &mut Args) -> TypResult { } /// Create a CMYK color. -pub fn cmyk(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn cmyk(_: &mut Vm, args: &mut Args) -> TypResult { let RatioComponent(c) = args.expect("cyan component")?; let RatioComponent(m) = args.expect("magenta component")?; let RatioComponent(y) = args.expect("yellow component")?; diff --git a/src/library/utility/data.rs b/src/library/utility/data.rs index f9e970dc8..59f3d351f 100644 --- a/src/library/utility/data.rs +++ b/src/library/utility/data.rs @@ -1,13 +1,13 @@ use crate::library::prelude::*; /// Read structured data from a CSV file. -pub fn csv(vm: &mut Machine, args: &mut Args) -> TypResult { +pub fn csv(vm: &mut Vm, args: &mut Args) -> TypResult { let Spanned { v: path, span } = args.expect::>("path to csv file")?; let path = vm.locate(&path).at(span)?; let try_load = || -> io::Result { - let data = vm.ctx.loader.file(&path)?; + let data = vm.world.file(&path)?; let mut builder = csv::ReaderBuilder::new(); builder.has_headers(false); diff --git a/src/library/utility/math.rs b/src/library/utility/math.rs index f68cc1bf8..47648282b 100644 --- a/src/library/utility/math.rs +++ b/src/library/utility/math.rs @@ -3,7 +3,7 @@ use std::cmp::Ordering; use crate::library::prelude::*; /// Convert a value to an integer. -pub fn int(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn int(_: &mut Vm, args: &mut Args) -> TypResult { let Spanned { v, span } = args.expect("value")?; Ok(Value::Int(match v { Value::Bool(v) => v as i64, @@ -18,7 +18,7 @@ pub fn int(_: &mut Machine, args: &mut Args) -> TypResult { } /// Convert a value to a float. -pub fn float(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn float(_: &mut Vm, args: &mut Args) -> TypResult { let Spanned { v, span } = args.expect("value")?; Ok(Value::Float(match v { Value::Int(v) => v as f64, @@ -32,7 +32,7 @@ pub fn float(_: &mut Machine, args: &mut Args) -> TypResult { } /// The absolute value of a numeric value. -pub fn abs(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn abs(_: &mut Vm, args: &mut Args) -> TypResult { let Spanned { v, span } = args.expect("numeric value")?; Ok(match v { Value::Int(v) => Value::Int(v.abs()), @@ -48,12 +48,12 @@ pub fn abs(_: &mut Machine, args: &mut Args) -> TypResult { } /// The minimum of a sequence of values. -pub fn min(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn min(_: &mut Vm, args: &mut Args) -> TypResult { minmax(args, Ordering::Less) } /// The maximum of a sequence of values. -pub fn max(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn max(_: &mut Vm, args: &mut Args) -> TypResult { minmax(args, Ordering::Greater) } @@ -79,17 +79,17 @@ fn minmax(args: &mut Args, goal: Ordering) -> TypResult { } /// Whether an integer is even. -pub fn even(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn even(_: &mut Vm, args: &mut Args) -> TypResult { Ok(Value::Bool(args.expect::("integer")? % 2 == 0)) } /// Whether an integer is odd. -pub fn odd(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn odd(_: &mut Vm, args: &mut Args) -> TypResult { Ok(Value::Bool(args.expect::("integer")? % 2 != 0)) } /// The modulo of two numbers. -pub fn mod_(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn mod_(_: &mut Vm, args: &mut Args) -> TypResult { let Spanned { v: v1, span: span1 } = args.expect("integer or float")?; let Spanned { v: v2, span: span2 } = args.expect("integer or float")?; @@ -119,7 +119,7 @@ pub fn mod_(_: &mut Machine, args: &mut Args) -> TypResult { } /// Create a sequence of numbers. -pub fn range(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn range(_: &mut Vm, args: &mut Args) -> TypResult { let first = args.expect::("end")?; let (start, end) = match args.eat::()? { Some(second) => (first, second), diff --git a/src/library/utility/mod.rs b/src/library/utility/mod.rs index bc7c6f255..40a107ba9 100644 --- a/src/library/utility/mod.rs +++ b/src/library/utility/mod.rs @@ -10,17 +10,17 @@ pub use data::*; pub use math::*; pub use string::*; -use crate::eval::{Eval, Machine, Scopes}; +use crate::eval::{Eval, Scopes, Vm}; use crate::library::prelude::*; use crate::source::Source; /// The name of a value's type. -pub fn type_(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn type_(_: &mut Vm, args: &mut Args) -> TypResult { Ok(args.expect::("value")?.type_name().into()) } /// Ensure that a condition is fulfilled. -pub fn assert(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn assert(_: &mut Vm, args: &mut Args) -> TypResult { let Spanned { v, span } = args.expect::>("condition")?; if !v { bail!(span, "assertion failed"); @@ -29,19 +29,18 @@ pub fn assert(_: &mut Machine, args: &mut Args) -> TypResult { } /// Evaluate a string as Typst markup. -pub fn eval(vm: &mut Machine, args: &mut Args) -> TypResult { - let Spanned { v: src, span } = args.expect::>("source")?; +pub fn eval(vm: &mut Vm, args: &mut Args) -> TypResult { + let Spanned { v: text, span } = args.expect::>("source")?; // Parse the source and set a synthetic span for all nodes. - let source = Source::synthesized(src, span); + let source = Source::synthesized(text, span); let ast = source.ast()?; // Evaluate the source. - let std = vm.ctx.config.std.clone(); - let scopes = Scopes::new(Some(&std)); - let mut sub = Machine::new(vm.ctx, vec![], scopes); + let std = &vm.world.config().std; + let scopes = Scopes::new(Some(std)); + let mut sub = Vm::new(vm.world, vec![], scopes); let result = ast.eval(&mut sub); - assert!(vm.deps.is_empty()); // Handle control flow. if let Some(flow) = sub.flow { diff --git a/src/library/utility/string.rs b/src/library/utility/string.rs index 972b44d70..d825d84bd 100644 --- a/src/library/utility/string.rs +++ b/src/library/utility/string.rs @@ -2,12 +2,12 @@ use crate::eval::Regex; use crate::library::prelude::*; /// The string representation of a value. -pub fn repr(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn repr(_: &mut Vm, args: &mut Args) -> TypResult { Ok(args.expect::("value")?.repr().into()) } /// Convert a value to a string. -pub fn str(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn str(_: &mut Vm, args: &mut Args) -> TypResult { let Spanned { v, span } = args.expect("value")?; Ok(Value::Str(match v { Value::Int(v) => format_str!("{}", v), @@ -18,29 +18,29 @@ pub fn str(_: &mut Machine, args: &mut Args) -> TypResult { } /// Create blind text. -pub fn lorem(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn lorem(_: &mut Vm, args: &mut Args) -> TypResult { let words: usize = args.expect("number of words")?; Ok(Value::Str(lipsum::lipsum(words).into())) } /// Create a regular expression. -pub fn regex(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn regex(_: &mut Vm, args: &mut Args) -> TypResult { let Spanned { v, span } = args.expect::>("regular expression")?; Ok(Regex::new(&v).at(span)?.into()) } /// Converts an integer into one or multiple letters. -pub fn letter(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn letter(_: &mut Vm, args: &mut Args) -> TypResult { numbered(Numbering::Letter, args) } /// Converts an integer into a roman numeral. -pub fn roman(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn roman(_: &mut Vm, args: &mut Args) -> TypResult { numbered(Numbering::Roman, args) } /// Convert a number into a symbol. -pub fn symbol(_: &mut Machine, args: &mut Args) -> TypResult { +pub fn symbol(_: &mut Vm, args: &mut Args) -> TypResult { numbered(Numbering::Symbol, args) } diff --git a/src/loading.rs b/src/loading.rs deleted file mode 100644 index 9a32d0257..000000000 --- a/src/loading.rs +++ /dev/null @@ -1,239 +0,0 @@ -//! Resource loading. - -use std::fmt::{self, Debug, Formatter}; -use std::io; -use std::ops::Deref; -use std::path::Path; -use std::sync::Arc; - -use crate::font::{Font, FontBook}; -use crate::util::Prehashed; - -/// A hash that identifies a file. -/// -/// Such a hash can be [resolved](Loader::resolve) from a path. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct FileHash(pub u64); - -/// Loads resources from a local or remote source. -pub trait Loader { - /// Metadata about all known fonts. - fn book(&self) -> &FontBook; - - /// Access the font with the given id. - fn font(&self, id: usize) -> io::Result; - - /// Resolve a hash that is the same for this and all other paths pointing to - /// the same file. - fn resolve(&self, path: &Path) -> io::Result; - - /// Load a file from a path. - fn file(&self, path: &Path) -> io::Result; -} - -/// A shared buffer that is cheap to clone. -#[derive(Clone, Hash, Eq, PartialEq)] -pub struct Buffer(Prehashed>>); - -impl Buffer { - /// Return a view into the buffer. - pub fn as_slice(&self) -> &[u8] { - self - } - - /// Return a copy of the buffer as a vector. - pub fn to_vec(&self) -> Vec { - self.0.to_vec() - } -} - -impl From<&[u8]> for Buffer { - fn from(slice: &[u8]) -> Self { - Self(Prehashed::new(Arc::new(slice.to_vec()))) - } -} - -impl From> for Buffer { - fn from(vec: Vec) -> Self { - Self(Prehashed::new(Arc::new(vec))) - } -} - -impl From>> for Buffer { - fn from(arc: Arc>) -> Self { - Self(Prehashed::new(arc)) - } -} - -impl Deref for Buffer { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl AsRef<[u8]> for Buffer { - fn as_ref(&self) -> &[u8] { - self - } -} - -impl Debug for Buffer { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad("Buffer(..)") - } -} - -#[cfg(feature = "fs")] -pub use fs::*; - -#[cfg(feature = "fs")] -mod fs { - use std::fs::{self, File}; - use std::io; - use std::path::{Path, PathBuf}; - - use memmap2::Mmap; - use same_file::Handle; - use walkdir::WalkDir; - - use super::{Buffer, FileHash, Loader}; - use crate::font::{Font, FontBook, FontInfo}; - - /// Loads fonts and files from the local file system. - /// - /// _This is only available when the `system` feature is enabled._ - pub struct FsLoader { - book: FontBook, - paths: Vec<(PathBuf, u32)>, - } - - impl FsLoader { - /// Create a new system loader. - pub fn new() -> Self { - Self { book: FontBook::new(), paths: vec![] } - } - - /// Builder-style variant of [`search_path`](Self::search_path). - pub fn with_path(mut self, dir: impl AsRef) -> Self { - self.search_path(dir); - self - } - - /// Search for all fonts at a path. - /// - /// If the path is a directory, all contained fonts will be searched for - /// recursively. - pub fn search_path(&mut self, path: impl AsRef) { - let walk = WalkDir::new(path) - .follow_links(true) - .sort_by(|a, b| a.file_name().cmp(b.file_name())) - .into_iter() - .filter_map(|e| e.ok()); - - for entry in walk { - let path = entry.path(); - if let Some(ext) = path.extension().and_then(|s| s.to_str()) { - if matches!( - ext, - "ttf" | "otf" | "TTF" | "OTF" | "ttc" | "otc" | "TTC" | "OTC", - ) { - self.search_file(path); - } - } - } - } - - /// Index the fonts in the file at the given path. - /// - /// The file may form a font collection and contain multiple fonts, - /// which will then all be indexed. - fn search_file(&mut self, path: impl AsRef) { - let path = path.as_ref(); - let path = path.strip_prefix(".").unwrap_or(path); - if let Ok(file) = File::open(path) { - if let Ok(mmap) = unsafe { Mmap::map(&file) } { - for (i, info) in FontInfo::from_data(&mmap).enumerate() { - self.book.push(info); - self.paths.push((path.into(), i as u32)); - } - } - } - } - - /// Builder-style variant of [`search_system`](Self::search_system). - pub fn with_system(mut self) -> Self { - self.search_system(); - self - } - - /// Search for fonts in the operating system's font directories. - pub fn search_system(&mut self) { - self.search_system_impl(); - } - - #[cfg(all(unix, not(target_os = "macos")))] - fn search_system_impl(&mut self) { - self.search_path("/usr/share/fonts"); - self.search_path("/usr/local/share/fonts"); - - if let Some(dir) = dirs::font_dir() { - self.search_path(dir); - } - } - - #[cfg(target_os = "macos")] - fn search_system_impl(&mut self) { - self.search_path("/Library/Fonts"); - self.search_path("/Network/Library/Fonts"); - self.search_path("/System/Library/Fonts"); - - if let Some(dir) = dirs::font_dir() { - self.search_path(dir); - } - } - - #[cfg(windows)] - fn search_system_impl(&mut self) { - let windir = - std::env::var("WINDIR").unwrap_or_else(|_| "C:\\Windows".to_string()); - - self.search_path(Path::new(&windir).join("Fonts")); - - if let Some(roaming) = dirs::config_dir() { - self.search_path(roaming.join("Microsoft\\Windows\\Fonts")); - } - - if let Some(local) = dirs::cache_dir() { - self.search_path(local.join("Microsoft\\Windows\\Fonts")); - } - } - } - - impl Loader for FsLoader { - fn book(&self) -> &FontBook { - &self.book - } - - fn font(&self, id: usize) -> io::Result { - let (path, index) = &self.paths[id]; - let data = self.file(path)?; - Font::new(data, *index).ok_or_else(|| io::ErrorKind::InvalidData.into()) - } - - fn resolve(&self, path: &Path) -> io::Result { - let meta = fs::metadata(path)?; - if meta.is_file() { - let handle = Handle::from_path(path)?; - Ok(FileHash(fxhash::hash64(&handle))) - } else { - Err(io::ErrorKind::NotFound.into()) - } - } - - fn file(&self, path: &Path) -> io::Result { - Ok(fs::read(path)?.into()) - } - } -} diff --git a/src/main.rs b/src/main.rs index 8d6a669af..e5af0d8b8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,32 @@ -use std::fs; +use std::cell::RefCell; +use std::collections::{hash_map::Entry, HashMap}; +use std::fs::{self, File}; +use std::hash::Hash; use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::process; -use std::sync::Arc; use codespan_reporting::diagnostic::{Diagnostic, Label}; use codespan_reporting::term::{self, termcolor}; +use elsa::FrozenVec; +use memmap2::Mmap; +use once_cell::unsync::OnceCell; use pico_args::Arguments; -use same_file::is_same_file; +use same_file::{is_same_file, Handle}; +use siphasher::sip128::{Hasher128, SipHasher}; use termcolor::{ColorChoice, StandardStream, WriteColor}; +use walkdir::WalkDir; -use typst::diag::{Error, StrResult}; -use typst::font::FontVariant; +use typst::diag::{failed_to_load, Error, StrResult}; +use typst::font::{Font, FontBook, FontInfo, FontVariant}; use typst::library::text::THEME; -use typst::loading::{FsLoader, Loader}; use typst::parse::TokenMode; -use typst::source::SourceStore; -use typst::{Config, Context}; +use typst::source::{Source, SourceId}; +use typst::util::Buffer; +use typst::{Config, World}; + +type CodespanResult = Result; +type CodespanError = codespan_reporting::files::Error; /// What to do. enum Command { @@ -191,25 +201,20 @@ fn dispatch(command: Command) -> StrResult<()> { /// Execute a typesetting command. fn typeset(command: TypesetCommand) -> StrResult<()> { - let mut config = Config::builder(); + let mut world = SystemWorld::new(); if let Some(root) = &command.root { - config.root(root); + world.config.root = root.clone(); } else if let Some(dir) = command.input.parent() { - config.root(dir); + world.config.root = dir.into(); } - // Create a loader for fonts and files. - let loader = FsLoader::new().with_system(); - - // Create the context which holds loaded source files, fonts, images and - // cached artifacts. - let mut ctx = Context::new(Arc::new(loader), config.build()); - - // Load the source file. - let id = ctx.sources.load(&command.input)?; + // Create the world that serves sources, fonts and files. + let id = world + .resolve(&command.input) + .map_err(|err| failed_to_load("source file", &command.input, err))?; // Typeset. - match typst::typeset(&mut ctx, id) { + match typst::typeset(&world, id) { // Export the PDF. Ok(frames) => { let buffer = typst::export::pdf(&frames); @@ -218,7 +223,7 @@ fn typeset(command: TypesetCommand) -> StrResult<()> { // Print diagnostics. Err(errors) => { - print_diagnostics(&ctx.sources, *errors) + print_diagnostics(&world, *errors) .map_err(|_| "failed to print diagnostics")?; } } @@ -228,7 +233,7 @@ fn typeset(command: TypesetCommand) -> StrResult<()> { /// Print diagnostic messages to the terminal. fn print_diagnostics( - sources: &SourceStore, + world: &SystemWorld, errors: Vec, ) -> Result<(), codespan_reporting::files::Error> { let mut w = StandardStream::stderr(ColorChoice::Always); @@ -236,21 +241,24 @@ fn print_diagnostics( for error in errors { // The main diagnostic. - let range = sources.range(error.span); + let range = world.source(error.span.source()).range(error.span); let diag = Diagnostic::error() .with_message(error.message) .with_labels(vec![Label::primary(error.span.source(), range)]); - term::emit(&mut w, &config, sources, &diag)?; + term::emit(&mut w, &config, world, &diag)?; // Stacktrace-like helper diagnostics. for point in error.trace { let message = point.v.to_string(); let help = Diagnostic::help().with_message(message).with_labels(vec![ - Label::primary(point.span.source(), sources.range(point.span)), + Label::primary( + point.span.source(), + world.source(point.span.source()).range(point.span), + ), ]); - term::emit(&mut w, &config, sources, &help)?; + term::emit(&mut w, &config, world, &help)?; } } @@ -259,8 +267,8 @@ fn print_diagnostics( /// Execute a highlighting command. fn highlight(command: HighlightCommand) -> StrResult<()> { - let input = std::fs::read_to_string(&command.input) - .map_err(|_| "failed to load source file")?; + let input = + fs::read_to_string(&command.input).map_err(|_| "failed to load source file")?; let html = typst::syntax::highlight_html(&input, TokenMode::Markup, &THEME); fs::write(&command.output, html).map_err(|_| "failed to write HTML file")?; @@ -270,8 +278,8 @@ fn highlight(command: HighlightCommand) -> StrResult<()> { /// Execute a font listing command. fn fonts(command: FontsCommand) -> StrResult<()> { - let loader = FsLoader::new().with_system(); - for (name, infos) in loader.book().families() { + let world = SystemWorld::new(); + for (name, infos) in world.book().families() { println!("{name}"); if command.variants { for info in infos { @@ -283,3 +291,234 @@ fn fonts(command: FontsCommand) -> StrResult<()> { Ok(()) } + +/// A world that provides access to the operating system. +struct SystemWorld { + config: Config, + sources: FrozenVec>, + nav: RefCell>, + book: FontBook, + fonts: Vec, + files: RefCell>, +} + +struct FontSlot { + path: PathBuf, + index: u32, + font: OnceCell>, +} + +impl SystemWorld { + fn new() -> Self { + let mut world = Self { + config: Config::default(), + book: FontBook::new(), + sources: FrozenVec::new(), + nav: RefCell::new(HashMap::new()), + fonts: vec![], + files: RefCell::new(HashMap::new()), + }; + world.search_system(); + world + } +} + +impl World for SystemWorld { + fn config(&self) -> &Config { + &self.config + } + + fn resolve(&self, path: &Path) -> io::Result { + let hash = PathHash::new(path)?; + if let Some(&id) = self.nav.borrow().get(&hash) { + return Ok(id); + } + + let data = fs::read(path)?; + let text = String::from_utf8(data).map_err(|_| { + io::Error::new(io::ErrorKind::InvalidData, "file is not valid utf-8") + })?; + + let id = SourceId::from_raw(self.sources.len() as u16); + let source = Source::new(id, path, text); + self.sources.push(Box::new(source)); + self.nav.borrow_mut().insert(hash, id); + + Ok(id) + } + + fn source(&self, id: SourceId) -> &Source { + &self.sources[id.into_raw() as usize] + } + + fn book(&self) -> &FontBook { + &self.book + } + + fn font(&self, id: usize) -> io::Result { + let slot = &self.fonts[id]; + slot.font + .get_or_init(|| { + let data = self.file(&slot.path).ok()?; + Font::new(data, slot.index) + }) + .clone() + .ok_or_else(|| io::ErrorKind::InvalidData.into()) + } + + fn file(&self, path: &Path) -> io::Result { + let hash = PathHash::new(path)?; + Ok(match self.files.borrow_mut().entry(hash) { + Entry::Occupied(entry) => entry.get().clone(), + Entry::Vacant(entry) => entry.insert(fs::read(path)?.into()).clone(), + }) + } +} + +/// A hash that is the same for all paths pointing to the same file. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +struct PathHash(u128); + +impl PathHash { + fn new(path: &Path) -> io::Result { + let file = File::open(path)?; + if file.metadata()?.is_file() { + let handle = Handle::from_file(file)?; + let mut state = SipHasher::new(); + handle.hash(&mut state); + Ok(Self(state.finish128().as_u128())) + } else { + Err(io::ErrorKind::NotFound.into()) + } + } +} + +impl SystemWorld { + /// Search for fonts in the linux system font directories. + #[cfg(all(unix, not(target_os = "macos")))] + fn search_system(&mut self) { + self.search_dir("/usr/share/fonts"); + self.search_dir("/usr/local/share/fonts"); + + if let Some(dir) = dirs::font_dir() { + self.search_dir(dir); + } + } + + /// Search for fonts in the macOS system font directories. + #[cfg(target_os = "macos")] + fn search_system(&mut self) { + self.search_dir("/Library/Fonts"); + self.search_dir("/Network/Library/Fonts"); + self.search_dir("/System/Library/Fonts"); + + if let Some(dir) = dirs::font_dir() { + self.search_dir(dir); + } + } + + /// Search for fonts in the Windows system font directories. + #[cfg(windows)] + fn search_system(&mut self) { + let windir = + std::env::var("WINDIR").unwrap_or_else(|_| "C:\\Windows".to_string()); + + self.search_dir(Path::new(&windir).join("Fonts")); + + if let Some(roaming) = dirs::config_dir() { + self.search_dir(roaming.join("Microsoft\\Windows\\Fonts")); + } + + if let Some(local) = dirs::cache_dir() { + self.search_dir(local.join("Microsoft\\Windows\\Fonts")); + } + } + + /// Search for all fonts in a directory. + /// recursively. + fn search_dir(&mut self, path: impl AsRef) { + for entry in WalkDir::new(path) + .follow_links(true) + .sort_by(|a, b| a.file_name().cmp(b.file_name())) + .into_iter() + .filter_map(|e| e.ok()) + { + let path = entry.path(); + if matches!( + path.extension().and_then(|s| s.to_str()), + Some("ttf" | "otf" | "TTF" | "OTF" | "ttc" | "otc" | "TTC" | "OTC"), + ) { + self.search_file(path); + } + } + } + + /// Index the fonts in the file at the given path. + fn search_file(&mut self, path: impl AsRef) { + let path = path.as_ref(); + if let Ok(file) = File::open(path) { + if let Ok(mmap) = unsafe { Mmap::map(&file) } { + for (i, info) in FontInfo::from_data(&mmap).enumerate() { + self.book.push(info); + self.fonts.push(FontSlot { + path: path.into(), + index: i as u32, + font: OnceCell::new(), + }); + } + } + } + } +} + +impl<'a> codespan_reporting::files::Files<'a> for SystemWorld { + type FileId = SourceId; + type Name = std::path::Display<'a>; + type Source = &'a str; + + fn name(&'a self, id: SourceId) -> CodespanResult { + Ok(World::source(self, id).path().display()) + } + + fn source(&'a self, id: SourceId) -> CodespanResult { + Ok(World::source(self, id).text()) + } + + fn line_index(&'a self, id: SourceId, given: usize) -> CodespanResult { + let source = World::source(self, id); + source + .byte_to_line(given) + .ok_or_else(|| CodespanError::IndexTooLarge { + given, + max: source.len_bytes(), + }) + } + + fn line_range( + &'a self, + id: SourceId, + given: usize, + ) -> CodespanResult> { + let source = World::source(self, id); + source + .line_to_range(given) + .ok_or_else(|| CodespanError::LineTooLarge { given, max: source.len_lines() }) + } + + fn column_number( + &'a self, + id: SourceId, + _: usize, + given: usize, + ) -> CodespanResult { + let source = World::source(self, id); + source.byte_to_column(given).ok_or_else(|| { + let max = source.len_bytes(); + if given <= max { + CodespanError::InvalidCharBoundary { given } + } else { + CodespanError::IndexTooLarge { given, max } + } + }) + } +} diff --git a/src/model/content.rs b/src/model/content.rs index ae86e2790..8076eff9f 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -18,20 +18,20 @@ use crate::library::text::{ DecoNode, EmphNode, ParChild, ParNode, StrongNode, UNDERLINE, }; use crate::util::EcoString; +use crate::World; /// Layout content into a collection of pages. /// /// Relayouts until all pinned locations are converged. -pub fn layout(ctx: &mut Context, content: &Content) -> TypResult> { - let copy = ctx.config.styles.clone(); - let styles = StyleChain::with_root(©); +pub fn layout(world: &dyn World, content: &Content) -> TypResult> { + let styles = StyleChain::with_root(&world.config().styles); let scratch = Scratch::default(); - let mut builder = Builder::new(ctx, &scratch, true); + let mut builder = Builder::new(world, &scratch, true); builder.accept(content, styles)?; let (doc, shared) = builder.into_doc(styles)?; - doc.layout(ctx, shared) + doc.layout(world, shared) } /// Composable representation of styled content. @@ -232,15 +232,15 @@ impl Content { impl Layout for Content { fn layout( &self, - ctx: &mut Context, + world: &dyn World, regions: &Regions, styles: StyleChain, ) -> TypResult> { let scratch = Scratch::default(); - let mut builder = Builder::new(ctx, &scratch, false); + let mut builder = Builder::new(world, &scratch, false); builder.accept(self, styles)?; let (flow, shared) = builder.into_flow(styles)?; - flow.layout(ctx, regions, shared) + flow.layout(world, regions, shared) } fn pack(self) -> LayoutNode { @@ -330,9 +330,9 @@ impl Sum for Content { } /// Builds a document or a flow node from content. -struct Builder<'a, 'ctx> { +struct Builder<'a, 'w> { /// The core context. - ctx: &'ctx mut Context, + world: &'w dyn World, /// Scratch arenas for building. scratch: &'a Scratch<'a>, /// The current document building state. @@ -354,10 +354,10 @@ struct Scratch<'a> { templates: Arena, } -impl<'a, 'ctx> Builder<'a, 'ctx> { - fn new(ctx: &'ctx mut Context, scratch: &'a Scratch<'a>, top: bool) -> Self { +impl<'a, 'w> Builder<'a, 'w> { + fn new(world: &'w dyn World, scratch: &'a Scratch<'a>, top: bool) -> Self { Self { - ctx, + world, scratch, doc: top.then(|| DocBuilder::default()), flow: FlowBuilder::default(), @@ -388,7 +388,7 @@ impl<'a, 'ctx> Builder<'a, 'ctx> { match content { Content::Empty => return Ok(()), Content::Text(text) => { - if let Some(realized) = styles.apply(self.ctx, Target::Text(text))? { + if let Some(realized) = styles.apply(self.world, Target::Text(text))? { let stored = self.scratch.templates.alloc(realized); return self.accept(stored, styles); } @@ -431,7 +431,7 @@ impl<'a, 'ctx> Builder<'a, 'ctx> { } fn show(&mut self, node: &ShowNode, styles: StyleChain<'a>) -> TypResult<()> { - if let Some(mut realized) = styles.apply(self.ctx, Target::Node(node))? { + if let Some(mut realized) = styles.apply(self.world, Target::Node(node))? { let mut map = StyleMap::new(); let barrier = Barrier::new(node.id()); map.push(StyleEntry::Barrier(barrier)); diff --git a/src/model/layout.rs b/src/model/layout.rs index d712a1781..911cb4d5d 100644 --- a/src/model/layout.rs +++ b/src/model/layout.rs @@ -15,7 +15,7 @@ use crate::geom::{ use crate::library::graphics::MoveNode; use crate::library::layout::{AlignNode, PadNode}; use crate::util::Prehashed; -use crate::Context; +use crate::World; /// A node that can be layouted into a sequence of regions. /// @@ -24,7 +24,7 @@ pub trait Layout: 'static { /// Layout this node into the given regions, producing frames. fn layout( &self, - ctx: &mut Context, + world: &dyn World, regions: &Regions, styles: StyleChain, ) -> TypResult>; @@ -216,14 +216,14 @@ impl LayoutNode { impl Layout for LayoutNode { fn layout( &self, - ctx: &mut Context, + world: &dyn World, regions: &Regions, styles: StyleChain, ) -> TypResult> { let barrier = StyleEntry::Barrier(Barrier::new(self.id())); let styles = barrier.chain(&styles); - let mut frames = self.0.layout(ctx, regions, styles)?; + let mut frames = self.0.layout(world, regions, styles)?; if let Some(role) = styles.role() { for frame in &mut frames { frame.apply_role(role); @@ -285,7 +285,7 @@ struct EmptyNode; impl Layout for EmptyNode { fn layout( &self, - _: &mut Context, + _: &dyn World, regions: &Regions, _: StyleChain, ) -> TypResult> { @@ -307,7 +307,7 @@ struct SizedNode { impl Layout for SizedNode { fn layout( &self, - ctx: &mut Context, + world: &dyn World, regions: &Regions, styles: StyleChain, ) -> TypResult> { @@ -331,7 +331,7 @@ impl Layout for SizedNode { }; // Layout the child. - let mut frames = self.child.layout(ctx, &pod, styles)?; + let mut frames = self.child.layout(world, &pod, styles)?; // Ensure frame size matches regions size if expansion is on. let frame = &mut frames[0]; @@ -354,11 +354,11 @@ struct FillNode { impl Layout for FillNode { fn layout( &self, - ctx: &mut Context, + world: &dyn World, regions: &Regions, styles: StyleChain, ) -> TypResult> { - let mut frames = self.child.layout(ctx, regions, styles)?; + let mut frames = self.child.layout(world, regions, styles)?; for frame in &mut frames { let shape = Geometry::Rect(frame.size()).filled(self.fill); frame.prepend(Point::zero(), Element::Shape(shape)); @@ -379,11 +379,11 @@ struct StrokeNode { impl Layout for StrokeNode { fn layout( &self, - ctx: &mut Context, + world: &dyn World, regions: &Regions, styles: StyleChain, ) -> TypResult> { - let mut frames = self.child.layout(ctx, regions, styles)?; + let mut frames = self.child.layout(world, regions, styles)?; for frame in &mut frames { let shape = Geometry::Rect(frame.size()).stroked(self.stroke); frame.prepend(Point::zero(), Element::Shape(shape)); diff --git a/src/model/recipe.rs b/src/model/recipe.rs index 6261e7045..f5ca2cb96 100644 --- a/src/model/recipe.rs +++ b/src/model/recipe.rs @@ -5,7 +5,7 @@ use crate::diag::TypResult; use crate::eval::{Args, Func, Regex, Value}; use crate::library::structure::{EnumNode, ListNode}; use crate::syntax::Spanned; -use crate::Context; +use crate::World; /// A show rule recipe. #[derive(Clone, PartialEq, Hash)] @@ -29,7 +29,7 @@ impl Recipe { /// Try to apply the recipe to the target. pub fn apply( &self, - ctx: &mut Context, + world: &dyn World, styles: StyleChain, sel: Selector, target: Target, @@ -37,7 +37,7 @@ impl Recipe { let content = match (target, &self.pattern) { (Target::Node(node), &Pattern::Node(id)) if node.id() == id => { let node = node.unguard(sel); - self.call(ctx, || { + self.call(world, || { let dict = node.encode(styles); Value::Content(Content::Show(node, Some(dict))) })? @@ -53,7 +53,7 @@ impl Recipe { result.push(Content::Text(text[cursor .. start].into())); } - result.push(self.call(ctx, || Value::Str(mat.as_str().into()))?); + result.push(self.call(world, || Value::Str(mat.as_str().into()))?); cursor = mat.end(); } @@ -75,7 +75,7 @@ impl Recipe { } /// Call the recipe function, with the argument if desired. - fn call(&self, ctx: &mut Context, arg: F) -> TypResult + fn call(&self, world: &dyn World, arg: F) -> TypResult where F: FnOnce() -> Value, { @@ -85,7 +85,7 @@ impl Recipe { Args::new(self.func.span, [arg()]) }; - Ok(self.func.v.call_detached(ctx, args)?.display()) + Ok(self.func.v.call_detached(world, args)?.display()) } /// What kind of structure the property interrupts. diff --git a/src/model/show.rs b/src/model/show.rs index ac73cb769..e8d279778 100644 --- a/src/model/show.rs +++ b/src/model/show.rs @@ -6,7 +6,7 @@ use super::{Content, NodeId, Selector, StyleChain}; use crate::diag::TypResult; use crate::eval::Dict; use crate::util::Prehashed; -use crate::Context; +use crate::World; /// A node that can be realized given some styles. pub trait Show: 'static { @@ -18,7 +18,7 @@ pub trait Show: 'static { /// The base recipe for this node that is executed if there is no /// user-defined show rule. - fn realize(&self, ctx: &mut Context, styles: StyleChain) -> TypResult; + fn realize(&self, world: &dyn World, styles: StyleChain) -> TypResult; /// Finalize this node given the realization of a base or user recipe. Use /// this for effects that should work even in the face of a user-defined @@ -30,7 +30,7 @@ pub trait Show: 'static { #[allow(unused_variables)] fn finalize( &self, - ctx: &mut Context, + world: &dyn World, styles: StyleChain, realized: Content, ) -> TypResult { @@ -74,17 +74,17 @@ impl Show for ShowNode { self.0.encode(styles) } - fn realize(&self, ctx: &mut Context, styles: StyleChain) -> TypResult { - self.0.realize(ctx, styles) + fn realize(&self, world: &dyn World, styles: StyleChain) -> TypResult { + self.0.realize(world, styles) } fn finalize( &self, - ctx: &mut Context, + world: &dyn World, styles: StyleChain, realized: Content, ) -> TypResult { - self.0.finalize(ctx, styles, realized) + self.0.finalize(world, styles, realized) } fn pack(self) -> ShowNode { diff --git a/src/model/styles.rs b/src/model/styles.rs index eab334028..53ef926c9 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -8,7 +8,7 @@ use crate::diag::TypResult; use crate::frame::Role; use crate::library::text::{FontFamily, TextNode}; use crate::util::ReadableTypeId; -use crate::Context; +use crate::World; /// A map of style properties. #[derive(Default, Clone, PartialEq, Hash)] @@ -277,7 +277,7 @@ impl<'a> StyleChain<'a> { } /// Apply show recipes in this style chain to a target. - pub fn apply(self, ctx: &mut Context, target: Target) -> TypResult> { + pub fn apply(self, world: &dyn World, target: Target) -> TypResult> { // Find out how many recipes there any and whether any of their patterns // match. let mut n = 0; @@ -296,7 +296,9 @@ impl<'a> StyleChain<'a> { let sel = Selector::Nth(n); if self.guarded(sel) { guarded = true; - } else if let Some(content) = recipe.apply(ctx, self, sel, target)? { + } else if let Some(content) = + recipe.apply(world, self, sel, target)? + { realized = Some(content); break; } @@ -312,7 +314,7 @@ impl<'a> StyleChain<'a> { if self.guarded(sel) { guarded = true; } else { - let content = node.unguard(sel).realize(ctx, self)?; + let content = node.unguard(sel).realize(world, self)?; realized = Some(content.styled_with_entry(StyleEntry::Guard(sel))); } } @@ -320,7 +322,7 @@ impl<'a> StyleChain<'a> { // Finalize only if guarding didn't stop any recipe. if !guarded { if let Some(content) = realized { - realized = Some(node.finalize(ctx, self, content)?); + realized = Some(node.finalize(world, self, content)?); } } } diff --git a/src/parse/incremental.rs b/src/parse/incremental.rs index 60f780b46..9272dcfc4 100644 --- a/src/parse/incremental.rs +++ b/src/parse/incremental.rs @@ -15,21 +15,21 @@ use super::{ /// Returns the range in the new source that was ultimately reparsed. pub fn reparse( root: &mut SyntaxNode, - src: &str, + text: &str, replaced: Range, replacement_len: usize, ) -> Range { if let SyntaxNode::Inner(inner) = root { - let change = Change { src, replaced, replacement_len }; + let change = Change { text, replaced, replacement_len }; if let Some(range) = try_reparse(&change, Arc::make_mut(inner), 0, true, true) { return range; } } let id = root.span().source(); - *root = parse(src); + *root = parse(text); root.numberize(id, Span::FULL).unwrap(); - 0 .. src.len() + 0 .. text.len() } /// Try to reparse inside the given node. @@ -228,27 +228,27 @@ fn replace( let newborn_span = superseded_span.start .. newborn_end; let mut prefix = ""; - for (i, c) in change.src[.. newborn_span.start].char_indices().rev() { + for (i, c) in change.text[.. newborn_span.start].char_indices().rev() { if is_newline(c) { break; } - prefix = &change.src[i .. newborn_span.start]; + prefix = &change.text[i .. newborn_span.start]; } let (newborns, terminated, amount) = match mode { ReparseMode::Code => reparse_code_block( &prefix, - &change.src[newborn_span.start ..], + &change.text[newborn_span.start ..], newborn_span.len(), ), ReparseMode::Content => reparse_content_block( &prefix, - &change.src[newborn_span.start ..], + &change.text[newborn_span.start ..], newborn_span.len(), ), ReparseMode::MarkupElements { at_start, min_indent } => reparse_markup_elements( &prefix, - &change.src[newborn_span.start ..], + &change.text[newborn_span.start ..], newborn_span.len(), differential, &node.children().as_slice()[superseded_start ..], @@ -272,7 +272,7 @@ fn replace( /// A description of a change. struct Change<'a> { /// The new source code, with the change applied. - src: &'a str, + text: &'a str, /// Which range in the old source file was changed. replaced: Range, /// How many characters replaced the text in `replaced`. @@ -396,7 +396,7 @@ mod tests { fn test(prev: &str, range: Range, with: &str, goal: Range) { let mut source = Source::detached(prev); let range = source.edit(range, with); - check(source.src(), source.root(), &parse(source.src())); + check(source.text(), source.root(), &parse(source.text())); assert_eq!(range, goal); } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 0737d4caf..a8dee7f38 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -16,15 +16,15 @@ use crate::syntax::{NodeKind, SpanPos, SyntaxNode}; use crate::util::EcoString; /// Parse a source file. -pub fn parse(src: &str) -> SyntaxNode { - let mut p = Parser::new(src, TokenMode::Markup); +pub fn parse(text: &str) -> SyntaxNode { + let mut p = Parser::new(text, TokenMode::Markup); markup(&mut p, true); p.finish().into_iter().next().unwrap() } /// Parse code directly, only used for syntax highlighting. -pub fn parse_code(src: &str) -> Vec { - let mut p = Parser::new(src, TokenMode::Code); +pub fn parse_code(text: &str) -> Vec { + let mut p = Parser::new(text, TokenMode::Code); code(&mut p); p.finish() } @@ -34,10 +34,10 @@ pub fn parse_code(src: &str) -> Vec { /// Returns `Some` if all of the input was consumed. fn reparse_code_block( prefix: &str, - src: &str, + text: &str, end_pos: usize, ) -> Option<(Vec, bool, usize)> { - let mut p = Parser::with_prefix(prefix, src, TokenMode::Code); + let mut p = Parser::with_prefix(prefix, text, TokenMode::Code); if !p.at(NodeKind::LeftBrace) { return None; } @@ -58,10 +58,10 @@ fn reparse_code_block( /// Returns `Some` if all of the input was consumed. fn reparse_content_block( prefix: &str, - src: &str, + text: &str, end_pos: usize, ) -> Option<(Vec, bool, usize)> { - let mut p = Parser::with_prefix(prefix, src, TokenMode::Code); + let mut p = Parser::with_prefix(prefix, text, TokenMode::Code); if !p.at(NodeKind::LeftBracket) { return None; } @@ -82,14 +82,14 @@ fn reparse_content_block( /// Returns `Some` if all of the input was consumed. fn reparse_markup_elements( prefix: &str, - src: &str, + text: &str, end_pos: usize, differential: isize, reference: &[SyntaxNode], mut at_start: bool, min_indent: usize, ) -> Option<(Vec, bool, usize)> { - let mut p = Parser::with_prefix(prefix, src, TokenMode::Markup); + let mut p = Parser::with_prefix(prefix, text, TokenMode::Markup); let mut node: Option<&SyntaxNode> = None; let mut iter = reference.iter(); @@ -996,12 +996,12 @@ mod tests { use std::fmt::Debug; #[track_caller] - pub fn check(src: &str, found: T, expected: T) + pub fn check(text: &str, found: T, expected: T) where T: Debug + PartialEq, { if found != expected { - println!("source: {src:?}"); + println!("source: {text:?}"); println!("expected: {expected:#?}"); println!("found: {found:#?}"); panic!("test failed"); diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 685f1e695..90b6e610d 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -30,15 +30,15 @@ pub struct Parser<'s> { impl<'s> Parser<'s> { /// Create a new parser for the source string. - pub fn new(src: &'s str, mode: TokenMode) -> Self { - Self::with_prefix("", src, mode) + pub fn new(text: &'s str, mode: TokenMode) -> Self { + Self::with_prefix("", text, mode) } /// Create a new parser for the source string that is prefixed by some text /// that does not need to be parsed but taken into account for column /// calculation. - pub fn with_prefix(prefix: &str, src: &'s str, mode: TokenMode) -> Self { - let mut tokens = Tokens::with_prefix(prefix, src, mode); + pub fn with_prefix(prefix: &str, text: &'s str, mode: TokenMode) -> Self { + let mut tokens = Tokens::with_prefix(prefix, text, mode); let current = tokens.next(); Self { tokens, diff --git a/src/parse/resolve.rs b/src/parse/resolve.rs index 6fab9f21d..bce2da3cc 100644 --- a/src/parse/resolve.rs +++ b/src/parse/resolve.rs @@ -23,7 +23,7 @@ pub fn resolve_string(string: &str) -> EcoString { Some('r') => out.push('\r'), Some('t') => out.push('\t'), Some('u') if s.eat_if('{') => { - // TODO: Feedback if closing brace is missing. + // TODO: Error if closing brace is missing. let sequence = s.eat_while(char::is_ascii_hexdigit); let _terminated = s.eat_if('}'); @@ -33,7 +33,7 @@ pub fn resolve_string(string: &str) -> EcoString { } } - // TODO: Feedback about invalid escape sequence. + // TODO: Error for invalid escape sequence. _ => out.push_str(s.from(start)), } } diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index 67d413fd0..84a63ba13 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -34,16 +34,16 @@ pub enum TokenMode { impl<'s> Tokens<'s> { /// Create a new token iterator with the given mode. #[inline] - pub fn new(src: &'s str, mode: TokenMode) -> Self { - Self::with_prefix("", src, mode) + pub fn new(text: &'s str, mode: TokenMode) -> Self { + Self::with_prefix("", text, mode) } /// Create a new token iterator with the given mode and a prefix to offset /// column calculations. #[inline] - pub fn with_prefix(prefix: &str, src: &'s str, mode: TokenMode) -> Self { + pub fn with_prefix(prefix: &str, text: &'s str, mode: TokenMode) -> Self { Self { - s: Scanner::new(src), + s: Scanner::new(text), mode, terminated: true, column_offset: column(prefix, prefix.len(), 0), @@ -770,9 +770,9 @@ mod tests { t!(Markup $($tts)*); t!(Code $($tts)*); }; - ($mode:ident $([$blocks:literal])?: $src:expr => $($token:expr),*) => {{ + ($mode:ident $([$blocks:literal])?: $text:expr => $($token:expr),*) => {{ // Test without suffix. - t!(@$mode: $src => $($token),*); + t!(@$mode: $text => $($token),*); // Suffixes described by four-tuples of: // @@ -810,21 +810,21 @@ mod tests { // Test with each applicable suffix. for &(block, mode, suffix, ref token) in suffixes { - let src = $src; + let text = $text; #[allow(unused_variables)] let blocks = BLOCKS; $(let blocks = $blocks;)? assert!(!blocks.contains(|c| !BLOCKS.contains(c))); if (mode.is_none() || mode == Some($mode)) && blocks.contains(block) { - t!(@$mode: format!("{}{}", src, suffix) => $($token,)* token); + t!(@$mode: format!("{}{}", text, suffix) => $($token,)* token); } } }}; - (@$mode:ident: $src:expr => $($token:expr),*) => {{ - let src = $src; - let found = Tokens::new(&src, $mode).collect::>(); + (@$mode:ident: $text:expr => $($token:expr),*) => {{ + let text = $text; + let found = Tokens::new(&text, $mode).collect::>(); let expected = vec![$($token.clone()),*]; - check(&src, found, expected); + check(&text, found, expected); }}; } diff --git a/src/source.rs b/src/source.rs index 24d830ad6..c3648e11d 100644 --- a/src/source.rs +++ b/src/source.rs @@ -1,158 +1,16 @@ //! Source file management. -use std::collections::HashMap; -use std::io; use std::ops::Range; use std::path::{Path, PathBuf}; -use std::sync::Arc; use unscanny::Scanner; -use crate::diag::{failed_to_load, StrResult, TypResult}; -use crate::loading::{FileHash, Loader}; +use crate::diag::TypResult; use crate::parse::{is_newline, parse, reparse}; use crate::syntax::ast::Markup; use crate::syntax::{Span, SyntaxNode}; use crate::util::{PathExt, StrExt}; -#[cfg(feature = "codespan-reporting")] -use codespan_reporting::files::{self, Files}; - -/// A unique identifier for a loaded source file. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct SourceId(u16); - -impl SourceId { - /// Create a new source id for a file that is not part of a store. - pub const fn detached() -> Self { - Self(u16::MAX) - } - - /// Create a source id from the raw underlying value. - /// - /// This should only be called with values returned by - /// [`into_raw`](Self::into_raw). - pub const fn from_raw(v: u16) -> Self { - Self(v) - } - - /// Convert into the raw underlying value. - pub const fn into_raw(self) -> u16 { - self.0 - } -} - -/// Storage for loaded source files. -pub struct SourceStore { - loader: Arc, - files: HashMap, - sources: Vec, -} - -impl SourceStore { - /// Create a new, empty source store. - pub fn new(loader: Arc) -> Self { - Self { - loader, - files: HashMap::new(), - sources: vec![], - } - } - - /// Get a reference to a loaded source file. - /// - /// This panics if no source file with this `id` exists. This function - /// should only be called with ids returned by this store's - /// [`load()`](Self::load) and [`provide()`](Self::provide) methods. - #[track_caller] - pub fn get(&self, id: SourceId) -> &Source { - &self.sources[id.0 as usize] - } - - /// Load a source file from a path relative to the compilation environment's - /// root. - /// - /// If there already exists a source file for this path, it is - /// [replaced](Source::replace). - pub fn load(&mut self, path: &Path) -> StrResult { - let mut try_load = || -> io::Result { - let hash = self.loader.resolve(path)?; - if let Some(&id) = self.files.get(&hash) { - return Ok(id); - } - - let data = self.loader.file(path)?; - let src = String::from_utf8(data.to_vec()).map_err(|_| { - io::Error::new(io::ErrorKind::InvalidData, "file is not valid utf-8") - })?; - - Ok(self.provide(path, src)) - }; - - try_load().map_err(|err| failed_to_load("source file", path, err)) - } - - /// Directly provide a source file. - /// - /// The `path` does not need to be [resolvable](Loader::resolve) through the - /// `loader`. If it is though, imports that resolve to the same file hash - /// will use the inserted file instead of going through [`Loader::file`]. - /// - /// If the path is resolvable and points to an existing source file, it is - /// [replaced](Source::replace). - pub fn provide(&mut self, path: impl AsRef, src: String) -> SourceId { - let path = path.as_ref(); - let hash = self.loader.resolve(path).ok(); - - // Check for existing file and replace if one exists. - if let Some(&id) = hash.and_then(|hash| self.files.get(&hash)) { - self.replace(id, src); - return id; - } - - // No existing file yet, so we allocate a new id. - let id = SourceId(self.sources.len() as u16); - self.sources.push(Source::new(id, path, src)); - - // Register in file map if the path was known to the loader. - if let Some(hash) = hash { - self.files.insert(hash, id); - } - - id - } - - /// Fully [replace](Source::replace) the source text of a file. - /// - /// This panics if no source file with this `id` exists. - #[track_caller] - pub fn replace(&mut self, id: SourceId, src: String) { - self.sources[id.0 as usize].replace(src) - } - - /// [Edit](Source::edit) a source file by replacing the given range. - /// - /// This panics if no source file with this `id` exists or if the `replace` - /// range is out of bounds. - #[track_caller] - pub fn edit( - &mut self, - id: SourceId, - replace: Range, - with: &str, - ) -> Range { - self.sources[id.0 as usize].edit(replace, with) - } - - /// Map a span that points into a [file](Source::range) stored in this - /// source store to a byte range. - /// - /// Panics if the span does not point into this source store. - pub fn range(&self, span: Span) -> Range { - self.get(span.source()).range(span) - } -} - /// A single source file. /// /// _Note_: All line and column indices start at zero, just like byte indices. @@ -160,7 +18,7 @@ impl SourceStore { pub struct Source { id: SourceId, path: PathBuf, - src: String, + text: String, lines: Vec, root: SyntaxNode, rev: usize, @@ -168,32 +26,32 @@ pub struct Source { impl Source { /// Create a new source file. - pub fn new(id: SourceId, path: &Path, src: String) -> Self { + pub fn new(id: SourceId, path: &Path, text: String) -> Self { let lines = std::iter::once(Line { byte_idx: 0, utf16_idx: 0 }) - .chain(lines(0, 0, &src)) + .chain(lines(0, 0, &text)) .collect(); - let mut root = parse(&src); + let mut root = parse(&text); root.numberize(id, Span::FULL).unwrap(); Self { id, path: path.normalize(), root, - src, + text, lines, rev: 0, } } /// Create a source file without a real id and path, usually for testing. - pub fn detached(src: impl Into) -> Self { - Self::new(SourceId::detached(), Path::new(""), src.into()) + pub fn detached(text: impl Into) -> Self { + Self::new(SourceId::detached(), Path::new(""), text.into()) } /// Create a source file with the same synthetic span for all nodes. - pub fn synthesized(src: impl Into, span: Span) -> Self { - let mut file = Self::detached(src); + pub fn synthesized(text: impl Into, span: Span) -> Self { + let mut file = Self::detached(text); file.root.synthesize(span); file.id = span.source(); file @@ -225,8 +83,8 @@ impl Source { } /// The whole source as a string slice. - pub fn src(&self) -> &str { - &self.src + pub fn text(&self) -> &str { + &self.text } /// The revision number of the file. @@ -239,15 +97,15 @@ impl Source { /// Slice out the part of the source code enclosed by the range. pub fn get(&self, range: Range) -> Option<&str> { - self.src.get(range) + self.text.get(range) } /// Fully replace the source text and increase the revision number. - pub fn replace(&mut self, src: String) { - self.src = src; + pub fn replace(&mut self, text: String) { + self.text = text; self.lines = vec![Line { byte_idx: 0, utf16_idx: 0 }]; - self.lines.extend(lines(0, 0, &self.src)); - self.root = parse(&self.src); + self.lines.extend(lines(0, 0, &self.text)); + self.root = parse(&self.text); self.root.numberize(self.id(), Span::FULL).unwrap(); self.rev = self.rev.wrapping_add(1); } @@ -263,34 +121,34 @@ impl Source { let start_byte = replace.start; let start_utf16 = self.byte_to_utf16(replace.start).unwrap(); - self.src.replace_range(replace.clone(), with); + self.text.replace_range(replace.clone(), with); // Remove invalidated line starts. let line = self.byte_to_line(start_byte).unwrap(); self.lines.truncate(line + 1); // Handle adjoining of \r and \n. - if self.src[.. start_byte].ends_with('\r') && with.starts_with('\n') { + if self.text[.. start_byte].ends_with('\r') && with.starts_with('\n') { self.lines.pop(); } // Recalculate the line starts after the edit. self.lines - .extend(lines(start_byte, start_utf16, &self.src[start_byte ..])); + .extend(lines(start_byte, start_utf16, &self.text[start_byte ..])); // Incrementally reparse the replaced range. - reparse(&mut self.root, &self.src, replace, with.len()) + reparse(&mut self.root, &self.text, replace, with.len()) } /// Get the length of the file in UTF-8 encoded bytes. pub fn len_bytes(&self) -> usize { - self.src.len() + self.text.len() } /// Get the length of the file in UTF-16 code units. pub fn len_utf16(&self) -> usize { let last = self.lines.last().unwrap(); - last.utf16_idx + self.src[last.byte_idx ..].len_utf16() + last.utf16_idx + self.text[last.byte_idx ..].len_utf16() } /// Get the length of the file in lines. @@ -311,13 +169,13 @@ impl Source { pub fn byte_to_utf16(&self, byte_idx: usize) -> Option { let line_idx = self.byte_to_line(byte_idx)?; let line = self.lines.get(line_idx)?; - let head = self.src.get(line.byte_idx .. byte_idx)?; + let head = self.text.get(line.byte_idx .. byte_idx)?; Some(line.utf16_idx + head.len_utf16()) } /// Return the index of the line that contains the given byte index. pub fn byte_to_line(&self, byte_idx: usize) -> Option { - (byte_idx <= self.src.len()).then(|| { + (byte_idx <= self.text.len()).then(|| { match self.lines.binary_search_by_key(&byte_idx, |line| line.byte_idx) { Ok(i) => i, Err(i) => i - 1, @@ -346,14 +204,14 @@ impl Source { )?; let mut k = line.utf16_idx; - for (i, c) in self.src[line.byte_idx ..].char_indices() { + for (i, c) in self.text[line.byte_idx ..].char_indices() { if k >= utf16_idx { return Some(line.byte_idx + i); } k += c.len_utf16(); } - (k == utf16_idx).then(|| self.src.len()) + (k == utf16_idx).then(|| self.text.len()) } @@ -365,7 +223,7 @@ impl Source { /// Return the range which encloses the given line. pub fn line_to_range(&self, line_idx: usize) -> Option> { let start = self.line_to_byte(line_idx)?; - let end = self.line_to_byte(line_idx + 1).unwrap_or(self.src.len()); + let end = self.line_to_byte(line_idx + 1).unwrap_or(self.text.len()); Some(start .. end) } @@ -388,6 +246,30 @@ impl Source { } } +/// A unique identifier for a loaded source file. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct SourceId(u16); + +impl SourceId { + /// Create a new source id for a file that is not part of a store. + pub const fn detached() -> Self { + Self(u16::MAX) + } + + /// Create a source id from the raw underlying value. + /// + /// This should only be called with values returned by + /// [`into_raw`](Self::into_raw). + pub const fn from_raw(v: u16) -> Self { + Self(v) + } + + /// Convert into the raw underlying value. + pub const fn into_raw(self) -> u16 { + self.0 + } +} + /// Metadata about a line. #[derive(Debug, Copy, Clone, Eq, PartialEq)] struct Line { @@ -401,9 +283,9 @@ struct Line { fn lines( byte_offset: usize, utf16_offset: usize, - string: &str, + text: &str, ) -> impl Iterator + '_ { - let mut s = Scanner::new(string); + let mut s = Scanner::new(text); let mut utf16_idx = utf16_offset; std::iter::from_fn(move || { @@ -427,56 +309,6 @@ fn lines( }) } -#[cfg(feature = "codespan-reporting")] -impl<'a> Files<'a> for SourceStore { - type FileId = SourceId; - type Name = std::path::Display<'a>; - type Source = &'a str; - - fn name(&'a self, id: SourceId) -> Result { - Ok(self.get(id).path().display()) - } - - fn source(&'a self, id: SourceId) -> Result { - Ok(self.get(id).src()) - } - - fn line_index(&'a self, id: SourceId, given: usize) -> Result { - let source = self.get(id); - source - .byte_to_line(given) - .ok_or_else(|| files::Error::IndexTooLarge { given, max: source.len_bytes() }) - } - - fn line_range( - &'a self, - id: SourceId, - given: usize, - ) -> Result, files::Error> { - let source = self.get(id); - source - .line_to_range(given) - .ok_or_else(|| files::Error::LineTooLarge { given, max: source.len_lines() }) - } - - fn column_number( - &'a self, - id: SourceId, - _: usize, - given: usize, - ) -> Result { - let source = self.get(id); - source.byte_to_column(given).ok_or_else(|| { - let max = source.len_bytes(); - if given <= max { - files::Error::InvalidCharBoundary { given } - } else { - files::Error::IndexTooLarge { given, max } - } - }) - } -} - #[cfg(test)] mod tests { use super::*; @@ -563,7 +395,7 @@ mod tests { let mut source = Source::detached(prev); let result = Source::detached(after); source.edit(range, with); - assert_eq!(source.src, result.src); + assert_eq!(source.text, result.text); assert_eq!(source.root, result.root); assert_eq!(source.lines, result.lines); } diff --git a/src/syntax/highlight.rs b/src/syntax/highlight.rs index 6470e4fbd..7f5ee0834 100644 --- a/src/syntax/highlight.rs +++ b/src/syntax/highlight.rs @@ -384,10 +384,10 @@ mod tests { use Category::*; #[track_caller] - fn test(src: &str, goal: &[(Range, Category)]) { + fn test(text: &str, goal: &[(Range, Category)]) { let mut vec = vec![]; - let source = Source::detached(src); - let full = 0 .. src.len(); + let source = Source::detached(text); + let full = 0 .. text.len(); highlight_node(source.root(), full, &mut |range, category| { vec.push((range, category)); }); diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index eb070a046..c2b06efe5 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -980,7 +980,7 @@ impl NodeKind { Self::LineComment => "line comment", Self::BlockComment => "block comment", Self::Error(_, _) => "parse error", - Self::Unknown(src) => match src.as_str() { + Self::Unknown(text) => match text.as_str() { "*/" => "end of block comment", _ => "invalid token", }, @@ -1107,7 +1107,7 @@ impl Hash for NodeKind { Self::LineComment => {} Self::BlockComment => {} Self::Error(pos, msg) => (pos, msg).hash(state), - Self::Unknown(src) => src.hash(state), + Self::Unknown(text) => text.hash(state), } } } diff --git a/src/syntax/span.rs b/src/syntax/span.rs index 3f66efa7a..4eae4124c 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -42,8 +42,8 @@ impl Debug for Spanned { /// A unique identifier for a syntax node. /// /// This is used throughout the compiler to track which source section an error -/// or element stems from. Can be [mapped back](crate::source::SourceStore::range) -/// to a source id + byte range for user facing display. +/// or element stems from. Can be [mapped back](crate::source::Source::range) +/// to a byte range for user facing display. /// /// Span ids are ordered in the tree to enable quickly finding the node with /// some id: diff --git a/src/util/buffer.rs b/src/util/buffer.rs new file mode 100644 index 000000000..daee86f94 --- /dev/null +++ b/src/util/buffer.rs @@ -0,0 +1,59 @@ +use std::fmt::{self, Debug, Formatter}; +use std::ops::Deref; +use std::sync::Arc; + +use super::Prehashed; + +/// A shared buffer that is cheap to clone and hash. +#[derive(Clone, Hash, Eq, PartialEq)] +pub struct Buffer(Prehashed>>); + +impl Buffer { + /// Return a view into the buffer. + pub fn as_slice(&self) -> &[u8] { + self + } + + /// Return a copy of the buffer as a vector. + pub fn to_vec(&self) -> Vec { + self.0.to_vec() + } +} + +impl From<&[u8]> for Buffer { + fn from(slice: &[u8]) -> Self { + Self(Prehashed::new(Arc::new(slice.to_vec()))) + } +} + +impl From> for Buffer { + fn from(vec: Vec) -> Self { + Self(Prehashed::new(Arc::new(vec))) + } +} + +impl From>> for Buffer { + fn from(arc: Arc>) -> Self { + Self(Prehashed::new(arc)) + } +} + +impl Deref for Buffer { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef<[u8]> for Buffer { + fn as_ref(&self) -> &[u8] { + self + } +} + +impl Debug for Buffer { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad("Buffer(..)") + } +} diff --git a/src/util/mod.rs b/src/util/mod.rs index e0ba312fa..4a48b1d32 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -2,8 +2,10 @@ #[macro_use] mod eco; +mod buffer; mod hash; +pub use buffer::Buffer; pub use eco::EcoString; pub use hash::Prehashed; diff --git a/tests/typ/coma.typ b/tests/typ/coma.typ index e312fc788..0e228d148 100644 --- a/tests/typ/coma.typ +++ b/tests/typ/coma.typ @@ -21,4 +21,4 @@ Die Tiefe eines Knotens _v_ ist die Länge des eindeutigen Weges von der Wurzel zu _v_, und die Höhe von _v_ ist die Länge eines längsten (absteigenden) Weges von _v_ zu einem Blatt. Die Höhe des Baumes ist die Höhe der Wurzel. -#align(center, image("/res/graph.png", width: 75%)) +#align(center, image("../res/graph.png", width: 75%)) diff --git a/tests/typeset.rs b/tests/typeset.rs index 4b25e1767..3ac353dbc 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -1,24 +1,31 @@ +use std::cell::RefCell; +use std::collections::{hash_map::Entry, HashMap}; use std::env; use std::ffi::OsStr; -use std::fs; +use std::fs::{self, File}; +use std::hash::Hash; +use std::io; use std::ops::Range; -use std::path::Path; -use std::sync::Arc; +use std::path::{Path, PathBuf}; +use elsa::FrozenVec; +use same_file::Handle; +use siphasher::sip128::{Hasher128, SipHasher}; use tiny_skia as sk; use unscanny::Scanner; use walkdir::WalkDir; use typst::eval::{Smart, Value}; +use typst::font::{Font, FontBook}; use typst::frame::{Element, Frame}; use typst::geom::{Length, RgbaColor, Sides}; use typst::library::layout::PageNode; use typst::library::text::{TextNode, TextSize}; -use typst::loading::FsLoader; use typst::model::StyleMap; -use typst::source::Source; +use typst::source::{Source, SourceId}; use typst::syntax::SyntaxNode; -use typst::{bail, Config, Context}; +use typst::util::Buffer; +use typst::{bail, Config, World}; const TYP_DIR: &str = "./typ"; const REF_DIR: &str = "./ref"; @@ -57,46 +64,8 @@ fn main() { println!("Running {len} tests"); } - // Set page width to 120pt with 10pt margins, so that the inner page is - // exactly 100pt wide. Page height is unbounded and font size is 10pt so - // that it multiplies to nice round numbers. - let mut styles = StyleMap::new(); - styles.set(PageNode::WIDTH, Smart::Custom(Length::pt(120.0).into())); - styles.set(PageNode::HEIGHT, Smart::Auto); - styles.set( - PageNode::MARGINS, - Sides::splat(Some(Smart::Custom(Length::pt(10.0).into()))), - ); - styles.set(TextNode::SIZE, TextSize(Length::pt(10.0).into())); - - // Hook up helpers into the global scope. - let mut std = typst::library::new(); - std.define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF)); - std.define("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF)); - std.def_fn("test", move |_, args| { - let lhs = args.expect::("left-hand side")?; - let rhs = args.expect::("right-hand side")?; - if lhs != rhs { - bail!(args.span, "Assertion failed: {:?} != {:?}", lhs, rhs,); - } - Ok(Value::None) - }); - std.def_fn("print", move |_, args| { - print!("> "); - for (i, value) in args.all::()?.into_iter().enumerate() { - if i > 0 { - print!(", ") - } - print!("{value:?}"); - } - println!(); - Ok(Value::None) - }); - // Create loader and context. - let loader = FsLoader::new().with_path(FONT_DIR); - let config = Config::builder().std(std).styles(styles).build(); - let mut ctx = Context::new(Arc::new(loader), config); + let mut world = TestWorld::new(args.print); // Run all the tests. let mut ok = 0; @@ -108,12 +77,11 @@ fn main() { args.pdf.then(|| Path::new(PDF_DIR).join(path).with_extension("pdf")); ok += test( - &mut ctx, + &mut world, &src_path, &png_path, &ref_path, pdf_path.as_deref(), - &args.print, ) as usize; } @@ -179,18 +147,166 @@ impl Args { } } +struct TestWorld { + config: Config, + print: PrintConfig, + sources: FrozenVec>, + nav: RefCell>, + book: FontBook, + fonts: Vec, + files: RefCell>, +} + +impl TestWorld { + fn new(print: PrintConfig) -> Self { + // Set page width to 120pt with 10pt margins, so that the inner page is + // exactly 100pt wide. Page height is unbounded and font size is 10pt so + // that it multiplies to nice round numbers. + let mut styles = StyleMap::new(); + styles.set(PageNode::WIDTH, Smart::Custom(Length::pt(120.0).into())); + styles.set(PageNode::HEIGHT, Smart::Auto); + styles.set( + PageNode::MARGINS, + Sides::splat(Some(Smart::Custom(Length::pt(10.0).into()))), + ); + styles.set(TextNode::SIZE, TextSize(Length::pt(10.0).into())); + + // Hook up helpers into the global scope. + let mut std = typst::library::new(); + std.define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF)); + std.define("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF)); + std.def_fn("test", move |_, args| { + let lhs = args.expect::("left-hand side")?; + let rhs = args.expect::("right-hand side")?; + if lhs != rhs { + bail!(args.span, "Assertion failed: {:?} != {:?}", lhs, rhs,); + } + Ok(Value::None) + }); + std.def_fn("print", move |_, args| { + print!("> "); + for (i, value) in args.all::()?.into_iter().enumerate() { + if i > 0 { + print!(", ") + } + print!("{value:?}"); + } + println!(); + Ok(Value::None) + }); + + let mut fonts = vec![]; + for entry in WalkDir::new(FONT_DIR) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|entry| entry.file_type().is_file()) + { + let buffer: Buffer = fs::read(entry.path()).unwrap().into(); + for index in 0 .. ttf_parser::fonts_in_collection(&buffer).unwrap_or(1) { + fonts.push(Font::new(buffer.clone(), index).unwrap()) + } + } + + Self { + config: Config { root: PathBuf::new(), std, styles }, + print, + sources: FrozenVec::new(), + nav: RefCell::new(HashMap::new()), + book: FontBook::from_fonts(&fonts), + fonts, + files: RefCell::new(HashMap::new()), + } + } + + fn provide(&mut self, path: &Path, text: String) -> SourceId { + let hash = PathHash::new(path).unwrap(); + if let Some(&id) = self.nav.borrow().get(&hash) { + self.sources.as_mut()[id.into_raw() as usize].replace(text); + return id; + } + + let id = SourceId::from_raw(self.sources.len() as u16); + let source = Source::new(id, path, text); + self.sources.push(Box::new(source)); + self.nav.borrow_mut().insert(hash, id); + id + } +} + +impl World for TestWorld { + fn config(&self) -> &Config { + &self.config + } + + fn resolve(&self, path: &Path) -> io::Result { + let hash = PathHash::new(path)?; + if let Some(&id) = self.nav.borrow().get(&hash) { + return Ok(id); + } + + let data = fs::read(path)?; + let text = String::from_utf8(data).map_err(|_| { + io::Error::new(io::ErrorKind::InvalidData, "file is not valid utf-8") + })?; + + let id = SourceId::from_raw(self.sources.len() as u16); + let source = Source::new(id, path, text); + self.sources.push(Box::new(source)); + self.nav.borrow_mut().insert(hash, id); + + Ok(id) + } + + fn source(&self, id: SourceId) -> &Source { + &self.sources[id.into_raw() as usize] + } + + fn book(&self) -> &FontBook { + &self.book + } + + fn font(&self, id: usize) -> io::Result { + Ok(self.fonts[id].clone()) + } + + fn file(&self, path: &Path) -> io::Result { + let hash = PathHash::new(path)?; + Ok(match self.files.borrow_mut().entry(hash) { + Entry::Occupied(entry) => entry.get().clone(), + Entry::Vacant(entry) => entry.insert(fs::read(path)?.into()).clone(), + }) + } +} + +/// A hash that is the same for all paths pointing to the same file. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +struct PathHash(u128); + +impl PathHash { + fn new(path: &Path) -> io::Result { + let file = File::open(path)?; + if file.metadata()?.is_file() { + let handle = Handle::from_file(file)?; + let mut state = SipHasher::new(); + handle.hash(&mut state); + Ok(Self(state.finish128().as_u128())) + } else { + Err(io::ErrorKind::NotFound.into()) + } + } +} + fn test( - ctx: &mut Context, + world: &mut TestWorld, src_path: &Path, png_path: &Path, ref_path: &Path, pdf_path: Option<&Path>, - print: &PrintConfig, ) -> bool { let name = src_path.strip_prefix(TYP_DIR).unwrap_or(src_path); println!("Testing {}", name.display()); - let src = fs::read_to_string(src_path).unwrap(); + let text = fs::read_to_string(src_path).unwrap(); let mut ok = true; let mut frames = vec![]; @@ -199,7 +315,7 @@ fn test( let mut compare_ever = false; let mut rng = LinearShift::new(); - let parts: Vec<_> = src.split("\n---").collect(); + let parts: Vec<_> = text.split("\n---").collect(); for (i, &part) in parts.iter().enumerate() { let is_header = i == 0 && parts.len() > 1 @@ -214,16 +330,8 @@ fn test( } } } else { - let (part_ok, compare_here, part_frames) = test_part( - ctx, - src_path, - part.into(), - i, - compare_ref, - line, - print, - &mut rng, - ); + let (part_ok, compare_here, part_frames) = + test_part(world, src_path, part.into(), i, compare_ref, line, &mut rng); ok &= part_ok; compare_ever |= compare_here; frames.extend(part_frames); @@ -239,7 +347,7 @@ fn test( fs::write(pdf_path, pdf_data).unwrap(); } - if print.frames { + if world.print.frames { for frame in &frames { println!("Frame: {:#?}", frame); } @@ -268,7 +376,7 @@ fn test( } if ok { - if *print == PrintConfig::default() { + if world.print == PrintConfig::default() { print!("\x1b[1A"); } println!("Testing {} ✔", name.display()); @@ -278,20 +386,19 @@ fn test( } fn test_part( - ctx: &mut Context, + world: &mut TestWorld, src_path: &Path, - src: String, + text: String, i: usize, compare_ref: bool, line: usize, - print: &PrintConfig, rng: &mut LinearShift, ) -> (bool, bool, Vec) { let mut ok = true; - let id = ctx.sources.provide(src_path, src); - let source = ctx.sources.get(id); - if print.syntax { + let id = world.provide(src_path, text); + let source = world.source(id); + if world.print.syntax { println!("Syntax Tree: {:#?}", source.root()) } @@ -299,9 +406,9 @@ fn test_part( let compare_ref = local_compare_ref.unwrap_or(compare_ref); ok &= test_spans(source.root()); - ok &= test_reparse(ctx.sources.get(id).src(), i, rng); + ok &= test_reparse(world.source(id).text(), i, rng); - let (mut frames, errors) = match typst::typeset(ctx, id) { + let (mut frames, errors) = match typst::typeset(world, id) { Ok(frames) => (frames, vec![]), Err(errors) => (vec![], *errors), }; @@ -317,7 +424,7 @@ fn test_part( .into_iter() .filter(|error| error.span.source() == id) .map(|error| { - let range = ctx.sources.range(error.span); + let range = world.source(error.span.source()).range(error.span); let msg = error.message.replace("\\", "/"); (range, msg) }) @@ -330,7 +437,7 @@ fn test_part( println!(" Subtest {i} does not match expected errors. ❌"); ok = false; - let source = ctx.sources.get(id); + let source = world.source(id); for error in errors.iter() { if !ref_errors.contains(error) { print!(" Not annotated | "); @@ -353,7 +460,7 @@ fn parse_metadata(source: &Source) -> (Option, Vec<(Range, String)> let mut compare_ref = None; let mut errors = vec![]; - let lines: Vec<_> = source.src().lines().map(str::trim).collect(); + let lines: Vec<_> = source.text().lines().map(str::trim).collect(); for (i, line) in lines.iter().enumerate() { if line.starts_with("// Ref: false") { compare_ref = Some(false); @@ -409,7 +516,7 @@ fn print_error(source: &Source, line: usize, (range, message): &(Range, S /// The method will first inject 10 strings once every 400 source characters /// and then select 5 leaf node boundries to inject an additional, randomly /// chosen string from the injection list. -fn test_reparse(src: &str, i: usize, rng: &mut LinearShift) -> bool { +fn test_reparse(text: &str, i: usize, rng: &mut LinearShift) -> bool { let supplements = [ "[", "]", @@ -441,19 +548,19 @@ fn test_reparse(src: &str, i: usize, rng: &mut LinearShift) -> bool { let mut ok = true; let apply = |replace: std::ops::Range, with| { - let mut incr_source = Source::detached(src); - if incr_source.root().len() != src.len() { + let mut incr_source = Source::detached(text); + if incr_source.root().len() != text.len() { println!( " Subtest {i} tree length {} does not match string length {} ❌", incr_source.root().len(), - src.len(), + text.len(), ); return false; } incr_source.edit(replace.clone(), with); - let edited_src = incr_source.src(); + let edited_src = incr_source.text(); let incr_root = incr_source.root(); let ref_source = Source::detached(edited_src); let ref_root = ref_source.root(); @@ -481,20 +588,20 @@ fn test_reparse(src: &str, i: usize, rng: &mut LinearShift) -> bool { (range.start as f64 + ratio * (range.end - range.start) as f64).floor() as usize }; - let insertions = (src.len() as f64 / 400.0).ceil() as usize; + let insertions = (text.len() as f64 / 400.0).ceil() as usize; for _ in 0 .. insertions { let supplement = supplements[pick(0 .. supplements.len())]; - let start = pick(0 .. src.len()); - let end = pick(start .. src.len()); + let start = pick(0 .. text.len()); + let end = pick(start .. text.len()); - if !src.is_char_boundary(start) || !src.is_char_boundary(end) { + if !text.is_char_boundary(start) || !text.is_char_boundary(end) { continue; } ok &= apply(start .. end, supplement); } - let source = Source::detached(src); + let source = Source::detached(text); let leafs = source.root().leafs(); let start = source.range(leafs[pick(0 .. leafs.len())].span()).start; let supplement = supplements[pick(0 .. supplements.len())];