1use comemo::{Track, Tracked};
2use ecow::EcoString;
3use indexmap::IndexMap;
4use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
5use rustc_hash::FxBuildHasher;
6use typst_html::{HtmlElement, HtmlOptions};
7use typst_layout::PagedDocument;
8use typst_library::diag::{At, ParallelCollectCombinedResult, SourceResult};
9use typst_library::foundations::Bytes;
10use typst_library::introspection::Location;
11use typst_library::model::{LateLinkResolver, PagedFormat};
12use typst_pdf::PdfOptions;
13use typst_render::RenderOptions;
14use typst_svg::SvgOptions;
15use typst_syntax::{Span, VirtualPath};
16
17use crate::{Bundle, BundleDocument, BundleFile};
18
19pub type VirtualFs = IndexMap<VirtualPath, Bytes, FxBuildHasher>;
21
22#[typst_macros::time(name = "export bundle")]
24pub fn export(bundle: &Bundle, options: &BundleOptions) -> SourceResult<VirtualFs> {
25 bundle
26 .files
27 .par_iter()
28 .map(|(path, file)| {
29 let data = match file {
30 BundleFile::Document(doc) => {
31 let link_resolver =
32 LateLinkResolver::new(Some(path), bundle.introspector.as_ref());
33 export_document(doc, options, link_resolver.track())
34 }
35 BundleFile::Asset(bytes) => Ok(bytes.clone()),
36 };
37 data.map(|data| (path.clone(), data))
38 })
39 .collect_combined_result()
40}
41
42#[derive(Debug, Default)]
44pub struct BundleOptions {
45 pub html: HtmlOptions,
47 pub pdf: PdfOptions,
49 pub png: RenderOptions,
51 pub svg: SvgOptions,
53}
54
55fn export_document(
57 doc: &BundleDocument,
58 options: &BundleOptions,
59 link_resolver: Tracked<LateLinkResolver>,
60) -> SourceResult<Bytes> {
61 match doc {
62 BundleDocument::Paged(doc, extras) => match extras.format {
63 PagedFormat::Pdf => {
64 export_pdf(doc, &options.pdf, &extras.anchors, link_resolver)
65 }
66 PagedFormat::Png => export_png(doc, &options.png),
67 PagedFormat::Svg => {
68 export_svg(doc, &options.svg, &extras.anchors, link_resolver)
69 }
70 },
71 BundleDocument::Html(doc) => {
72 export_html(doc.root(), &options.html, link_resolver)
73 }
74 }
75}
76
77#[comemo::memoize]
79#[typst_macros::time(name = "export pdf")]
80fn export_pdf(
81 doc: &PagedDocument,
82 options: &PdfOptions,
83 anchors: &[(Location, EcoString)],
84 link_resolver: Tracked<LateLinkResolver>,
85) -> SourceResult<Bytes> {
86 typst_pdf::pdf_in_bundle(doc, options, anchors, link_resolver).map(Bytes::new)
87}
88
89#[comemo::memoize]
91#[typst_macros::time(name = "export png")]
92fn export_png(doc: &PagedDocument, options: &RenderOptions) -> SourceResult<Bytes> {
93 typst_render::render(&doc.pages()[0], options)
94 .encode_png()
95 .map(Bytes::new)
96 .map_err(|_| "failed to encode PNG")
97 .at(Span::detached())
98}
99
100#[comemo::memoize]
102#[typst_macros::time(name = "export svg")]
103fn export_svg(
104 doc: &PagedDocument,
105 options: &SvgOptions,
106 anchors: &[(Location, EcoString)],
107 link_resolver: Tracked<LateLinkResolver>,
108) -> SourceResult<Bytes> {
109 let anchors = anchors
110 .iter()
111 .filter_map(|(loc, name)| {
112 let point = doc.introspector().position(*loc)?.point;
116 Some((point, name.clone()))
117 })
118 .collect::<Vec<_>>();
119 Ok(Bytes::from_string(typst_svg::svg_in_bundle(
120 &doc.pages()[0],
121 options,
122 &anchors,
123 link_resolver,
124 )))
125}
126
127#[comemo::memoize]
135#[typst_macros::time(name = "export html")]
136fn export_html(
137 root: &HtmlElement,
138 options: &HtmlOptions,
139 link_resolver: Tracked<LateLinkResolver>,
140) -> SourceResult<Bytes> {
141 typst_html::html_in_bundle(root, options, link_resolver).map(Bytes::from_string)
142}