use super::{
AuthorPositioning, FooterTemplate, Margin,
raw::{self, RawColor},
};
use crate::{
markdown::text_style::{Color, Colors, TextStyle, UndefinedPaletteColorError},
resource::Resources,
terminal::image::{Image, printer::RegisterImageError},
};
use std::collections::BTreeMap;
const DEFAULT_CODE_HIGHLIGHT_THEME: &str = "base16-eighties.dark";
const DEFAULT_BLOCK_QUOTE_PREFIX: &str = "▍ ";
const DEFAULT_PROGRESS_BAR_CHAR: char = '█';
const DEFAULT_FOOTER_HEIGHT: u16 = 3;
const DEFAULT_TYPST_HORIZONTAL_MARGIN: u16 = 5;
const DEFAULT_TYPST_VERTICAL_MARGIN: u16 = 7;
const DEFAULT_MERMAID_THEME: &str = "default";
const DEFAULT_MERMAID_BACKGROUND: &str = "transparent";
const DEFAULT_D2_THEME: u32 = 0;
const DEFAULT_PTY_CURSOR_SYMBOL: char = '█';
#[derive(Clone, Debug, Default)]
pub(crate) struct ThemeOptions {
pub(crate) font_size_supported: bool,
}
impl ThemeOptions {
fn adjust_font_size(&self, font_size: Option<u8>) -> u8 {
if !self.font_size_supported { 1 } else { font_size.unwrap_or(1).clamp(1, 7) }
}
}
#[derive(Clone, Debug)]
pub(crate) struct PresentationTheme {
pub(crate) slide_title: SlideTitleStyle,
pub(crate) code: CodeBlockStyle,
pub(crate) execution_output: ExecutionOutputBlockStyle,
pub(crate) pty_output: PtyOutputBlockStyle,
pub(crate) inline_code: ModifierStyle,
pub(crate) bold: ModifierStyle,
pub(crate) italics: ModifierStyle,
pub(crate) table: Option<Alignment>,
pub(crate) block_quote: BlockQuoteStyle,
pub(crate) alert: AlertStyle,
pub(crate) default_style: DefaultStyle,
pub(crate) headings: HeadingStyles,
pub(crate) intro_slide: IntroSlideStyle,
pub(crate) footer: FooterStyle,
pub(crate) typst: TypstStyle,
pub(crate) mermaid: MermaidStyle,
pub(crate) d2: D2Style,
pub(crate) modals: ModalStyle,
pub(crate) layout_grid: LayoutGridStyle,
pub(crate) palette: ColorPalette,
}
impl PresentationTheme {
pub(crate) fn new(
raw: &raw::PresentationTheme,
resources: &Resources,
options: &ThemeOptions,
) -> Result<Self, ProcessingThemeError> {
let raw::PresentationTheme {
slide_title,
code,
execution_output,
pty_output,
inline_code,
bold,
italics,
table,
block_quote,
alert,
default_style,
headings,
intro_slide,
footer,
typst,
mermaid,
d2,
modals,
layout_grid,
palette,
extends: _,
} = raw;
let palette = ColorPalette::try_from(palette)?;
let default_style = DefaultStyle::new(default_style, &palette)?;
Ok(Self {
slide_title: SlideTitleStyle::new(slide_title, &palette, options)?,
code: CodeBlockStyle::new(code),
execution_output: ExecutionOutputBlockStyle::new(execution_output, &palette)?,
pty_output: PtyOutputBlockStyle::new(pty_output, &palette)?,
inline_code: ModifierStyle::new(inline_code, &palette)?,
bold: ModifierStyle::new(bold, &palette)?,
italics: ModifierStyle::new(italics, &palette)?,
table: table.clone().map(Into::into),
block_quote: BlockQuoteStyle::new(block_quote, &palette)?,
alert: AlertStyle::new(alert, &palette)?,
default_style: default_style.clone(),
headings: HeadingStyles::new(headings, &palette, options)?,
intro_slide: IntroSlideStyle::new(intro_slide, &palette, options)?,
footer: FooterStyle::new(&footer.clone().unwrap_or_default(), &palette, resources)?,
typst: TypstStyle::new(typst, &palette)?,
mermaid: MermaidStyle::new(mermaid),
d2: D2Style::new(d2),
modals: ModalStyle::new(modals, &default_style, &palette)?,
layout_grid: LayoutGridStyle::new(layout_grid, &default_style, &palette)?,
palette,
})
}
pub(crate) fn alignment(&self, element: &ElementType) -> Alignment {
use ElementType::*;
let alignment = match element {
SlideTitle => self.slide_title.alignment,
Heading1 => self.headings.h1.alignment,
Heading2 => self.headings.h2.alignment,
Heading3 => self.headings.h3.alignment,
Heading4 => self.headings.h4.alignment,
Heading5 => self.headings.h5.alignment,
Heading6 => self.headings.h6.alignment,
Paragraph | List => Some(self.default_style.alignment),
PresentationTitle => self.intro_slide.title.alignment,
PresentationSubTitle => self.intro_slide.subtitle.alignment,
PresentationEvent => self.intro_slide.event.alignment,
PresentationLocation => self.intro_slide.location.alignment,
PresentationDate => self.intro_slide.date.alignment,
PresentationAuthor => self.intro_slide.author.alignment,
Table => self.table,
BlockQuote => self.block_quote.alignment,
};
alignment.unwrap_or(self.default_style.alignment)
}
}
#[derive(Debug, thiserror::Error)]
pub(crate) enum ProcessingThemeError {
#[error(transparent)]
Palette(#[from] UndefinedPaletteColorError),
#[error("palette cannot contain other palette colors")]
PaletteColorInPalette,
#[error("invalid footer image: {0}")]
FooterImage(RegisterImageError),
}
#[derive(Clone, Debug)]
pub(crate) struct SlideTitleStyle {
pub(crate) alignment: Option<Alignment>,
pub(crate) separator: bool,
pub(crate) padding_top: u8,
pub(crate) padding_bottom: u8,
pub(crate) style: TextStyle,
pub(crate) prefix: String,
}
impl SlideTitleStyle {
fn new(
raw: &raw::SlideTitleStyle,
palette: &ColorPalette,
options: &ThemeOptions,
) -> Result<Self, ProcessingThemeError> {
let raw::SlideTitleStyle {
alignment,
separator,
padding_top,
padding_bottom,
colors,
bold,
italics,
underlined,
font_size,
prefix,
} = raw;
let colors = colors.resolve(palette)?;
let mut style = TextStyle::colored(colors).size(options.adjust_font_size(*font_size));
if bold.unwrap_or_default() {
style = style.bold();
}
if italics.unwrap_or_default() {
style = style.italics();
}
if underlined.unwrap_or_default() {
style = style.underlined();
}
Ok(Self {
alignment: alignment.clone().map(Into::into),
separator: *separator,
padding_top: padding_top.unwrap_or_default(),
padding_bottom: padding_bottom.unwrap_or_default(),
style,
prefix: prefix.clone().unwrap_or_default(),
})
}
}
#[derive(Clone, Debug)]
pub(crate) struct HeadingStyles {
pub(crate) h1: HeadingStyle,
pub(crate) h2: HeadingStyle,
pub(crate) h3: HeadingStyle,
pub(crate) h4: HeadingStyle,
pub(crate) h5: HeadingStyle,
pub(crate) h6: HeadingStyle,
}
impl HeadingStyles {
fn new(
raw: &raw::HeadingStyles,
palette: &ColorPalette,
options: &ThemeOptions,
) -> Result<Self, ProcessingThemeError> {
let raw::HeadingStyles { h1, h2, h3, h4, h5, h6 } = raw;
Ok(Self {
h1: HeadingStyle::new(h1, palette, options)?,
h2: HeadingStyle::new(h2, palette, options)?,
h3: HeadingStyle::new(h3, palette, options)?,
h4: HeadingStyle::new(h4, palette, options)?,
h5: HeadingStyle::new(h5, palette, options)?,
h6: HeadingStyle::new(h6, palette, options)?,
})
}
}
#[derive(Clone, Debug)]
pub(crate) struct HeadingStyle {
pub(crate) alignment: Option<Alignment>,
pub(crate) prefix: Option<String>,
pub(crate) style: TextStyle,
}
impl HeadingStyle {
fn new(
raw: &raw::HeadingStyle,
palette: &ColorPalette,
options: &ThemeOptions,
) -> Result<Self, ProcessingThemeError> {
let raw::HeadingStyle { alignment, prefix, colors, font_size, bold, underlined, italics } = raw;
let alignment = alignment.clone().map(Into::into);
let mut style = TextStyle::colored(colors.resolve(palette)?).size(options.adjust_font_size(*font_size));
if bold.unwrap_or_default() {
style = style.bold();
}
if underlined.unwrap_or_default() {
style = style.underlined();
}
if italics.unwrap_or_default() {
style = style.italics();
}
Ok(Self { alignment, prefix: prefix.clone(), style })
}
}
#[derive(Clone, Debug)]
pub(crate) struct BlockQuoteStyle {
pub(crate) alignment: Option<Alignment>,
pub(crate) prefix: String,
pub(crate) base_style: TextStyle,
pub(crate) prefix_style: TextStyle,
}
impl BlockQuoteStyle {
fn new(raw: &raw::BlockQuoteStyle, palette: &ColorPalette) -> Result<Self, ProcessingThemeError> {
let raw::BlockQuoteStyle { alignment, prefix, colors } = raw;
let alignment = alignment.clone().map(Into::into);
let prefix = prefix.as_deref().unwrap_or(DEFAULT_BLOCK_QUOTE_PREFIX).to_string();
let base_style = TextStyle::colored(colors.base.resolve(palette)?);
let mut prefix_style = TextStyle::colored(colors.base.resolve(palette)?);
if let Some(color) = &colors.prefix {
prefix_style.colors.foreground = color.resolve(palette)?;
}
Ok(Self { alignment, prefix, base_style, prefix_style })
}
}
#[derive(Clone, Debug)]
pub(crate) struct AlertStyle {
pub(crate) alignment: Alignment,
pub(crate) base_style: TextStyle,
pub(crate) prefix: String,
pub(crate) styles: AlertTypeStyles,
}
impl AlertStyle {
fn new(raw: &raw::AlertStyle, palette: &ColorPalette) -> Result<Self, ProcessingThemeError> {
let raw::AlertStyle { alignment, base_colors, prefix, styles } = raw;
let alignment = alignment.clone().unwrap_or_default().into();
let base_style = TextStyle::colored(base_colors.resolve(palette)?);
let prefix = prefix.as_deref().unwrap_or(DEFAULT_BLOCK_QUOTE_PREFIX).to_string();
let styles = AlertTypeStyles::new(styles, base_style, palette)?;
Ok(Self { alignment, base_style, prefix, styles })
}
}
#[derive(Clone, Debug)]
pub(crate) struct AlertTypeStyles {
pub(crate) note: AlertTypeStyle,
pub(crate) tip: AlertTypeStyle,
pub(crate) important: AlertTypeStyle,
pub(crate) warning: AlertTypeStyle,
pub(crate) caution: AlertTypeStyle,
}
impl AlertTypeStyles {
fn new(
raw: &raw::AlertTypeStyles,
base_style: TextStyle,
palette: &ColorPalette,
) -> Result<Self, ProcessingThemeError> {
let raw::AlertTypeStyles { note, tip, important, warning, caution } = raw;
Ok(Self {
note: AlertTypeStyle::new(
note,
&AlertTypeDefaults { title: "Note", icon: "", color: Color::Blue },
base_style,
palette,
)?,
tip: AlertTypeStyle::new(
tip,
&AlertTypeDefaults { title: "Tip", icon: "", color: Color::Green },
base_style,
palette,
)?,
important: AlertTypeStyle::new(
important,
&AlertTypeDefaults { title: "Important", icon: "", color: Color::Cyan },
base_style,
palette,
)?,
warning: AlertTypeStyle::new(
warning,
&AlertTypeDefaults { title: "Warning", icon: "", color: Color::Yellow },
base_style,
palette,
)?,
caution: AlertTypeStyle::new(
caution,
&AlertTypeDefaults { title: "Caution", icon: "", color: Color::Red },
base_style,
palette,
)?,
})
}
}
#[derive(Clone, Debug)]
pub(crate) struct AlertTypeStyle {
pub(crate) style: TextStyle,
pub(crate) title: String,
pub(crate) icon: String,
}
impl AlertTypeStyle {
fn new(
raw: &raw::AlertTypeStyle,
defaults: &AlertTypeDefaults,
base_style: TextStyle,
palette: &ColorPalette,
) -> Result<Self, ProcessingThemeError> {
let raw::AlertTypeStyle { color, title, icon, .. } = raw;
let color = color.as_ref().map(|c| c.resolve(palette)).transpose()?.flatten().unwrap_or(defaults.color);
let style = base_style.fg_color(color);
let title = title.as_deref().unwrap_or(defaults.title).to_string();
let icon = icon.as_deref().unwrap_or(defaults.icon).to_string();
Ok(Self { style, title, icon })
}
}
struct AlertTypeDefaults {
title: &'static str,
icon: &'static str,
color: Color,
}
#[derive(Clone, Debug)]
pub(crate) struct IntroSlideStyle {
pub(crate) title: IntroSlideTitleStyle,
pub(crate) subtitle: IntroSlideLabelStyle,
pub(crate) event: IntroSlideLabelStyle,
pub(crate) location: IntroSlideLabelStyle,
pub(crate) date: IntroSlideLabelStyle,
pub(crate) author: AuthorStyle,
pub(crate) footer: bool,
}
impl IntroSlideStyle {
fn new(
raw: &raw::IntroSlideStyle,
palette: &ColorPalette,
options: &ThemeOptions,
) -> Result<Self, ProcessingThemeError> {
let raw::IntroSlideStyle { title, subtitle, event, location, date, author, footer } = raw;
Ok(Self {
title: IntroSlideTitleStyle::new(title, palette, options)?,
subtitle: IntroSlideLabelStyle::new(subtitle, palette)?,
event: IntroSlideLabelStyle::new(event, palette)?,
location: IntroSlideLabelStyle::new(location, palette)?,
date: IntroSlideLabelStyle::new(date, palette)?,
author: AuthorStyle::new(author, palette)?,
footer: footer.unwrap_or(false),
})
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct IntroSlideLabelStyle {
pub(crate) alignment: Option<Alignment>,
pub(crate) style: TextStyle,
}
impl IntroSlideLabelStyle {
fn new(raw: &raw::BasicStyle, palette: &ColorPalette) -> Result<Self, ProcessingThemeError> {
let raw::BasicStyle { alignment, colors } = raw;
let style = TextStyle::colored(colors.resolve(palette)?);
Ok(Self { alignment: alignment.clone().map(Into::into), style })
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct IntroSlideTitleStyle {
pub(crate) alignment: Option<Alignment>,
pub(crate) style: TextStyle,
}
impl IntroSlideTitleStyle {
fn new(
raw: &raw::IntroSlideTitleStyle,
palette: &ColorPalette,
options: &ThemeOptions,
) -> Result<Self, ProcessingThemeError> {
let raw::IntroSlideTitleStyle { alignment, colors, font_size } = raw;
let style = TextStyle::colored(colors.resolve(palette)?).size(options.adjust_font_size(*font_size));
Ok(Self { alignment: alignment.clone().map(Into::into), style })
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct AuthorStyle {
pub(crate) alignment: Option<Alignment>,
pub(crate) style: TextStyle,
pub(crate) positioning: AuthorPositioning,
}
impl AuthorStyle {
fn new(raw: &raw::AuthorStyle, palette: &ColorPalette) -> Result<Self, ProcessingThemeError> {
let raw::AuthorStyle { alignment, colors, positioning } = raw;
let style = TextStyle::colored(colors.resolve(palette)?);
Ok(Self { alignment: alignment.clone().map(Into::into), style, positioning: positioning.clone() })
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct DefaultStyle {
pub(crate) margin: Margin,
pub(crate) style: TextStyle,
pub(crate) alignment: Alignment,
}
impl DefaultStyle {
fn new(raw: &raw::DefaultStyle, palette: &ColorPalette) -> Result<Self, ProcessingThemeError> {
let raw::DefaultStyle { margin, colors, alignment } = raw;
let margin = margin.unwrap_or_default();
let style = TextStyle::colored(colors.resolve(palette)?);
let alignment = alignment.clone().unwrap_or_default().into();
Ok(Self { margin, style, alignment })
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub(crate) enum Alignment {
Left { margin: Margin },
Right { margin: Margin },
Center { minimum_margin: Margin, minimum_size: u16 },
}
impl Alignment {
pub(crate) fn adjust_size(&self, size: u16) -> u16 {
match self {
Self::Left { .. } | Self::Right { .. } => size,
Self::Center { minimum_size, .. } => size.max(*minimum_size),
}
}
}
impl From<raw::Alignment> for Alignment {
fn from(alignment: raw::Alignment) -> Self {
match alignment {
raw::Alignment::Left { margin } => Self::Left { margin },
raw::Alignment::Right { margin } => Self::Right { margin },
raw::Alignment::Center { minimum_margin, minimum_size } => Self::Center { minimum_margin, minimum_size },
}
}
}
impl Default for Alignment {
fn default() -> Self {
Self::Left { margin: Margin::Fixed(0) }
}
}
#[derive(Clone, Debug, Default)]
pub(crate) enum FooterStyle {
Template {
left: Option<FooterContent>,
center: Option<FooterContent>,
right: Option<FooterContent>,
style: TextStyle,
height: u16,
},
ProgressBar {
character: char,
style: TextStyle,
},
#[default]
Empty,
}
impl FooterStyle {
fn new(
raw: &raw::FooterStyle,
palette: &ColorPalette,
resources: &Resources,
) -> Result<Self, ProcessingThemeError> {
match raw {
raw::FooterStyle::Template { left, center, right, colors, height } => {
let left = left.as_ref().map(|t| FooterContent::new(t, resources)).transpose()?;
let center = center.as_ref().map(|t| FooterContent::new(t, resources)).transpose()?;
let right = right.as_ref().map(|t| FooterContent::new(t, resources)).transpose()?;
let style = TextStyle::colored(colors.resolve(palette)?);
let height = height.unwrap_or(DEFAULT_FOOTER_HEIGHT);
Ok(Self::Template { left, center, right, style, height })
}
raw::FooterStyle::ProgressBar { character, colors } => {
let character = character.unwrap_or(DEFAULT_PROGRESS_BAR_CHAR);
let style = TextStyle::colored(colors.resolve(palette)?);
Ok(Self::ProgressBar { character, style })
}
raw::FooterStyle::Empty => Ok(Self::Empty),
}
}
pub(crate) fn height(&self) -> u16 {
match self {
Self::Template { height, .. } => *height,
_ => DEFAULT_FOOTER_HEIGHT,
}
}
}
#[derive(Clone, Debug)]
pub(crate) enum FooterContent {
Template(FooterTemplate),
Image(Image),
}
impl FooterContent {
fn new(raw: &raw::FooterContent, resources: &Resources) -> Result<Self, ProcessingThemeError> {
match raw {
raw::FooterContent::Template(template) => Ok(Self::Template(template.clone())),
raw::FooterContent::Image { path } => {
let image = resources.theme_image(path).map_err(ProcessingThemeError::FooterImage)?;
Ok(Self::Image(image))
}
}
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct CodeBlockStyle {
pub(crate) alignment: Alignment,
pub(crate) padding: PaddingRect,
pub(crate) theme_name: String,
pub(crate) background: bool,
pub(crate) line_numbers: bool,
}
impl CodeBlockStyle {
fn new(raw: &raw::CodeBlockStyle) -> Self {
let raw::CodeBlockStyle { alignment, padding, theme_name, background, line_numbers } = raw;
let padding = PaddingRect {
horizontal: padding.horizontal.unwrap_or_default(),
vertical: padding.vertical.unwrap_or_default(),
};
Self {
alignment: alignment.clone().unwrap_or_default().into(),
padding,
theme_name: theme_name.as_deref().unwrap_or(DEFAULT_CODE_HIGHLIGHT_THEME).to_string(),
background: background.unwrap_or(true),
line_numbers: line_numbers.unwrap_or_default(),
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct PaddingRect {
pub(crate) horizontal: u8,
pub(crate) vertical: u8,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct ExecutionOutputBlockStyle {
pub(crate) style: TextStyle,
pub(crate) status: ExecutionStatusBlockStyle,
pub(crate) padding: PaddingRect,
}
impl ExecutionOutputBlockStyle {
fn new(raw: &raw::ExecutionOutputBlockStyle, palette: &ColorPalette) -> Result<Self, ProcessingThemeError> {
let raw::ExecutionOutputBlockStyle { colors, status, padding } = raw;
let colors = colors.resolve(palette)?;
let style = TextStyle::colored(colors);
let padding = PaddingRect {
horizontal: padding.horizontal.unwrap_or_default(),
vertical: padding.vertical.unwrap_or_default(),
};
Ok(Self { style, status: ExecutionStatusBlockStyle::new(status, palette)?, padding })
}
}
#[derive(Copy, Clone, Debug, Default)]
pub(crate) struct ExecutionStatusBlockStyle {
pub(crate) running_style: TextStyle,
pub(crate) success_style: TextStyle,
pub(crate) failure_style: TextStyle,
pub(crate) not_started_style: TextStyle,
}
impl ExecutionStatusBlockStyle {
fn new(raw: &raw::ExecutionStatusBlockStyle, palette: &ColorPalette) -> Result<Self, ProcessingThemeError> {
let raw::ExecutionStatusBlockStyle { running, success, failure, not_started } = raw;
let running_style = TextStyle::colored(running.resolve(palette)?);
let success_style = TextStyle::colored(success.resolve(palette)?);
let failure_style = TextStyle::colored(failure.resolve(palette)?);
let not_started_style = TextStyle::colored(not_started.resolve(palette)?);
Ok(Self { running_style, success_style, failure_style, not_started_style })
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct PtyOutputBlockStyle {
pub(crate) style: TextStyle,
pub(crate) standby: PtyStandbyStyle,
pub(crate) cursor: PtyCursorStyle,
}
impl PtyOutputBlockStyle {
fn new(raw: &raw::PtyOutputBlockStyle, palette: &ColorPalette) -> Result<Self, ProcessingThemeError> {
let raw::PtyOutputBlockStyle { colors, standby, cursor } = raw;
let colors = colors.resolve(palette)?;
let style = TextStyle::colored(colors);
let standby = match standby {
Some(raw::PtyStandbyStyle::LargePlay) => PtyStandbyStyle::LargePlay,
None => Default::default(),
};
let cursor = PtyCursorStyle::new(cursor, palette)?;
Ok(Self { style, standby, cursor })
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct PtyCursorStyle {
pub(crate) symbol: String,
pub(crate) highlight_style: TextStyle,
}
impl PtyCursorStyle {
fn new(raw: &raw::PtyCursorStyle, palette: &ColorPalette) -> Result<Self, ProcessingThemeError> {
let raw::PtyCursorStyle { symbol, highlight_colors } = raw;
let symbol = symbol.unwrap_or(DEFAULT_PTY_CURSOR_SYMBOL).to_string();
let highlight_style = TextStyle::colored(highlight_colors.resolve(palette)?);
Ok(Self { symbol, highlight_style })
}
}
#[derive(Clone, Debug, Default)]
pub(crate) enum PtyStandbyStyle {
#[default]
LargePlay,
}
impl PtyStandbyStyle {
pub(crate) fn as_lines(&self) -> &[&str] {
match self {
Self::LargePlay => &[
"⠀⠀⠀⠀⠀⠀⠀⢀⣠⣤⣤⣶⣶⣶⣶⣤⣤⣄⡀⠀⠀⠀⠀⠀⠀⠀",
"⠀⠀⠀⠀⢀⣤⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣤⡀⠀⠀⠀⠀",
"⠀⠀⠀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⠀⠀⠀",
"⠀⢀⣾⣿⣿⣿⣿⣿⣿⣿⡿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡀⠀",
"⠀⣾⣿⣿⣿⣿⣿⣿⣿⣿⡇⠈⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⠀",
"⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠈⠻⢿⣿⣿⣿⣿⣿⣿⣿⣿⡄",
"⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⣉⣿⣿⣿⣿⣿⣿⣿⡇",
"⠘⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⢀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⠃",
"⠀⢿⣿⣿⣿⣿⣿⣿⣿⣿⡇⢀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠀",
"⠀⠈⢿⣿⣿⣿⣿⣿⣿⣿⣷⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠁⠀",
"⠀⠀⠀⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠀⠀⠀",
"⠀⠀⠀⠀⠈⠛⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠛⠁⠀⠀⠀⠀",
"⠀⠀⠀⠀⠀⠀⠀⠈⠙⠛⠛⠿⠿⠿⠿⠛⠛⠋⠁⠀⠀⠀⠀⠀⠀⠀",
],
}
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct ModifierStyle {
pub(crate) style: TextStyle,
}
impl ModifierStyle {
fn new(raw: &raw::ModifierStyle, palette: &ColorPalette) -> Result<Self, ProcessingThemeError> {
let raw::ModifierStyle { colors } = raw;
let style = TextStyle::colored(colors.resolve(palette)?);
Ok(Self { style })
}
}
#[derive(Clone, Debug)]
pub(crate) enum ElementType {
SlideTitle,
Heading1,
Heading2,
Heading3,
Heading4,
Heading5,
Heading6,
Paragraph,
PresentationTitle,
PresentationSubTitle,
PresentationEvent,
PresentationLocation,
PresentationDate,
PresentationAuthor,
Table,
BlockQuote,
List,
}
#[derive(Clone, Debug)]
pub(crate) struct TypstStyle {
pub(crate) horizontal_margin: u16,
pub(crate) vertical_margin: u16,
pub(crate) style: TextStyle,
}
impl TypstStyle {
fn new(raw: &raw::TypstStyle, palette: &ColorPalette) -> Result<Self, ProcessingThemeError> {
let raw::TypstStyle { horizontal_margin, vertical_margin, colors } = raw;
let horizontal_margin = horizontal_margin.unwrap_or(DEFAULT_TYPST_HORIZONTAL_MARGIN);
let vertical_margin = vertical_margin.unwrap_or(DEFAULT_TYPST_VERTICAL_MARGIN);
let style = TextStyle::colored(colors.resolve(palette)?);
Ok(Self { horizontal_margin, vertical_margin, style })
}
}
#[derive(Clone, Debug)]
pub(crate) struct MermaidStyle {
pub(crate) theme: String,
pub(crate) background: String,
}
impl MermaidStyle {
fn new(raw: &raw::MermaidStyle) -> Self {
let raw::MermaidStyle { theme, background } = raw;
let theme = theme.as_deref().unwrap_or(DEFAULT_MERMAID_THEME).to_string();
let background = background.as_deref().unwrap_or(DEFAULT_MERMAID_BACKGROUND).to_string();
Self { theme, background }
}
}
#[derive(Clone, Debug)]
pub(crate) struct D2Style {
pub(crate) theme: String,
}
impl D2Style {
fn new(raw: &raw::D2Style) -> Self {
let raw::D2Style { theme } = raw;
let theme = theme.unwrap_or(DEFAULT_D2_THEME).to_string();
Self { theme }
}
}
#[derive(Clone, Debug)]
pub(crate) struct ModalStyle {
pub(crate) style: TextStyle,
pub(crate) selection_style: TextStyle,
}
impl ModalStyle {
fn new(
raw: &raw::ModalStyle,
default_style: &DefaultStyle,
palette: &ColorPalette,
) -> Result<Self, ProcessingThemeError> {
let raw::ModalStyle { colors, selection_colors } = raw;
let mut style = default_style.style;
style.merge(&TextStyle::colored(colors.resolve(palette)?));
let mut selection_style = style.bold();
selection_style.merge(&TextStyle::colored(selection_colors.resolve(palette)?));
Ok(Self { style, selection_style })
}
}
#[derive(Clone, Debug)]
pub(crate) struct LayoutGridStyle {
pub(crate) style: TextStyle,
}
impl LayoutGridStyle {
fn new(
raw: &raw::LayoutGridStyle,
default_style: &DefaultStyle,
palette: &ColorPalette,
) -> Result<Self, ProcessingThemeError> {
let raw::LayoutGridStyle { color } = raw;
let mut style = default_style.style;
if let Some(color) = color {
style.colors.foreground = color.resolve(palette)?;
}
Ok(Self { style })
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct ColorPalette {
pub(crate) colors: BTreeMap<String, Color>,
pub(crate) classes: BTreeMap<String, Colors>,
}
impl TryFrom<&raw::ColorPalette> for ColorPalette {
type Error = ProcessingThemeError;
fn try_from(palette: &raw::ColorPalette) -> Result<Self, Self::Error> {
let mut colors = BTreeMap::new();
let mut classes = BTreeMap::new();
for (name, color) in &palette.colors {
let raw::RawColor::Color(color) = color else {
return Err(ProcessingThemeError::PaletteColorInPalette);
};
colors.insert(name.clone(), *color);
}
let resolve_local = |color: &RawColor| match color {
raw::RawColor::Color(c) => Ok(*c),
raw::RawColor::Palette(name) => colors
.get(name)
.copied()
.ok_or_else(|| ProcessingThemeError::Palette(UndefinedPaletteColorError(name.clone()))),
_ => Err(ProcessingThemeError::PaletteColorInPalette),
};
for (name, colors) in &palette.classes {
let foreground = colors.foreground.as_ref().map(resolve_local).transpose()?;
let background = colors.background.as_ref().map(resolve_local).transpose()?;
classes.insert(name.clone(), Colors { foreground, background });
}
Ok(Self { colors, classes })
}
}