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