use crate::util::decompress;
use include_dir::Dir;
use std::borrow::Cow;
use std::collections::HashMap;
use typst::diag::{FileError, FileResult};
use typst::foundations::Bytes;
use typst::syntax::{FileId, Source};
pub use typst_as_lib::file_resolver::FileResolver;
pub struct EmbeddedResolver {
files: HashMap<String, &'static [u8]>,
runtime_files: HashMap<String, Vec<u8>>,
}
impl EmbeddedResolver {
pub fn new(templates: &'static Dir<'static>, packages: &'static Dir<'static>) -> Self {
let mut files = HashMap::new();
collect_files(templates, "", &mut files);
collect_files(packages, "", &mut files);
Self {
files,
runtime_files: HashMap::new(),
}
}
fn get_path(&self, id: FileId) -> String {
if let Some(pkg) = id.package() {
format!(
"{}/{}/{}/{}",
pkg.namespace,
pkg.name,
pkg.version,
normalize_path(id.vpath().as_rootless_path())
)
} else {
normalize_path(id.vpath().as_rootless_path())
}
}
pub(crate) fn insert_runtime_file(&mut self, path: String, data: Vec<u8>) {
self.runtime_files.insert(path, data);
}
fn decompress_file(&self, id: FileId) -> FileResult<Vec<u8>> {
let path = self.get_path(id);
if let Some(data) = self.runtime_files.get(&path) {
return Ok(data.clone());
}
let compressed = self
.files
.get(&path)
.copied()
.ok_or_else(|| not_found(id))?;
decompress(compressed).map_err(|e| {
FileError::Other(Some(format!("Decompression failed for {path}: {e}").into()))
})
}
}
impl FileResolver for EmbeddedResolver {
fn resolve_binary(&self, id: FileId) -> FileResult<Cow<'_, Bytes>> {
let data = self.decompress_file(id)?;
Ok(Cow::Owned(Bytes::new(data)))
}
fn resolve_source(&self, id: FileId) -> FileResult<Cow<'_, Source>> {
let bytes = self.decompress_file(id)?;
let source = bytes_to_source(id, &bytes)?;
Ok(Cow::Owned(source))
}
}
fn normalize_path(path: &std::path::Path) -> String {
path.display().to_string().replace('\\', "/")
}
pub(crate) fn normalize_file_path(path: &str) -> String {
path.trim_start_matches("./").replace('\\', "/")
}
fn join_path(prefix: &str, name: &str) -> String {
if prefix.is_empty() {
name.to_owned()
} else {
format!("{prefix}/{name}")
}
}
fn collect_files(
dir: &'static Dir<'static>,
prefix: &str,
map: &mut HashMap<String, &'static [u8]>,
) {
for file in dir.files() {
let full_path = join_path(prefix, &normalize_path(file.path()));
map.insert(full_path, file.contents());
}
for subdir in dir.dirs() {
let new_prefix = join_path(prefix, &normalize_path(subdir.path()));
collect_files(subdir, &new_prefix, map);
}
}
fn not_found(id: FileId) -> FileError {
FileError::NotFound(id.vpath().as_rootless_path().into())
}
fn bytes_to_source(id: FileId, bytes: &[u8]) -> FileResult<Source> {
let text = if bytes.starts_with(&[0xEF, 0xBB, 0xBF]) {
std::str::from_utf8(&bytes[3..])
} else {
std::str::from_utf8(bytes)
};
let text = text.map_err(|_| FileError::InvalidUtf8)?;
Ok(Source::new(id, text.to_string()))
}