1use std::str::FromStr;
4use std::sync::Arc;
5
6use tinymist_std::error::prelude::*;
7use tinymist_std::typst::TypstPagedDocument;
8use tinymist_world::{CompileSnapshot, CompilerFeat, ExportComputation, WorldComputeGraph};
9use typst::foundations::Bytes;
10use typst::layout::Abs;
11use typst::model::Document;
12use typst::syntax::{SyntaxNode, ast};
13use typst::visualize::Color;
14use typst_layout::Page;
15
16use crate::{Pages, TaskWhen, exported_page_ranges};
17
18mod html;
19pub use html::*;
20mod png;
21pub use png::*;
22mod query;
23pub use query::*;
24mod svg;
25pub use svg::*;
26#[cfg(feature = "pdf")]
27pub mod pdf;
28#[cfg(feature = "pdf")]
29pub use pdf::*;
30#[cfg(feature = "text")]
31pub mod text;
32#[cfg(feature = "text")]
33pub use text::*;
34
35pub struct SvgFlag;
37pub struct PngFlag;
39pub struct HtmlFlag;
41
42pub struct ExportTimings;
44
45impl ExportTimings {
46 pub fn needs_run<F: CompilerFeat, D: Document>(
48 snap: &CompileSnapshot<F>,
49 timing: Option<&TaskWhen>,
50 docs: Option<&D>,
51 ) -> Option<bool> {
52 snap.signal
53 .should_run_task(timing.unwrap_or(&TaskWhen::Never), docs)
54 }
55}
56
57pub enum ImageOutput<T> {
59 Paged(Vec<PagedOutput<T>>),
61 Merged(T),
63}
64
65pub struct PagedOutput<T> {
67 pub page: usize,
69 pub value: T,
71}
72
73fn select_pages<'a>(
74 document: &'a TypstPagedDocument,
75 pages: &Option<Vec<Pages>>,
76) -> Vec<(usize, &'a Page)> {
77 let pages = pages.as_ref().map(|pages| exported_page_ranges(pages));
78 document
79 .pages()
80 .iter()
81 .enumerate()
82 .filter(|(i, _)| {
83 pages
84 .as_ref()
85 .is_none_or(|exported_page_ranges| exported_page_ranges.includes_page_index(*i))
86 })
87 .collect::<Vec<_>>()
88}
89
90fn parse_length(gap: &str) -> Result<Abs> {
91 let length = typst::syntax::parse_code(gap);
92 if length.diagnosis().errors {
93 bail!(
94 "invalid length: {gap}, errors: {:?}",
95 length.errors_and_warnings().0
96 );
97 }
98
99 let length: Option<ast::Numeric> = descendants(&length).into_iter().find_map(SyntaxNode::cast);
100
101 let Some(length) = length else {
102 bail!("not a length: {gap}");
103 };
104
105 let (value, unit) = length.get();
106 match unit {
107 ast::Unit::Pt => Ok(Abs::pt(value)),
108 ast::Unit::Mm => Ok(Abs::mm(value)),
109 ast::Unit::Cm => Ok(Abs::cm(value)),
110 ast::Unit::In => Ok(Abs::inches(value)),
111 _ => bail!("invalid unit: {unit:?} in {gap}"),
112 }
113}
114
115fn descendants(node: &SyntaxNode) -> impl IntoIterator<Item = &SyntaxNode> + '_ {
117 let mut res = vec![];
118 for child in node.children() {
119 res.push(child);
120 res.extend(descendants(child));
121 }
122
123 res
124}
125
126fn parse_color(fill: &str) -> anyhow::Result<Color> {
127 match fill {
128 "black" => Ok(Color::BLACK),
129 "white" => Ok(Color::WHITE),
130 "red" => Ok(Color::RED),
131 "green" => Ok(Color::GREEN),
132 "blue" => Ok(Color::BLUE),
133 hex if hex.starts_with('#') => {
134 Color::from_str(&hex[1..]).map_err(|e| anyhow::anyhow!("failed to parse color: {e}"))
135 }
136 _ => anyhow::bail!("invalid color: {fill}"),
137 }
138}
139
140#[cfg(test)]
141mod tests {
142
143 use super::*;
144
145 #[test]
146 fn test_parse_color() {
147 assert_eq!(parse_color("black").unwrap(), Color::BLACK);
148 assert_eq!(parse_color("white").unwrap(), Color::WHITE);
149 assert_eq!(parse_color("red").unwrap(), Color::RED);
150 assert_eq!(parse_color("green").unwrap(), Color::GREEN);
151 assert_eq!(parse_color("blue").unwrap(), Color::BLUE);
152 assert_eq!(parse_color("#000000").unwrap().to_hex(), "#000000");
153 assert_eq!(parse_color("#ffffff").unwrap().to_hex(), "#ffffff");
154 assert_eq!(parse_color("#000000cc").unwrap().to_hex(), "#000000cc");
155 assert!(parse_color("invalid").is_err());
156 }
157
158 #[test]
159 fn test_parse_length() {
160 assert_eq!(parse_length("1pt").unwrap(), Abs::pt(1.));
161 assert_eq!(parse_length("1mm").unwrap(), Abs::mm(1.));
162 assert_eq!(parse_length("1cm").unwrap(), Abs::cm(1.));
163 assert_eq!(parse_length("1in").unwrap(), Abs::inches(1.));
164 assert!(parse_length("1").is_err());
165 assert!(parse_length("1px").is_err());
166 }
167}