use std::collections::HashMap;
use chrono::{Datelike, Duration, Local};
use comemo::Prehashed;
use typst::diag::{FileError, FileResult, SourceResult};
use typst::eval::Tracer;
use typst::foundations::{Bytes, Datetime, Dict};
use typst::model::Document;
use typst::syntax::{FileId, Source, VirtualPath};
use typst::text::{Font, FontBook};
use typst::Library;
#[derive(Debug, Clone)]
pub struct TypstTemplate {
book: Prehashed<FontBook>,
source: Source,
other_sources: HashMap<FileId, Source>,
files: HashMap<FileId, Bytes>,
fonts: Vec<Font>,
}
impl TypstTemplate {
pub fn new<S>(fonts: Vec<Font>, source: S) -> Self
where
S: Into<String>,
{
Self {
book: Prehashed::new(FontBook::from_fonts(&fonts)),
source: Source::new(
FileId::new(None, VirtualPath::new("/template.typ")),
source.into(),
),
fonts,
other_sources: Default::default(),
files: Default::default(),
}
}
pub fn add_other_sources_from_strings<I, S>(mut self, other_sources: I) -> Self
where
I: IntoIterator<Item = (FileId, S)>,
S: Into<String>,
{
let new_other_sources = other_sources
.into_iter()
.map(|(id, s)| (id, Source::new(id, s.into())));
self.other_sources.extend(new_other_sources);
self
}
pub fn add_other_sources<I, S>(mut self, other_sources: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<Source>,
{
let new_other_sources = other_sources.into_iter().map(|s| {
let source: Source = s.into();
(source.id(), source)
});
self.other_sources.extend(new_other_sources);
self
}
pub fn add_binary_files<'a, I, B>(mut self, files: I) -> Self
where
I: IntoIterator<Item = (FileId, B)>,
B: Into<Bytes>,
{
let new_files = files.into_iter().map(|(id, b)| (id, b.into()));
self.files.extend(new_files);
self
}
pub fn compile_with_input<D>(&self, tracer: &mut Tracer, input: D) -> SourceResult<Document>
where
D: Into<Dict>,
{
let library = Prehashed::new(Library::builder().with_inputs(input.into()).build());
let world = TypstWorld {
library,
template: self,
};
typst::compile(&world, tracer)
}
pub fn compile(&self, tracer: &mut Tracer) -> SourceResult<Document> {
let library = Prehashed::new(Default::default());
let world = TypstWorld {
library,
template: self,
};
typst::compile(&world, tracer)
}
}
struct TypstWorld<'a> {
library: Prehashed<Library>,
template: &'a TypstTemplate,
}
impl typst::World for TypstWorld<'_> {
fn library(&self) -> &Prehashed<Library> {
&self.library
}
fn book(&self) -> &Prehashed<FontBook> {
&self.template.book
}
fn main(&self) -> Source {
self.template.source.clone()
}
fn source(&self, id: FileId) -> FileResult<Source> {
let TypstWorld {
template: TypstTemplate { other_sources, .. },
..
} = self;
if let Some(source) = other_sources.get(&id).cloned() {
return Ok(source);
}
if id == self.main().id() {
return Ok(self.main());
}
Err(FileError::NotFound(
id.vpath().as_rooted_path().to_path_buf(),
))
}
fn file(&self, id: FileId) -> FileResult<Bytes> {
let TypstWorld {
template: TypstTemplate { files, .. },
..
} = self;
files
.get(&id)
.cloned()
.ok_or_else(|| FileError::NotFound(id.vpath().as_rooted_path().to_path_buf()))
}
fn font(&self, id: usize) -> Option<Font> {
self.template.fonts.get(id).cloned()
}
fn today(&self, offset: Option<i64>) -> Option<Datetime> {
let mut now = Local::now();
if let Some(offset) = offset {
now += Duration::hours(offset);
}
let date = now.date_naive();
let year = date.year();
let month = (date.month0() + 1) as u8;
let day = (date.day0() + 1) as u8;
Datetime::from_ymd(year, month, day)
}
}