From 6d2f354c6665578c37231c2d8689aa0c7059bcce Mon Sep 17 00:00:00 2001 From: backwardspy Date: Sun, 8 Sep 2024 16:19:29 +0100 Subject: [PATCH] feat: add signed & unsigned integer colour repr (#27) * feat: add signed & unsigned integer colour repr * feat: add 24-bit int repr and tests * docs: add int24/uint32/sint32 to color table --- README.md | 23 +++++++++------- src/models.rs | 54 +++++++++++++++++++++++++++++++++++++ tests/cli.rs | 16 ++++++++++- tests/fixtures/formats.tera | 7 +++++ 4 files changed, 89 insertions(+), 11 deletions(-) create mode 100644 tests/fixtures/formats.tera diff --git a/README.md b/README.md index 7976e52..0c83eb9 100644 --- a/README.md +++ b/README.md @@ -142,16 +142,19 @@ These types are designed to closely match the [palette.json](https://github.com/ ##### Color -| Field | Type | Description | Examples | -| ------------ | -------- | --------------------------------------- | -------------------------------------- | -| `name` | `String` | The name of the color. | `"Rosewater"`, `"Surface 0"`, `"Base"` | -| `identifier` | `String` | The identifier of the color. | `"rosewater"`, `"surface0"`, `"base"` | -| `order` | `u32` | Order of the color in the palette spec. | `0` to `25` | -| `accent` | `bool` | Whether the color is an accent color. | | -| `hex` | `String` | The color in hexadecimal format. | `"1e1e2e"` | -| `rgb` | `RGB` | The color in RGB format. | | -| `hsl` | `HSL` | The color in HSL format. | | -| `opacity` | `u8` | The opacity of the color. | `0` to `255` | +| Field | Type | Description | Examples | +| ------------ | -------- | ----------------------------------------------- | -------------------------------------- | +| `name` | `String` | The name of the color. | `"Rosewater"`, `"Surface 0"`, `"Base"` | +| `identifier` | `String` | The identifier of the color. | `"rosewater"`, `"surface0"`, `"base"` | +| `order` | `u32` | Order of the color in the palette spec. | `0` to `25` | +| `accent` | `bool` | Whether the color is an accent color. | | +| `hex` | `String` | The color in hexadecimal format. | `"1e1e2e"` | +| `int24` | `u32` | Big-endian 24-bit color in RGB order. | `1973806` | +| `uint32` | `u32` | Big-endian unsigned 32-bit color in ARGB order. | `4280163886` | +| `sint32` | `i32` | Big-endian signed 32-bit color in ARGB order. | `-14803410` | +| `rgb` | `RGB` | The color in RGB format. | | +| `hsl` | `HSL` | The color in HSL format. | | +| `opacity` | `u8` | The opacity of the color. | `0` to `255` | ##### RGB diff --git a/src/models.rs b/src/models.rs index cdaa791..7d33621 100644 --- a/src/models.rs +++ b/src/models.rs @@ -32,6 +32,9 @@ pub struct Color { pub order: u32, pub accent: bool, pub hex: String, + pub int24: u32, + pub uint32: u32, + pub sint32: i32, pub rgb: RGB, pub hsl: HSL, pub opacity: u8, @@ -101,6 +104,18 @@ fn format_hex(r: u8, g: u8, b: u8, a: u8, hex_format: &str) -> tera::Result) -> (u32, u32, i32) { + let opacity = opacity.unwrap_or(0xFF); + let uint24 = u32::from_be_bytes([0x00, rgb.r, rgb.g, rgb.b]); + let uint32 = u32::from_be_bytes([opacity, rgb.r, rgb.g, rgb.b]); + (uint24, uint32, uint32 as i32) +} + fn color_from_hex_override(hex: &str, blueprint: &catppuccin::Color) -> Result { let i = u32::from_str_radix(hex, 16)?; let rgb = RGB { @@ -110,12 +125,16 @@ fn color_from_hex_override(hex: &str, blueprint: &catppuccin::Color) -> Result Result tera::Result { let hex = format_hex!(color.rgb.r, color.rgb.g, color.rgb.b, 0xFF)?; + let rgb: RGB = color.rgb.into(); + let (int24, uint32, sint32) = rgb_to_ints(&rgb, None); Ok(Color { name: color.name.to_string(), identifier: color.name.identifier().to_string(), order: color.order, accent: color.accent, hex, + int24, + uint32, + sint32, rgb: RGB { r: color.rgb.r, g: color.rgb.g, @@ -253,12 +277,16 @@ impl Color { l: hsla.l.as_f32(), }; let opacity = hsla.a.as_u8(); + let (int24, uint32, sint32) = rgb_to_ints(&rgb, Some(opacity)); Ok(Self { name: blueprint.name.clone(), identifier: blueprint.identifier.clone(), order: blueprint.order, accent: blueprint.accent, hex: rgb_to_hex(&rgb, opacity)?, + int24, + uint32, + sint32, rgb, hsl, opacity, @@ -278,12 +306,16 @@ impl Color { l: hsl.l.as_f32(), }; let opacity = rgba.a.as_u8(); + let (int24, uint32, sint32) = rgb_to_ints(&rgb, Some(opacity)); Ok(Self { name: blueprint.name.clone(), identifier: blueprint.identifier.clone(), order: blueprint.order, accent: blueprint.accent, hex: rgb_to_hex(&rgb, opacity)?, + int24, + uint32, + sint32, rgb, hsl, opacity, @@ -356,9 +388,13 @@ impl Color { pub fn mod_opacity(&self, opacity: f32) -> tera::Result { let opacity = (opacity * 255.0).round() as u8; + let (int24, uint32, sint32) = rgb_to_ints(&self.rgb, Some(opacity)); Ok(Self { opacity, hex: rgb_to_hex(&self.rgb, opacity)?, + int24, + uint32, + sint32, ..self.clone() }) } @@ -366,9 +402,13 @@ impl Color { pub fn add_opacity(&self, opacity: f32) -> tera::Result { let opacity = (opacity * 255.0).round() as u8; let opacity = self.opacity.saturating_add(opacity); + let (int24, uint32, sint32) = rgb_to_ints(&self.rgb, Some(opacity)); Ok(Self { opacity, hex: rgb_to_hex(&self.rgb, opacity)?, + int24, + uint32, + sint32, ..self.clone() }) } @@ -376,9 +416,13 @@ impl Color { pub fn sub_opacity(&self, opacity: f32) -> tera::Result { let opacity = (opacity * 255.0).round() as u8; let opacity = self.opacity.saturating_sub(opacity); + let (int24, uint32, sint32) = rgb_to_ints(&self.rgb, Some(opacity)); Ok(Self { opacity, hex: rgb_to_hex(&self.rgb, opacity)?, + int24, + uint32, + sint32, ..self.clone() }) } @@ -425,3 +469,13 @@ impl From<&Color> for css_colors::HSLA { } } } + +impl From for RGB { + fn from(rgb: catppuccin::Rgb) -> Self { + Self { + r: rgb.r, + g: rgb.g, + b: rgb.b, + } + } +} diff --git a/tests/cli.rs b/tests/cli.rs index 1fc0692..5d3320e 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -1,7 +1,7 @@ #[cfg(test)] mod happy_path { use assert_cmd::Command; - use predicates::prelude::predicate; + use predicates::prelude::{predicate, PredicateBooleanExt}; /// Test that the CLI can render a single-flavor template file #[test] @@ -49,6 +49,20 @@ mod happy_path { .stdout(include_str!("fixtures/read_file/read_file.md")); } + /// Test that the CLI can render colours in specific formats + #[test] + fn test_formats() { + let mut cmd = Command::cargo_bin("whiskers").expect("binary exists"); + let assert = cmd + .args(["tests/fixtures/formats.tera", "-f", "latte"]) + .assert(); + assert.success().stdout( + predicate::str::contains("24-bit red: 13766457") + .and(predicate::str::contains("unsigned 32-bit red: 4291956537")) + .and(predicate::str::contains("signed 32-bit red: -3010759")), + ); + } + /// Test that the CLI can render a UTF-8 template file #[test] fn test_utf8() { diff --git a/tests/fixtures/formats.tera b/tests/fixtures/formats.tera new file mode 100644 index 0000000..15ed32a --- /dev/null +++ b/tests/fixtures/formats.tera @@ -0,0 +1,7 @@ +--- +whiskers: + version: "2.0.0" +--- +24-bit red: {{red.int24}} +unsigned 32-bit red: {{red.uint32}} +signed 32-bit red: {{red.sint32}} \ No newline at end of file