Skip to main content

pulldown_cmark_mdcat/
theme.rs

1// Copyright 2018-2020 Sebastian Wiesner <sebastian@swsnr.de>
2
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7//! Provide a colour theme for mdcat.
8
9use anstyle::{AnsiColor, Color, RgbColor, Style};
10
11/// A colour theme for mdcat.
12#[derive(Debug, Clone)]
13pub struct Theme {
14    /// Style for HTML blocks.
15    pub(crate) html_block_style: Style,
16    /// Style for inline HTML.
17    pub(crate) inline_html_style: Style,
18    /// Style for code, unless the code is syntax-highlighted.
19    pub(crate) code_style: Style,
20    /// Style for links.
21    pub(crate) link_style: Style,
22    /// Color for image links (unless the image is rendered inline)
23    pub(crate) image_link_style: Style,
24    /// Color for rulers.
25    pub(crate) rule_color: Color,
26    /// Style for block quote borders (`│`).
27    pub(crate) quote_border_style: Style,
28    /// Style for H2 headings.
29    pub(crate) h2_style: Style,
30    /// Style for H3 headings.
31    pub(crate) h3_style: Style,
32    /// Style for H4 headings.
33    pub(crate) h4_style: Style,
34    /// Style for H5 headings.
35    pub(crate) h5_style: Style,
36    /// Style for H6 headings.
37    pub(crate) h6_style: Style,
38    /// Style for footnote references and definitions.
39    pub(crate) footnote_style: Style,
40    /// Style for math expressions.
41    pub(crate) math_style: Style,
42    /// Style for `[!NOTE]` alerts.
43    pub(crate) alert_note_style: Style,
44    /// Style for `[!TIP]` alerts.
45    pub(crate) alert_tip_style: Style,
46    /// Style for `[!IMPORTANT]` alerts.
47    pub(crate) alert_important_style: Style,
48    /// Style for `[!WARNING]` alerts.
49    pub(crate) alert_warning_style: Style,
50    /// Style for `[!CAUTION]` alerts.
51    pub(crate) alert_caution_style: Style,
52    /// Background-colored padding space written before H1 text.
53    pub(crate) h1_prefix_style: Style,
54    /// Style for H1 heading text (fg + bg color).
55    pub(crate) h1_text_style: Style,
56}
57
58fn rgb(r: u8, g: u8, b: u8) -> Color {
59    RgbColor(r, g, b).into()
60}
61
62fn h1(bg: Color, fg: Color) -> (Style, Style) {
63    (
64        Style::new().bg_color(Some(bg)).fg_color(Some(bg)),
65        Style::new().bg_color(Some(bg)).fg_color(Some(fg)).bold(),
66    )
67}
68
69impl Theme {
70    /// A theme for dark terminal backgrounds (the default).
71    pub fn dark() -> Self {
72        let (h1_prefix_style, h1_text_style) = (
73            Style::new()
74                .bg_color(Some(AnsiColor::BrightBlue.into()))
75                .fg_color(Some(AnsiColor::BrightBlue.into())),
76            Style::new()
77                .bg_color(Some(AnsiColor::BrightBlue.into()))
78                .fg_color(Some(AnsiColor::BrightWhite.into()))
79                .bold(),
80        );
81        Self {
82            html_block_style: Style::new().fg_color(Some(AnsiColor::Green.into())),
83            inline_html_style: Style::new().fg_color(Some(AnsiColor::Green.into())),
84            code_style: Style::new().fg_color(Some(AnsiColor::Yellow.into())),
85            link_style: Style::new().fg_color(Some(AnsiColor::Blue.into())),
86            image_link_style: Style::new().fg_color(Some(AnsiColor::Magenta.into())),
87            rule_color: AnsiColor::Green.into(),
88            quote_border_style: Style::new().fg_color(Some(AnsiColor::Cyan.into())),
89            h2_style: Style::new().fg_color(Some(AnsiColor::Blue.into())).bold(),
90            h3_style: Style::new().fg_color(Some(AnsiColor::Cyan.into())).bold(),
91            h4_style: Style::new().fg_color(Some(AnsiColor::Green.into())).bold(),
92            h5_style: Style::new().fg_color(Some(AnsiColor::Yellow.into())).bold(),
93            h6_style: Style::new()
94                .fg_color(Some(AnsiColor::Magenta.into()))
95                .bold(),
96            footnote_style: Style::new().fg_color(Some(AnsiColor::Cyan.into())),
97            math_style: Style::new().fg_color(Some(AnsiColor::Yellow.into())),
98            alert_note_style: Style::new()
99                .fg_color(Some(AnsiColor::BrightBlue.into()))
100                .bold(),
101            alert_tip_style: Style::new()
102                .fg_color(Some(AnsiColor::BrightGreen.into()))
103                .bold(),
104            alert_important_style: Style::new()
105                .fg_color(Some(AnsiColor::BrightMagenta.into()))
106                .bold(),
107            alert_warning_style: Style::new()
108                .fg_color(Some(AnsiColor::BrightYellow.into()))
109                .bold(),
110            alert_caution_style: Style::new()
111                .fg_color(Some(AnsiColor::BrightRed.into()))
112                .bold(),
113            h1_prefix_style,
114            h1_text_style,
115        }
116    }
117
118    /// A theme for light terminal backgrounds.
119    pub fn light() -> Self {
120        let (h1_prefix_style, h1_text_style) = (
121            Style::new()
122                .bg_color(Some(AnsiColor::BrightCyan.into()))
123                .fg_color(Some(AnsiColor::BrightCyan.into())),
124            Style::new()
125                .bg_color(Some(AnsiColor::BrightCyan.into()))
126                .fg_color(Some(AnsiColor::Black.into()))
127                .bold(),
128        );
129        Self {
130            html_block_style: Style::new().fg_color(Some(AnsiColor::Green.into())),
131            inline_html_style: Style::new().fg_color(Some(AnsiColor::Green.into())),
132            code_style: Style::new().fg_color(Some(AnsiColor::Blue.into())),
133            link_style: Style::new().fg_color(Some(AnsiColor::Blue.into())),
134            image_link_style: Style::new().fg_color(Some(AnsiColor::Magenta.into())),
135            rule_color: AnsiColor::Green.into(),
136            quote_border_style: Style::new().fg_color(Some(AnsiColor::Cyan.into())),
137            h2_style: Style::new()
138                .fg_color(Some(AnsiColor::Magenta.into()))
139                .bold(),
140            h3_style: Style::new().fg_color(Some(AnsiColor::Blue.into())).bold(),
141            h4_style: Style::new().fg_color(Some(AnsiColor::Cyan.into())).bold(),
142            h5_style: Style::new().fg_color(Some(AnsiColor::Green.into())).bold(),
143            h6_style: Style::new().fg_color(Some(AnsiColor::Red.into())).bold(),
144            footnote_style: Style::new().fg_color(Some(AnsiColor::Cyan.into())),
145            math_style: Style::new().fg_color(Some(AnsiColor::Blue.into())),
146            alert_note_style: Style::new().fg_color(Some(AnsiColor::Blue.into())).bold(),
147            alert_tip_style: Style::new().fg_color(Some(AnsiColor::Green.into())).bold(),
148            alert_important_style: Style::new()
149                .fg_color(Some(AnsiColor::Magenta.into()))
150                .bold(),
151            alert_warning_style: Style::new().fg_color(Some(AnsiColor::Red.into())).bold(),
152            alert_caution_style: Style::new()
153                .fg_color(Some(AnsiColor::BrightRed.into()))
154                .bold(),
155            h1_prefix_style,
156            h1_text_style,
157        }
158    }
159
160    /// Catppuccin Mocha (dark).
161    pub fn catppuccin_mocha() -> Self {
162        let (h1_prefix_style, h1_text_style) = h1(rgb(203, 166, 247), rgb(17, 17, 27));
163        Self {
164            html_block_style: Style::new().fg_color(Some(rgb(250, 179, 135))),
165            inline_html_style: Style::new().fg_color(Some(rgb(250, 179, 135))),
166            code_style: Style::new().fg_color(Some(rgb(137, 220, 235))),
167            link_style: Style::new().fg_color(Some(rgb(137, 180, 250))),
168            image_link_style: Style::new().fg_color(Some(rgb(245, 194, 231))),
169            rule_color: rgb(148, 226, 213),
170            quote_border_style: Style::new().fg_color(Some(rgb(108, 112, 134))),
171            h2_style: Style::new().fg_color(Some(rgb(203, 166, 247))).bold(),
172            h3_style: Style::new().fg_color(Some(rgb(137, 180, 250))).bold(),
173            h4_style: Style::new().fg_color(Some(rgb(137, 220, 235))).bold(),
174            h5_style: Style::new().fg_color(Some(rgb(166, 227, 161))).bold(),
175            h6_style: Style::new().fg_color(Some(rgb(250, 179, 135))).bold(),
176            footnote_style: Style::new().fg_color(Some(rgb(108, 112, 134))),
177            math_style: Style::new().fg_color(Some(rgb(137, 220, 235))),
178            alert_note_style: Style::new().fg_color(Some(rgb(116, 199, 236))).bold(),
179            alert_tip_style: Style::new().fg_color(Some(rgb(166, 227, 161))).bold(),
180            alert_important_style: Style::new().fg_color(Some(rgb(203, 166, 247))).bold(),
181            alert_warning_style: Style::new().fg_color(Some(rgb(249, 226, 175))).bold(),
182            alert_caution_style: Style::new().fg_color(Some(rgb(243, 139, 168))).bold(),
183            h1_prefix_style,
184            h1_text_style,
185        }
186    }
187
188    /// Catppuccin Latte (light).
189    pub fn catppuccin_latte() -> Self {
190        let (h1_prefix_style, h1_text_style) = h1(rgb(136, 57, 239), rgb(239, 241, 245));
191        Self {
192            html_block_style: Style::new().fg_color(Some(rgb(254, 100, 11))),
193            inline_html_style: Style::new().fg_color(Some(rgb(254, 100, 11))),
194            code_style: Style::new().fg_color(Some(rgb(4, 165, 229))),
195            link_style: Style::new().fg_color(Some(rgb(30, 102, 245))),
196            image_link_style: Style::new().fg_color(Some(rgb(234, 118, 203))),
197            rule_color: rgb(23, 146, 153),
198            quote_border_style: Style::new().fg_color(Some(rgb(124, 127, 147))),
199            h2_style: Style::new().fg_color(Some(rgb(136, 57, 239))).bold(),
200            h3_style: Style::new().fg_color(Some(rgb(30, 102, 245))).bold(),
201            h4_style: Style::new().fg_color(Some(rgb(23, 146, 153))).bold(),
202            h5_style: Style::new().fg_color(Some(rgb(64, 160, 43))).bold(),
203            h6_style: Style::new().fg_color(Some(rgb(254, 100, 11))).bold(),
204            footnote_style: Style::new().fg_color(Some(rgb(124, 127, 147))),
205            math_style: Style::new().fg_color(Some(rgb(4, 165, 229))),
206            alert_note_style: Style::new().fg_color(Some(rgb(32, 159, 181))).bold(),
207            alert_tip_style: Style::new().fg_color(Some(rgb(64, 160, 43))).bold(),
208            alert_important_style: Style::new().fg_color(Some(rgb(136, 57, 239))).bold(),
209            alert_warning_style: Style::new().fg_color(Some(rgb(223, 142, 29))).bold(),
210            alert_caution_style: Style::new().fg_color(Some(rgb(210, 15, 57))).bold(),
211            h1_prefix_style,
212            h1_text_style,
213        }
214    }
215
216    /// Gruvbox Dark.
217    pub fn gruvbox_dark() -> Self {
218        let (h1_prefix_style, h1_text_style) = h1(rgb(250, 189, 47), rgb(40, 40, 40));
219        Self {
220            html_block_style: Style::new().fg_color(Some(rgb(214, 93, 14))),
221            inline_html_style: Style::new().fg_color(Some(rgb(214, 93, 14))),
222            code_style: Style::new().fg_color(Some(rgb(131, 165, 152))),
223            link_style: Style::new().fg_color(Some(rgb(131, 165, 152))),
224            image_link_style: Style::new().fg_color(Some(rgb(211, 134, 155))),
225            rule_color: rgb(184, 187, 38),
226            quote_border_style: Style::new().fg_color(Some(rgb(142, 192, 124))),
227            h2_style: Style::new().fg_color(Some(rgb(250, 189, 47))).bold(),
228            h3_style: Style::new().fg_color(Some(rgb(131, 165, 152))).bold(),
229            h4_style: Style::new().fg_color(Some(rgb(142, 192, 124))).bold(),
230            h5_style: Style::new().fg_color(Some(rgb(211, 134, 155))).bold(),
231            h6_style: Style::new().fg_color(Some(rgb(254, 128, 25))).bold(),
232            footnote_style: Style::new().fg_color(Some(rgb(146, 131, 116))),
233            math_style: Style::new().fg_color(Some(rgb(131, 165, 152))),
234            alert_note_style: Style::new().fg_color(Some(rgb(131, 165, 152))).bold(),
235            alert_tip_style: Style::new().fg_color(Some(rgb(184, 187, 38))).bold(),
236            alert_important_style: Style::new().fg_color(Some(rgb(211, 134, 155))).bold(),
237            alert_warning_style: Style::new().fg_color(Some(rgb(254, 128, 25))).bold(),
238            alert_caution_style: Style::new().fg_color(Some(rgb(251, 73, 52))).bold(),
239            h1_prefix_style,
240            h1_text_style,
241        }
242    }
243
244    /// Gruvbox Light.
245    pub fn gruvbox_light() -> Self {
246        let (h1_prefix_style, h1_text_style) = h1(rgb(181, 118, 20), rgb(251, 241, 199));
247        Self {
248            html_block_style: Style::new().fg_color(Some(rgb(175, 58, 3))),
249            inline_html_style: Style::new().fg_color(Some(rgb(175, 58, 3))),
250            code_style: Style::new().fg_color(Some(rgb(7, 102, 120))),
251            link_style: Style::new().fg_color(Some(rgb(7, 102, 120))),
252            image_link_style: Style::new().fg_color(Some(rgb(143, 63, 113))),
253            rule_color: rgb(66, 123, 88),
254            quote_border_style: Style::new().fg_color(Some(rgb(66, 123, 88))),
255            h2_style: Style::new().fg_color(Some(rgb(181, 118, 20))).bold(),
256            h3_style: Style::new().fg_color(Some(rgb(7, 102, 120))).bold(),
257            h4_style: Style::new().fg_color(Some(rgb(66, 123, 88))).bold(),
258            h5_style: Style::new().fg_color(Some(rgb(143, 63, 113))).bold(),
259            h6_style: Style::new().fg_color(Some(rgb(175, 58, 3))).bold(),
260            footnote_style: Style::new().fg_color(Some(rgb(124, 111, 100))),
261            math_style: Style::new().fg_color(Some(rgb(7, 102, 120))),
262            alert_note_style: Style::new().fg_color(Some(rgb(7, 102, 120))).bold(),
263            alert_tip_style: Style::new().fg_color(Some(rgb(121, 116, 14))).bold(),
264            alert_important_style: Style::new().fg_color(Some(rgb(143, 63, 113))).bold(),
265            alert_warning_style: Style::new().fg_color(Some(rgb(181, 118, 20))).bold(),
266            alert_caution_style: Style::new().fg_color(Some(rgb(157, 0, 6))).bold(),
267            h1_prefix_style,
268            h1_text_style,
269        }
270    }
271
272    /// Dracula.
273    pub fn dracula() -> Self {
274        let (h1_prefix_style, h1_text_style) = h1(rgb(189, 147, 249), rgb(40, 42, 54));
275        Self {
276            html_block_style: Style::new().fg_color(Some(rgb(255, 184, 108))),
277            inline_html_style: Style::new().fg_color(Some(rgb(255, 184, 108))),
278            code_style: Style::new().fg_color(Some(rgb(241, 250, 140))),
279            link_style: Style::new().fg_color(Some(rgb(139, 233, 253))),
280            image_link_style: Style::new().fg_color(Some(rgb(255, 121, 198))),
281            rule_color: rgb(80, 250, 123),
282            quote_border_style: Style::new().fg_color(Some(rgb(98, 114, 164))),
283            h2_style: Style::new().fg_color(Some(rgb(189, 147, 249))).bold(),
284            h3_style: Style::new().fg_color(Some(rgb(139, 233, 253))).bold(),
285            h4_style: Style::new().fg_color(Some(rgb(80, 250, 123))).bold(),
286            h5_style: Style::new().fg_color(Some(rgb(255, 121, 198))).bold(),
287            h6_style: Style::new().fg_color(Some(rgb(255, 184, 108))).bold(),
288            footnote_style: Style::new().fg_color(Some(rgb(98, 114, 164))),
289            math_style: Style::new().fg_color(Some(rgb(241, 250, 140))),
290            alert_note_style: Style::new().fg_color(Some(rgb(139, 233, 253))).bold(),
291            alert_tip_style: Style::new().fg_color(Some(rgb(80, 250, 123))).bold(),
292            alert_important_style: Style::new().fg_color(Some(rgb(255, 121, 198))).bold(),
293            alert_warning_style: Style::new().fg_color(Some(rgb(255, 184, 108))).bold(),
294            alert_caution_style: Style::new().fg_color(Some(rgb(255, 85, 85))).bold(),
295            h1_prefix_style,
296            h1_text_style,
297        }
298    }
299
300    /// Nord.
301    pub fn nord() -> Self {
302        let (h1_prefix_style, h1_text_style) = h1(rgb(94, 129, 172), rgb(236, 239, 244));
303        Self {
304            html_block_style: Style::new().fg_color(Some(rgb(208, 135, 112))),
305            inline_html_style: Style::new().fg_color(Some(rgb(208, 135, 112))),
306            code_style: Style::new().fg_color(Some(rgb(143, 188, 187))),
307            link_style: Style::new().fg_color(Some(rgb(136, 192, 208))),
308            image_link_style: Style::new().fg_color(Some(rgb(180, 142, 173))),
309            rule_color: rgb(136, 192, 208),
310            quote_border_style: Style::new().fg_color(Some(rgb(76, 86, 106))),
311            h2_style: Style::new().fg_color(Some(rgb(94, 129, 172))).bold(),
312            h3_style: Style::new().fg_color(Some(rgb(129, 161, 193))).bold(),
313            h4_style: Style::new().fg_color(Some(rgb(136, 192, 208))).bold(),
314            h5_style: Style::new().fg_color(Some(rgb(163, 190, 140))).bold(),
315            h6_style: Style::new().fg_color(Some(rgb(235, 203, 139))).bold(),
316            footnote_style: Style::new().fg_color(Some(rgb(76, 86, 106))),
317            math_style: Style::new().fg_color(Some(rgb(143, 188, 187))),
318            alert_note_style: Style::new().fg_color(Some(rgb(143, 188, 187))).bold(),
319            alert_tip_style: Style::new().fg_color(Some(rgb(163, 190, 140))).bold(),
320            alert_important_style: Style::new().fg_color(Some(rgb(180, 142, 173))).bold(),
321            alert_warning_style: Style::new().fg_color(Some(rgb(235, 203, 139))).bold(),
322            alert_caution_style: Style::new().fg_color(Some(rgb(191, 97, 106))).bold(),
323            h1_prefix_style,
324            h1_text_style,
325        }
326    }
327
328    /// Solarized Dark.
329    pub fn solarized_dark() -> Self {
330        let (h1_prefix_style, h1_text_style) = h1(rgb(38, 139, 210), rgb(0, 43, 54));
331        Self {
332            html_block_style: Style::new().fg_color(Some(rgb(203, 75, 22))),
333            inline_html_style: Style::new().fg_color(Some(rgb(203, 75, 22))),
334            code_style: Style::new().fg_color(Some(rgb(42, 161, 152))),
335            link_style: Style::new().fg_color(Some(rgb(38, 139, 210))),
336            image_link_style: Style::new().fg_color(Some(rgb(211, 54, 130))),
337            rule_color: rgb(42, 161, 152),
338            quote_border_style: Style::new().fg_color(Some(rgb(88, 110, 117))),
339            h2_style: Style::new().fg_color(Some(rgb(38, 139, 210))).bold(),
340            h3_style: Style::new().fg_color(Some(rgb(42, 161, 152))).bold(),
341            h4_style: Style::new().fg_color(Some(rgb(133, 153, 0))).bold(),
342            h5_style: Style::new().fg_color(Some(rgb(211, 54, 130))).bold(),
343            h6_style: Style::new().fg_color(Some(rgb(203, 75, 22))).bold(),
344            footnote_style: Style::new().fg_color(Some(rgb(88, 110, 117))),
345            math_style: Style::new().fg_color(Some(rgb(42, 161, 152))),
346            alert_note_style: Style::new().fg_color(Some(rgb(108, 113, 196))).bold(),
347            alert_tip_style: Style::new().fg_color(Some(rgb(133, 153, 0))).bold(),
348            alert_important_style: Style::new().fg_color(Some(rgb(211, 54, 130))).bold(),
349            alert_warning_style: Style::new().fg_color(Some(rgb(181, 137, 0))).bold(),
350            alert_caution_style: Style::new().fg_color(Some(rgb(220, 50, 47))).bold(),
351            h1_prefix_style,
352            h1_text_style,
353        }
354    }
355
356    /// Solarized Light.
357    pub fn solarized_light() -> Self {
358        let (h1_prefix_style, h1_text_style) = h1(rgb(38, 139, 210), rgb(253, 246, 227));
359        Self {
360            h1_prefix_style,
361            h1_text_style,
362            ..Self::solarized_dark()
363        }
364    }
365}
366
367impl Default for Theme {
368    fn default() -> Self {
369        Self::dark()
370    }
371}
372
373/// Combine styles.
374pub trait CombineStyle {
375    /// Put this style on top of the other style.
376    ///
377    /// Return a new style which falls back to the `other` style for all style attributes not
378    /// specified in this style.
379    fn on_top_of(self, other: &Self) -> Self;
380}
381
382impl CombineStyle for Style {
383    /// Put this style on top of the `other` style.
384    fn on_top_of(self, other: &Style) -> Style {
385        Style::new()
386            .fg_color(self.get_fg_color().or(other.get_fg_color()))
387            .bg_color(self.get_bg_color().or(other.get_bg_color()))
388            .effects(other.get_effects() | self.get_effects())
389            .underline_color(self.get_underline_color().or(other.get_underline_color()))
390    }
391}