raui_html_renderer/
lib.rs

1use raui_core::prelude::*;
2use std::{
3    collections::HashMap,
4    fmt::{Error, Write},
5};
6
7#[allow(unused_macros)]
8macro_rules! table {
9    () => (HashMap::new());
10    { $( $key:expr => $value:expr ),* } => {
11        {
12            let mut result = HashMap::new();
13            $(
14                result.insert($key.to_string(), $value.to_string());
15            )*
16            result
17        }
18    };
19}
20
21#[allow(unused_macros)]
22macro_rules! node {
23    (
24        $context:ident : $tag:ident
25        [ $writer:expr ]
26        $( level = { $level:expr } )?
27        $( styles = { $( $style_key:expr => $style_value:expr ),* } )?
28        $( attribs = { $( $attrib_key:expr => $attrib_value:expr ),* } )?
29        ($writer_name:ident, $level_name:ident)
30    ) => {
31        #[allow(unused_variables)]
32        #[allow(unused_mut)]
33        #[allow(unused_assignments)]
34        {
35            let mut level = 0;
36            $(
37                level = $level;
38            )?
39            let mut styles = Styles::new();
40            $(
41                styles = table!{ $( $style_key => $style_value ),* };
42            )?
43            let mut attribs = Attribs::new();
44            $(
45                attribs = table!{ $( $attrib_key => $attrib_value ),* };
46            )?
47            $context.inline_node(
48                &stringify!($tag),
49                &styles,
50                &attribs,
51                $writer,
52                level,
53            )?;
54        }
55    };
56    (
57        $context:ident : $tag:ident
58        [ $writer:expr ]
59        $( level = { $level:expr } )?
60        $( styles = { $( $style_key:expr => $style_value:expr ),* } )?
61        $( attribs = { $( $attrib_key:expr => $attrib_value:expr ),* } )?
62        $code:block
63        ($writer_name:ident, $level_name:ident)
64    ) => {
65        #[allow(unused_variables)]
66        #[allow(unused_mut)]
67        #[allow(unused_assignments)]
68        {
69            let mut level = 0;
70            $(
71                level = $level;
72            )?
73            let mut styles = Styles::new();
74            $(
75                styles = table!{ $( $style_key => $style_value ),* };
76            )?
77            let mut attribs = Attribs::new();
78            $(
79                attribs = table!{ $( $attrib_key => $attrib_value ),* };
80            )?
81            $context.with_node(
82                &stringify!($tag),
83                &styles,
84                &attribs,
85                $writer,
86                level,
87                |$writer_name, $level_name| {
88                    $code
89                    Ok(())
90                }
91            )?;
92        }
93    };
94}
95
96type Styles = HashMap<String, String>;
97type Attribs = HashMap<String, String>;
98
99#[derive(Debug, Clone)]
100pub struct HtmlRenderer {
101    pub indent: usize,
102    pub title: Option<String>,
103}
104
105impl Default for HtmlRenderer {
106    fn default() -> Self {
107        Self {
108            indent: 2,
109            title: None,
110        }
111    }
112}
113
114impl Renderer<String, Error> for HtmlRenderer {
115    fn render(
116        &mut self,
117        tree: &WidgetUnit,
118        _: &CoordsMapping,
119        _layout: &Layout,
120    ) -> Result<String, Error> {
121        let mut result = String::new();
122        self.write_document(&mut result, tree)?;
123        Ok(result)
124    }
125}
126
127#[allow(dead_code)]
128impl HtmlRenderer {
129    fn write_line<W>(&self, line: &str, writer: &mut W, level: usize) -> Result<(), Error>
130    where
131        W: Write,
132    {
133        for _ in 0..(level * self.indent) {
134            writer.write_char(' ')?;
135        }
136        writer.write_str(line)?;
137        writer.write_char('\n')?;
138        Ok(())
139    }
140
141    fn inline_node<W>(
142        &self,
143        name: &str,
144        styles: &Styles,
145        attribs: &Attribs,
146        writer: &mut W,
147        level: usize,
148    ) -> Result<(), Error>
149    where
150        W: Write,
151    {
152        let styles = Self::stringify_styles_attr(styles)?;
153        let attribs = Self::stringify_attribs(attribs);
154        self.write_line(
155            &format!(r#"<{} {} {}/>"#, name, styles, attribs),
156            writer,
157            level,
158        )
159    }
160
161    fn with_node<W, F>(
162        &self,
163        name: &str,
164        styles: &Styles,
165        attribs: &Attribs,
166        writer: &mut W,
167        level: usize,
168        mut f: F,
169    ) -> Result<(), Error>
170    where
171        W: Write,
172        F: FnMut(&mut W, usize) -> Result<(), Error>,
173    {
174        self.start_node(name, styles, attribs, writer, level)?;
175        f(writer, level + 1)?;
176        self.stop_node(name, writer, level)
177    }
178
179    fn start_node<W>(
180        &self,
181        name: &str,
182        styles: &Styles,
183        attribs: &Attribs,
184        writer: &mut W,
185        level: usize,
186    ) -> Result<(), Error>
187    where
188        W: Write,
189    {
190        let styles = Self::stringify_styles_attr(styles)?;
191        let attribs = Self::stringify_attribs(attribs);
192        self.write_line(
193            &format!(r#"<{} {} {}>"#, name, styles, attribs),
194            writer,
195            level,
196        )
197    }
198
199    fn stop_node<W>(&self, name: &str, writer: &mut W, level: usize) -> Result<(), Error>
200    where
201        W: Write,
202    {
203        self.write_line(&format!(r#"</{}>"#, name), writer, level)
204    }
205
206    fn stringify_styles_attr(styles: &Styles) -> Result<String, Error> {
207        let mut result = String::new();
208        if !styles.is_empty() {
209            result.write_str(r#"style=""#)?;
210            for (key, value) in styles {
211                result.write_str(key)?;
212                result.write_str(": ")?;
213                result.write_str(value)?;
214                result.write_char(';')?;
215            }
216            result.write_str(r#"""#)?;
217        }
218        Ok(result)
219    }
220
221    fn stringify_attribs(attribs: &Attribs) -> String {
222        attribs
223            .iter()
224            .map(|(k, v)| format!(r#"{}="{}""#, k, v))
225            .collect::<Vec<String>>()
226            .join(" ")
227    }
228
229    fn write_document<W>(&self, writer: &mut W, tree: &WidgetUnit) -> Result<(), Error>
230    where
231        W: Write,
232    {
233        self.write_line(r#"<!DOCTYPE html>"#, writer, 0)?;
234        node!(self: html [writer] attribs={"lang" => "en", "dir" => "ltr"} {
235            node!(self: head [writer] level={level} {
236                self.write_line(r#"<meta charset="utf-8">"#, writer, level)?;
237                if let Some(title) = &self.title {
238                    self.write_line(&format!(r#"<title>{}</title>"#, title), writer, level)?;
239                }
240            } (writer, level));
241            node!(self: body [writer] level={level} {
242                self.write_node(writer, tree, level)?;
243            } (writer, level));
244        } (writer, level));
245        Ok(())
246    }
247
248    fn write_node<W>(&self, writer: &mut W, tree: &WidgetUnit, level: usize) -> Result<(), Error>
249    where
250        W: Write,
251    {
252        self.write_node_with_styles_and_attribs(writer, tree, level, Styles::new(), Attribs::new())
253    }
254
255    fn write_node_with_styles_and_attribs<W>(
256        &self,
257        writer: &mut W,
258        tree: &WidgetUnit,
259        level: usize,
260        _styles: Styles,
261        _attribs: Attribs,
262    ) -> Result<(), Error>
263    where
264        W: Write,
265    {
266        match tree {
267            WidgetUnit::None | WidgetUnit::PortalBox(_) => {}
268            WidgetUnit::AreaBox(AreaBox { slot, .. }) => {
269                node!(self: div [writer] level={level} {
270                    self.write_node(writer, slot, level)?;
271                } (writer, level));
272            }
273            WidgetUnit::ContentBox(ContentBox { items, .. }) => {
274                node!(self: div [writer] level={level} {
275                    for item in items {
276                        self.write_node(writer, &item.slot, level)?;
277                    }
278                } (writer, level));
279            }
280            WidgetUnit::FlexBox(FlexBox { items, .. }) => {
281                node!(self: div [writer] level={level} {
282                    for item in items {
283                        self.write_node(writer, &item.slot, level)?;
284                    }
285                } (writer, level));
286            }
287            WidgetUnit::GridBox(GridBox { items, .. }) => {
288                node!(self: div [writer] level={level} {
289                    for item in items {
290                        self.write_node(writer, &item.slot, level)?;
291                    }
292                } (writer, level));
293            }
294            WidgetUnit::SizeBox(SizeBox { slot, .. }) => {
295                node!(self: div [writer] level={level} {
296                    self.write_node(writer, slot, level)?;
297                } (writer, level));
298            }
299            WidgetUnit::ImageBox(ImageBox { .. }) => {
300                node!(self: div [writer] level={level} {
301                } (writer, level));
302            }
303            WidgetUnit::TextBox(TextBox { text, .. }) => {
304                node!(self: span [writer] level={level} {
305                    self.write_line(text, writer, level)?;
306                } (writer, level));
307            }
308        }
309        Ok(())
310    }
311}