diff --git a/benches/oneshot.rs b/benches/oneshot.rs index 6fd16d594..5c0fc701e 100644 --- a/benches/oneshot.rs +++ b/benches/oneshot.rs @@ -1,4 +1,5 @@ use std::path::Path; +use std::sync::Arc; use iai::{black_box, main, Iai}; use unscanny::Scanner; @@ -7,14 +8,14 @@ use typst::loading::MemLoader; use typst::parse::{parse, TokenMode, Tokens}; use typst::source::SourceId; use typst::syntax::highlight_node; -use typst::Context; +use typst::{Config, Context}; const SRC: &str = include_str!("bench.typ"); const FONT: &[u8] = include_bytes!("../fonts/IBMPlexSans-Regular.ttf"); fn context() -> (Context, SourceId) { - let loader = MemLoader::new().with(Path::new("font.ttf"), FONT).wrap(); - let mut ctx = Context::new(loader); + let loader = MemLoader::new().with(Path::new("font.ttf"), FONT); + let mut ctx = Context::new(Arc::new(loader), Config::default()); let id = ctx.sources.provide(Path::new("src.typ"), SRC.to_string()); (ctx, id) } diff --git a/src/font.rs b/src/font.rs index 9280ff8d5..34ce63897 100644 --- a/src/font.rs +++ b/src/font.rs @@ -73,6 +73,32 @@ impl FontStore { } } + /// An ordered iterator over all font families this loader knows and details + /// about the faces that are part of them. + pub fn families( + &self, + ) -> impl Iterator)> + '_ { + // Since the keys are lowercased, we instead use the family field of the + // first face's info. + let faces = self.loader.faces(); + self.families.values().map(|ids| { + let family = faces[ids[0].0 as usize].family.as_str(); + let infos = ids.iter().map(|&id| &faces[id.0 as usize]); + (family, infos) + }) + } + + /// Get a reference to a loaded face. + /// + /// This panics if the face with this `id` was not loaded. This function + /// should only be called with ids returned by this store's + /// [`select()`](Self::select) and + /// [`select_fallback()`](Self::select_fallback) methods. + #[track_caller] + pub fn get(&self, id: FaceId) -> &Face { + self.faces[id.0 as usize].as_ref().expect("font face was not loaded") + } + /// Try to find and load a font face from the given `family` that matches /// the given `variant` as closely as possible. pub fn select(&mut self, family: &str, variant: FontVariant) -> Option { @@ -200,32 +226,6 @@ impl FontStore { Some(id) } - - /// Get a reference to a loaded face. - /// - /// This panics if the face with this `id` was not loaded. This function - /// should only be called with ids returned by this store's - /// [`select()`](Self::select) and - /// [`select_fallback()`](Self::select_fallback) methods. - #[track_caller] - pub fn get(&self, id: FaceId) -> &Face { - self.faces[id.0 as usize].as_ref().expect("font face was not loaded") - } - - /// An ordered iterator over all font families this loader knows and details - /// about the faces that are part of them. - pub fn families( - &self, - ) -> impl Iterator)> + '_ { - // Since the keys are lowercased, we instead use the family field of the - // first face's info. - let faces = self.loader.faces(); - self.families.values().map(|ids| { - let family = faces[ids[0].0 as usize].family.as_str(); - let infos = ids.iter().map(|&id| &faces[id.0 as usize]); - (family, infos) - }) - } } /// How many words the two strings share in their prefix. diff --git a/src/image.rs b/src/image.rs index 87c093d38..1392ecf11 100644 --- a/src/image.rs +++ b/src/image.rs @@ -48,6 +48,16 @@ impl ImageStore { } } + /// Get a reference to a loaded image. + /// + /// This panics if no image with this `id` was loaded. This function should + /// only be called with ids returned by this store's [`load()`](Self::load) + /// method. + #[track_caller] + pub fn get(&self, id: ImageId) -> &Image { + &self.images[id.0 as usize] + } + /// Load and decode an image file from a path relative to the compilation /// environment's root. pub fn load(&mut self, path: &Path) -> io::Result { @@ -64,16 +74,6 @@ impl ImageStore { } }) } - - /// Get a reference to a loaded image. - /// - /// This panics if no image with this `id` was loaded. This function should - /// only be called with ids returned by this store's [`load()`](Self::load) - /// method. - #[track_caller] - pub fn get(&self, id: ImageId) -> &Image { - &self.images[id.0 as usize] - } } /// A loaded image. diff --git a/src/lib.rs b/src/lib.rs index 5173b0221..41487f87d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,22 +66,16 @@ use crate::model::StyleMap; use crate::source::{SourceId, SourceStore}; use crate::util::PathExt; -/// The core context which holds the loader, stores, and configuration. +/// The core context which holds the configuration and stores. pub struct Context { - /// The loader the context was created with. - pub loader: Arc, /// Stores loaded source files. pub sources: SourceStore, /// Stores parsed font faces. pub fonts: FontStore, /// Stores decoded images. pub images: ImageStore, - /// The compilation root. - root: PathBuf, - /// The standard library scope. - std: Arc, - /// The default styles. - styles: Arc, + /// The context's configuration. + pub config: Config, /// Cached modules. modules: HashMap, /// The stack of imported files that led to evaluation of the current file. @@ -93,24 +87,18 @@ pub struct Context { } impl Context { - /// Create a new context with the default settings. - pub fn new(loader: Arc) -> Self { - Self::builder().build(loader) - } - - /// Create a new context with advanced settings. - pub fn builder() -> ContextBuilder { - ContextBuilder::default() - } - - /// A read-only reference to the standard library scope. - pub fn std(&self) -> &Scope { - &self.std - } - - /// A read-only reference to the styles. - pub fn styles(&self) -> &StyleMap { - &self.styles + /// Create a new context. + pub fn new(loader: Arc, config: Config) -> Self { + Self { + sources: SourceStore::new(Arc::clone(&loader)), + fonts: FontStore::new(Arc::clone(&loader)), + images: ImageStore::new(loader), + config, + modules: HashMap::new(), + route: vec![], + deps: vec![], + flow: None, + } } /// Evaluate a source file and return the resulting module. @@ -144,7 +132,7 @@ impl Context { self.route.push(id); // Evaluate the module. - let std = self.std.clone(); + let std = self.config.std.clone(); let mut scp = Scopes::new(Some(&std)); let result = ast.eval(self, &mut scp); @@ -175,10 +163,10 @@ impl Context { /// Resolve a user-entered path to be relative to the compilation /// environment's root. - pub fn locate(&self, path: &str) -> StrResult { + fn locate(&self, path: &str) -> StrResult { if let Some(&id) = self.route.last() { if let Some(path) = path.strip_prefix('/') { - return Ok(self.root.join(path).normalize()); + return Ok(self.config.root.join(path).normalize()); } if let Some(dir) = self.sources.get(id).path().parent() { @@ -190,51 +178,70 @@ impl Context { } } -/// A builder for a [`Context`]. +/// Compilation configuration. +pub struct Config { + /// The compilation root. + 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() + } +} + +impl Default for Config { + fn default() -> Self { + Self::builder().build() + } +} + +/// A builder for a [`Config`]. /// -/// This struct is created by [`Context::builder`]. -#[derive(Default)] -pub struct ContextBuilder { +/// This struct is created by [`Config::builder`]. +#[derive(Debug, Default, Clone)] +pub struct ConfigBuilder { root: PathBuf, std: Option>, styles: Option>, } -impl ContextBuilder { +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 - /// (the standard library). + /// 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 context by providing the `loader` used to load - /// fonts, images, source files and other resources. - pub fn build(&self, loader: Arc) -> Context { - Context { - sources: SourceStore::new(Arc::clone(&loader)), - fonts: FontStore::new(Arc::clone(&loader)), - images: ImageStore::new(Arc::clone(&loader)), - loader, + /// 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(), - modules: HashMap::new(), - route: vec![], - deps: vec![], - flow: None, } } } diff --git a/src/library/utility/mod.rs b/src/library/utility/mod.rs index 355315e4c..c4e847c38 100644 --- a/src/library/utility/mod.rs +++ b/src/library/utility/mod.rs @@ -42,7 +42,7 @@ pub fn eval(ctx: &mut Context, args: &mut Args) -> TypResult { let prev_route = mem::take(&mut ctx.route); // Evaluate the source. - let std = ctx.std.clone(); + let std = ctx.config.std.clone(); let mut scp = Scopes::new(Some(&std)); let result = ast.eval(ctx, &mut scp); diff --git a/src/loading/fs.rs b/src/loading/fs.rs index 3398ebd37..23b67e34d 100644 --- a/src/loading/fs.rs +++ b/src/loading/fs.rs @@ -1,7 +1,6 @@ use std::fs::{self, File}; use std::io; use std::path::Path; -use std::sync::Arc; use memmap2::Mmap; use same_file::Handle; @@ -35,12 +34,6 @@ impl FsLoader { self } - /// Builder-style method to wrap the loader in an [`Arc`] to make it usable - /// with the [`Context`](crate::Context). - pub fn wrap(self) -> Arc { - Arc::new(self) - } - /// Search for fonts in the operating system's font directories. pub fn search_system(&mut self) { self.search_system_impl(); diff --git a/src/loading/mem.rs b/src/loading/mem.rs index d4c0e7e45..662de1007 100644 --- a/src/loading/mem.rs +++ b/src/loading/mem.rs @@ -2,7 +2,6 @@ use std::borrow::Cow; use std::collections::HashMap; use std::io; use std::path::{Path, PathBuf}; -use std::sync::Arc; use super::{FileHash, Loader}; use crate::font::FaceInfo; @@ -31,12 +30,6 @@ impl MemLoader { self } - /// Builder-style method to wrap the loader in an [`Arc`] to make it usable - /// with the [`Context`](crate::Context). - pub fn wrap(self) -> Arc { - Arc::new(self) - } - /// Insert a path-file mapping. If the data forms a font, then that font /// will be available for layouting. /// diff --git a/src/main.rs b/src/main.rs index c017e7356..2865c67ee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,9 +2,10 @@ use std::fs; 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, Config, Styles}; +use codespan_reporting::term::{self, termcolor}; use pico_args::Arguments; use same_file::is_same_file; use termcolor::{ColorChoice, StandardStream, WriteColor}; @@ -17,7 +18,7 @@ use typst::loading::FsLoader; use typst::parse::TokenMode; use typst::source::SourceStore; use typst::syntax; -use typst::Context; +use typst::{Config, Context}; /// What to do. enum Command { @@ -172,7 +173,7 @@ fn print_help(help: &'static str) { /// Print an application-level error (independent from a source file). fn print_error(msg: &str) -> io::Result<()> { let mut w = StandardStream::stderr(ColorChoice::Always); - let styles = Styles::default(); + let styles = term::Styles::default(); w.set_color(&styles.header_error)?; write!(w, "error")?; @@ -192,11 +193,11 @@ fn dispatch(command: Command) -> StrResult<()> { /// Execute a typesetting command. fn typeset(command: TypesetCommand) -> StrResult<()> { - let mut builder = Context::builder(); + let mut config = Config::builder(); if let Some(root) = &command.root { - builder.root(root); + config.root(root); } else if let Some(dir) = command.input.parent() { - builder.root(dir); + config.root(dir); } // Create a loader for fonts and files. @@ -204,7 +205,7 @@ fn typeset(command: TypesetCommand) -> StrResult<()> { // Create the context which holds loaded source files, fonts, images and // cached artifacts. - let mut ctx = builder.build(loader.wrap()); + let mut ctx = Context::new(Arc::new(loader), config.build()); // Load the source file. let id = ctx @@ -236,7 +237,7 @@ fn print_diagnostics( errors: Vec, ) -> Result<(), codespan_reporting::files::Error> { let mut w = StandardStream::stderr(ColorChoice::Always); - let config = Config { tab_width: 2, ..Default::default() }; + let config = term::Config { tab_width: 2, ..Default::default() }; for error in errors { // The main diagnostic. @@ -274,7 +275,7 @@ fn highlight(command: HighlightCommand) -> StrResult<()> { /// Execute a font listing command. fn fonts(command: FontsCommand) -> StrResult<()> { let loader = FsLoader::new().with_system(); - let fonts = FontStore::new(loader.wrap()); + let fonts = FontStore::new(Arc::new(loader)); for (name, infos) in fonts.families() { println!("{name}"); diff --git a/src/model/content.rs b/src/model/content.rs index 8b76c7959..645797479 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -210,7 +210,7 @@ impl Content { /// Layout this content into a collection of pages. pub fn layout(&self, ctx: &mut Context) -> TypResult>> { - let copy = ctx.styles.clone(); + let copy = ctx.config.styles.clone(); let styles = StyleChain::with_root(©); let scratch = Scratch::default(); diff --git a/src/source.rs b/src/source.rs index 7973a2ee3..cd5a453a4 100644 --- a/src/source.rs +++ b/src/source.rs @@ -59,6 +59,16 @@ impl SourceStore { } } + /// 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) -> &SourceFile { + &self.sources[id.0 as usize] + } + /// Load a source file from a path relative to the compilation environment's /// root. /// @@ -109,14 +119,6 @@ impl SourceStore { id } - /// Get a reference to a loaded source file. - /// - /// This panics if no source file with this `id` exists. - #[track_caller] - pub fn get(&self, id: SourceId) -> &SourceFile { - &self.sources[id.0 as usize] - } - /// Fully [replace](SourceFile::replace) the source text of a file. /// /// This panics if no source file with this `id` exists. diff --git a/tests/typeset.rs b/tests/typeset.rs index b0452163b..caa30d7ed 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -19,7 +19,7 @@ use typst::loading::FsLoader; use typst::model::StyleMap; use typst::source::SourceFile; use typst::syntax::Span; -use typst::{bail, Context}; +use typst::{bail, Config, Context}; const TYP_DIR: &str = "./typ"; const REF_DIR: &str = "./ref"; @@ -84,8 +84,9 @@ fn main() { }); // Create loader and context. - let loader = FsLoader::new().with_path(FONT_DIR).wrap(); - let mut ctx = Context::builder().std(std).styles(styles).build(loader); + 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); // Run all the tests. let mut ok = 0;