1
0
mirror of https://github.com/mfontanini/presenterm.git synced 2026-09-01 23:33:21 -06:00

Use hex encoding for colors

This commit is contained in:
Matias Fontanini 2023-10-15 07:57:49 -07:00
parent b08468fe20
commit d45e7b894c
14 changed files with 313 additions and 81 deletions

151
Cargo.lock generated
View File

@ -17,6 +17,21 @@ dependencies = [
"memchr",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "ansi_colours"
version = "1.2.2"
@ -149,6 +164,19 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
dependencies = [
"android-tzdata",
"iana-time-zone",
"num-traits",
"serde",
"windows-targets 0.48.5",
]
[[package]]
name = "clap"
version = "4.4.0"
@ -230,6 +258,12 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "crc32fast"
version = "1.3.2"
@ -320,8 +354,18 @@ version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850"
dependencies = [
"darling_core",
"darling_macro",
"darling_core 0.14.4",
"darling_macro 0.14.4",
]
[[package]]
name = "darling"
version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e"
dependencies = [
"darling_core 0.20.3",
"darling_macro 0.20.3",
]
[[package]]
@ -338,22 +382,50 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "darling_core"
version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.29",
]
[[package]]
name = "darling_macro"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
dependencies = [
"darling_core",
"darling_core 0.14.4",
"quote",
"syn 1.0.109",
]
[[package]]
name = "darling_macro"
version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [
"darling_core 0.20.3",
"quote",
"syn 2.0.29",
]
[[package]]
name = "deranged"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
dependencies = [
"serde",
]
[[package]]
name = "derive_builder"
@ -370,7 +442,7 @@ version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f"
dependencies = [
"darling",
"darling 0.14.4",
"proc-macro2",
"quote",
"syn 1.0.109",
@ -571,6 +643,35 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "iana-time-zone"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "ident_case"
version = "1.0.1"
@ -604,6 +705,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
"serde",
]
[[package]]
@ -614,6 +716,7 @@ checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
dependencies = [
"equivalent",
"hashbrown 0.14.0",
"serde",
]
[[package]]
@ -908,11 +1011,13 @@ dependencies = [
"clap",
"comrak",
"crossterm",
"hex",
"image",
"merge-struct",
"once_cell",
"rstest",
"serde",
"serde_with",
"serde_yaml",
"strum",
"syntect",
@ -1151,6 +1256,35 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_with"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237"
dependencies = [
"base64",
"chrono",
"hex",
"indexmap 1.9.3",
"indexmap 2.0.0",
"serde",
"serde_json",
"serde_with_macros",
"time",
]
[[package]]
name = "serde_with_macros"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c"
dependencies = [
"darling 0.20.3",
"proc-macro2",
"quote",
"syn 2.0.29",
]
[[package]]
name = "serde_yaml"
version = "0.9.25"
@ -1554,6 +1688,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.45.0"

View File

@ -11,11 +11,13 @@ edition = "2021"
clap = { version = "4.4", features = ["derive"] }
comrak = { version = "0.19", default-features = false }
crossterm = { version = "0.27", features = ["serde"] }
merge-struct = "0.1.0"
hex = "0.4"
image = "0.24"
merge-struct = "0.1.0"
once_cell = "1.18"
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.9"
serde_with = "3.3"
syntect = "5.1"
strum = { version = "0.25", features = ["derive"] }
thiserror = "1"

29
scripts/migrate-theme.py Executable file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env python3
import re
import sys
def migrate_color(line: str) -> str:
def as_hex(number_str: str) -> int:
number = int(number_str)
return f"{number:02x}"
matches = re.match(r'.*"rgb_\(([\d]+),([\d]+),([\d]+)\)"', line)
if not matches:
return line
hex_color = "".join(map(as_hex, matches.group(1, 2, 3)))
original_color = f"rgb_({matches[1]},{matches[2]},{matches[3]})"
return line.replace(original_color, hex_color)
def migrate(line: str) -> str:
migrators = [migrate_color]
for migrator in migrators:
line = migrator(line)
return line
for line in sys.stdin:
new_line = migrate(line)
sys.stdout.write(new_line)

View File

@ -14,10 +14,8 @@ use crate::{
properties::WindowSize,
},
resource::{LoadImageError, Resources},
style::TextStyle,
theme::{
Alignment, AuthorPositioning, Colors, ElementType, FooterStyle, LoadThemeError, Margin, PresentationTheme,
},
style::{Colors, TextStyle},
theme::{Alignment, AuthorPositioning, ElementType, FooterStyle, LoadThemeError, Margin, PresentationTheme},
};
use serde::Deserialize;
use std::{borrow::Cow, cell::RefCell, iter, mem, path::PathBuf, rc::Rc, str::FromStr};
@ -100,11 +98,7 @@ impl<'a> PresentationBuilder<'a> {
}
self.needs_enter_column = false;
let last_valid = matches!(last, RenderOperation::EnterColumn { .. } | RenderOperation::ExitLayout);
if last_valid {
Ok(())
} else {
Err(BuildError::NotInsideColumn)
}
if last_valid { Ok(()) } else { Err(BuildError::NotInsideColumn) }
}
fn push_slide_prelude(&mut self) {

View File

@ -91,9 +91,9 @@ mod test {
use crate::{
presentation::{AsRenderOperations, PreformattedLine},
render::properties::WindowSize,
theme::{Alignment, Colors, Margin},
style::{Color, Colors},
theme::{Alignment, Margin},
};
use crossterm::style::Color;
use rstest::rstest;
use std::rc::Rc;
@ -151,8 +151,8 @@ mod test {
#[test]
fn different_colors() {
let lhs = RenderOperation::SetColors(Colors { background: None, foreground: Some(Color::Red) });
let rhs = RenderOperation::SetColors(Colors { background: None, foreground: Some(Color::Black) });
let lhs = RenderOperation::SetColors(Colors { background: None, foreground: Some(Color::new(1, 2, 3)) });
let rhs = RenderOperation::SetColors(Colors { background: None, foreground: Some(Color::new(3, 2, 1)) });
assert!(!lhs.is_content_different(&rhs));
}
@ -209,13 +209,13 @@ mod test {
let lhs = Presentation::new(vec![Slide {
render_operations: vec![RenderOperation::SetColors(Colors {
background: None,
foreground: Some(Color::Red),
foreground: Some(Color::new(255, 0, 0)),
})],
}]);
let rhs = Presentation::new(vec![Slide {
render_operations: vec![RenderOperation::SetColors(Colors {
background: None,
foreground: Some(Color::Black),
foreground: Some(Color::new(0, 0, 0)),
})],
}]);

View File

@ -1,7 +1,8 @@
use crate::{
markdown::text::WeightedLine,
render::{media::Image, properties::WindowSize},
theme::{Alignment, Colors, PresentationTheme},
style::Colors,
theme::{Alignment, PresentationTheme},
};
use serde::Deserialize;
use std::rc::Rc;

View File

@ -6,10 +6,9 @@ use crate::{
},
presentation::{Presentation, RenderOperation},
render::properties::WindowSize,
style::TextStyle,
theme::{Alignment, Colors, Margin},
style::{Color, Colors, TextStyle},
theme::{Alignment, Margin},
};
use crossterm::style::Color;
use std::io;
/// The result of a render operation.
@ -52,7 +51,10 @@ where
let alignment = Alignment::Center { minimum_size: 0, minimum_margin: Margin::Percent(8) };
let operations = [
RenderOperation::ClearScreen,
RenderOperation::SetColors(Colors { foreground: Some(Color::Red), background: Some(Color::Black) }),
RenderOperation::SetColors(Colors {
foreground: Some(Color::new(255, 0, 0)),
background: Some(Color::new(0, 0, 0)),
}),
RenderOperation::JumpToVerticalCenter,
RenderOperation::RenderTextLine { line: WeightedLine::from(heading), alignment: alignment.clone() },
RenderOperation::RenderLineBreak,

View File

@ -10,7 +10,8 @@ use crate::{
markdown::text::WeightedLine,
presentation::{AsRenderOperations, PreformattedLine, RenderOperation},
render::{layout::Positioning, properties::WindowSize},
theme::{Alignment, Colors},
style::Colors,
theme::Alignment,
};
use std::{io, mem};

View File

@ -1,5 +1,5 @@
use super::properties::CursorPosition;
use crate::theme::Colors;
use crate::style::Colors;
use crossterm::{
cursor,
style::{self, StyledContent},
@ -72,8 +72,7 @@ impl<W: io::Write> Terminal<W> {
}
pub(crate) fn set_colors(&mut self, colors: Colors) -> io::Result<()> {
let colors = style::Colors { background: colors.background, foreground: colors.foreground };
self.writer.queue(style::SetColors(colors))?;
self.writer.queue(style::SetColors(colors.into()))?;
Ok(())
}

View File

@ -1,3 +1,4 @@
use super::terminal::Terminal;
use crate::{
markdown::text::WeightedLine,
render::{
@ -5,13 +6,10 @@ use crate::{
layout::{Layout, Positioning},
properties::WindowSize,
},
style::TextStyle,
theme::Colors,
style::{Colors, TextStyle},
};
use std::io;
use super::terminal::Terminal;
const MINIMUM_LINE_LENGTH: u16 = 10;
/// Draws text on the screen.

View File

@ -1,5 +1,11 @@
use crate::theme::Colors;
use crossterm::style::Stylize;
use hex::{FromHex, FromHexError};
use serde::{Deserialize, Serialize};
use serde_with::{DeserializeFromStr, SerializeDisplay};
use std::{
fmt::{self, Display},
str::FromStr,
};
/// The style of a piece of text.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
@ -94,10 +100,10 @@ impl TextStyle {
styled = styled.italic().underlined();
}
if let Some(color) = self.colors.background {
styled = styled.on(color);
styled = styled.on(color.into());
}
if let Some(color) = self.colors.foreground {
styled = styled.with(color);
styled = styled.with(color.into());
}
styled
}
@ -111,3 +117,70 @@ enum TextFormatFlags {
Strikethrough = 8,
Link = 16,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, SerializeDisplay, DeserializeFromStr)]
pub struct Color(crossterm::style::Color);
impl Color {
pub fn new(r: u8, g: u8, b: u8) -> Self {
Self(crossterm::style::Color::Rgb { r, g, b })
}
}
impl FromStr for Color {
type Err = ParseColorError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let values = <[u8; 3]>::from_hex(input)?;
Ok(Self(crossterm::style::Color::Rgb { r: values[0], g: values[1], b: values[2] }))
}
}
impl Display for Color {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let rgb = match self.0 {
crossterm::style::Color::Rgb { r, g, b } => [r, g, b],
_ => panic!("not rgb"),
};
write!(f, "{}", hex::encode(rgb))
}
}
impl From<Color> for crossterm::style::Color {
fn from(value: Color) -> Self {
value.0
}
}
/// Text colors.
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
pub struct Colors {
/// The background color.
pub background: Option<Color>,
/// The foreground color.
pub foreground: Option<Color>,
}
impl From<Colors> for crossterm::style::Colors {
fn from(value: Colors) -> Self {
let foreground = value.foreground.map(Color::into);
let background = value.background.map(Color::into);
Self { foreground, background }
}
}
#[derive(thiserror::Error, Debug)]
#[error("invalid color: {0}")]
pub struct ParseColorError(#[from] FromHexError);
#[cfg(test)]
mod test {
use super::*;
#[test]
fn color_serde() {
let color: Color = "beef42".parse().unwrap();
assert_eq!(color.to_string(), "beef42");
}
}

View File

@ -1,4 +1,4 @@
use crossterm::style::Color;
use crate::style::Colors;
use serde::{Deserialize, Serialize};
use std::{fs, io, path::Path};
@ -400,16 +400,6 @@ pub enum ElementType {
BlockQuote,
}
/// Text colors.
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
pub struct Colors {
/// The background color.
pub background: Option<Color>,
/// The foreground color.
pub foreground: Option<Color>,
}
/// Where to position the author's name in the intro slide.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]

View File

@ -3,15 +3,15 @@ default:
margin:
percent: 8
colors:
foreground: "rgb_(230,230,230)"
background: "rgb_(4,3,18)"
foreground: "e6e6e6"
background: "040312"
slide_title:
alignment: center
padding_bottom: 1
padding_top: 1
colors:
foreground: "rgb_(238,147,34)"
foreground: "ee9322"
code:
alignment: center
@ -23,8 +23,8 @@ code:
inline_code:
colors:
foreground: "rgb_(4,222,32)"
background: "rgb_(69,80,69)"
foreground: "04de20"
background: "455045"
table:
alignment: center
@ -33,51 +33,51 @@ intro_slide:
title:
alignment: center
colors:
foreground: "rgb_(180,204,255)"
foreground: "b4ccff"
subtitle:
alignment: center
colors:
foreground: "rgb_(165,215,232)"
foreground: "a5d7e8"
author:
alignment: center
colors:
foreground: "rgb_(182,234,218)"
foreground: "b6eada"
positioning: page_bottom
headings:
h1:
prefix: "██"
colors:
foreground: "rgb_(48,133,195)"
foreground: "3085c3"
h2:
prefix: "▓▓▓"
colors:
foreground: "rgb_(168,223,142)"
foreground: "a8df8e"
h3:
prefix: "▒▒▒▒"
colors:
foreground: "rgb_(247,140,162)"
foreground: "f78ca2"
h4:
prefix: "░░░░░"
colors:
foreground: "rgb_(210,210,210)"
foreground: "d2d2d2"
h5:
prefix: "░░░░░░"
colors:
foreground: "rgb_(210,210,210)"
foreground: "d2d2d2"
h6:
prefix: "░░░░░░░"
colors:
foreground: "rgb_(210,210,210)"
foreground: "d2d2d2"
block_quote:
prefix: "▍ "
colors:
foreground: "rgb_(240,240,240)"
background: "rgb_(41,46,66)"
foreground: "f0f0f0"
background: "292e42"
footer:
style: progress_bar
colors:
foreground: "rgb_(122,162,247)"
foreground: "7aa2f7"

View File

@ -3,15 +3,15 @@ default:
margin:
percent: 8
colors:
foreground: "rgb_(192,202,245)"
background: "rgb_(36,40,59)"
foreground: "c0caf5"
background: "24283b"
slide_title:
alignment: center
padding_bottom: 1
padding_top: 1
colors:
foreground: "rgb_(224,175,104)"
foreground: "e0af68"
code:
alignment: center
@ -23,8 +23,8 @@ code:
inline_code:
colors:
foreground: "rgb_(158,206,106)"
background: "rgb_(54,74,130)"
foreground: "9ece6a"
background: "364a82"
table:
alignment: center
@ -33,51 +33,51 @@ intro_slide:
title:
alignment: center
colors:
foreground: "rgb_(122,162,247)"
foreground: "7aa2f7"
subtitle:
alignment: center
colors:
foreground: "rgb_(169,177,214)"
foreground: "a9b1d6"
author:
alignment: center
colors:
foreground: "rgb_(158,206,106)"
foreground: "9ece6a"
positioning: page_bottom
headings:
h1:
prefix: "██"
colors:
foreground: "rgb_(158,206,106)"
foreground: "9ece6a"
h2:
prefix: "▓▓▓"
colors:
foreground: "rgb_(247,118,142)"
foreground: "f7768e"
h3:
prefix: "▒▒▒▒"
colors:
foreground: "rgb_(122,162,247)"
foreground: "7aa2f7"
h4:
prefix: "░░░░░"
colors:
foreground: "rgb_(187,154,247)"
foreground: "bb9af7"
h5:
prefix: "░░░░░░"
colors:
foreground: "rgb_(187,154,247)"
foreground: "bb9af7"
h6:
prefix: "░░░░░░░"
colors:
foreground: "rgb_(187,154,247)"
foreground: "bb9af7"
block_quote:
prefix: "▍ "
colors:
foreground: "rgb_(240,240,240)"
background: "rgb_(84,92,126)"
foreground: "f0f0f0"
background: "545c7e"
footer:
style: progress_bar
colors:
foreground: "rgb_(122,162,247)"
foreground: "7aa2f7"