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 "{} url('{}') no-repeat {} / cover",
115 color,
117 url,
118 position
120 )))
121 } else {
122 self.attribute("background-color").map(Cow::Borrowed)
123 }
124 }
125
126 fn set_style_hero<'a, 't>(&'a self, tag: Tag<'t>) -> Tag<'t>
127 where
128 'root: 'a,
129 'a: 't,
130 {
131 tag.maybe_add_style("background", self.get_background())
132 .maybe_add_style("background-position", self.attribute("background-position"))
133 .add_style("background-repeat", "no-repeat")
134 .maybe_add_style("padding", self.attribute("padding"))
135 .maybe_add_style("padding-top", self.attribute("padding-top"))
136 .maybe_add_style("padding-right", self.attribute("padding-right"))
137 .maybe_add_style("padding-bottom", self.attribute("padding-bottom"))
138 .maybe_add_style("padding-left", self.attribute("padding-left"))
139 .maybe_add_style("vertical-align", self.attribute("vertical-align"))
140 }
141
142 fn render_children(&self, cursor: &mut RenderCursor) -> Result<(), Error> {
143 let siblings = self.element.children.len();
144 let raw_siblings = self.element.children.iter().filter(|c| c.is_raw()).count();
145 for (index, child) in self.element.children.iter().enumerate() {
146 let mut renderer = child.renderer(self.context());
147 renderer.set_index(index);
148 renderer.set_siblings(siblings);
149 renderer.set_raw_siblings(raw_siblings);
150 if child.is_raw() {
151 renderer.render(cursor)?;
152 } else {
153 let tr = Tag::tr();
154 let td = Tag::td()
155 .maybe_add_style(
156 "background",
157 renderer.attribute("container-background-color"),
158 )
159 .add_style("font-size", "0px")
160 .maybe_add_style("padding", renderer.attribute("padding"))
161 .maybe_add_style("padding-top", renderer.attribute("padding-top"))
162 .maybe_add_style("padding-right", renderer.attribute("padding-right"))
163 .maybe_add_style("padding-bottom", renderer.attribute("padding-bottom"))
164 .maybe_add_style("padding-left", renderer.attribute("padding-left"))
165 .add_style("word-break", "break-word")
166 .maybe_add_attribute("align", renderer.attribute("align"))
167 .maybe_add_attribute(
168 "background",
169 renderer.attribute("container-background-color"),
170 )
171 .maybe_add_attribute("class", renderer.attribute("css-class"));
172
173 tr.render_open(&mut cursor.buffer)?;
174 td.render_open(&mut cursor.buffer)?;
175 renderer.render(cursor)?;
176 td.render_close(&mut cursor.buffer);
177 tr.render_close(&mut cursor.buffer);
178 };
179 }
180
181 Ok(())
182 }
183
184 fn render_content(&self, cursor: &mut RenderCursor) -> Result<(), Error> {
185 let table = self
186 .set_style_outlook_inner_table(Tag::table_borderless())
187 .maybe_add_attribute("align", self.attribute("align"))
188 .maybe_add_attribute(
189 "width",
190 self.container_width.as_ref().map(|w| w.value().to_string()),
191 );
192 let tbody = Tag::tbody();
193 let tr = Tag::tr();
194 let td = Tag::td();
195 let outlook_inner_td = self.set_style_outlook_inner_td(Tag::td());
196 let outlook_inner_div = self
197 .set_style_inner_div(Tag::div())
198 .maybe_add_attribute("width", self.attribute("align"))
199 .add_class("mj-hero-content");
200 let inner_table = self.set_style_inner_table(Tag::table_presentation());
201
202 cursor.buffer.start_conditional_tag();
203 table.render_open(&mut cursor.buffer)?;
204 tr.render_open(&mut cursor.buffer)?;
205 outlook_inner_td.render_open(&mut cursor.buffer)?;
206 cursor.buffer.end_conditional_tag();
207
208 outlook_inner_div.render_open(&mut cursor.buffer)?;
209 inner_table.render_open(&mut cursor.buffer)?;
210 tbody.render_open(&mut cursor.buffer)?;
211 tr.render_open(&mut cursor.buffer)?;
212 td.render_open(&mut cursor.buffer)?;
213 inner_table.render_open(&mut cursor.buffer)?;
214 tbody.render_open(&mut cursor.buffer)?;
215 self.render_children(cursor)?;
216 tbody.render_close(&mut cursor.buffer);
217 inner_table.render_close(&mut cursor.buffer);
218 td.render_close(&mut cursor.buffer);
219 tr.render_close(&mut cursor.buffer);
220 tbody.render_close(&mut cursor.buffer);
221 inner_table.render_close(&mut cursor.buffer);
222 outlook_inner_div.render_close(&mut cursor.buffer);
223
224 cursor.buffer.start_conditional_tag();
225 outlook_inner_td.render_close(&mut cursor.buffer);
226 tr.render_close(&mut cursor.buffer);
227 table.render_close(&mut cursor.buffer);
228 cursor.buffer.end_conditional_tag();
229
230 Ok(())
231 }
232
233 fn render_mode_fluid(&self, cursor: &mut RenderCursor) -> Result<(), Error> {
234 let td_fluid = self.set_style_td_fluid(Tag::td());
235 let td = self
236 .set_style_hero(Tag::td())
237 .maybe_add_attribute("background", self.attribute("background-url"));
238
239 td_fluid.render_closed(&mut cursor.buffer)?;
240 td.render_open(&mut cursor.buffer)?;
241 self.render_content(cursor)?;
242 td.render_close(&mut cursor.buffer);
243 td_fluid.render_closed(&mut cursor.buffer)?;
244
245 Ok(())
246 }
247
248 fn render_mode_fixed(&self, cursor: &mut RenderCursor) -> Result<(), Error> {
249 let height = self
251 .attribute_as_pixel("height")
252 .map(|v| v.value())
253 .unwrap_or(0.0);
254 let padding = self.get_padding_vertical().value();
255 let height = height - padding;
256 let td = self
257 .set_style_hero(Tag::td())
258 .add_style("height", format!("{height}px"))
259 .maybe_add_attribute("background", self.attribute("background-url"))
260 .add_attribute("height", height.to_string());
261
262 td.render_open(&mut cursor.buffer)?;
263 self.render_content(cursor)?;
264 td.render_close(&mut cursor.buffer);
265
266 Ok(())
267 }
268
269 fn render_mode(&self, cursor: &mut RenderCursor) -> Result<(), Error> {
270 match self.attribute("mode") {
271 Some(inner) if inner.eq("fluid") => self.render_mode_fluid(cursor),
272 _ => self.render_mode_fixed(cursor),
273 }
274 }
275}
276
277impl<'root> Render<'root> for Renderer<'root, MjHero, ()> {
278 fn default_attribute(&self, name: &str) -> Option<&'static str> {
279 match name {
280 "background-color" => Some("#ffffff"),
281 "background-position" => Some("center center"),
282 "height" => Some("0px"),
283 "mode" => Some("fixed-height"),
284 "padding" => Some("0px"),
285 "vertical-align" => Some("top"),
286 _ => None,
287 }
288 }
289
290 fn raw_attribute(&self, key: &str) -> Option<&'root str> {
291 match self.element.attributes.get(key) {
292 Some(Some(inner)) => Some(inner),
293 _ => None,
294 }
295 }
296
297 fn tag(&self) -> Option<&str> {
298 Some(NAME)
299 }
300
301 fn context(&self) -> &'root RenderContext<'root> {
302 self.context
303 }
304
305 fn set_container_width(&mut self, width: Option<Pixel>) {
306 self.container_width = width;
307 }
308
309 fn set_siblings(&mut self, value: usize) {
310 self.siblings = value;
311 }
312
313 fn set_raw_siblings(&mut self, value: usize) {
314 self.raw_siblings = value;
315 }
316
317 fn render(&self, cursor: &mut RenderCursor) -> Result<(), Error> {
318 let outlook_table = self
319 .set_style_outlook_table(Tag::table_presentation())
320 .add_attribute("align", "center")
321 .maybe_add_attribute(
322 "width",
323 self.container_width.as_ref().map(|v| v.value().to_string()),
324 );
325 let outlook_tr = Tag::tr();
326 let outlook_td = self.set_style_outlook_td(Tag::td());
327 let v_image = self
328 .set_style_outlook_image(Tag::new("v:image"))
329 .maybe_add_attribute("src", self.attribute("background-url"))
330 .add_attribute("xmlns:v", "urn:schemas-microsoft-com:vml");
331 let div = self
332 .set_style_div(Tag::div())
333 .maybe_add_attribute("align", self.attribute("align"))
334 .maybe_add_class(self.attribute("css-class"));
335 let table = self.set_style_table(Tag::table_presentation());
336 let tbody = Tag::tbody();
337 let tr = self.set_style_tr(Tag::tr());
338
339 cursor.buffer.start_conditional_tag();
340 outlook_table.render_open(&mut cursor.buffer)?;
341 outlook_tr.render_open(&mut cursor.buffer)?;
342 outlook_td.render_open(&mut cursor.buffer)?;
343 v_image.render_closed(&mut cursor.buffer)?;
344 cursor.buffer.end_conditional_tag();
345
346 div.render_open(&mut cursor.buffer)?;
347 table.render_open(&mut cursor.buffer)?;
348 tbody.render_open(&mut cursor.buffer)?;
349 tr.render_open(&mut cursor.buffer)?;
350
351 self.render_mode(cursor)?;
352
353 tr.render_close(&mut cursor.buffer);
354 tbody.render_close(&mut cursor.buffer);
355 table.render_close(&mut cursor.buffer);
356 div.render_close(&mut cursor.buffer);
357
358 cursor.buffer.start_conditional_tag();
359 outlook_td.render_close(&mut cursor.buffer);
360 outlook_tr.render_close(&mut cursor.buffer);
361 outlook_table.render_close(&mut cursor.buffer);
362 cursor.buffer.end_conditional_tag();
363
364 Ok(())
365 }
366}
367
368impl<'render, 'root: 'render> Renderable<'render, 'root> for MjHero {
369 fn renderer(
370 &'root self,
371 context: &'root RenderContext<'root>,
372 ) -> Box<dyn Render<'root> + 'render> {
373 Box::new(Renderer::new(context, self, ()))
374 }
375}
376
377#[cfg(test)]
378mod tests {
379 crate::should_render!(basic, "mj-hero");
380 crate::should_render!(background_color, "mj-hero-background-color");
381 crate::should_render!(background_height, "mj-hero-background-height");
382 crate::should_render!(background_position, "mj-hero-background-position");
383 crate::should_render!(background_url, "mj-hero-background-url");
384 crate::should_render!(background_width, "mj-hero-background-width");
385 crate::should_render!(class, "mj-hero-class");
386 crate::should_render!(height, "mj-hero-height");
387 crate::should_render!(mode, "mj-hero-mode");
388 crate::should_render!(vertical_align, "mj-hero-vertical-align");
389 crate::should_render!(width, "mj-hero-width");
390}