-
-
Notifications
You must be signed in to change notification settings - Fork 174
Expand file tree
/
Copy pathhtml.rs
More file actions
127 lines (118 loc) · 5.13 KB
/
html.rs
File metadata and controls
127 lines (118 loc) · 5.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use crate::markdown::text_style::{Color, TextAttribute, TextStyle};
use std::{borrow::Cow, fmt};
pub(crate) enum HtmlText {
Plain(String),
Styled { text: String, style: String },
}
impl HtmlText {
pub(crate) fn new(text: &str, style: &TextStyle, font_size: FontSize) -> Self {
let mut text = text.to_string();
if style == &TextStyle::default() {
return Self::Plain(text);
}
let mut css_styles = Vec::new();
let mut text_decorations = Vec::new();
for attr in style.iter_attributes() {
match attr {
TextAttribute::Bold => css_styles.push(Cow::Borrowed("font-weight: bold")),
TextAttribute::Italics => css_styles.push(Cow::Borrowed("font-style: italic")),
TextAttribute::Strikethrough => text_decorations.push(Cow::Borrowed("line-through")),
TextAttribute::Underlined => text_decorations.push(Cow::Borrowed("underline")),
TextAttribute::Superscript => text = format!("<sup>{text}</sup>"),
TextAttribute::ForegroundColor(color) => {
let color = color_to_html(&color);
css_styles.push(format!("color: {color}").into());
}
TextAttribute::BackgroundColor(color) => {
let color = color_to_html(&color);
css_styles.push(format!("background-color: {color}").into());
}
};
}
if !text_decorations.is_empty() {
let text_decoration = text_decorations.join(" ");
css_styles.push(format!("text-decoration: {text_decoration}").into());
}
if style.size > 1 {
let font_size = font_size.scale(style.size);
css_styles.push(format!("font-size: {font_size}").into());
}
let css_style = css_styles.join("; ");
Self::Styled { text, style: css_style }
}
}
impl fmt::Display for HtmlText {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Plain(text) => write!(f, "{text}"),
Self::Styled { text, style } => write!(f, "<span style=\"{style}\">{text}</span>"),
}
}
}
pub(crate) enum FontSize {
Pixels(u16),
}
impl FontSize {
fn scale(&self, size: u8) -> String {
match self {
Self::Pixels(scale) => format!("{}px", scale * size as u16),
}
}
}
pub(crate) fn color_to_html(color: &Color) -> String {
match color {
Color::Black => "#000000".into(),
Color::DarkGrey => "#5a5a5a".into(),
Color::Red => "#ff0000".into(),
Color::DarkRed => "#8b0000".into(),
Color::Green => "#00ff00".into(),
Color::DarkGreen => "#006400".into(),
Color::Yellow => "#ffff00".into(),
Color::DarkYellow => "#8b8000".into(),
Color::Blue => "#0000ff".into(),
Color::DarkBlue => "#00008b".into(),
Color::Magenta => "#ff00ff".into(),
Color::DarkMagenta => "#8b008b".into(),
Color::Cyan => "#00ffff".into(),
Color::DarkCyan => "#008b8b".into(),
Color::White => "#ffffff".into(),
Color::Grey => "#808080".into(),
Color::Rgb { r, g, b } => format!("#{r:02x}{g:02x}{b:02x}"),
Color::Rgba { r, g, b, a } => format!("#{r:02x}{g:02x}{b:02x}{a:02x}"),
}
}
#[cfg(test)]
mod test {
use super::*;
use rstest::rstest;
#[rstest]
#[case::none(TextStyle::default(), "")]
#[case::bold(TextStyle::default().bold(), "font-weight: bold")]
#[case::italics(TextStyle::default().italics(), "font-style: italic")]
#[case::bold_italics(TextStyle::default().bold().italics(), "font-weight: bold; font-style: italic")]
#[case::strikethrough(TextStyle::default().strikethrough(), "text-decoration: line-through")]
#[case::underlined(TextStyle::default().underlined(), "text-decoration: underline")]
#[case::strikethrough_underlined(
TextStyle::default().strikethrough().underlined(),
"text-decoration: line-through underline"
)]
#[case::foreground_color(TextStyle::default().fg_color(Color::new(1,2,3)), "color: #010203")]
#[case::background_color(TextStyle::default().bg_color(Color::new(1,2,3)), "background-color: #010203")]
#[case::foreground_color_alpha(TextStyle::default().fg_color(Color::Rgba {r: 1, g: 2, b: 3, a:4 }), "color: #01020304")]
#[case::background_color_alpha(TextStyle::default().bg_color(Color::Rgba { r: 1, g: 2, b: 3, a: 4 }), "background-color: #01020304")]
#[case::font_size(TextStyle::default().size(3), "font-size: 6px")]
fn html_text(#[case] style: TextStyle, #[case] expected_style: &str) {
let html_text = HtmlText::new("", &style, FontSize::Pixels(2));
let style = match &html_text {
HtmlText::Plain(_) => "",
HtmlText::Styled { style, .. } => style,
};
assert_eq!(style, expected_style);
}
#[test]
fn render_span() {
let html_text = HtmlText::new("hi", &TextStyle::default().bold(), FontSize::Pixels(1));
let rendered = html_text.to_string();
assert_eq!(rendered, "<span style=\"font-weight: bold\">hi</span>");
}
}