📄 src/typst_renderer/receipt_world.rs
use std::{cell::LazyCell, sync::Arc};

use typst::{
    Library, LibraryExt, World,
    diag::{FileError, FileResult},
    foundations::{Bytes, Datetime, Dict, IntoValue},
    syntax::{FileId, Source},
    text::{Font, FontBook},
    utils::LazyHash,
};

const TEMPLATE_STRING: &str = include_str!("../../assets/template.typ");
const LIBREBASKERVILLE_REGULAR_BYTES: &[u8] =
    include_bytes!("../../assets/LibreBaskerville-Regular.ttf");
const CASCADIACODE_REGULAR_BYTES: &[u8] = include_bytes!("../../assets/CascadiaCode-Regular.ttf");
thread_local! {
    static TEMPLATE_SOURCE: LazyCell<Source> =
        LazyCell::new(|| Source::detached(TEMPLATE_STRING));

    static FONTS: LazyCell<(Arc<LazyHash<FontBook>>, Vec<Font>)> = LazyCell::new(|| {
        let fonts = vec![
            Font::new(Bytes::new(LIBREBASKERVILLE_REGULAR_BYTES), 0).unwrap(),
            Font::new(Bytes::new(CASCADIACODE_REGULAR_BYTES), 0).unwrap(),
        ];
        let font_book = Arc::new(LazyHash::new(FontBook::from_fonts(&fonts)));
        (font_book, fonts)
    });
}

pub struct ReceiptWorld {
    library: LazyHash<Library>,
    font_book: Arc<LazyHash<FontBook>>,
}

impl ReceiptWorld {
    pub fn new(content: &str) -> Self {
        let mut input = Dict::new();
        input.insert("content".into(), content.into_value());

        let font_book = FONTS.with(|fonts| fonts.0.clone());

        Self {
            library: LazyHash::new(Library::builder().with_inputs(input).build()),
            font_book,
        }
    }
}

impl World for ReceiptWorld {
    fn library(&self) -> &LazyHash<Library> {
        &self.library
    }

    fn book(&self) -> &LazyHash<FontBook> {
        &self.font_book
    }

    fn main(&self) -> FileId {
        TEMPLATE_SOURCE.with(|source| source.id())
    }

    fn source(&self, id: FileId) -> FileResult<Source> {
        if id == self.main() {
            Ok(TEMPLATE_SOURCE.with(|source| (*source).clone()))
        } else {
            Err(FileError::NotFound(
                id.vpath().as_rootless_path().to_path_buf(),
            ))
        }
    }

    fn file(&self, id: FileId) -> FileResult<Bytes> {
        Err(FileError::NotFound(
            id.vpath().as_rootless_path().to_path_buf(),
        ))
    }

    fn font(&self, index: usize) -> Option<Font> {
        FONTS.with(|fonts| fonts.1.get(index).cloned())
    }

    fn today(&self, _offset: Option<i64>) -> Option<Datetime> {
        None
    }
}