feat: compile tinymist-query for wasm32 target (#1947)

- #2025 
- #2026

---------

Co-authored-by: Momijiichigo <ichigomomiji436@gmail.com>
This commit is contained in:
Myriad-Dreamin 2025-08-11 09:35:07 +08:00 committed by GitHub
parent b102b42d2c
commit 79f68dc94d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 191 additions and 88 deletions

3
Cargo.lock generated
View file

@ -4250,7 +4250,8 @@ dependencies = [
"console_error_panic_hook", "console_error_panic_hook",
"js-sys", "js-sys",
"reflexo-typst", "reflexo-typst",
"tinymist-world", "tinymist-project",
"tinymist-query",
"vergen", "vergen",
"wasm-bindgen", "wasm-bindgen",
] ]

View file

@ -22,7 +22,7 @@ web = [
"js-sys", "js-sys",
"console_error_panic_hook", "console_error_panic_hook",
"no-content-hint", "no-content-hint",
"tinymist-world/web", "tinymist-project/web",
"reflexo-typst/web", "reflexo-typst/web",
] ]
@ -31,7 +31,8 @@ no-content-hint = ["reflexo-typst/no-content-hint"]
[dependencies] [dependencies]
wasm-bindgen = { version = "0.2.100", optional = true } wasm-bindgen = { version = "0.2.100", optional = true }
js-sys = { version = "0.3.77", optional = true } js-sys = { version = "0.3.77", optional = true }
tinymist-world.workspace = true tinymist-project.workspace = true
tinymist-query.workspace = true
reflexo-typst.workspace = true reflexo-typst.workspace = true
console_error_panic_hook = { version = "0.1.2", optional = true } console_error_panic_hook = { version = "0.1.2", optional = true }

View file

@ -40,9 +40,11 @@ notify.workspace = true
fonts = ["typst-assets/fonts"] fonts = ["typst-assets/fonts"]
no-content-hint = ["tinymist-task/no-content-hint"] no-content-hint = ["tinymist-task/no-content-hint"]
lsp = ["system", "toml"] lsp = ["toml"]
# "system",
system = ["tinymist-std/system", "tinymist-world/system"] system = ["tinymist-std/system", "tinymist-world/system"]
web = ["tinymist-std/web", "tinymist-world/web"]
[lints] [lints]
workspace = true workspace = true

View file

@ -11,22 +11,20 @@ mod lock;
mod lsp; mod lsp;
#[cfg(feature = "system")] #[cfg(feature = "system")]
mod watch; mod watch;
#[cfg(feature = "system")]
pub mod world; pub mod world;
pub use args::*; pub use args::*;
pub use compiler::*; pub use compiler::*;
pub use entry::*; pub use entry::*;
pub use model::*;
#[cfg(feature = "lsp")]
pub use lock::*; pub use lock::*;
pub use model::*;
pub use world::*;
#[cfg(feature = "lsp")] #[cfg(feature = "lsp")]
pub use lsp::*; pub use lsp::*;
#[cfg(feature = "system")] #[cfg(feature = "system")]
pub use watch::*; pub use watch::*;
#[cfg(feature = "system")]
pub use world::*;
pub use tinymist_world::{CompileSignal, CompileSnapshot, ProjectInsId}; pub use tinymist_world::{CompileSignal, CompileSnapshot, ProjectInsId};

View file

@ -1,24 +1,20 @@
use std::path::Path; use std::path::Path;
use std::{borrow::Cow, sync::Arc}; use std::sync::Arc;
use tinymist_std::error::prelude::*; use tinymist_std::error::prelude::*;
use tinymist_std::{bail, ImmutPath}; use tinymist_std::ImmutPath;
use tinymist_task::ExportTarget; use tinymist_task::ExportTarget;
use tinymist_world::config::CompileFontOpts; use tinymist_world::package::RegistryPathMapper;
use tinymist_world::font::system::SystemFontSearcher; use tinymist_world::vfs::Vfs;
use tinymist_world::package::{registry::HttpRegistry, RegistryPathMapper};
use tinymist_world::vfs::{system::SystemAccessModel, Vfs};
use tinymist_world::{args::*, WorldComputeGraph}; use tinymist_world::{args::*, WorldComputeGraph};
use tinymist_world::{ use tinymist_world::{
CompileSnapshot, CompilerFeat, CompilerUniverse, CompilerWorld, EntryOpts, EntryState, CompileSnapshot, CompilerFeat, CompilerUniverse, CompilerWorld, EntryOpts, EntryState,
}; };
use typst::diag::FileResult; use typst::diag::FileResult;
use typst::foundations::{Bytes, Dict, Str, Value}; use typst::foundations::{Bytes, Dict};
use typst::utils::LazyHash; use typst::utils::LazyHash;
use typst::Features; use typst::Features;
use crate::ProjectInput;
use crate::world::font::FontResolverImpl; use crate::world::font::FontResolverImpl;
use crate::{CompiledArtifact, Interrupt}; use crate::{CompiledArtifact, Interrupt};
@ -33,7 +29,11 @@ impl CompilerFeat for LspCompilerFeat {
/// It accesses a physical file system. /// It accesses a physical file system.
type AccessModel = DynAccessModel; type AccessModel = DynAccessModel;
/// It performs native HTTP requests for fetching package data. /// It performs native HTTP requests for fetching package data.
type Registry = HttpRegistry; #[cfg(feature = "system")]
type Registry = tinymist_world::package::registry::HttpRegistry;
// todo: registry in browser
#[cfg(not(feature = "system"))]
type Registry = tinymist_world::package::registry::DummyRegistry;
} }
/// LSP universe that spawns LSP worlds. /// LSP universe that spawns LSP worlds.
@ -59,6 +59,7 @@ pub trait WorldProvider {
fn resolve(&self) -> Result<LspUniverse>; fn resolve(&self) -> Result<LspUniverse>;
} }
#[cfg(feature = "system")]
impl WorldProvider for CompileOnceArgs { impl WorldProvider for CompileOnceArgs {
fn resolve(&self) -> Result<LspUniverse> { fn resolve(&self) -> Result<LspUniverse> {
let entry = self.entry()?.try_into()?; let entry = self.entry()?.try_into()?;
@ -78,7 +79,7 @@ impl WorldProvider for CompileOnceArgs {
packages, packages,
fonts, fonts,
self.creation_timestamp, self.creation_timestamp,
DynAccessModel(Arc::new(SystemAccessModel {})), DynAccessModel(Arc::new(tinymist_world::vfs::system::SystemAccessModel {})),
)) ))
} }
@ -129,8 +130,11 @@ impl WorldProvider for CompileOnceArgs {
} }
// todo: merge me with the above impl // todo: merge me with the above impl
impl WorldProvider for (ProjectInput, ImmutPath) { #[cfg(feature = "system")]
impl WorldProvider for (crate::ProjectInput, ImmutPath) {
fn resolve(&self) -> Result<LspUniverse> { fn resolve(&self) -> Result<LspUniverse> {
use typst::foundations::{Str, Value};
let (proj, lock_dir) = self; let (proj, lock_dir) = self;
let entry = self.entry()?.try_into()?; let entry = self.entry()?.try_into()?;
let inputs = proj let inputs = proj
@ -172,7 +176,7 @@ impl WorldProvider for (ProjectInput, ImmutPath) {
packages, packages,
Arc::new(fonts), Arc::new(fonts),
None, // creation_timestamp - not available in project file context None, // creation_timestamp - not available in project file context
DynAccessModel(Arc::new(SystemAccessModel {})), DynAccessModel(Arc::new(tinymist_world::vfs::system::SystemAccessModel {})),
)) ))
} }
@ -207,6 +211,11 @@ impl WorldProvider for (ProjectInput, ImmutPath) {
} }
} }
#[cfg(not(feature = "system"))]
type LspRegistry = tinymist_world::package::registry::DummyRegistry;
#[cfg(feature = "system")]
type LspRegistry = tinymist_world::package::registry::HttpRegistry;
/// Builder for LSP universe. /// Builder for LSP universe.
pub struct LspUniverseBuilder; pub struct LspUniverseBuilder;
@ -219,7 +228,7 @@ impl LspUniverseBuilder {
export_target: ExportTarget, export_target: ExportTarget,
features: Features, features: Features,
inputs: ImmutDict, inputs: ImmutDict,
package_registry: HttpRegistry, package_registry: LspRegistry,
font_resolver: Arc<FontResolverImpl>, font_resolver: Arc<FontResolverImpl>,
creation_timestamp: Option<i64>, creation_timestamp: Option<i64>,
access_model: DynAccessModel, access_model: DynAccessModel,
@ -246,38 +255,54 @@ impl LspUniverseBuilder {
} }
/// Resolve fonts from given options. /// Resolve fonts from given options.
#[cfg(feature = "system")]
pub fn only_embedded_fonts() -> Result<FontResolverImpl> { pub fn only_embedded_fonts() -> Result<FontResolverImpl> {
let mut searcher = SystemFontSearcher::new(); let mut searcher = tinymist_world::font::system::SystemFontSearcher::new();
searcher.resolve_opts(CompileFontOpts { searcher.resolve_opts(tinymist_world::config::CompileFontOpts {
font_paths: vec![], font_paths: vec![],
no_system_fonts: true, no_system_fonts: true,
with_embedded_fonts: typst_assets::fonts().map(Cow::Borrowed).collect(), with_embedded_fonts: typst_assets::fonts()
.map(std::borrow::Cow::Borrowed)
.collect(),
})?; })?;
Ok(searcher.build()) Ok(searcher.build())
} }
/// Resolve fonts from given options. /// Resolve fonts from given options.
#[cfg(feature = "system")]
pub fn resolve_fonts(args: CompileFontArgs) -> Result<FontResolverImpl> { pub fn resolve_fonts(args: CompileFontArgs) -> Result<FontResolverImpl> {
let mut searcher = SystemFontSearcher::new(); let mut searcher = tinymist_world::font::system::SystemFontSearcher::new();
searcher.resolve_opts(CompileFontOpts { searcher.resolve_opts(tinymist_world::config::CompileFontOpts {
font_paths: args.font_paths, font_paths: args.font_paths,
no_system_fonts: args.ignore_system_fonts, no_system_fonts: args.ignore_system_fonts,
with_embedded_fonts: typst_assets::fonts().map(Cow::Borrowed).collect(), with_embedded_fonts: typst_assets::fonts()
.map(std::borrow::Cow::Borrowed)
.collect(),
})?; })?;
Ok(searcher.build()) Ok(searcher.build())
} }
/// Resolve package registry from given options. /// Resolves package registry from given options.
#[cfg(feature = "system")]
pub fn resolve_package( pub fn resolve_package(
cert_path: Option<ImmutPath>, cert_path: Option<ImmutPath>,
args: Option<&CompilePackageArgs>, args: Option<&CompilePackageArgs>,
) -> HttpRegistry { ) -> tinymist_world::package::registry::HttpRegistry {
HttpRegistry::new( tinymist_world::package::registry::HttpRegistry::new(
cert_path, cert_path,
args.and_then(|args| Some(args.package_path.clone()?.into())), args.and_then(|args| Some(args.package_path.clone()?.into())),
args.and_then(|args| Some(args.package_cache_path.clone()?.into())), args.and_then(|args| Some(args.package_cache_path.clone()?.into())),
) )
} }
/// Resolves package registry from given options.
#[cfg(not(feature = "system"))]
pub fn resolve_package(
_cert_path: Option<ImmutPath>,
_args: Option<&CompilePackageArgs>,
) -> tinymist_world::package::registry::DummyRegistry {
tinymist_world::package::registry::DummyRegistry
}
} }
/// Access model for LSP universe and worlds. /// Access model for LSP universe and worlds.

View file

@ -4,8 +4,11 @@ pub use tinymist_world as base;
pub use tinymist_world::args::*; pub use tinymist_world::args::*;
pub use tinymist_world::config::CompileFontOpts; pub use tinymist_world::config::CompileFontOpts;
pub use tinymist_world::entry::*; pub use tinymist_world::entry::*;
pub use tinymist_world::{diag, font, package, system, vfs}; pub use tinymist_world::{diag, font, package, vfs};
pub use tinymist_world::{ pub use tinymist_world::{
with_main, CompilerUniverse, CompilerWorld, DiagnosticFormat, EntryOpts, EntryState, with_main, CompilerUniverse, CompilerWorld, DiagnosticFormat, EntryOpts, EntryState,
RevisingUniverse, SourceWorld, TaskInputs, RevisingUniverse, SourceWorld, TaskInputs,
}; };
#[cfg(feature = "system")]
pub use tinymist_world::system;

View file

@ -209,26 +209,28 @@ impl TypeCompletionWorker<'_, '_, '_, '_> {
BuiltinTy::TextSize => return None, BuiltinTy::TextSize => return None,
BuiltinTy::TextLang => { BuiltinTy::TextLang => {
for (&key, desc) in rust_iso639::ALL_MAP.entries() { for (&key, desc) in rust_iso639::ALL_MAP.entries() {
let detail = eco_format!("An ISO 639-1/2/3 language code, {}.", desc.name); let detail =
eco_format!("An ISO 639-1/2/3 language code, {}.", desc.get_name());
self.base.push_completion(Completion { self.base.push_completion(Completion {
kind: CompletionKind::Syntax, kind: CompletionKind::Syntax,
label: key.to_lowercase().into(), label: key.to_lowercase().into(),
apply: Some(eco_format!("\"{}\"", key.to_lowercase())), apply: Some(eco_format!("\"{}\"", key.to_lowercase())),
detail: Some(detail), detail: Some(detail),
label_details: Some(desc.name.into()), label_details: Some(desc.get_name()),
..Completion::default() ..Completion::default()
}); });
} }
} }
BuiltinTy::TextRegion => { BuiltinTy::TextRegion => {
for (&key, desc) in rust_iso3166::ALPHA2_MAP.entries() { for (&key, desc) in rust_iso3166::ALPHA2_MAP.entries() {
let detail = eco_format!("An ISO 3166-1 alpha-2 region code, {}.", desc.name); let detail =
eco_format!("An ISO 3166-1 alpha-2 region code, {}.", desc.get_name());
self.base.push_completion(Completion { self.base.push_completion(Completion {
kind: CompletionKind::Syntax, kind: CompletionKind::Syntax,
label: key.to_lowercase().into(), label: key.to_lowercase().into(),
apply: Some(eco_format!("\"{}\"", key.to_lowercase())), apply: Some(eco_format!("\"{}\"", key.to_lowercase())),
detail: Some(detail), detail: Some(detail),
label_details: Some(desc.name.into()), label_details: Some(desc.get_name()),
..Completion::default() ..Completion::default()
}); });
} }
@ -346,3 +348,37 @@ impl TypeCompletionWorker<'_, '_, '_, '_> {
Some(()) Some(())
} }
} }
// desc.name()
trait GetName {
fn get_name(&self) -> EcoString;
}
#[cfg(not(target_arch = "wasm32"))]
impl GetName for rust_iso639::LanguageCode<'_> {
fn get_name(&self) -> EcoString {
self.name.into()
}
}
#[cfg(not(all(direct_wasm, target_arch = "wasm32")))]
impl GetName for rust_iso3166::CountryCode {
fn get_name(&self) -> EcoString {
self.name.into()
}
}
#[cfg(target_arch = "wasm32")]
impl GetName for rust_iso639::LanguageCode {
fn get_name(&self) -> EcoString {
self.name().into()
}
}
#[cfg(all(direct_wasm, target_arch = "wasm32"))]
impl GetName for rust_iso3166::CountryCode {
fn get_name(&self) -> EcoString {
self.name().into()
}
}

View file

@ -20,9 +20,7 @@ use tinymist_std::typst::TypstDocument;
use tinymist_world::debug_loc::DataSource; use tinymist_world::debug_loc::DataSource;
use tinymist_world::vfs::{PathResolution, WorkspaceResolver}; use tinymist_world::vfs::{PathResolution, WorkspaceResolver};
use tinymist_world::{EntryReader, DETACHED_ENTRY}; use tinymist_world::{EntryReader, DETACHED_ENTRY};
use typst::diag::{ use typst::diag::{At, FileError, FileResult, SourceDiagnostic, SourceResult, StrResult};
eco_format, At, FileError, FileResult, SourceDiagnostic, SourceResult, StrResult,
};
use typst::foundations::{Bytes, IntoValue, Module, StyleChain, Styles}; use typst::foundations::{Bytes, IntoValue, Module, StyleChain, Styles};
use typst::introspection::Introspector; use typst::introspection::Introspector;
use typst::layout::Position; use typst::layout::Position;
@ -674,6 +672,7 @@ impl SharedContext {
} }
/// Get the local packages and their descriptions. /// Get the local packages and their descriptions.
#[cfg(not(target_arch = "wasm32"))]
pub fn local_packages(&self) -> EcoVec<PackageSpec> { pub fn local_packages(&self) -> EcoVec<PackageSpec> {
crate::package::list_package_by_namespace(&self.world.registry, eco_format!("local")) crate::package::list_package_by_namespace(&self.world.registry, eco_format!("local"))
.into_iter() .into_iter()
@ -681,6 +680,12 @@ impl SharedContext {
.collect() .collect()
} }
/// Get the local packages and their descriptions.
#[cfg(target_arch = "wasm32")]
pub fn local_packages(&self) -> EcoVec<PackageSpec> {
eco_vec![]
}
pub(crate) fn const_eval(rr: ast::Expr<'_>) -> Option<Value> { pub(crate) fn const_eval(rr: ast::Expr<'_>) -> Option<Value> {
Some(match rr { Some(match rr {
ast::Expr::None(_) => Value::None, ast::Expr::None(_) => Value::None,

View file

@ -31,11 +31,7 @@ pub fn path_to_url(path: &Path) -> anyhow::Result<Url> {
return untitled_url(untitled); return untitled_url(untitled);
} }
Url::from_file_path(path).or_else(|never| { url_from_file_path(path)
let _: () = never;
anyhow::bail!("could not convert path to URI: path: {path:?}",)
})
} }
/// Convert a path resolution to a URL. /// Convert a path resolution to a URL.
@ -54,9 +50,7 @@ pub fn url_to_path(uri: Url) -> PathBuf {
return PathBuf::from("/untitled/nEoViM-BuG"); return PathBuf::from("/untitled/nEoViM-BuG");
} }
return uri return url_to_file_path(&uri);
.to_file_path()
.unwrap_or_else(|_| panic!("could not convert URI to path: URI: {uri:?}",));
} }
if uri.scheme() == "untitled" { if uri.scheme() == "untitled" {
@ -73,7 +67,40 @@ pub fn url_to_path(uri: Url) -> PathBuf {
return Path::new(String::from_utf8_lossy(&bytes).as_ref()).clean(); return Path::new(String::from_utf8_lossy(&bytes).as_ref()).clean();
} }
uri.to_file_path().unwrap() url_to_file_path(&uri)
}
#[cfg(not(target_arch = "wasm32"))]
fn url_from_file_path(path: &Path) -> anyhow::Result<Url> {
Url::from_file_path(path).or_else(|never| {
let _: () = never;
anyhow::bail!("could not convert path to URI: path: {path:?}",)
})
}
#[cfg(target_arch = "wasm32")]
fn url_from_file_path(path: &Path) -> anyhow::Result<Url> {
// In WASM, create a simple file:// URL
let path_str = path.to_string_lossy();
let url_str = if path_str.starts_with('/') {
format!("file://{}", path_str)
} else {
format!("file:///{}", path_str)
};
Url::parse(&url_str).map_err(|e| anyhow::anyhow!("could not convert path to URI: {}", e))
}
#[cfg(not(target_arch = "wasm32"))]
fn url_to_file_path(uri: &Url) -> PathBuf {
uri.to_file_path()
.unwrap_or_else(|_| panic!("could not convert URI to path: URI: {uri:?}",))
}
#[cfg(target_arch = "wasm32")]
fn url_to_file_path(uri: &Url) -> PathBuf {
// In WASM, manually parse the URL path
PathBuf::from(uri.path())
} }
#[cfg(test)] #[cfg(test)]

View file

@ -1,14 +1,10 @@
//! Package management tools. //! Package management tools.
use std::collections::HashSet;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::OnceLock;
use ecow::{eco_format, eco_vec, EcoVec}; use ecow::eco_format;
use parking_lot::Mutex;
// use reflexo_typst::typst::prelude::*; // use reflexo_typst::typst::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tinymist_world::package::registry::HttpRegistry;
use tinymist_world::package::PackageSpec; use tinymist_world::package::PackageSpec;
use typst::diag::{EcoString, StrResult}; use typst::diag::{EcoString, StrResult};
use typst::syntax::package::PackageManifest; use typst::syntax::package::PackageManifest;
@ -77,11 +73,47 @@ pub fn check_package(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<()
Ok(()) Ok(())
} }
#[cfg(not(target_arch = "wasm32"))]
/// Get the packages in namespaces and their descriptions. /// Get the packages in namespaces and their descriptions.
pub fn list_package_by_namespace( pub fn list_package_by_namespace(
registry: &HttpRegistry, registry: &tinymist_world::package::registry::HttpRegistry,
ns: EcoString, ns: EcoString,
) -> EcoVec<(PathBuf, PackageSpec)> { ) -> ecow::EcoVec<(PathBuf, PackageSpec)> {
use std::collections::HashSet;
use std::sync::OnceLock;
use ecow::eco_vec;
use parking_lot::Mutex;
trait IsDirFollowLinks {
fn is_dir_follow_links(&self) -> bool;
}
impl IsDirFollowLinks for PathBuf {
fn is_dir_follow_links(&self) -> bool {
// Although `canonicalize` is heavy, we must use it because `symlink_metadata`
// is not reliable.
self.canonicalize()
.map(|meta| meta.is_dir())
.unwrap_or(false)
}
}
fn once_log<T, E: std::fmt::Display>(result: Result<T, E>, site: &'static str) -> Option<T> {
let err = match result {
Ok(value) => return Some(value),
Err(err) => err,
};
static ONCE: OnceLock<Mutex<HashSet<&'static str>>> = OnceLock::new();
let mut once = ONCE.get_or_init(Default::default).lock();
if once.insert(site) {
log::error!("failed to perform {site}: {err}");
}
None
}
// search packages locally. We only search in the data // search packages locally. We only search in the data
// directory and not the cache directory, because the latter is not // directory and not the cache directory, because the latter is not
// intended for storage of local packages. // intended for storage of local packages.
@ -148,32 +180,3 @@ pub fn list_package_by_namespace(
packages packages
} }
trait IsDirFollowLinks {
fn is_dir_follow_links(&self) -> bool;
}
impl IsDirFollowLinks for PathBuf {
fn is_dir_follow_links(&self) -> bool {
// Although `canonicalize` is heavy, we must use it because `symlink_metadata`
// is not reliable.
self.canonicalize()
.map(|meta| meta.is_dir())
.unwrap_or(false)
}
}
fn once_log<T, E: std::fmt::Display>(result: Result<T, E>, site: &'static str) -> Option<T> {
let err = match result {
Ok(value) => return Some(value),
Err(err) => err,
};
static ONCE: OnceLock<Mutex<HashSet<&'static str>>> = OnceLock::new();
let mut once = ONCE.get_or_init(Default::default).lock();
if once.insert(site) {
log::error!("failed to perform {site}: {err}");
}
None
}

View file

@ -814,7 +814,6 @@ impl<F: CompilerFeat> World for CompilerWorld<F> {
if let Some(timestamp) = self.creation_timestamp { if let Some(timestamp) = self.creation_timestamp {
tinymist_std::time::UtcDateTime::from_unix_timestamp(timestamp) tinymist_std::time::UtcDateTime::from_unix_timestamp(timestamp)
.unwrap_or_else(|_| now().into()) .unwrap_or_else(|_| now().into())
.into()
} else { } else {
now().into() now().into()
} }

View file

@ -51,10 +51,13 @@ clap = ["dep:clap"]
# Note: this is the feature for typlite as a CLI, not for others. # Note: this is the feature for typlite as a CLI, not for others.
# `docx` is enabled in CLI mode, but not in library mode. # `docx` is enabled in CLI mode, but not in library mode.
# `fonts` is enabled in CLI mode. # `fonts` is enabled in CLI mode.
cli = ["clap", "clap/wrap_help", "docx", "fonts"] cli = ["clap", "clap/wrap_help", "docx", "fonts", "system"]
no-content-hint = ["tinymist-project/no-content-hint"] no-content-hint = ["tinymist-project/no-content-hint"]
docx = ["docx-rs", "image", "resvg"] docx = ["docx-rs", "image", "resvg"]
system = ["tinymist-project/system"]
web = ["tinymist-project/web"]
# Embeds Typst's default fonts for # Embeds Typst's default fonts for
# - text (Linux Libertine), # - text (Linux Libertine),
# - math (New Computer Modern Math), and # - math (New Computer Modern Math), and