reflexo_typst2hast/
lib.rs1use std::sync::Arc;
2
3use base64::Engine;
4use ecow::{eco_format, EcoString};
5use reflexo::typst::TypstHtmlDocument;
6use typst::diag::SourceResult;
7use typst::layout::Frame;
8use typst::syntax::Span;
9use typst_html::{HtmlElement, HtmlNode};
10
11use crate::hast::{HastElement, HastElementContent, HastText};
12
13pub mod hast;
14
15pub fn hast(document: &Arc<TypstHtmlDocument>) -> SourceResult<HastElementContent> {
17 write_element(&document.root)
18}
19
20fn write_node(node: &HtmlNode, buf: &mut Vec<HastElementContent>) -> SourceResult<()> {
22 match node {
23 HtmlNode::Tag(_) => {}
24 HtmlNode::Text(text, span) => {
25 buf.push(write_text(text, *span)?);
26 }
27 HtmlNode::Element(element) => {
28 buf.push(write_element(element)?);
29 }
30 HtmlNode::Frame(frame) => {
31 write_frame(&frame.inner, buf);
32 }
33 }
34 Ok(())
35}
36
37fn write_text(text: &EcoString, _span: Span) -> SourceResult<HastElementContent> {
39 Ok(HastElementContent::Text(HastText {
40 value: EcoString::from(text),
41 }))
43}
44
45fn write_element(element: &HtmlElement) -> SourceResult<HastElementContent> {
47 write_element_with_tag(element, &element.tag.resolve())
48}
49
50fn write_element_with_tag(element: &HtmlElement, tag: &str) -> SourceResult<HastElementContent> {
52 let properties = element
53 .attrs
54 .0
55 .iter()
56 .map(|(attr, value)| (attr.resolve().as_str().into(), value.clone()))
57 .collect();
58
59 let mut buf = Vec::new();
62
63 if !element.children.is_empty() {
64 for c in &element.children {
65 write_node(c, &mut buf)?;
66 }
67 }
68
69 Ok(HastElementContent::Element(Box::new(HastElement {
70 tag_name: EcoString::from(tag),
71 properties,
72 children: buf,
73 data: None,
74 })))
75}
76
77fn write_frame(frame: &Frame, buf: &mut Vec<HastElementContent>) {
79 let svg = typst_svg::svg_frame(frame)
81 .replace("<svg class", "<svg style=\"overflow: visible;\" class");
82
83 let base64_svg = base64::engine::general_purpose::STANDARD.encode(svg.as_bytes());
85
86 buf.push(HastElementContent::Element(Box::new(HastElement {
87 tag_name: EcoString::from("img"),
88 properties: std::collections::BTreeMap::from([
89 (EcoString::inline("class"), EcoString::inline("typst-frame")),
90 (EcoString::inline("data-typst-doc"), EcoString::inline("1")),
91 (
92 EcoString::inline("src"),
93 EcoString::from(format!("data:image/svg+xml;base64,{base64_svg}")),
94 ),
95 ]),
96 children: vec![],
97 data: Some(crate::hast::HastElementData {
98 hash: Some(eco_format!(
100 "siphash128_13:{:016x}",
101 reflexo::hash::hash128(&frame)
102 )),
103 }),
104 })));
105}