tinymist-world 0.15.0-rc2

Typst's World implementation for tinymist.
Documentation
use std::{borrow::Cow, sync::Arc};

use tinymist_std::{ImmutPath, error::prelude::*};
use tinymist_vfs::{ImmutDict, Vfs, system::SystemAccessModel};
use typst::{Features, utils::LazyHash};

use crate::{
    EntryState,
    args::{CompileFontArgs, CompilePackageArgs},
    config::{CompileFontOpts, CompileOpts},
    font::{FontResolverImpl, system::SystemFontSearcher},
    package::{RegistryPathMapper, registry::HttpRegistry},
};

mod diag;
pub use diag::*;

/// type trait of [`TypstSystemWorld`].
#[derive(Debug, Clone, Copy)]
pub struct SystemCompilerFeat;

impl crate::CompilerFeat for SystemCompilerFeat {
    /// Uses [`FontResolverImpl`] directly.
    type FontResolver = FontResolverImpl;
    /// It accesses a physical file system.
    type AccessModel = SystemAccessModel;
    /// It performs native HTTP requests for fetching package data.
    type Registry = HttpRegistry;
}

/// The compiler universe in system environment.
pub type TypstSystemUniverse = crate::world::CompilerUniverse<SystemCompilerFeat>;
/// The compiler world in system environment.
pub type TypstSystemWorld = crate::world::CompilerWorld<SystemCompilerFeat>;
/// The compute graph in system environment.
pub type SystemWorldComputeGraph = crate::WorldComputeGraph<SystemCompilerFeat>;

impl TypstSystemUniverse {
    /// Create [`TypstSystemWorld`] with the given options.
    /// See SystemCompilerFeat for instantiation details.
    /// See [`CompileOpts`] for available options.
    pub fn new(mut opts: CompileOpts) -> Result<Self> {
        let registry: Arc<HttpRegistry> = Arc::default();
        let resolver = Arc::new(RegistryPathMapper::new(registry.clone()));
        let inputs = std::mem::take(&mut opts.inputs);
        let timestamp = opts.creation_timestamp;

        // todo: enable html
        Ok(Self::new_raw(
            opts.entry.clone().try_into()?,
            Features::default(),
            Some(Arc::new(LazyHash::new(inputs))),
            Vfs::new(resolver, SystemAccessModel {}),
            registry,
            Arc::new(Self::resolve_fonts(opts)?),
            timestamp,
        ))
    }

    /// Resolve fonts from given options.
    fn resolve_fonts(opts: CompileOpts) -> Result<FontResolverImpl> {
        let mut searcher = SystemFontSearcher::new();
        searcher.resolve_opts(opts.into())?;
        Ok(searcher.build())
    }
}

/// Builders for Typst universe.
pub struct SystemUniverseBuilder;

impl SystemUniverseBuilder {
    /// Create [`TypstSystemUniverse`] with the given options.
    /// See [`SystemCompilerFeat`] for instantiation details.
    pub fn build(
        entry: EntryState,
        inputs: ImmutDict,
        font_resolver: Arc<FontResolverImpl>,
        package_registry: HttpRegistry,
    ) -> TypstSystemUniverse {
        let registry = Arc::new(package_registry);
        let resolver = Arc::new(RegistryPathMapper::new(registry.clone()));

        // todo: enable html
        TypstSystemUniverse::new_raw(
            entry,
            Features::default(),
            Some(inputs),
            Vfs::new(resolver, SystemAccessModel {}),
            registry,
            font_resolver,
            None, // creation_timestamp - not used in this context
        )
    }

    /// Resolve fonts from given options.
    pub fn resolve_fonts(args: CompileFontArgs) -> Result<FontResolverImpl> {
        let mut searcher = SystemFontSearcher::new();
        searcher.resolve_opts(CompileFontOpts {
            font_paths: args.font_paths,
            no_system_fonts: args.ignore_system_fonts,
            with_embedded_fonts: typst_assets::fonts().map(Cow::Borrowed).collect(),
        })?;
        Ok(searcher.build())
    }

    /// Resolve package registry from given options.
    pub fn resolve_package(
        cert_path: Option<ImmutPath>,
        args: Option<&CompilePackageArgs>,
    ) -> HttpRegistry {
        HttpRegistry::new(
            cert_path,
            args.and_then(|args| Some(args.package_path.clone()?.into())),
            args.and_then(|args| Some(args.package_cache_path.clone()?.into())),
        )
    }
}

#[cfg(test)]
mod tests {
    use std::sync::atomic::AtomicBool;

    use super::*;
    use clap::Parser;

    use crate::args::CompileOnceArgs;
    use crate::{CompileSnapshot, WorldComputable, WorldComputeGraph};

    #[test]
    fn test_args() {
        use tinymist_std::typst::TypstPagedDocument;

        let args = CompileOnceArgs::parse_from(["tinymist", "main.typ"]);
        let verse = args
            .resolve_system()
            .expect("failed to resolve system universe");

        let world = verse.snapshot();
        let _res = typst_shim::compile_opt::<TypstPagedDocument>(&world);
    }

    static FONT_COMPUTED: AtomicBool = AtomicBool::new(false);

    pub struct FontsOnce {
        fonts: Arc<FontResolverImpl>,
    }

    impl WorldComputable<SystemCompilerFeat> for FontsOnce {
        type Output = Self;

        fn compute(graph: &Arc<WorldComputeGraph<SystemCompilerFeat>>) -> Result<Self> {
            // Ensure that this function is only called once.
            if FONT_COMPUTED.swap(true, std::sync::atomic::Ordering::SeqCst) {
                bail!("font already computed");
            }

            Ok(Self {
                fonts: graph.snap.world.font_resolver.clone(),
            })
        }
    }

    #[test]
    fn compute_system_fonts() {
        let args = CompileOnceArgs::parse_from(["tinymist", "main.typ"]);
        let verse = args
            .resolve_system()
            .expect("failed to resolve system universe");

        let snap = CompileSnapshot::from_world(verse.snapshot());

        let graph = WorldComputeGraph::new(snap);

        let font = graph.compute::<FontsOnce>().expect("font").fonts.clone();
        let _ = font;

        let font = graph.compute::<FontsOnce>().expect("font").fonts.clone();
        let _ = font;

        assert!(FONT_COMPUTED.load(std::sync::atomic::Ordering::SeqCst));
    }
}