1use std::borrow::Cow;
2
3use super::{MjHero, NAME};
4use crate::helper::size::Pixel;
5use crate::prelude::render::*;
6
7impl<'root> Renderer<'root, MjHero, ()> {
8 fn set_style_div<'t>(&self, tag: Tag<'t>) -> Tag<'t> {
9 tag.add_style("margin", "0 auto").maybe_add_style(
10 "max-width",
11 self.container_width.as_ref().map(|w| w.to_string()),
12 )
13 }
14
15 fn set_style_table<'t>(&self, tag: Tag<'t>) -> Tag<'t> {
16 tag.add_style("width", "100%")
17 }
18
19 fn set_style_tr<'t>(&self, tag: Tag<'t>) -> Tag<'t> {
20 tag.add_style("vertical-align", "top")
21 }
22
23 fn set_style_td_fluid<'t>(&self, tag: Tag<'t>) -> Tag<'t> {
24 let bg_ratio = self
26 .attribute_as_size("background-height")
27 .and_then(|height| {
28 self.attribute_as_size("background-width")
29 .map(|width| height.value() * 100.0 / width.value())
30 });
31 tag.add_style("mso-padding-bottom-alt", "0")
32 .maybe_add_style("padding-bottom", bg_ratio.map(|v| v.to_string()))
33 .add_style("width", "0.01%")
34 }
35
36 fn set_style_outlook_table<'t>(&self, tag: Tag<'t>) -> Tag<'t> {
37 tag.maybe_add_style(
38 "width",
39 self.container_width.as_ref().map(|w| w.to_string()),
40 )
41 }
42
43 fn set_style_outlook_inner_table<'t>(&self, tag: Tag<'t>) -> Tag<'t> {
44 self.set_style_outlook_table(tag)
45 }
46
47 fn set_style_outlook_inner_td<'a, 't>(&'a self, tag: Tag<'t>) -> Tag<'t>
48 where
49 'root: 'a,
50 'a: 't,
51 {
52 tag.maybe_add_style("background-color", self.attribute("inner-background-color"))
53 .maybe_add_style("padding", self.attribute("inner-padding"))
54 .maybe_add_style("padding-top", self.attribute("inner-padding-top"))
55 .maybe_add_style("padding-right", self.attribute("inner-padding-right"))
56 .maybe_add_style("padding-bottom", self.attribute("inner-padding-bottom"))
57 .maybe_add_style("padding-left", self.attribute("inner-padding-left"))
58 }
59
60 fn set_style_inner_div<'a, 't>(&'a self, tag: Tag<'t>) -> Tag<'t>
61 where
62 'root: 'a,
63 'a: 't,
64 {
65 tag.maybe_add_style("background-color", self.attribute("inner-background-color"))
66 .maybe_add_style("float", self.attribute("align"))
67 .add_style("margin", "0px auto")
68 .maybe_add_style("width", self.attribute("width"))
69 }
70
71 fn set_style_inner_table<'t>(&self, tag: Tag<'t>) -> Tag<'t> {
72 tag.add_style("width", "100%").add_style("margin", "0px")
73 }
74
75 fn set_style_outlook_image<'a, 't>(&'a self, tag: Tag<'t>) -> Tag<'t>
76 where
77 'root: 'a,
78 'a: 't,
79 {
80 tag.add_style("border", "0")
81 .maybe_add_style("height", self.attribute("background-height"))
82 .add_style("mso-position-horizontal", "center")
83 .add_style("position", "absolute")
84 .add_style("top", "0")
85 .maybe_add_style(
86 "width",
87 self.attribute("background-width")
88 .map(Cow::Borrowed)
89 .or_else(|| {
90 self.container_width
91 .as_ref()
92 .map(|w| Cow::Owned(w.to_string()))
93 }),
94 )
95 .add_style("z-index", "-3")
96 }
97
98 fn set_style_outlook_td<'t>(&self, tag: Tag<'t>) -> Tag<'t> {
99 tag.add_style("line-height", "0")
100 .add_style("font-size", "0")
101 .add_style("mso-line-height-rule", "exactly")
102 }
103
104 fn get_background<'a>(&'a self) -> Option<Cow<'a, str>>
105 where
106 'root: 'a,
107 {
108 if let (Some(url), Some(color), Some(position)) = (
109 self.attribute("background-url"),
110 self.attribute("background-color"),
111 self.attribute("background-position"),
112 ) {
113 Some(Cow::Owned(format!(
114 "{color} url('{url}') no-repeat {position} / cover"
115 )))
116 } else {
117 self.attribute("background-color").map(Cow::Borrowed)
118 }
119 }
120
121 fn set_style_hero<'a, 't>(&'a self, tag: Tag<'t>) -> Tag<'t>
122 where
123 'root: 'a,
124 'a: 't,
125 {
126 tag.maybe_add_style("background", self.get_background())
127 .maybe_add_style("background-position", self.attribute("background-position"))
128 .add_style("background-repeat", "no-repeat")
129 .maybe_add_style("padding", self.attribute("padding"))
130 .maybe_add_style("padding-top", self.attribute("padding-top"))
131 .maybe_add_style("padding-right", self.attribute("padding-right"))
132 .maybe_add_style("padding-bottom", self.attribute("padding-bottom"))
133 .maybe_add_style("padding-left", self.attribute("padding-left"))
134 .maybe_add_style("vertical-align", self.attribute("vertical-align"))
135 }
136
137 fn render_children(&self, cursor: &mut RenderCursor) -> Result<(), Error> {
138 let siblings = self.element.children.len();
139 let raw_siblings = self.element.children.iter().filter(|c| c.is_raw()).count();
140 for (index, child) in self.element.children.iter().enumerate() {
141 let mut renderer = child.renderer(self.context());
142 renderer.set_index(index);
143 renderer.set_siblings(siblings);
144 renderer.set_raw_siblings(raw_siblings);
145 if child.is_raw() {
146 renderer.render(cursor)?;
147 } else {
148 let tr = Tag::tr();
149 let td = Tag::td()
150 .maybe_add_style(
151 "background",
152 renderer.attribute("container-background-color"),
153 )
154 .add_style("font-size", "0px")
155 .maybe_add_style("padding", renderer.attribute("padding"))
156 .maybe_add_style("padding-top", renderer.attribute("padding-top"))
157 .maybe_add_style("padding-right", renderer.attribute("padding-right"))
158 .maybe_add_style("padding-bottom", renderer.attribute("padding-bottom"))
159 .maybe_add_style("padding-left", renderer.attribute("padding-left"))
160 .add_style("word-break", "break-word")
161 .maybe_add_attribute("align", renderer.attribute("align"))
162 .maybe_add_attribute(
163 "background",
164 renderer.attribute("container-background-color"),
165 )
166 .maybe_add_attribute("class", renderer.attribute("css-class"));
167
168 tr.render_open(&mut cursor.buffer)?;
169 td.render_open(&mut cursor.buffer)?;
170 renderer.render(cursor)?;
171 td.render_close(&mut cursor.buffer);
172 tr.render_close(&mut cursor.buffer);
173 };
174 }
175
176 Ok(())
177 }
178
179 fn render_content(&self, cursor: &mut RenderCursor) -> Result<(), Error> {
180 let table = self
181 .set_style_outlook_inner_table(Tag::table_borderless())
182 .maybe_add_attribute("align", self.attribute("align"))
183 .maybe_add_attribute(
184 "width",
185 self.container_width.as_ref().map(|w| w.value().to_string()),
186 );
187 let tbody = Tag::tbody();
188 let tr = Tag::tr();
189 let td = Tag::td();
190 let outlook_inner_td = self.set_style_outlook_inner_td(Tag::td());
191 let outlook_inner_div = self
192 .set_style_inner_div(Tag::div())
193 .maybe_add_attribute("width", self.attribute("align"))
194 .add_class("mj-hero-content");
195 let inner_table = self.set_style_inner_table(Tag::table_presentation());
196
197 cursor.buffer.start_conditional_tag();
198 table.render_open(&mut cursor.buffer)?;
199 tr.render_open(&mut cursor.buffer)?;
200 outlook_inner_td.render_open(&mut cursor.buffer)?;
201 cursor.buffer.end_conditional_tag();
202
203 outlook_inner_div.render_open(&mut cursor.buffer)?;
204 inner_table.render_open(&mut cursor.buffer)?;
205 tbody.render_open(&mut cursor.buffer)?;
206 tr.render_open(&mut cursor.buffer)?;
207 td.render_open(&mut cursor.buffer)?;
208 inner_table.render_open(&mut cursor.buffer)?;
209 tbody.render_open(&mut cursor.buffer)?;
210 self.render_children(cursor)?;
211 tbody.render_close(&mut cursor.buffer);
212 inner_table.render_close(&mut cursor.buffer);
213 td.render_close(&mut cursor.buffer);
214 tr.render_close(&mut cursor.buffer);
215 tbody.render_close(&mut cursor.buffer);
216 inner_table.render_close(&mut cursor.buffer);
217 outlook_inner_div.render_close(&mut cursor.buffer);
218
219 cursor.buffer.start_conditional_tag();
220 outlook_inner_td.render_close(&mut cursor.buffer);
221 tr.render_close(&mut cursor.buffer);
222 table.render_close(&mut cursor.buffer);
223 cursor.buffer.end_conditional_tag();
224
225 Ok(())
226 }
227
228 fn render_mode_fluid(&self, cursor: &mut RenderCursor) -> Result<(), Error> {
229 let td_fluid = self.set_style_td_fluid(Tag::td());
230 let td = self
231 .set_style_hero(Tag::td())
232 .maybe_add_attribute("background", self.attribute("background-url"));
233
234 td_fluid.render_closed(&mut cursor.buffer)?;
235 td.render_open(&mut cursor.buffer)?;
236 self.render_content(cursor)?;
237 td.render_close(&mut cursor.buffer);
238 td_fluid.render_closed(&mut cursor.buffer)?;
239
240 Ok(())
241 }
242
243 fn render_mode_fixed(&self, cursor: &mut RenderCursor) -> Result<(), Error> {
244 let height = self
246 .attribute_as_pixel("height")
247 .map(|v| v.value())
248 .unwrap_or(0.0);
249 let padding = self.get_padding_vertical().value();
250 let height = height - padding;
251 let td = self
252 .set_style_hero(Tag::td())
253 .add_style("height", format!("{height}px"))
254 .maybe_add_attribute("background", self.attribute("background-url"))
255 .add_attribute("height", height.to_string());
256
257 td.render_open(&mut cursor.buffer)?;
258 self.render_content(cursor)?;
259 td.render_close(&mut cursor.buffer);
260
261 Ok(())
262 }
263
264 fn render_mode(&self, cursor: &mut RenderCursor) -> Result<(), Error> {
265 match self.attribute("mode") {
266 Some(inner) if inner.eq("fluid") => self.render_mode_fluid(cursor),
267 _ => self.render_mode_fixed(cursor),
268 }
269 }
270}
271
272impl<'root> Render<'root> for Renderer<'root, MjHero, ()> {
273 fn default_attribute(&self, name: &str) -> Option<&'static str> {
274 match name {
275 "background-color" => Some("#ffffff"),
276 "background-position" => Some("center center"),
277 "height" => Some("0px"),
278 "mode" => Some("fixed-height"),
279 "padding" => Some("0px"),
280 "vertical-align" => Some("top"),
281 _ => None,
282 }
283 }
284
285 fn raw_attribute(&self, key: &str) -> Option<&'root str> {
286 match self.element.attributes.get(key) {
287 Some(Some(inner)) => Some(inner),
288 _ => None,
289 }
290 }
291
292 fn tag(&self) -> Option<&str> {
293 Some(NAME)
294 }
295
296 fn context(&self) -> &'root RenderContext<'root> {
297 self.context
298 }
299
300 fn set_container_width(&mut self, width: Option<Pixel>) {
301 self.container_width = width;
302 }
303
304 fn set_siblings(&mut self, value: usize) {
305 self.siblings = value;
306 }
307
308 fn set_raw_siblings(&mut self, value: usize) {
309 self.raw_siblings = value;
310 }
311
312 fn render(&self, cursor: &mut RenderCursor) -> Result<(), Error> {
313 let outlook_table = self
314 .set_style_outlook_table(Tag::table_presentation())
315 .add_attribute("align", "center")
316 .maybe_add_attribute(
317 "width",
318 self.container_width.as_ref().map(|v| v.value().to_string()),
319 );
320 let outlook_tr = Tag::tr();
321 let outlook_td = self.set_style_outlook_td(Tag::td());
322 let v_image = self
323 .set_style_outlook_image(Tag::new("v:image"))
324 .maybe_add_attribute("src", self.attribute("background-url"))
325 .add_attribute("xmlns:v", "urn:schemas-microsoft-com:vml");
326 let div = self
327 .set_style_div(Tag::div())
328 .maybe_add_attribute("align", self.attribute("align"))
329 .maybe_add_class(self.attribute("css-class"));
330 let table = self.set_style_table(Tag::table_presentation());
331 let tbody = Tag::tbody();
332 let tr = self.set_style_tr(Tag::tr());
333
334 cursor.buffer.start_conditional_tag();
335 outlook_table.render_open(&mut cursor.buffer)?;
336 outlook_tr.render_open(&mut cursor.buffer)?;
337 outlook_td.render_open(&mut cursor.buffer)?;
338 v_image.render_closed(&mut cursor.buffer)?;
339 cursor.buffer.end_conditional_tag();
340
341 div.render_open(&mut cursor.buffer)?;
342 table.render_open(&mut cursor.buffer)?;
343 tbody.render_open(&mut cursor.buffer)?;
344 tr.render_open(&mut cursor.buffer)?;
345
346 self.render_mode(cursor)?;
347
348 tr.render_close(&mut cursor.buffer);
349 tbody.render_close(&mut cursor.buffer);
350 table.render_close(&mut cursor.buffer);
351 div.render_close(&mut cursor.buffer);
352
353 cursor.buffer.start_conditional_tag();
354 outlook_td.render_close(&mut cursor.buffer);
355 outlook_tr.render_close(&mut cursor.buffer);
356 outlook_table.render_close(&mut cursor.buffer);
357 cursor.buffer.end_conditional_tag();
358
359 Ok(())
360 }
361}
362
363impl<'render, 'root: 'render> Renderable<'render, 'root> for MjHero {
364 fn renderer(
365 &'root self,
366 context: &'root RenderContext<'root>,
367 ) -> Box<dyn Render<'root> + 'render> {
368 Box::new(Renderer::new(context, self, ()))
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 crate::should_render!(basic, "mj-hero");
375 crate::should_render!(background_color, "mj-hero-background-color");
376 crate::should_render!(background_height, "mj-hero-background-height");
377 crate::should_render!(background_position, "mj-hero-background-position");
378 crate::should_render!(background_url, "mj-hero-background-url");
379 crate::should_render!(background_width, "mj-hero-background-width");
380 crate::should_render!(class, "mj-hero-class");
381 crate::should_render!(height, "mj-hero-height");
382 crate::should_render!(mode, "mj-hero-mode");
383 crate::should_render!(vertical_align, "mj-hero-vertical-align");
384 crate::should_render!(width, "mj-hero-width");
385}