urushi/render/
settings.rs1use crate::{TextAttributes, TextStyle, UnderlineStyleSet};
4use urushi_terminal::{ColorLevel, TerminalCapabilities};
5
6use super::palette::{quantize_to_ansi16, quantize_to_ansi256};
7
8#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
14pub struct RenderSettings {
15 color_level: ColorLevel,
16 attributes: TextAttributes,
17 underline_styles: UnderlineStyleSet,
18 underline_colors: bool,
19 hyperlinks: bool,
20}
21
22impl RenderSettings {
23 pub const fn all() -> Self {
29 Self {
30 color_level: ColorLevel::TrueColor,
31 attributes: TextAttributes::all(),
32 underline_styles: UnderlineStyleSet::all(),
33 underline_colors: true,
34 hyperlinks: true,
35 }
36 }
37
38 pub const fn get_color_level(self) -> ColorLevel {
39 self.color_level
40 }
41
42 pub const fn get_attributes(self) -> TextAttributes {
43 self.attributes
44 }
45
46 pub const fn get_underline_styles(self) -> UnderlineStyleSet {
47 self.underline_styles
48 }
49
50 pub const fn get_underline_colors(self) -> bool {
51 self.underline_colors
52 }
53
54 pub const fn get_hyperlinks(self) -> bool {
55 self.hyperlinks
56 }
57
58 pub const fn color_level(mut self, color_level: ColorLevel) -> Self {
59 self.color_level = color_level;
60 self
61 }
62
63 pub const fn attributes(mut self, attributes: TextAttributes) -> Self {
64 self.attributes = attributes;
65 self
66 }
67
68 pub const fn underline_styles(mut self, styles: UnderlineStyleSet) -> Self {
69 self.underline_styles = styles;
70 self
71 }
72
73 pub const fn underline_colors(mut self, enabled: bool) -> Self {
74 self.underline_colors = enabled;
75 self
76 }
77
78 pub const fn hyperlinks(mut self, enabled: bool) -> Self {
79 self.hyperlinks = enabled;
80 self
81 }
82
83 pub const fn reset_color_level(mut self) -> Self {
84 self.color_level = ColorLevel::None;
85 self
86 }
87
88 pub const fn reset_attributes(mut self) -> Self {
89 self.attributes = TextAttributes::empty();
90 self
91 }
92
93 pub const fn reset_underline_styles(mut self) -> Self {
94 self.underline_styles = UnderlineStyleSet::empty();
95 self
96 }
97
98 pub const fn reset_underline_colors(mut self) -> Self {
99 self.underline_colors = false;
100 self
101 }
102
103 pub const fn reset_hyperlinks(mut self) -> Self {
104 self.hyperlinks = false;
105 self
106 }
107
108 pub fn resolve_text_style(self, style: &TextStyle) -> TextStyle {
114 let mut resolved = match self.color_level {
115 ColorLevel::None => style.clone().without_colors(),
116 ColorLevel::Ansi16 => style.clone().map_colors(quantize_to_ansi16),
117 ColorLevel::Ansi256 => style.clone().map_colors(quantize_to_ansi256),
118 ColorLevel::TrueColor => style.clone(),
119 };
120 resolved.attributes = resolved.attributes.intersection(self.attributes);
121 resolved.underline = resolved.underline.and_then(|underline| {
122 self.underline_styles
123 .contains(underline.get_style())
124 .then_some(if self.underline_colors {
125 underline
126 } else {
127 underline.reset_color()
128 })
129 });
130 if !self.hyperlinks {
131 resolved.hyperlink = None;
132 }
133 resolved.canonical()
134 }
135}
136
137impl From<TerminalCapabilities> for RenderSettings {
138 fn from(capabilities: TerminalCapabilities) -> Self {
139 Self::default()
140 .color_level(capabilities.color_level())
141 .attributes(capabilities.attributes())
142 .underline_styles(capabilities.underline_styles())
143 .underline_colors(capabilities.underline_colors())
144 .hyperlinks(capabilities.hyperlinks())
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use crate::{Color, Hyperlink, TextAttribute, Underline, UnderlineStyle};
152
153 #[test]
154 fn default_is_plain_text() {
155 let style = TextStyle::new()
156 .foreground(Color::RED)
157 .bold()
158 .underline_style(UnderlineStyle::Curly)
159 .underline_color(Color::BLUE)
160 .hyperlink(Hyperlink::new("https://example.com"));
161
162 assert_eq!(
163 RenderSettings::default().resolve_text_style(&style),
164 TextStyle::new()
165 );
166 }
167
168 #[test]
169 fn settings_select_each_feature_axis_independently() {
170 let style = TextStyle::new()
171 .foreground(Color::Rgb(255, 0, 0))
172 .bold()
173 .italic()
174 .underline_style(UnderlineStyle::Curly)
175 .underline_color(Color::BLUE)
176 .hyperlink("https://example.com");
177 let settings = RenderSettings::default()
178 .color_level(ColorLevel::Ansi16)
179 .attributes(TextAttribute::Italic.into())
180 .underline_styles(UnderlineStyleSet::CURLY)
181 .underline_colors(false)
182 .hyperlinks(false);
183 let resolved = settings.resolve_text_style(&style);
184
185 assert_eq!(resolved.get_foreground(), Some(Color::BRIGHT_RED));
186 assert_eq!(resolved.get_attributes(), TextAttribute::Italic.into());
187 assert_eq!(
188 resolved.get_underline(),
189 Some(Underline::new(UnderlineStyle::Curly))
190 );
191 assert_eq!(resolved.get_hyperlink(), None);
192 }
193
194 #[test]
195 fn all_preserves_every_style_feature() {
196 let style = TextStyle::new()
197 .foreground(Color::Rgb(1, 2, 3))
198 .background(Color::Rgb(4, 5, 6))
199 .bold()
200 .italic()
201 .underline_style(UnderlineStyle::Curly)
202 .underline_color(Color::BLUE)
203 .hyperlink("https://example.com");
204
205 assert_eq!(RenderSettings::all().resolve_text_style(&style), style);
206 }
207
208 #[test]
209 fn reset_builders_restore_every_setting_default() {
210 let settings = RenderSettings::default()
211 .color_level(ColorLevel::TrueColor)
212 .attributes(TextAttributes::all())
213 .underline_styles(UnderlineStyleSet::all())
214 .underline_colors(true)
215 .hyperlinks(true)
216 .reset_color_level()
217 .reset_attributes()
218 .reset_underline_styles()
219 .reset_underline_colors()
220 .reset_hyperlinks();
221
222 assert_eq!(settings, RenderSettings::default());
223 }
224}