typlite/
common.rs

1//! Common types and interfaces for the conversion system
2
3use cmark_writer::HtmlAttribute;
4use cmark_writer::HtmlElement;
5use cmark_writer::HtmlWriteResult;
6use cmark_writer::HtmlWriter;
7use cmark_writer::HtmlWriterOptions;
8use cmark_writer::WriteResult;
9use cmark_writer::ast::Node;
10use cmark_writer::custom_node;
11use cmark_writer::writer::{BlockWriterProxy, InlineWriterProxy};
12use ecow::EcoString;
13use ecow::eco_format;
14use std::path::PathBuf;
15
16use crate::Result;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum ListState {
20    Ordered,
21    Unordered,
22}
23
24/// Valid formats for the conversion.
25#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
26pub enum Format {
27    #[default]
28    Md,
29    LaTeX,
30    Text,
31    #[cfg(feature = "docx")]
32    Docx,
33}
34
35/// Figure node implementation for all formats
36#[derive(Debug, PartialEq, Clone)]
37#[custom_node(block = true, html_impl = true)]
38pub struct FigureNode {
39    /// The main content of the figure, can be any block node
40    pub body: Box<Node>,
41    /// The caption text for the figure
42    pub caption: String,
43}
44
45impl FigureNode {
46    fn write_custom(&self, writer: &mut BlockWriterProxy) -> WriteResult<()> {
47        let content = writer.capture_block(|block| {
48            block.write_block(&self.body)?;
49            Ok(())
50        })?;
51        writer.write_str(&content)?;
52        writer.write_str("\n")?;
53        writer.write_str(&self.caption)?;
54        Ok(())
55    }
56
57    fn write_html_custom(&self, writer: &mut HtmlWriter) -> HtmlWriteResult<()> {
58        let body = self.body.clone();
59        let node = Node::HtmlElement(HtmlElement {
60            tag: EcoString::inline("figure"),
61            attributes: vec![HtmlAttribute {
62                name: EcoString::inline("class"),
63                value: EcoString::inline("figure"),
64            }],
65            children: vec![*body],
66            self_closing: false,
67        });
68        writer.write_node(&node)?;
69        Ok(())
70    }
71}
72
73/// External Frame node for handling frames stored as external files
74#[derive(Debug, PartialEq, Clone)]
75#[custom_node(block = true, html_impl = true)]
76pub struct ExternalFrameNode {
77    /// The path to the external file containing the frame
78    pub file_path: PathBuf,
79    /// Alternative text for the frame
80    pub alt_text: EcoString,
81    /// Original SVG data (needed for DOCX that still embeds images)
82    pub svg: String,
83}
84
85impl ExternalFrameNode {
86    fn write_custom(&self, writer: &mut BlockWriterProxy) -> WriteResult<()> {
87        // The actual handling is implemented in format-specific writers
88        writer.write_str(&format!(
89            "![{}]({})",
90            self.alt_text,
91            self.file_path.display()
92        ))?;
93        Ok(())
94    }
95
96    fn write_html_custom(&self, writer: &mut HtmlWriter) -> HtmlWriteResult<()> {
97        let node = Node::HtmlElement(HtmlElement {
98            tag: EcoString::inline("img"),
99            attributes: vec![
100                HtmlAttribute {
101                    name: EcoString::inline("src"),
102                    value: self.file_path.display().to_string().into(),
103                },
104                HtmlAttribute {
105                    name: EcoString::inline("alt"),
106                    value: self.alt_text.clone(),
107                },
108            ],
109            children: vec![],
110            self_closing: true,
111        });
112        writer.write_node(&node)?;
113        Ok(())
114    }
115}
116
117/// Highlight node for highlighted text
118#[derive(Debug, PartialEq, Clone)]
119#[custom_node(block = false, html_impl = true)]
120pub struct HighlightNode {
121    /// The content to be highlighted
122    pub content: Vec<Node>,
123}
124
125impl HighlightNode {
126    fn write_custom(&self, writer: &mut InlineWriterProxy) -> WriteResult<()> {
127        writer.write_str("==")?;
128        writer.write_inline_nodes(&self.content)?;
129        writer.write_str("==")?;
130        Ok(())
131    }
132
133    fn write_html_custom(&self, writer: &mut HtmlWriter) -> HtmlWriteResult<()> {
134        let node = Node::HtmlElement(HtmlElement {
135            tag: EcoString::inline("mark"),
136            attributes: vec![],
137            children: self.content.clone(),
138            self_closing: false,
139        });
140        writer.write_node(&node)?;
141        Ok(())
142    }
143}
144
145/// Node for centered content
146#[derive(Debug, PartialEq, Clone)]
147#[custom_node(block = true, html_impl = true)]
148pub struct CenterNode {
149    /// The content to be centered
150    pub node: Node,
151}
152
153impl CenterNode {
154    pub fn new(children: Vec<Node>) -> Self {
155        CenterNode {
156            node: Node::HtmlElement(cmark_writer::ast::HtmlElement {
157                tag: EcoString::inline("p"),
158                attributes: vec![cmark_writer::ast::HtmlAttribute {
159                    name: EcoString::inline("align"),
160                    value: EcoString::inline("center"),
161                }],
162                children,
163                self_closing: false,
164            }),
165        }
166    }
167
168    fn write_custom(&self, writer: &mut BlockWriterProxy) -> WriteResult<()> {
169        let content = writer.capture_inline(|inline| {
170            inline.write_inline(&self.node)?;
171            Ok(())
172        })?;
173        writer.write_str(&content)?;
174        writer.write_str("\n")?;
175        Ok(())
176    }
177
178    fn write_html_custom(&self, writer: &mut HtmlWriter) -> HtmlWriteResult<()> {
179        let mut temp_writer = HtmlWriter::with_options(HtmlWriterOptions {
180            strict: false,
181            ..Default::default()
182        });
183        temp_writer.write_node(&self.node)?;
184        let content = temp_writer.into_string()?;
185        writer.write_trusted_html(&content)?;
186        Ok(())
187    }
188}
189
190/// Inline node for flattened inline content (useful for table cells)
191#[derive(Debug, PartialEq, Clone)]
192#[custom_node(block = false, html_impl = true)]
193pub struct InlineNode {
194    /// The inline content nodes
195    pub content: Vec<Node>,
196}
197
198impl InlineNode {
199    fn write_custom(&self, writer: &mut InlineWriterProxy) -> WriteResult<()> {
200        writer.write_inline_nodes(&self.content)
201    }
202
203    fn write_html_custom(&self, writer: &mut HtmlWriter) -> HtmlWriteResult<()> {
204        for node in &self.content {
205            writer.write_node(node)?;
206        }
207        Ok(())
208    }
209}
210
211/// Verbatim node for raw text output
212#[derive(Debug, PartialEq, Clone)]
213#[custom_node(block = false, html_impl = true)]
214pub struct VerbatimNode {
215    /// The content to directly output
216    pub content: EcoString,
217}
218
219impl VerbatimNode {
220    fn write_custom(&self, writer: &mut InlineWriterProxy) -> WriteResult<()> {
221        writer.write_str(&self.content)
222    }
223
224    fn write_html_custom(&self, writer: &mut HtmlWriter) -> HtmlWriteResult<()> {
225        writer.write_trusted_html(&self.content)
226    }
227}
228
229/// Alert node for alert messages
230#[derive(Debug, PartialEq, Clone)]
231#[custom_node(block = true, html_impl = false)]
232pub struct AlertNode {
233    /// The content of the alert
234    pub content: Vec<Node>,
235    /// The class of the alert
236    pub class: EcoString,
237}
238
239impl AlertNode {
240    fn write_custom(&self, writer: &mut BlockWriterProxy) -> WriteResult<()> {
241        let quote = Node::BlockQuote(vec![
242            Node::Paragraph(vec![Node::Text(eco_format!(
243                "[!{}]",
244                self.class.to_ascii_uppercase()
245            ))]),
246            Node::Paragraph(vec![Node::Text("".into())]),
247        ]);
248        writer.with_temporary_options(
249            |options| options.escape_special_chars = false,
250            |writer| writer.write_block(&quote),
251        )?;
252        let quote = Node::BlockQuote(self.content.clone());
253        writer.write_block(&quote)?;
254        Ok(())
255    }
256}
257
258/// Common writer interface for different formats
259pub trait FormatWriter {
260    /// Write AST document to output format
261    fn write_eco(&mut self, document: &Node, output: &mut EcoString) -> Result<()>;
262
263    /// Write AST document to vector
264    fn write_vec(&mut self, document: &Node) -> Result<Vec<u8>>;
265}