use comemo::{Track, Tracked};
use ecow::EcoString;
use indexmap::IndexMap;
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use rustc_hash::FxBuildHasher;
use typst_html::{HtmlElement, HtmlOptions};
use typst_layout::PagedDocument;
use typst_library::diag::{At, ParallelCollectCombinedResult, SourceResult};
use typst_library::foundations::Bytes;
use typst_library::introspection::Location;
use typst_library::model::{LateLinkResolver, PagedFormat};
use typst_pdf::PdfOptions;
use typst_render::RenderOptions;
use typst_svg::SvgOptions;
use typst_syntax::{Span, VirtualPath};
use crate::{Bundle, BundleDocument, BundleFile};
pub type VirtualFs = IndexMap<VirtualPath, Bytes, FxBuildHasher>;
#[typst_macros::time(name = "export bundle")]
pub fn export(bundle: &Bundle, options: &BundleOptions) -> SourceResult<VirtualFs> {
bundle
.files
.par_iter()
.map(|(path, file)| {
let data = match file {
BundleFile::Document(doc) => {
let link_resolver =
LateLinkResolver::new(Some(path), bundle.introspector.as_ref());
export_document(doc, options, link_resolver.track())
}
BundleFile::Asset(bytes) => Ok(bytes.clone()),
};
data.map(|data| (path.clone(), data))
})
.collect_combined_result()
}
#[derive(Debug, Default)]
pub struct BundleOptions {
pub html: HtmlOptions,
pub pdf: PdfOptions,
pub png: RenderOptions,
pub svg: SvgOptions,
}
fn export_document(
doc: &BundleDocument,
options: &BundleOptions,
link_resolver: Tracked<LateLinkResolver>,
) -> SourceResult<Bytes> {
match doc {
BundleDocument::Paged(doc, extras) => match extras.format {
PagedFormat::Pdf => {
export_pdf(doc, &options.pdf, &extras.anchors, link_resolver)
}
PagedFormat::Png => export_png(doc, &options.png),
PagedFormat::Svg => {
export_svg(doc, &options.svg, &extras.anchors, link_resolver)
}
},
BundleDocument::Html(doc) => {
export_html(doc.root(), &options.html, link_resolver)
}
}
}
#[comemo::memoize]
#[typst_macros::time(name = "export pdf")]
fn export_pdf(
doc: &PagedDocument,
options: &PdfOptions,
anchors: &[(Location, EcoString)],
link_resolver: Tracked<LateLinkResolver>,
) -> SourceResult<Bytes> {
typst_pdf::pdf_in_bundle(doc, options, anchors, link_resolver).map(Bytes::new)
}
#[comemo::memoize]
#[typst_macros::time(name = "export png")]
fn export_png(doc: &PagedDocument, options: &RenderOptions) -> SourceResult<Bytes> {
typst_render::render(&doc.pages()[0], options)
.encode_png()
.map(Bytes::new)
.map_err(|_| "failed to encode PNG")
.at(Span::detached())
}
#[comemo::memoize]
#[typst_macros::time(name = "export svg")]
fn export_svg(
doc: &PagedDocument,
options: &SvgOptions,
anchors: &[(Location, EcoString)],
link_resolver: Tracked<LateLinkResolver>,
) -> SourceResult<Bytes> {
let anchors = anchors
.iter()
.filter_map(|(loc, name)| {
let point = doc.introspector().position(*loc)?.point;
Some((point, name.clone()))
})
.collect::<Vec<_>>();
Ok(Bytes::from_string(typst_svg::svg_in_bundle(
&doc.pages()[0],
options,
&anchors,
link_resolver,
)))
}
#[comemo::memoize]
#[typst_macros::time(name = "export html")]
fn export_html(
root: &HtmlElement,
options: &HtmlOptions,
link_resolver: Tracked<LateLinkResolver>,
) -> SourceResult<Bytes> {
typst_html::html_in_bundle(root, options, link_resolver).map(Bytes::from_string)
}