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/// Which of merman's built-in editor-theme presets a Mermaid diagram's non-accent colors (node
12/// fills, canvas, text) should be based on.
13///
14/// merman ships a handful of hand-tuned presets (`editor_dark`/`light`, `one_dark`,
15/// `gruvbox_dark`/`light`, `ayu_dark`/`light`) matching well-known editor color schemes; picking
16/// the closest one for a given mdcat theme makes a diagram's own colors (not just its accent
17/// line, which is always synced to [`Theme::mermaid_style`]) resemble the terminal theme it's
18/// rendered under, instead of always using the same generic dark/light palette. Chosen by
19/// comparing each theme's well-known standard background color against the presets' canvas
20/// colors (RGB distance), falling back to `Generic` where no preset is a good fit rather than
21/// forcing a tenuous match.
22#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default)]
23pub enum MermaidPalette {
24    /// merman's generic `editor_dark`/`editor_light` preset — the default for themes with no
25    /// closer match among merman's named presets.
26    #[default]
27    Generic,
28    /// merman's `one_dark` preset (Atom One Dark).
29    OneDark,
30    /// merman's `gruvbox_dark` preset.
31    GruvboxDark,
32    /// merman's `gruvbox_light` preset.
33    GruvboxLight,
34    /// merman's `ayu_dark` preset.
35    AyuDark,
36    /// merman's `ayu_light` preset.
37    AyuLight,
38}
39
40/// A colour theme for mdcat.
41///
42/// All fields are public so themes can be fully customised, e.g. from a user config file.
43/// `h1_prefix_style` is a derived convenience (background-only padding matching `h1_text_style`'s
44/// background); use [`Theme::with_h1`] to keep the two in sync when changing the H1 background.
45#[derive(Debug, Clone, Hash, PartialEq, Eq)]
46pub struct Theme {
47    /// Whether this theme targets a dark terminal background.
48    pub is_dark: bool,
49    /// Which merman editor-theme preset Mermaid diagrams should be based on (see
50    /// [`MermaidPalette`]).
51    pub mermaid_palette: MermaidPalette,
52    /// Style for HTML blocks.
53    pub html_block_style: Style,
54    /// Style for inline HTML.
55    pub inline_html_style: Style,
56    /// Style for code, unless the code is syntax-highlighted.
57    pub code_style: Style,
58    /// Style for links.
59    pub link_style: Style,
60    /// Color for image links (unless the image is rendered inline)
61    pub image_link_style: Style,
62    /// Color for rulers.
63    pub rule_color: Color,
64    /// Style for block quote borders (`│`).
65    pub quote_border_style: Style,
66    /// Style for H2 headings.
67    pub h2_style: Style,
68    /// Marker written before H2 heading text (default `"━━ "`).
69    pub h2_marker: String,
70    /// Style for H3 headings.
71    pub h3_style: Style,
72    /// Marker written before H3 heading text (default `"── "`).
73    pub h3_marker: String,
74    /// Style for H4 headings.
75    pub h4_style: Style,
76    /// Marker written before H4 heading text (default `"┄ "`).
77    pub h4_marker: String,
78    /// Style for H5 headings.
79    pub h5_style: Style,
80    /// Marker written before H5 heading text (default `"╌ "`).
81    pub h5_marker: String,
82    /// Style for H6 headings.
83    pub h6_style: Style,
84    /// Marker written before H6 heading text (default `"· "`).
85    pub h6_marker: String,
86    /// Style for footnote references and definitions.
87    pub footnote_style: Style,
88    /// Style for math expressions.
89    pub math_style: Style,
90    /// Style for the Unicode/text fallback of Mermaid diagrams (used when no image protocol is
91    /// available).
92    pub mermaid_style: Style,
93    /// Style for `[!NOTE]` alerts.
94    pub alert_note_style: Style,
95    /// Icon and label written for `[!NOTE]` alerts (default `"ℹ NOTE"`).
96    pub alert_note_label: String,
97    /// Style for `[!TIP]` alerts.
98    pub alert_tip_style: Style,
99    /// Icon and label written for `[!TIP]` alerts (default `"◆ TIP"`).
100    pub alert_tip_label: String,
101    /// Style for `[!IMPORTANT]` alerts.
102    pub alert_important_style: Style,
103    /// Icon and label written for `[!IMPORTANT]` alerts (default `"★ IMPORTANT"`).
104    pub alert_important_label: String,
105    /// Style for `[!WARNING]` alerts.
106    pub alert_warning_style: Style,
107    /// Icon and label written for `[!WARNING]` alerts (default `"⚠ WARNING"`).
108    pub alert_warning_label: String,
109    /// Style for `[!CAUTION]` alerts.
110    pub alert_caution_style: Style,
111    /// Icon and label written for `[!CAUTION]` alerts (default `"✖ CAUTION"`).
112    pub alert_caution_label: String,
113    /// Background-colored padding space written before H1 text.
114    pub h1_prefix_style: Style,
115    /// Style for H1 heading text (fg + bg color).
116    pub h1_text_style: Style,
117}
118
119impl Theme {
120    /// Set the H1 style, keeping `h1_prefix_style`'s background in sync with `text_style`'s.
121    pub fn with_h1(mut self, text_style: Style) -> Self {
122        let bg = text_style.get_bg_color();
123        self.h1_prefix_style = Style::new().bg_color(bg).fg_color(bg);
124        self.h1_text_style = text_style;
125        self
126    }
127}
128
129/// Default heading markers and alert labels, shared by every built-in theme; only colors vary
130/// between themes.
131#[allow(clippy::type_complexity)]
132fn default_markers_and_labels() -> (
133    String,
134    String,
135    String,
136    String,
137    String,
138    String,
139    String,
140    String,
141    String,
142    String,
143) {
144    (
145        "━━ ".to_string(),
146        "── ".to_string(),
147        "┄ ".to_string(),
148        "╌ ".to_string(),
149        "· ".to_string(),
150        "ℹ NOTE".to_string(),
151        "◆ TIP".to_string(),
152        "★ IMPORTANT".to_string(),
153        "⚠ WARNING".to_string(),
154        "✖ CAUTION".to_string(),
155    )
156}
157
158fn rgb(r: u8, g: u8, b: u8) -> Color {
159    RgbColor(r, g, b).into()
160}
161
162fn h1(bg: Color, fg: Color) -> (Style, Style) {
163    (
164        Style::new().bg_color(Some(bg)).fg_color(Some(bg)),
165        Style::new().bg_color(Some(bg)).fg_color(Some(fg)).bold(),
166    )
167}
168
169impl Theme {
170    /// A theme for dark terminal backgrounds (the default).
171    pub fn dark() -> Self {
172        let (h1_prefix_style, h1_text_style) = (
173            Style::new()
174                .bg_color(Some(AnsiColor::BrightBlue.into()))
175                .fg_color(Some(AnsiColor::BrightBlue.into())),
176            Style::new()
177                .bg_color(Some(AnsiColor::BrightBlue.into()))
178                .fg_color(Some(AnsiColor::BrightWhite.into()))
179                .bold(),
180        );
181        let (
182            h2_marker,
183            h3_marker,
184            h4_marker,
185            h5_marker,
186            h6_marker,
187            alert_note_label,
188            alert_tip_label,
189            alert_important_label,
190            alert_warning_label,
191            alert_caution_label,
192        ) = default_markers_and_labels();
193        Self {
194            is_dark: true,
195            mermaid_palette: MermaidPalette::Generic,
196            html_block_style: Style::new().fg_color(Some(AnsiColor::Green.into())),
197            inline_html_style: Style::new().fg_color(Some(AnsiColor::Green.into())),
198            code_style: Style::new().fg_color(Some(AnsiColor::Yellow.into())),
199            link_style: Style::new().fg_color(Some(AnsiColor::Blue.into())),
200            image_link_style: Style::new().fg_color(Some(AnsiColor::Magenta.into())),
201            rule_color: AnsiColor::Green.into(),
202            quote_border_style: Style::new().fg_color(Some(AnsiColor::Cyan.into())),
203            h2_style: Style::new().fg_color(Some(AnsiColor::Blue.into())).bold(),
204            h3_style: Style::new().fg_color(Some(AnsiColor::Cyan.into())).bold(),
205            h4_style: Style::new().fg_color(Some(AnsiColor::Green.into())).bold(),
206            h5_style: Style::new().fg_color(Some(AnsiColor::Yellow.into())).bold(),
207            h6_style: Style::new()
208                .fg_color(Some(AnsiColor::Magenta.into()))
209                .bold(),
210            footnote_style: Style::new().fg_color(Some(AnsiColor::Cyan.into())),
211            math_style: Style::new().fg_color(Some(AnsiColor::Yellow.into())),
212            mermaid_style: Style::new().fg_color(Some(AnsiColor::Yellow.into())),
213            alert_note_style: Style::new()
214                .fg_color(Some(AnsiColor::BrightBlue.into()))
215                .bold(),
216            alert_tip_style: Style::new()
217                .fg_color(Some(AnsiColor::BrightGreen.into()))
218                .bold(),
219            alert_important_style: Style::new()
220                .fg_color(Some(AnsiColor::BrightMagenta.into()))
221                .bold(),
222            alert_warning_style: Style::new()
223                .fg_color(Some(AnsiColor::BrightYellow.into()))
224                .bold(),
225            alert_caution_style: Style::new()
226                .fg_color(Some(AnsiColor::BrightRed.into()))
227                .bold(),
228            h2_marker,
229            h3_marker,
230            h4_marker,
231            h5_marker,
232            h6_marker,
233            alert_note_label,
234            alert_tip_label,
235            alert_important_label,
236            alert_warning_label,
237            alert_caution_label,
238            h1_prefix_style,
239            h1_text_style,
240        }
241    }
242
243    /// A theme for light terminal backgrounds.
244    pub fn light() -> Self {
245        let (h1_prefix_style, h1_text_style) = (
246            Style::new()
247                .bg_color(Some(AnsiColor::BrightCyan.into()))
248                .fg_color(Some(AnsiColor::BrightCyan.into())),
249            Style::new()
250                .bg_color(Some(AnsiColor::BrightCyan.into()))
251                .fg_color(Some(AnsiColor::Black.into()))
252                .bold(),
253        );
254        let (
255            h2_marker,
256            h3_marker,
257            h4_marker,
258            h5_marker,
259            h6_marker,
260            alert_note_label,
261            alert_tip_label,
262            alert_important_label,
263            alert_warning_label,
264            alert_caution_label,
265        ) = default_markers_and_labels();
266        Self {
267            is_dark: false,
268            mermaid_palette: MermaidPalette::Generic,
269            html_block_style: Style::new().fg_color(Some(AnsiColor::Green.into())),
270            inline_html_style: Style::new().fg_color(Some(AnsiColor::Green.into())),
271            code_style: Style::new().fg_color(Some(AnsiColor::Blue.into())),
272            link_style: Style::new().fg_color(Some(AnsiColor::Blue.into())),
273            image_link_style: Style::new().fg_color(Some(AnsiColor::Magenta.into())),
274            rule_color: AnsiColor::Green.into(),
275            quote_border_style: Style::new().fg_color(Some(AnsiColor::Cyan.into())),
276            h2_style: Style::new()
277                .fg_color(Some(AnsiColor::Magenta.into()))
278                .bold(),
279            h3_style: Style::new().fg_color(Some(AnsiColor::Blue.into())).bold(),
280            h4_style: Style::new().fg_color(Some(AnsiColor::Cyan.into())).bold(),
281            h5_style: Style::new().fg_color(Some(AnsiColor::Green.into())).bold(),
282            h6_style: Style::new().fg_color(Some(AnsiColor::Red.into())).bold(),
283            footnote_style: Style::new().fg_color(Some(AnsiColor::Cyan.into())),
284            math_style: Style::new().fg_color(Some(AnsiColor::Blue.into())),
285            mermaid_style: Style::new().fg_color(Some(AnsiColor::Blue.into())),
286            alert_note_style: Style::new().fg_color(Some(AnsiColor::Blue.into())).bold(),
287            alert_tip_style: Style::new().fg_color(Some(AnsiColor::Green.into())).bold(),
288            alert_important_style: Style::new()
289                .fg_color(Some(AnsiColor::Magenta.into()))
290                .bold(),
291            alert_warning_style: Style::new().fg_color(Some(AnsiColor::Red.into())).bold(),
292            alert_caution_style: Style::new()
293                .fg_color(Some(AnsiColor::BrightRed.into()))
294                .bold(),
295            h2_marker,
296            h3_marker,
297            h4_marker,
298            h5_marker,
299            h6_marker,
300            alert_note_label,
301            alert_tip_label,
302            alert_important_label,
303            alert_warning_label,
304            alert_caution_label,
305            h1_prefix_style,
306            h1_text_style,
307        }
308    }
309
310    /// Catppuccin Mocha (dark).
311    pub fn catppuccin_mocha() -> Self {
312        let (h1_prefix_style, h1_text_style) = h1(rgb(203, 166, 247), rgb(17, 17, 27));
313        let (
314            h2_marker,
315            h3_marker,
316            h4_marker,
317            h5_marker,
318            h6_marker,
319            alert_note_label,
320            alert_tip_label,
321            alert_important_label,
322            alert_warning_label,
323            alert_caution_label,
324        ) = default_markers_and_labels();
325        Self {
326            is_dark: true,
327            mermaid_palette: MermaidPalette::Generic,
328            html_block_style: Style::new().fg_color(Some(rgb(250, 179, 135))),
329            inline_html_style: Style::new().fg_color(Some(rgb(250, 179, 135))),
330            code_style: Style::new().fg_color(Some(rgb(137, 220, 235))),
331            link_style: Style::new().fg_color(Some(rgb(137, 180, 250))),
332            image_link_style: Style::new().fg_color(Some(rgb(245, 194, 231))),
333            rule_color: rgb(148, 226, 213),
334            quote_border_style: Style::new().fg_color(Some(rgb(108, 112, 134))),
335            h2_style: Style::new().fg_color(Some(rgb(203, 166, 247))).bold(),
336            h3_style: Style::new().fg_color(Some(rgb(137, 180, 250))).bold(),
337            h4_style: Style::new().fg_color(Some(rgb(137, 220, 235))).bold(),
338            h5_style: Style::new().fg_color(Some(rgb(166, 227, 161))).bold(),
339            h6_style: Style::new().fg_color(Some(rgb(250, 179, 135))).bold(),
340            footnote_style: Style::new().fg_color(Some(rgb(108, 112, 134))),
341            math_style: Style::new().fg_color(Some(rgb(137, 220, 235))),
342            mermaid_style: Style::new().fg_color(Some(rgb(137, 220, 235))),
343            alert_note_style: Style::new().fg_color(Some(rgb(116, 199, 236))).bold(),
344            alert_tip_style: Style::new().fg_color(Some(rgb(166, 227, 161))).bold(),
345            alert_important_style: Style::new().fg_color(Some(rgb(203, 166, 247))).bold(),
346            alert_warning_style: Style::new().fg_color(Some(rgb(249, 226, 175))).bold(),
347            alert_caution_style: Style::new().fg_color(Some(rgb(243, 139, 168))).bold(),
348            h2_marker,
349            h3_marker,
350            h4_marker,
351            h5_marker,
352            h6_marker,
353            alert_note_label,
354            alert_tip_label,
355            alert_important_label,
356            alert_warning_label,
357            alert_caution_label,
358            h1_prefix_style,
359            h1_text_style,
360        }
361    }
362
363    /// Catppuccin Latte (light).
364    pub fn catppuccin_latte() -> Self {
365        let (h1_prefix_style, h1_text_style) = h1(rgb(136, 57, 239), rgb(239, 241, 245));
366        let (
367            h2_marker,
368            h3_marker,
369            h4_marker,
370            h5_marker,
371            h6_marker,
372            alert_note_label,
373            alert_tip_label,
374            alert_important_label,
375            alert_warning_label,
376            alert_caution_label,
377        ) = default_markers_and_labels();
378        Self {
379            is_dark: false,
380            mermaid_palette: MermaidPalette::AyuLight,
381            html_block_style: Style::new().fg_color(Some(rgb(254, 100, 11))),
382            inline_html_style: Style::new().fg_color(Some(rgb(254, 100, 11))),
383            code_style: Style::new().fg_color(Some(rgb(4, 165, 229))),
384            link_style: Style::new().fg_color(Some(rgb(30, 102, 245))),
385            image_link_style: Style::new().fg_color(Some(rgb(234, 118, 203))),
386            rule_color: rgb(23, 146, 153),
387            quote_border_style: Style::new().fg_color(Some(rgb(124, 127, 147))),
388            h2_style: Style::new().fg_color(Some(rgb(136, 57, 239))).bold(),
389            h3_style: Style::new().fg_color(Some(rgb(30, 102, 245))).bold(),
390            h4_style: Style::new().fg_color(Some(rgb(23, 146, 153))).bold(),
391            h5_style: Style::new().fg_color(Some(rgb(64, 160, 43))).bold(),
392            h6_style: Style::new().fg_color(Some(rgb(254, 100, 11))).bold(),
393            footnote_style: Style::new().fg_color(Some(rgb(124, 127, 147))),
394            math_style: Style::new().fg_color(Some(rgb(4, 165, 229))),
395            mermaid_style: Style::new().fg_color(Some(rgb(4, 165, 229))),
396            alert_note_style: Style::new().fg_color(Some(rgb(32, 159, 181))).bold(),
397            alert_tip_style: Style::new().fg_color(Some(rgb(64, 160, 43))).bold(),
398            alert_important_style: Style::new().fg_color(Some(rgb(136, 57, 239))).bold(),
399            alert_warning_style: Style::new().fg_color(Some(rgb(223, 142, 29))).bold(),
400            alert_caution_style: Style::new().fg_color(Some(rgb(210, 15, 57))).bold(),
401            h2_marker,
402            h3_marker,
403            h4_marker,
404            h5_marker,
405            h6_marker,
406            alert_note_label,
407            alert_tip_label,
408            alert_important_label,
409            alert_warning_label,
410            alert_caution_label,
411            h1_prefix_style,
412            h1_text_style,
413        }
414    }
415
416    /// Gruvbox Dark.
417    pub fn gruvbox_dark() -> Self {
418        let (h1_prefix_style, h1_text_style) = h1(rgb(250, 189, 47), rgb(40, 40, 40));
419        let (
420            h2_marker,
421            h3_marker,
422            h4_marker,
423            h5_marker,
424            h6_marker,
425            alert_note_label,
426            alert_tip_label,
427            alert_important_label,
428            alert_warning_label,
429            alert_caution_label,
430        ) = default_markers_and_labels();
431        Self {
432            is_dark: true,
433            mermaid_palette: MermaidPalette::GruvboxDark,
434            html_block_style: Style::new().fg_color(Some(rgb(214, 93, 14))),
435            inline_html_style: Style::new().fg_color(Some(rgb(214, 93, 14))),
436            code_style: Style::new().fg_color(Some(rgb(131, 165, 152))),
437            link_style: Style::new().fg_color(Some(rgb(131, 165, 152))),
438            image_link_style: Style::new().fg_color(Some(rgb(211, 134, 155))),
439            rule_color: rgb(184, 187, 38),
440            quote_border_style: Style::new().fg_color(Some(rgb(142, 192, 124))),
441            h2_style: Style::new().fg_color(Some(rgb(250, 189, 47))).bold(),
442            h3_style: Style::new().fg_color(Some(rgb(131, 165, 152))).bold(),
443            h4_style: Style::new().fg_color(Some(rgb(142, 192, 124))).bold(),
444            h5_style: Style::new().fg_color(Some(rgb(211, 134, 155))).bold(),
445            h6_style: Style::new().fg_color(Some(rgb(254, 128, 25))).bold(),
446            footnote_style: Style::new().fg_color(Some(rgb(146, 131, 116))),
447            math_style: Style::new().fg_color(Some(rgb(131, 165, 152))),
448            mermaid_style: Style::new().fg_color(Some(rgb(131, 165, 152))),
449            alert_note_style: Style::new().fg_color(Some(rgb(131, 165, 152))).bold(),
450            alert_tip_style: Style::new().fg_color(Some(rgb(184, 187, 38))).bold(),
451            alert_important_style: Style::new().fg_color(Some(rgb(211, 134, 155))).bold(),
452            alert_warning_style: Style::new().fg_color(Some(rgb(254, 128, 25))).bold(),
453            alert_caution_style: Style::new().fg_color(Some(rgb(251, 73, 52))).bold(),
454            h2_marker,
455            h3_marker,
456            h4_marker,
457            h5_marker,
458            h6_marker,
459            alert_note_label,
460            alert_tip_label,
461            alert_important_label,
462            alert_warning_label,
463            alert_caution_label,
464            h1_prefix_style,
465            h1_text_style,
466        }
467    }
468
469    /// Gruvbox Light.
470    pub fn gruvbox_light() -> Self {
471        let (h1_prefix_style, h1_text_style) = h1(rgb(181, 118, 20), rgb(251, 241, 199));
472        let (
473            h2_marker,
474            h3_marker,
475            h4_marker,
476            h5_marker,
477            h6_marker,
478            alert_note_label,
479            alert_tip_label,
480            alert_important_label,
481            alert_warning_label,
482            alert_caution_label,
483        ) = default_markers_and_labels();
484        Self {
485            is_dark: false,
486            mermaid_palette: MermaidPalette::GruvboxLight,
487            html_block_style: Style::new().fg_color(Some(rgb(175, 58, 3))),
488            inline_html_style: Style::new().fg_color(Some(rgb(175, 58, 3))),
489            code_style: Style::new().fg_color(Some(rgb(7, 102, 120))),
490            link_style: Style::new().fg_color(Some(rgb(7, 102, 120))),
491            image_link_style: Style::new().fg_color(Some(rgb(143, 63, 113))),
492            rule_color: rgb(66, 123, 88),
493            quote_border_style: Style::new().fg_color(Some(rgb(66, 123, 88))),
494            h2_style: Style::new().fg_color(Some(rgb(181, 118, 20))).bold(),
495            h3_style: Style::new().fg_color(Some(rgb(7, 102, 120))).bold(),
496            h4_style: Style::new().fg_color(Some(rgb(66, 123, 88))).bold(),
497            h5_style: Style::new().fg_color(Some(rgb(143, 63, 113))).bold(),
498            h6_style: Style::new().fg_color(Some(rgb(175, 58, 3))).bold(),
499            footnote_style: Style::new().fg_color(Some(rgb(124, 111, 100))),
500            math_style: Style::new().fg_color(Some(rgb(7, 102, 120))),
501            mermaid_style: Style::new().fg_color(Some(rgb(7, 102, 120))),
502            alert_note_style: Style::new().fg_color(Some(rgb(7, 102, 120))).bold(),
503            alert_tip_style: Style::new().fg_color(Some(rgb(121, 116, 14))).bold(),
504            alert_important_style: Style::new().fg_color(Some(rgb(143, 63, 113))).bold(),
505            alert_warning_style: Style::new().fg_color(Some(rgb(181, 118, 20))).bold(),
506            alert_caution_style: Style::new().fg_color(Some(rgb(157, 0, 6))).bold(),
507            h2_marker,
508            h3_marker,
509            h4_marker,
510            h5_marker,
511            h6_marker,
512            alert_note_label,
513            alert_tip_label,
514            alert_important_label,
515            alert_warning_label,
516            alert_caution_label,
517            h1_prefix_style,
518            h1_text_style,
519        }
520    }
521
522    /// Dracula.
523    pub fn dracula() -> Self {
524        let (h1_prefix_style, h1_text_style) = h1(rgb(189, 147, 249), rgb(40, 42, 54));
525        let (
526            h2_marker,
527            h3_marker,
528            h4_marker,
529            h5_marker,
530            h6_marker,
531            alert_note_label,
532            alert_tip_label,
533            alert_important_label,
534            alert_warning_label,
535            alert_caution_label,
536        ) = default_markers_and_labels();
537        Self {
538            is_dark: true,
539            mermaid_palette: MermaidPalette::OneDark,
540            html_block_style: Style::new().fg_color(Some(rgb(255, 184, 108))),
541            inline_html_style: Style::new().fg_color(Some(rgb(255, 184, 108))),
542            code_style: Style::new().fg_color(Some(rgb(241, 250, 140))),
543            link_style: Style::new().fg_color(Some(rgb(139, 233, 253))),
544            image_link_style: Style::new().fg_color(Some(rgb(255, 121, 198))),
545            rule_color: rgb(80, 250, 123),
546            quote_border_style: Style::new().fg_color(Some(rgb(98, 114, 164))),
547            h2_style: Style::new().fg_color(Some(rgb(189, 147, 249))).bold(),
548            h3_style: Style::new().fg_color(Some(rgb(139, 233, 253))).bold(),
549            h4_style: Style::new().fg_color(Some(rgb(80, 250, 123))).bold(),
550            h5_style: Style::new().fg_color(Some(rgb(255, 121, 198))).bold(),
551            h6_style: Style::new().fg_color(Some(rgb(255, 184, 108))).bold(),
552            footnote_style: Style::new().fg_color(Some(rgb(98, 114, 164))),
553            math_style: Style::new().fg_color(Some(rgb(241, 250, 140))),
554            mermaid_style: Style::new().fg_color(Some(rgb(241, 250, 140))),
555            alert_note_style: Style::new().fg_color(Some(rgb(139, 233, 253))).bold(),
556            alert_tip_style: Style::new().fg_color(Some(rgb(80, 250, 123))).bold(),
557            alert_important_style: Style::new().fg_color(Some(rgb(255, 121, 198))).bold(),
558            alert_warning_style: Style::new().fg_color(Some(rgb(255, 184, 108))).bold(),
559            alert_caution_style: Style::new().fg_color(Some(rgb(255, 85, 85))).bold(),
560            h2_marker,
561            h3_marker,
562            h4_marker,
563            h5_marker,
564            h6_marker,
565            alert_note_label,
566            alert_tip_label,
567            alert_important_label,
568            alert_warning_label,
569            alert_caution_label,
570            h1_prefix_style,
571            h1_text_style,
572        }
573    }
574
575    /// Nord.
576    pub fn nord() -> Self {
577        let (h1_prefix_style, h1_text_style) = h1(rgb(94, 129, 172), rgb(236, 239, 244));
578        let (
579            h2_marker,
580            h3_marker,
581            h4_marker,
582            h5_marker,
583            h6_marker,
584            alert_note_label,
585            alert_tip_label,
586            alert_important_label,
587            alert_warning_label,
588            alert_caution_label,
589        ) = default_markers_and_labels();
590        Self {
591            is_dark: true,
592            mermaid_palette: MermaidPalette::AyuDark,
593            html_block_style: Style::new().fg_color(Some(rgb(208, 135, 112))),
594            inline_html_style: Style::new().fg_color(Some(rgb(208, 135, 112))),
595            code_style: Style::new().fg_color(Some(rgb(143, 188, 187))),
596            link_style: Style::new().fg_color(Some(rgb(136, 192, 208))),
597            image_link_style: Style::new().fg_color(Some(rgb(180, 142, 173))),
598            rule_color: rgb(136, 192, 208),
599            quote_border_style: Style::new().fg_color(Some(rgb(76, 86, 106))),
600            h2_style: Style::new().fg_color(Some(rgb(94, 129, 172))).bold(),
601            h3_style: Style::new().fg_color(Some(rgb(129, 161, 193))).bold(),
602            h4_style: Style::new().fg_color(Some(rgb(136, 192, 208))).bold(),
603            h5_style: Style::new().fg_color(Some(rgb(163, 190, 140))).bold(),
604            h6_style: Style::new().fg_color(Some(rgb(235, 203, 139))).bold(),
605            footnote_style: Style::new().fg_color(Some(rgb(76, 86, 106))),
606            math_style: Style::new().fg_color(Some(rgb(143, 188, 187))),
607            mermaid_style: Style::new().fg_color(Some(rgb(143, 188, 187))),
608            alert_note_style: Style::new().fg_color(Some(rgb(143, 188, 187))).bold(),
609            alert_tip_style: Style::new().fg_color(Some(rgb(163, 190, 140))).bold(),
610            alert_important_style: Style::new().fg_color(Some(rgb(180, 142, 173))).bold(),
611            alert_warning_style: Style::new().fg_color(Some(rgb(235, 203, 139))).bold(),
612            alert_caution_style: Style::new().fg_color(Some(rgb(191, 97, 106))).bold(),
613            h2_marker,
614            h3_marker,
615            h4_marker,
616            h5_marker,
617            h6_marker,
618            alert_note_label,
619            alert_tip_label,
620            alert_important_label,
621            alert_warning_label,
622            alert_caution_label,
623            h1_prefix_style,
624            h1_text_style,
625        }
626    }
627
628    /// Solarized Dark.
629    pub fn solarized_dark() -> Self {
630        let (h1_prefix_style, h1_text_style) = h1(rgb(38, 139, 210), rgb(0, 43, 54));
631        let (
632            h2_marker,
633            h3_marker,
634            h4_marker,
635            h5_marker,
636            h6_marker,
637            alert_note_label,
638            alert_tip_label,
639            alert_important_label,
640            alert_warning_label,
641            alert_caution_label,
642        ) = default_markers_and_labels();
643        Self {
644            is_dark: true,
645            mermaid_palette: MermaidPalette::Generic,
646            html_block_style: Style::new().fg_color(Some(rgb(203, 75, 22))),
647            inline_html_style: Style::new().fg_color(Some(rgb(203, 75, 22))),
648            code_style: Style::new().fg_color(Some(rgb(42, 161, 152))),
649            link_style: Style::new().fg_color(Some(rgb(38, 139, 210))),
650            image_link_style: Style::new().fg_color(Some(rgb(211, 54, 130))),
651            rule_color: rgb(42, 161, 152),
652            quote_border_style: Style::new().fg_color(Some(rgb(88, 110, 117))),
653            h2_style: Style::new().fg_color(Some(rgb(38, 139, 210))).bold(),
654            h3_style: Style::new().fg_color(Some(rgb(42, 161, 152))).bold(),
655            h4_style: Style::new().fg_color(Some(rgb(133, 153, 0))).bold(),
656            h5_style: Style::new().fg_color(Some(rgb(211, 54, 130))).bold(),
657            h6_style: Style::new().fg_color(Some(rgb(203, 75, 22))).bold(),
658            footnote_style: Style::new().fg_color(Some(rgb(88, 110, 117))),
659            math_style: Style::new().fg_color(Some(rgb(42, 161, 152))),
660            mermaid_style: Style::new().fg_color(Some(rgb(42, 161, 152))),
661            alert_note_style: Style::new().fg_color(Some(rgb(108, 113, 196))).bold(),
662            alert_tip_style: Style::new().fg_color(Some(rgb(133, 153, 0))).bold(),
663            alert_important_style: Style::new().fg_color(Some(rgb(211, 54, 130))).bold(),
664            alert_warning_style: Style::new().fg_color(Some(rgb(181, 137, 0))).bold(),
665            alert_caution_style: Style::new().fg_color(Some(rgb(220, 50, 47))).bold(),
666            h2_marker,
667            h3_marker,
668            h4_marker,
669            h5_marker,
670            h6_marker,
671            alert_note_label,
672            alert_tip_label,
673            alert_important_label,
674            alert_warning_label,
675            alert_caution_label,
676            h1_prefix_style,
677            h1_text_style,
678        }
679    }
680
681    /// Solarized Light.
682    pub fn solarized_light() -> Self {
683        let (h1_prefix_style, h1_text_style) = h1(rgb(38, 139, 210), rgb(253, 246, 227));
684        Self {
685            is_dark: false,
686            mermaid_palette: MermaidPalette::Generic,
687            h1_prefix_style,
688            h1_text_style,
689            ..Self::solarized_dark()
690        }
691    }
692}
693
694impl Default for Theme {
695    fn default() -> Self {
696        Self::dark()
697    }
698}
699
700/// Combine styles.
701pub trait CombineStyle {
702    /// Put this style on top of the other style.
703    ///
704    /// Return a new style which falls back to the `other` style for all style attributes not
705    /// specified in this style.
706    fn on_top_of(self, other: &Self) -> Self;
707}
708
709impl CombineStyle for Style {
710    /// Put this style on top of the `other` style.
711    fn on_top_of(self, other: &Style) -> Style {
712        Style::new()
713            .fg_color(self.get_fg_color().or(other.get_fg_color()))
714            .bg_color(self.get_bg_color().or(other.get_bg_color()))
715            .effects(other.get_effects() | self.get_effects())
716            .underline_color(self.get_underline_color().or(other.get_underline_color()))
717    }
718}