1use crate::{
2 canvas::Canvas,
3 css::{CssType, Mode, NodeStyle, Style},
4 theme::ColorTable,
5};
6
7#[allow(clippy::too_many_arguments)]
8pub fn to_html<S: AsRef<str>>(
9 str: S,
10 theme: impl ColorTable,
11 width: Option<usize>,
12 font: Option<String>,
13 mode: Option<Mode>,
14 light_bg: Option<String>,
15 dark_bg: Option<String>,
16 font_size: Option<usize>,
17 sourcemap: bool,
18) -> String {
19 let font_size = font_size.unwrap_or(16);
20 let s = str.as_ref();
21 let canvas = Canvas::new(s, width);
22 let mut s = String::new();
23
24 let mut style = Style::default();
25
26 let mut font_style = "".into();
27 let mut font_family = "Consolas,Courier New,Monaco".into();
28
29 if let Some(url) = font {
30 if url.starts_with("http") || url.starts_with("data:font;base64") {
31 font_family = "ansi2-custom-font".into();
32 font_style =
33 format!(r#"@font-face {{font-family: ansi2-custom-font;src: url({url});}}"#)
34 } else {
35 font_family = url;
36 }
37 }
38
39 s.push_str(&format!("<div class='{}'>", NodeStyle::Main.class_name()));
40
41 let row_style = format!("<div class='{}'>", NodeStyle::Row.class_name());
42 for row in canvas.minify().iter() {
43 s.push_str(&row_style);
44 for c in row.iter() {
45 let mut text_class = vec![NodeStyle::Text.class_name().to_string()];
46 if c.bold {
47 text_class.push(NodeStyle::Bold.class_name().to_string());
48 style.bold = true;
49 }
50 if c.italic {
51 text_class.push(NodeStyle::Italic.class_name().to_string());
52 style.italic = true;
53 }
54 if c.dim {
55 text_class.push(NodeStyle::Dim.class_name().to_string());
56 style.dim = true;
57 }
58 if c.underline {
59 text_class.push(NodeStyle::Underline.class_name().to_string());
60 style.underline = true;
61 }
62 if c.hide {
63 text_class.push(NodeStyle::Hide.class_name().to_string());
64 style.hide = true;
65 }
66 if c.blink {
67 text_class.push(NodeStyle::Blink.class_name().to_string());
68 style.blink = true;
69 }
70 if c.strike {
71 text_class.push(NodeStyle::Strike.class_name().to_string());
72 style.strike = true;
73 }
74 if !c.color.is_default() {
75 let name = c.color.class_name();
76 text_class.push(name);
77 style.add_color(c.color);
78 }
79
80 if !c.bg_color.is_default() {
81 let name = c.bg_color.bg_class_name();
82 text_class.push(name);
83 style.add_bg_color(c.bg_color);
84 }
85
86 if sourcemap {
87 text_class.push(format!("text:{}:{}", c.text_r.0, c.text_r.1));
88 text_class.push(format!("color:{}:{}", c.color_r.0, c.color_r.1));
89 text_class.push(format!("bg:{}:{}", c.bg_color_r.0, c.bg_color_r.1));
90 text_class.push(format!("bold:{}:{}", c.bold_r.0, c.bold_r.1));
91 text_class.push(format!("blink:{}:{}", c.blink_r.0, c.blink_r.1));
92 text_class.push(format!("dim:{}:{}", c.dim_r.0, c.dim_r.1));
93 text_class.push(format!("italic:{}:{}", c.italic_r.0, c.italic_r.1));
94 text_class.push(format!("underline:{}:{}", c.underline_r.0, c.underline_r.1));
95 text_class.push(format!("hide:{}:{}", c.hide_r.0, c.hide_r.1));
96 text_class.push(format!("strike:{}:{}", c.strike_r.0, c.strike_r.1));
97 }
98
99 let text_class = text_class.join(" ").trim().to_string();
100 let html_char = c.text.to_string();
101 let html_char = html_escape::encode_text(&html_char);
102 let class_str = if text_class.is_empty() {
103 String::new()
104 } else {
105 format!("class='{text_class}'")
106 };
107 s.push_str(&format!("<p {class_str}>{html_char}</p>",))
108 }
109
110 if row.is_empty() {
111 s.push_str("<br>");
112 }
113 s.push_str("</div>");
115 }
116
117 s.push_str("</div>");
119
120 let style_css = style.to_css(
121 theme,
122 CssType::Html,
123 mode,
124 light_bg,
125 dark_bg,
126 font_family,
127 font_size,
128 );
129 format!(
130 r#"<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>{font_style}{style_css}</style></head><body>{s}</body></html>"#
131 )
132}