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}