summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cache.rs30
-rw-r--r--src/eval/mod.rs34
-rw-r--r--src/exec/context.rs9
-rw-r--r--src/exec/mod.rs5
-rw-r--r--src/export/pdf.rs54
-rw-r--r--src/font.rs15
-rw-r--r--src/image.rs17
-rw-r--r--src/layout/image.rs2
-rw-r--r--src/layout/mod.rs56
-rw-r--r--src/layout/shaping.rs16
-rw-r--r--src/lib.rs127
-rw-r--r--src/library/elements.rs2
-rw-r--r--src/loading/fs.rs23
-rw-r--r--src/loading/mod.rs8
-rw-r--r--src/main.rs25
15 files changed, 228 insertions, 195 deletions
diff --git a/src/cache.rs b/src/cache.rs
deleted file mode 100644
index 2aa276aa..00000000
--- a/src/cache.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-//! Caching of compilation artifacts.
-
-use crate::font::FontCache;
-use crate::image::ImageCache;
-#[cfg(feature = "layout-cache")]
-use crate::layout::LayoutCache;
-use crate::loading::Loader;
-
-/// Caches compilation artifacts.
-pub struct Cache {
- /// Caches parsed font faces.
- pub font: FontCache,
- /// Caches decoded images.
- pub image: ImageCache,
- /// Caches layouting artifacts.
- #[cfg(feature = "layout-cache")]
- pub layout: LayoutCache,
-}
-
-impl Cache {
- /// Create a new, empty cache.
- pub fn new(loader: &dyn Loader) -> Self {
- Self {
- font: FontCache::new(loader),
- image: ImageCache::new(),
- #[cfg(feature = "layout-cache")]
- layout: LayoutCache::new(),
- }
- }
-}
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index cbb61153..0efaaa90 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -25,24 +25,19 @@ use std::mem;
use std::path::Path;
use std::rc::Rc;
-use crate::cache::Cache;
use crate::diag::{Diag, DiagSet, Pass};
use crate::eco::EcoString;
use crate::geom::{Angle, Fractional, Length, Relative};
+use crate::image::ImageCache;
use crate::loading::{FileId, Loader};
use crate::parse::parse;
use crate::syntax::visit::Visit;
use crate::syntax::*;
+use crate::Context;
/// Evaluate a parsed source file into a module.
-pub fn eval(
- loader: &mut dyn Loader,
- cache: &mut Cache,
- location: FileId,
- ast: Rc<SyntaxTree>,
- scope: &Scope,
-) -> Pass<Module> {
- let mut ctx = EvalContext::new(loader, cache, location, scope);
+pub fn eval(ctx: &mut Context, location: FileId, ast: Rc<SyntaxTree>) -> Pass<Module> {
+ let mut ctx = EvalContext::new(ctx, location);
let template = ast.eval(&mut ctx);
let module = Module { scope: ctx.scopes.top, template };
Pass::new(module, ctx.diags)
@@ -60,9 +55,9 @@ pub struct Module {
/// The context for evaluation.
pub struct EvalContext<'a> {
/// The loader from which resources (files and images) are loaded.
- pub loader: &'a mut dyn Loader,
- /// A cache for loaded resources.
- pub cache: &'a mut Cache,
+ pub loader: &'a dyn Loader,
+ /// The cache for decoded images.
+ pub images: &'a mut ImageCache,
/// The active scopes.
pub scopes: Scopes<'a>,
/// Evaluation diagnostics.
@@ -74,17 +69,12 @@ pub struct EvalContext<'a> {
}
impl<'a> EvalContext<'a> {
- /// Create a new evaluation context with a base scope.
- pub fn new(
- loader: &'a mut dyn Loader,
- cache: &'a mut Cache,
- location: FileId,
- scope: &'a Scope,
- ) -> Self {
+ /// Create a new evaluation context.
+ pub fn new(ctx: &'a mut Context, location: FileId) -> Self {
Self {
- loader,
- cache,
- scopes: Scopes::new(Some(scope)),
+ loader: ctx.loader.as_ref(),
+ images: &mut ctx.images,
+ scopes: Scopes::new(Some(&ctx.std)),
diags: DiagSet::new(),
route: vec![location],
modules: HashMap::new(),
diff --git a/src/exec/context.rs b/src/exec/context.rs
index 04c0169d..925fd7de 100644
--- a/src/exec/context.rs
+++ b/src/exec/context.rs
@@ -10,6 +10,7 @@ use crate::layout::{
LayoutNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild, StackNode,
};
use crate::syntax::{Span, SyntaxTree};
+use crate::Context;
/// The context for execution.
pub struct ExecContext {
@@ -28,13 +29,13 @@ pub struct ExecContext {
impl ExecContext {
/// Create a new execution context with a base state.
- pub fn new(state: State) -> Self {
+ pub fn new(ctx: &mut Context) -> Self {
Self {
+ state: ctx.state.clone(),
diags: DiagSet::new(),
tree: LayoutTree { runs: vec![] },
- page: Some(PageBuilder::new(&state, true)),
- stack: StackBuilder::new(&state),
- state,
+ page: Some(PageBuilder::new(&ctx.state, true)),
+ stack: StackBuilder::new(&ctx.state),
}
}
diff --git a/src/exec/mod.rs b/src/exec/mod.rs
index 752bdba5..d61e0793 100644
--- a/src/exec/mod.rs
+++ b/src/exec/mod.rs
@@ -16,10 +16,11 @@ use crate::geom::{Dir, Gen};
use crate::layout::{LayoutTree, StackChild, StackNode};
use crate::pretty::pretty;
use crate::syntax::*;
+use crate::Context;
/// Execute a template to produce a layout tree.
-pub fn exec(template: &Template, state: State) -> Pass<LayoutTree> {
- let mut ctx = ExecContext::new(state);
+pub fn exec(ctx: &mut Context, template: &Template) -> Pass<LayoutTree> {
+ let mut ctx = ExecContext::new(ctx);
template.exec(&mut ctx);
ctx.finish()
}
diff --git a/src/export/pdf.rs b/src/export/pdf.rs
index 7e55a601..bfd36421 100644
--- a/src/export/pdf.rs
+++ b/src/export/pdf.rs
@@ -13,12 +13,12 @@ use pdf_writer::{
};
use ttf_parser::{name_id, GlyphId};
-use crate::cache::Cache;
use crate::color::Color;
-use crate::font::{Em, FaceId};
+use crate::font::{Em, FaceId, FontCache};
use crate::geom::{self, Length, Size};
-use crate::image::{Image, ImageId};
+use crate::image::{Image, ImageCache, ImageId};
use crate::layout::{Element, Frame, Geometry, Paint};
+use crate::Context;
/// Export a collection of frames into a PDF document.
///
@@ -27,53 +27,55 @@ use crate::layout::{Element, Frame, Geometry, Paint};
/// can be included in the PDF.
///
/// Returns the raw bytes making up the PDF document.
-pub fn pdf(cache: &Cache, frames: &[Rc<Frame>]) -> Vec<u8> {
- PdfExporter::new(cache, frames).write()
+pub fn pdf(ctx: &Context, frames: &[Rc<Frame>]) -> Vec<u8> {
+ PdfExporter::new(ctx, frames).write()
}
struct PdfExporter<'a> {
writer: PdfWriter,
frames: &'a [Rc<Frame>],
- cache: &'a Cache,
+ fonts: &'a FontCache,
+ font_map: Remapper<FaceId>,
+ images: &'a ImageCache,
+ image_map: Remapper<ImageId>,
refs: Refs,
- fonts: Remapper<FaceId>,
- images: Remapper<ImageId>,
}
impl<'a> PdfExporter<'a> {
- fn new(cache: &'a Cache, frames: &'a [Rc<Frame>]) -> Self {
+ fn new(ctx: &'a Context, frames: &'a [Rc<Frame>]) -> Self {
let mut writer = PdfWriter::new(1, 7);
writer.set_indent(2);
- let mut fonts = Remapper::new();
- let mut images = Remapper::new();
+ let mut font_map = Remapper::new();
+ let mut image_map = Remapper::new();
let mut alpha_masks = 0;
for frame in frames {
for (_, element) in frame.elements() {
match *element {
- Element::Text(ref shaped) => fonts.insert(shaped.face_id),
+ Element::Text(ref shaped) => font_map.insert(shaped.face_id),
Element::Geometry(_, _) => {}
Element::Image(id, _) => {
- let img = cache.image.get(id);
+ let img = ctx.images.get(id);
if img.buf.color().has_alpha() {
alpha_masks += 1;
}
- images.insert(id);
+ image_map.insert(id);
}
}
}
}
- let refs = Refs::new(frames.len(), fonts.len(), images.len(), alpha_masks);
+ let refs = Refs::new(frames.len(), font_map.len(), image_map.len(), alpha_masks);
Self {
writer,
frames,
- cache,
+ fonts: &ctx.fonts,
+ images: &ctx.images,
refs,
- fonts,
- images,
+ font_map,
+ image_map,
}
}
@@ -95,7 +97,7 @@ impl<'a> PdfExporter<'a> {
let mut resources = pages.resources();
let mut fonts = resources.fonts();
- for (refs, f) in self.refs.fonts().zip(self.fonts.pdf_indices()) {
+ for (refs, f) in self.refs.fonts().zip(self.font_map.pdf_indices()) {
let name = format!("F{}", f);
fonts.pair(Name(name.as_bytes()), refs.type0_font);
}
@@ -103,7 +105,7 @@ impl<'a> PdfExporter<'a> {
drop(fonts);
let mut images = resources.x_objects();
- for (id, im) in self.refs.images().zip(self.images.pdf_indices()) {
+ for (id, im) in self.refs.images().zip(self.image_map.pdf_indices()) {
let name = format!("Im{}", im);
images.pair(Name(name.as_bytes()), id);
}
@@ -163,7 +165,7 @@ impl<'a> PdfExporter<'a> {
face = Some(shaped.face_id);
size = shaped.size;
- let name = format!("F{}", self.fonts.map(shaped.face_id));
+ let name = format!("F{}", self.font_map.map(shaped.face_id));
text.font(Name(name.as_bytes()), size.to_pt() as f32);
}
@@ -206,7 +208,7 @@ impl<'a> PdfExporter<'a> {
}
Element::Image(id, Size { width, height }) => {
- let name = format!("Im{}", self.images.map(id));
+ let name = format!("Im{}", self.image_map.map(id));
let w = width.to_pt() as f32;
let h = height.to_pt() as f32;
@@ -222,8 +224,8 @@ impl<'a> PdfExporter<'a> {
}
fn write_fonts(&mut self) {
- for (refs, face_id) in self.refs.fonts().zip(self.fonts.layout_indices()) {
- let face = self.cache.font.get(face_id);
+ for (refs, face_id) in self.refs.fonts().zip(self.font_map.layout_indices()) {
+ let face = self.fonts.get(face_id);
let ttf = face.ttf();
let name = ttf
@@ -327,8 +329,8 @@ impl<'a> PdfExporter<'a> {
fn write_images(&mut self) {
let mut masks_seen = 0;
- for (id, image_id) in self.refs.images().zip(self.images.layout_indices()) {
- let img = self.cache.image.get(image_id);
+ for (id, image_id) in self.refs.images().zip(self.image_map.layout_indices()) {
+ let img = self.images.get(image_id);
let (width, height) = img.buf.dimensions();
// Add the primary image.
diff --git a/src/font.rs b/src/font.rs
index 273c2914..0d6cd880 100644
--- a/src/font.rs
+++ b/src/font.rs
@@ -203,6 +203,7 @@ impl Add for Em {
/// Caches parsed font faces.
pub struct FontCache {
+ loader: Rc<dyn Loader>,
faces: Vec<Option<Face>>,
families: HashMap<String, Vec<FaceId>>,
buffers: HashMap<FileId, Rc<Vec<u8>>>,
@@ -211,7 +212,7 @@ pub struct FontCache {
impl FontCache {
/// Create a new, empty font cache.
- pub fn new(loader: &dyn Loader) -> Self {
+ pub fn new(loader: Rc<dyn Loader>) -> Self {
let mut faces = vec![];
let mut families = HashMap::<String, Vec<FaceId>>::new();
@@ -225,6 +226,7 @@ impl FontCache {
}
Self {
+ loader,
faces,
families,
buffers: HashMap::new(),
@@ -234,15 +236,10 @@ impl FontCache {
/// Query for and load the font face from the given `family` that most
/// closely matches the given `variant`.
- pub fn select(
- &mut self,
- loader: &mut dyn Loader,
- family: &str,
- variant: FontVariant,
- ) -> Option<FaceId> {
+ pub fn select(&mut self, family: &str, variant: FontVariant) -> Option<FaceId> {
// Check whether a family with this name exists.
let ids = self.families.get(family)?;
- let infos = loader.faces();
+ let infos = self.loader.faces();
let mut best = None;
let mut best_key = None;
@@ -284,7 +281,7 @@ impl FontCache {
let buffer = match self.buffers.entry(file) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
- let buffer = loader.load_file(file)?;
+ let buffer = self.loader.load_file(file)?;
entry.insert(Rc::new(buffer))
}
};
diff --git a/src/image.rs b/src/image.rs
index 28283259..d0719ac7 100644
--- a/src/image.rs
+++ b/src/image.rs
@@ -3,6 +3,7 @@
use std::collections::{hash_map::Entry, HashMap};
use std::fmt::{self, Debug, Formatter};
use std::io::Cursor;
+use std::rc::Rc;
use image::io::Reader as ImageReader;
use image::{DynamicImage, GenericImageView, ImageFormat};
@@ -53,25 +54,27 @@ impl Debug for Image {
}
/// Caches decoded images.
-#[derive(Default)]
pub struct ImageCache {
- /// Maps from file hashes to ids of decoded images.
+ loader: Rc<dyn Loader>,
images: HashMap<ImageId, Image>,
- /// Callback for loaded images.
on_load: Option<Box<dyn Fn(ImageId, &Image)>>,
}
impl ImageCache {
/// Create a new, empty image cache.
- pub fn new() -> Self {
- Self::default()
+ pub fn new(loader: Rc<dyn Loader>) -> Self {
+ Self {
+ loader,
+ images: HashMap::new(),
+ on_load: None,
+ }
}
/// Load and decode an image file from a path.
- pub fn load(&mut self, loader: &mut dyn Loader, file: FileId) -> Option<ImageId> {
+ pub fn load(&mut self, file: FileId) -> Option<ImageId> {
let id = ImageId(file.into_raw());
if let Entry::Vacant(entry) = self.images.entry(id) {
- let buffer = loader.load_file(file)?;
+ let buffer = self.loader.load_file(file)?;
let image = Image::parse(&buffer)?;
if let Some(callback) = &self.on_load {
callback(id, &image);
diff --git a/src/layout/image.rs b/src/layout/image.rs
index 07d7799c..2c20642b 100644
--- a/src/layout/image.rs
+++ b/src/layout/image.rs
@@ -28,7 +28,7 @@ impl Layout for ImageNode {
let width = self.width.map(|w| w.resolve(base.width));
let height = self.height.map(|w| w.resolve(base.height));
- let dimensions = ctx.cache.image.get(self.id).buf.dimensions();
+ let dimensions = ctx.images.get(self.id).buf.dimensions();
let pixel_width = dimensions.0 as f64;
let pixel_height = dimensions.1 as f64;
let pixel_ratio = pixel_width / pixel_height;
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 523d1a92..7f8ee4ff 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -32,22 +32,16 @@ use std::rc::Rc;
#[cfg(feature = "layout-cache")]
use fxhash::FxHasher64;
-use crate::cache::Cache;
+use crate::font::FontCache;
use crate::geom::*;
+use crate::image::ImageCache;
use crate::loading::Loader;
+use crate::Context;
/// Layout a tree into a collection of frames.
-pub fn layout(
- loader: &mut dyn Loader,
- cache: &mut Cache,
- tree: &LayoutTree,
-) -> Vec<Rc<Frame>> {
- tree.layout(&mut LayoutContext {
- loader,
- cache,
- #[cfg(feature = "layout-cache")]
- level: 0,
- })
+pub fn layout(ctx: &mut Context, tree: &LayoutTree) -> Vec<Rc<Frame>> {
+ let mut ctx = LayoutContext::new(ctx);
+ tree.layout(&mut ctx)
}
/// A tree of layout nodes.
@@ -129,15 +123,15 @@ impl Layout for LayoutNode {
#[cfg(feature = "layout-cache")]
{
ctx.level += 1;
- let frames =
- ctx.cache.layout.get(self.hash, regions.clone()).unwrap_or_else(|| {
- let frames = self.node.layout(ctx, regions);
- ctx.cache.layout.insert(self.hash, frames.clone(), ctx.level - 1);
- frames
- });
+ let frames = ctx.layouts.get(self.hash, regions.clone()).unwrap_or_else(|| {
+ let frames = self.node.layout(ctx, regions);
+ ctx.layouts.insert(self.hash, frames.clone(), ctx.level - 1);
+ frames
+ });
ctx.level -= 1;
frames
}
+
#[cfg(not(feature = "layout-cache"))]
self.node.layout(ctx, regions)
}
@@ -214,14 +208,34 @@ pub trait Layout {
/// The context for layouting.
pub struct LayoutContext<'a> {
/// The loader from which fonts are loaded.
- pub loader: &'a mut dyn Loader,
- /// A cache for loaded fonts and artifacts from past layouting.
- pub cache: &'a mut Cache,
+ pub loader: &'a dyn Loader,
+ /// The cache for parsed font faces.
+ pub fonts: &'a mut FontCache,
+ /// The cache for decoded imges.
+ pub images: &'a mut ImageCache,
+ /// The cache for layouting artifacts.
+ #[cfg(feature = "layout-cache")]
+ pub layouts: &'a mut LayoutCache,
/// How deeply nested the current layout tree position is.
#[cfg(feature = "layout-cache")]
pub level: usize,
}
+impl<'a> LayoutContext<'a> {
+ /// Create a new layout context.
+ pub fn new(ctx: &'a mut Context) -> Self {
+ Self {
+ loader: ctx.loader.as_ref(),
+ fonts: &mut ctx.fonts,
+ images: &mut ctx.images,
+ #[cfg(feature = "layout-cache")]
+ layouts: &mut ctx.layouts,
+ #[cfg(feature = "layout-cache")]
+ level: 0,
+ }
+ }
+}
+
/// A sequence of regions to layout into.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Regions {
diff --git a/src/layout/shaping.rs b/src/layout/shaping.rs
index 9b8774cc..5e1bc327 100644
--- a/src/layout/shaping.rs
+++ b/src/layout/shaping.rs
@@ -235,7 +235,7 @@ fn shape_segment<'a>(
}
}
- if let Some(id) = ctx.cache.font.select(ctx.loader, family, variant) {
+ if let Some(id) = ctx.fonts.select(family, variant) {
break (id, true);
}
}
@@ -262,7 +262,7 @@ fn shape_segment<'a>(
});
// Shape!
- let mut face = ctx.cache.font.get(face_id);
+ let mut face = ctx.fonts.get(face_id);
let buffer = rustybuzz::shape(face.ttf(), &[], buffer);
let infos = buffer.glyph_infos();
let pos = buffer.glyph_positions();
@@ -337,7 +337,7 @@ fn shape_segment<'a>(
first_face,
);
- face = ctx.cache.font.get(face_id);
+ face = ctx.fonts.get(face_id);
}
i += 1;
@@ -351,8 +351,6 @@ fn measure(
glyphs: &[ShapedGlyph],
state: &FontState,
) -> (Size, Length) {
- let cache = &mut ctx.cache.font;
-
let mut width = Length::zero();
let mut top = Length::zero();
let mut bottom = Length::zero();
@@ -365,14 +363,14 @@ fn measure(
// When there are no glyphs, we just use the vertical metrics of the
// first available font.
for family in state.families.iter() {
- if let Some(face_id) = cache.select(ctx.loader, family, state.variant) {
- expand_vertical(cache.get(face_id));
+ if let Some(face_id) = ctx.fonts.select(family, state.variant) {
+ expand_vertical(ctx.fonts.get(face_id));
break;
}
}
} else {
for (face_id, group) in glyphs.group_by_key(|g| g.face_id) {
- let face = cache.get(face_id);
+ let face = ctx.fonts.get(face_id);
expand_vertical(face);
for glyph in group {
@@ -394,7 +392,7 @@ fn decorate(
state: &FontState,
) {
let mut apply = |substate: &LineState, metrics: fn(&Face) -> &LineMetrics| {
- let metrics = metrics(&ctx.cache.font.get(face_id));
+ let metrics = metrics(ctx.fonts.get(face_id));
let stroke = substate.stroke.unwrap_or(state.fill);
diff --git a/src/lib.rs b/src/lib.rs
index d1f43380..90fd8774 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -32,7 +32,6 @@
pub mod diag;
#[macro_use]
pub mod eval;
-pub mod cache;
pub mod color;
pub mod eco;
pub mod exec;
@@ -51,46 +50,106 @@ pub mod util;
use std::rc::Rc;
-use crate::cache::Cache;
use crate::diag::Pass;
use crate::eval::Scope;
use crate::exec::State;
+use crate::font::FontCache;
+use crate::image::ImageCache;
use crate::layout::Frame;
+#[cfg(feature = "layout-cache")]
+use crate::layout::LayoutCache;
use crate::loading::{FileId, Loader};
-/// Process source code directly into a collection of layouted frames.
-///
-/// # Parameters
-/// - The `loader` is used to load fonts, images and other source files.
-/// - The `cache` stores things that are reusable across several compilations
-/// like loaded fonts, decoded images and layouting artifacts.
-/// - The `location` is the file id of the source file and is used to resolve
-/// relative paths (for importing and image loading).
-/// - The `src` is the _Typst_ source code to typeset.
-/// - The `scope` contains definitions that are available everywhere, typically
-/// the standard library.
-/// - The `state` defines initial properties for page size, font selection and
-/// so on.
-///
-/// # Return value
-/// Returns a vector of frames representing individual pages alongside
-/// diagnostic information (errors and warnings).
-pub fn typeset(
- loader: &mut dyn Loader,
- cache: &mut Cache,
- location: FileId,
- src: &str,
- scope: &Scope,
+/// The core context which holds the loader, configuration and cached artifacts.
+pub struct Context {
+ /// The loader the context was created with.
+ pub loader: Rc<dyn Loader>,
+ /// Caches parsed font faces.
+ pub fonts: FontCache,
+ /// Caches decoded images.
+ pub images: ImageCache,
+ /// Caches layouting artifacts.
+ #[cfg(feature = "layout-cache")]
+ pub layouts: LayoutCache,
+ /// The standard library scope.
+ std: Scope,
+ /// The default state.
state: State,
-) -> Pass<Vec<Rc<Frame>>> {
- let ast = parse::parse(src);
- let module = eval::eval(loader, cache, location, Rc::new(ast.output), scope);
- let tree = exec::exec(&module.output.template, state);
- let frames = layout::layout(loader, cache, &tree.output);
+}
+
+impl Context {
+ /// Create a new context with the default settings.
+ pub fn new(loader: Rc<dyn Loader>) -> Self {
+ Self::builder().build(loader)
+ }
+
+ /// Create a new context with advanced settings.
+ pub fn builder() -> ContextBuilder {
+ ContextBuilder::default()
+ }
+
+ /// Garbage-collect caches.
+ pub fn turnaround(&mut self) {
+ #[cfg(feature = "layout-cache")]
+ self.layouts.turnaround();
+ }
+
+ /// Typeset a source file into a collection of layouted frames.
+ ///
+ /// The `file` is the file id of the source file and is used to resolve
+ /// relative paths (for importing and image loading).
+ ///
+ /// Returns a vector of frames representing individual pages alongside
+ /// diagnostic information (errors and warnings).
+ pub fn typeset(&mut self, file: FileId, src: &str) -> Pass<Vec<Rc<Frame>>> {
+ let ast = parse::parse(src);
+ let module = eval::eval(self, file, Rc::new(ast.output));
+ let tree = exec::exec(self, &module.output.template);
+ let frames = layout::layout(self, &tree.output);
+
+ let mut diags = ast.diags;
+ diags.extend(module.diags);
+ diags.extend(tree.diags);
+
+ Pass::new(frames, diags)
+ }
+}
+
+/// A builder for a [`Context`].
+///
+/// This struct is created by [`Context::builder`].
+#[derive(Default)]
+pub struct ContextBuilder {
+ std: Option<Scope>,
+ state: Option<State>,
+}
+
+impl ContextBuilder {
+ /// The scope containing definitions that are available everywhere,
+ /// (the standard library).
+ pub fn std(mut self, std: Scope) -> Self {
+ self.std = Some(std);
+ self
+ }
- let mut diags = ast.diags;
- diags.extend(module.diags);
- diags.extend(tree.diags);
+ /// The `state` defining initial properties for page size, font selection
+ /// and so on.
+ pub fn state(mut self, state: State) -> Self {
+ self.state = Some(state);
+ self
+ }
- Pass::new(frames, diags)
+ /// Finish building the context by providing the `loader` used to load
+ /// fonts, images, source files and other resources.
+ pub fn build(self, loader: Rc<dyn Loader>) -> Context {
+ Context {
+ loader: Rc::clone(&loader),
+ fonts: FontCache::new(Rc::clone(&loader)),
+ images: ImageCache::new(loader),
+ #[cfg(feature = "layout-cache")]
+ layouts: LayoutCache::new(),
+ std: self.std.unwrap_or(library::new()),
+ state: self.state.unwrap_or_default(),
+ }
+ }
}
diff --git a/src/library/elements.rs b/src/library/elements.rs
index 03fccfe7..33e8069c 100644
--- a/src/library/elements.rs
+++ b/src/library/elements.rs
@@ -16,7 +16,7 @@ pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let mut node = None;
if let Some(path) = &path {
if let Some(file) = ctx.resolve(&path.v, path.span) {
- if let Some(id) = ctx.cache.image.load(ctx.loader, file) {
+ if let Some(id) = ctx.images.load(file) {
node = Some(ImageNode { id, width, height });
} else {
ctx.diag(error!(path.span, "failed to load image"));
diff --git a/src/loading/fs.rs b/src/loading/fs.rs
index ea33016c..8785499e 100644
--- a/src/loading/fs.rs
+++ b/src/loading/fs.rs
@@ -1,3 +1,4 @@
+use std::cell::RefCell;
use std::collections::HashMap;
use std::fs::{self, File};
use std::io;
@@ -18,23 +19,23 @@ use crate::util::PathExt;
#[derive(Default, Debug, Clone)]
pub struct FsLoader {
faces: Vec<FaceInfo>,
- paths: HashMap<FileId, PathBuf>,
+ paths: RefCell<HashMap<FileId, PathBuf>>,
}
impl FsLoader {
/// Create a new loader without any fonts.
pub fn new() -> Self {
- Self { faces: vec![], paths: HashMap::new() }
+ Self { faces: vec![], paths: RefCell::default() }
}
/// Resolve a file id for a path.
- pub fn resolve_path(&mut self, path: &Path) -> io::Result<FileId> {
+ pub fn resolve_path(&self, path: &Path) -> io::Result<FileId> {
let file = File::open(path)?;
let meta = file.metadata()?;
if meta.is_file() {
let handle = Handle::from_file(file)?;
let id = FileId(fxhash::hash64(&handle));
- self.paths.insert(id, path.normalize());
+ self.paths.borrow_mut().insert(id, path.normalize());
Ok(id)
} else {
Err(io::Error::new(io::ErrorKind::Other, "not a file"))
@@ -165,14 +166,13 @@ impl Loader for FsLoader {
&self.faces
}
- fn resolve_from(&mut self, base: FileId, path: &Path) -> Option<FileId> {
- let dir = self.paths[&base].parent()?;
- let full = dir.join(path);
- self.resolve_path(&full).ok()
+ fn resolve_from(&self, base: FileId, path: &Path) -> Option<FileId> {
+ let full = self.paths.borrow()[&base].parent()?.join(path);
+ self.resolve(&full).ok()
}
- fn load_file(&mut self, id: FileId) -> Option<Vec<u8>> {
- fs::read(&self.paths[&id]).ok()
+ fn load_file(&self, id: FileId) -> Option<Vec<u8>> {
+ fs::read(&self.paths.borrow()[&id]).ok()
}
}
@@ -185,7 +185,8 @@ mod tests {
let mut loader = FsLoader::new();
loader.search_path("fonts");
- let mut paths: Vec<_> = loader.paths.values().collect();
+ let map = loader.paths.borrow();
+ let mut paths: Vec<_> = map.values().collect();
paths.sort();
assert_eq!(paths, [
diff --git a/src/loading/mod.rs b/src/loading/mod.rs
index c2f7ca39..3be74428 100644
--- a/src/loading/mod.rs
+++ b/src/loading/mod.rs
@@ -21,10 +21,10 @@ pub trait Loader {
///
/// This should return the same id for all paths pointing to the same file
/// and `None` if the file does not exist.
- fn resolve_from(&mut self, base: FileId, path: &Path) -> Option<FileId>;
+ fn resolve_from(&self, base: FileId, path: &Path) -> Option<FileId>;
/// Load a file by id.
- fn load_file(&mut self, id: FileId) -> Option<Vec<u8>>;
+ fn load_file(&self, id: FileId) -> Option<Vec<u8>>;
}
/// A file id that can be [resolved](Loader::resolve_from) from a path.
@@ -53,11 +53,11 @@ impl Loader for BlankLoader {
&[]
}
- fn resolve_from(&mut self, _: FileId, _: &Path) -> Option<FileId> {
+ fn resolve_from(&self, _: FileId, _: &Path) -> Option<FileId> {
None
}
- fn load_file(&mut self, _: FileId) -> Option<Vec<u8>> {
+ fn load_file(&self, _: FileId) -> Option<Vec<u8>> {
None
}
}
diff --git a/src/main.rs b/src/main.rs
index 03680204..f9da37fa 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,6 @@
use std::fs;
use std::path::{Path, PathBuf};
+use std::rc::Rc;
use anyhow::{anyhow, bail, Context};
use same_file::is_same_file;
@@ -11,11 +12,6 @@ fn main() -> anyhow::Result<()> {
return Ok(());
}
- // Create a loader for fonts and files.
- let mut loader = typst::loading::FsLoader::new();
- loader.search_path("fonts");
- loader.search_system();
-
// Determine source and destination path.
let src_path = Path::new(&args[1]);
let dest_path = if let Some(arg) = args.get(2) {
@@ -33,18 +29,19 @@ fn main() -> anyhow::Result<()> {
bail!("source and destination files are the same");
}
- // Resolve the file id of the source file.
- let src_id = loader.resolve_path(src_path).context("source file not found")?;
+ // Create a loader for fonts and files.
+ let mut loader = typst::loading::FsLoader::new();
+ loader.search_path("fonts");
+ loader.search_system();
- // Read the source.
+ // Resolve the file id of the source file and read the file.
+ let src_id = loader.resolve_path(src_path).context("source file not found")?;
let src = fs::read_to_string(&src_path)
.map_err(|_| anyhow!("failed to read source file"))?;
- // Compile.
- let mut cache = typst::cache::Cache::new(&loader);
- let scope = typst::library::new();
- let state = typst::exec::State::default();
- let pass = typst::typeset(&mut loader, &mut cache, src_id, &src, &scope, state);
+ // Typeset.
+ let mut ctx = typst::Context::new(Rc::new(loader));
+ let pass = ctx.typeset(src_id, &src);
// Print diagnostics.
let map = typst::parse::LineMap::new(&src);
@@ -62,7 +59,7 @@ fn main() -> anyhow::Result<()> {
}
// Export the PDF.
- let buffer = typst::export::pdf(&cache, &pass.output);
+ let buffer = typst::export::pdf(&ctx, &pass.output);
fs::write(&dest_path, buffer).context("failed to write PDF file")?;
Ok(())