//! [`LessonWorld`] — a typst [`World`] over an engineering-file directory tree //! plus an in-memory augmented manifest and a disk-mounted `cph-render` package. //! //! ## Model (ADR-0011) //! //! The `main` file is the target's **template** (e.g. `/exports/student.typ`), //! a *real* file under the lesson root — no framework-generated driver. The //! template `toml()`-reads an injected manifest and `include`s each part's //! content via computed root-relative paths. The engine injects the manifest as //! a virtual file (see below) and sets `sys.inputs.manifest` to its vpath. //! //! ## File resolution //! //! - **The template** (`main()`): a `FileId` with `VirtualRoot::Project` whose //! vpath is the template's path under the lesson root (e.g. `/exports/student.typ`). //! It is read from disk like any other project file; its `include "//…"` //! paths anchor at the lesson root (`--root`). //! - **The augmented manifest**: served in-memory at `VirtualRoot::Project` + //! vpath [`MANIFEST_VPATH`] (`/.cph/manifest.toml`). It is **never** written to //! disk — the engine builds it (original manifest + a per-part `fields` array) //! and the template reads it via `toml(sys.inputs.manifest)`. //! - **Project files**: any other `FileId` with `VirtualRoot::Project` is read //! from `root.join(vpath.realize)`. Used for content `.typ`, images, //! `element.toml` `toml()`, etc. //! - **`@local/cph-render`**: a `FileId` whose root is the render package spec is //! read from `render_dir.join(vpath.realize)`, avoiding installing the package //! into the typst local-package directory. //! - **`@preview/:`**: resolved offline from the in-repo vendored //! preview tree at `/vendor/typst-packages/preview///`. //! The render package imports `@preview/numbly:0.1.0`; reading it from the //! vendored dir keeps compilation network-free. //! - Any *other* `@package` namespace yields a `NotFound` error. //! //! Sources are cached in a `Mutex>` so repeated accesses //! during one compilation are cheap, as the `World` contract expects. use std::collections::HashMap; use std::path::{Component, Path, PathBuf}; use std::sync::{Arc, Mutex}; use typst::diag::{FileError, FileResult}; use typst::foundations::{Bytes, Datetime, Dict, Duration, Value}; use typst::syntax::package::{PackageSpec, PackageVersion}; use typst::syntax::{FileId, RootedPath, Source, VirtualPath, VirtualRoot}; use typst::text::{Font, FontBook}; use typst::utils::LazyHash; use typst::{Library, LibraryExt, World}; use typst_kit::fonts::FontStore; /// Root-relative vpath the augmented manifest is served at (in-memory only). /// /// A **leading slash** is essential: the template lives under `exports/`, and /// `toml(sys.inputs.manifest)` resolves a relative path against the template's /// own directory — a bare name would miss. A root-relative absolute path anchors /// at `--root` (the lesson root) regardless of where the template sits. pub const MANIFEST_VPATH: &str = "/.cph/manifest.toml"; /// The package spec the template imports and the World mounts from `render_dir`. pub fn render_package_spec() -> PackageSpec { PackageSpec { namespace: "local".into(), name: "cph-render".into(), version: PackageVersion { major: 0, minor: 1, patch: 0, }, } } /// A typst `World` for one lesson + target compilation. pub struct LessonWorld { /// Engineering-file root (the typst project root). root: PathBuf, /// On-disk location of the `cph-render` package. render_dir: PathBuf, /// The render package spec (`@local/cph-render:0.1.0`). render_spec: PackageSpec, /// FileId of the template entrypoint (a real file under `root`). main: FileId, /// FileId of the in-memory augmented manifest. manifest_id: FileId, /// The augmented-manifest source (in-memory; never on disk). manifest_source: Source, /// Standard library, with `sys.inputs.manifest` set. library: LazyHash, /// Shared font store (book + lazily-loaded fonts). fonts: Arc, /// Source cache for on-disk files. sources: Mutex>, } impl LessonWorld { /// Build a world whose `main` is the template at `/