summaryrefslogtreecommitdiff
path: root/src/geom
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-10-10 20:26:58 +0200
committerLaurenz <laurmaedje@gmail.com>2021-10-10 20:26:58 +0200
commitd4cc8c775d4c579aeac69ca2d212a604c67043b0 (patch)
tree90c6bd20a3a0a4a1e471385a536c1333a4b05c99 /src/geom
parentf4ed775df073ceeff292810f19ca8d01b054eff1 (diff)
Move paint and colors into `geom`
Diffstat (limited to 'src/geom')
-rw-r--r--src/geom/mod.rs2
-rw-r--r--src/geom/paint.rs157
2 files changed, 159 insertions, 0 deletions
diff --git a/src/geom/mod.rs b/src/geom/mod.rs
index 936f1868..ad92813e 100644
--- a/src/geom/mod.rs
+++ b/src/geom/mod.rs
@@ -10,6 +10,7 @@ mod fr;
mod gen;
mod length;
mod linear;
+mod paint;
mod path;
mod point;
mod relative;
@@ -25,6 +26,7 @@ pub use fr::*;
pub use gen::*;
pub use length::*;
pub use linear::*;
+pub use paint::*;
pub use path::*;
pub use point::*;
pub use relative::*;
diff --git a/src/geom/paint.rs b/src/geom/paint.rs
new file mode 100644
index 00000000..2ac11b56
--- /dev/null
+++ b/src/geom/paint.rs
@@ -0,0 +1,157 @@
+use std::fmt::Display;
+use std::str::FromStr;
+
+use super::*;
+
+/// How a fill or stroke should be painted.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
+pub enum Paint {
+ /// A solid color.
+ Color(Color),
+}
+
+/// A color in a dynamic format.
+#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
+pub enum Color {
+ /// An 8-bit RGBA color.
+ Rgba(RgbaColor),
+}
+
+impl Debug for Color {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ match self {
+ Self::Rgba(c) => Debug::fmt(c, f),
+ }
+ }
+}
+
+/// An 8-bit RGBA color.
+#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
+pub struct RgbaColor {
+ /// Red channel.
+ pub r: u8,
+ /// Green channel.
+ pub g: u8,
+ /// Blue channel.
+ pub b: u8,
+ /// Alpha channel.
+ pub a: u8,
+}
+
+impl RgbaColor {
+ /// Black color.
+ pub const BLACK: Self = Self { r: 0, g: 0, b: 0, a: 255 };
+
+ /// White color.
+ pub const WHITE: Self = Self { r: 255, g: 255, b: 255, a: 255 };
+
+ /// Constructs a new RGBA color.
+ pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
+ Self { r, g, b, a }
+ }
+}
+
+impl FromStr for RgbaColor {
+ type Err = ParseRgbaError;
+
+ /// Constructs a new color from hex strings like the following:
+ /// - `#aef` (shorthand, with leading hashtag),
+ /// - `7a03c2` (without alpha),
+ /// - `abcdefff` (with alpha).
+ ///
+ /// Both lower and upper case is fine.
+ fn from_str(hex_str: &str) -> Result<Self, Self::Err> {
+ let hex_str = hex_str.strip_prefix('#').unwrap_or(hex_str);
+ if !hex_str.is_ascii() {
+ return Err(ParseRgbaError);
+ }
+
+ let len = hex_str.len();
+ let long = len == 6 || len == 8;
+ let short = len == 3 || len == 4;
+ let alpha = len == 4 || len == 8;
+
+ if !long && !short {
+ return Err(ParseRgbaError);
+ }
+
+ let mut values: [u8; 4] = [255; 4];
+
+ for elem in if alpha { 0 .. 4 } else { 0 .. 3 } {
+ let item_len = if long { 2 } else { 1 };
+ let pos = elem * item_len;
+
+ let item = &hex_str[pos .. (pos + item_len)];
+ values[elem] = u8::from_str_radix(item, 16).map_err(|_| ParseRgbaError)?;
+
+ if short {
+ // Duplicate number for shorthand notation, i.e. `a` -> `aa`
+ values[elem] += values[elem] * 16;
+ }
+ }
+
+ Ok(Self::new(values[0], values[1], values[2], values[3]))
+ }
+}
+
+impl Debug for RgbaColor {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ if f.alternate() {
+ write!(
+ f,
+ "rgba({:02}, {:02}, {:02}, {:02})",
+ self.r, self.g, self.b, self.a,
+ )?;
+ } else {
+ write!(f, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b)?;
+ if self.a != 255 {
+ write!(f, "{:02x}", self.a)?;
+ }
+ }
+ Ok(())
+ }
+}
+
+/// The error when parsing an [`RgbaColor`] from a string fails.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub struct ParseRgbaError;
+
+impl Display for ParseRgbaError {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad("invalid color")
+ }
+}
+
+impl std::error::Error for ParseRgbaError {}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_parse_color_strings() {
+ #[track_caller]
+ fn test(hex: &str, r: u8, g: u8, b: u8, a: u8) {
+ assert_eq!(RgbaColor::from_str(hex), Ok(RgbaColor::new(r, g, b, a)));
+ }
+
+ test("f61243ff", 0xf6, 0x12, 0x43, 0xff);
+ test("b3d8b3", 0xb3, 0xd8, 0xb3, 0xff);
+ test("fCd2a9AD", 0xfc, 0xd2, 0xa9, 0xad);
+ test("233", 0x22, 0x33, 0x33, 0xff);
+ test("111b", 0x11, 0x11, 0x11, 0xbb);
+ }
+
+ #[test]
+ fn test_parse_invalid_colors() {
+ #[track_caller]
+ fn test(hex: &str) {
+ assert_eq!(RgbaColor::from_str(hex), Err(ParseRgbaError));
+ }
+
+ test("12345");
+ test("a5");
+ test("14B2AH");
+ test("f075ff011");
+ }
+}