summaryrefslogtreecommitdiff
path: root/src/attributes/color.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/attributes/color.rs')
-rw-r--r--src/attributes/color.rs165
1 files changed, 165 insertions, 0 deletions
diff --git a/src/attributes/color.rs b/src/attributes/color.rs
new file mode 100644
index 0000000..92a26eb
--- /dev/null
+++ b/src/attributes/color.rs
@@ -0,0 +1,165 @@
+use crate::dot::DotString;
+use std::borrow::Cow;
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub enum Color<'a> {
+ RGB {
+ red: u8,
+ green: u8,
+ blue: u8,
+ },
+ RGBA {
+ red: u8,
+ green: u8,
+ blue: u8,
+ alpha: u8,
+ },
+ // TODO: constrain?
+ // Hue-Saturation-Value (HSV) 0.0 <= H,S,V <= 1.0
+ HSV {
+ hue: f32,
+ saturation: f32,
+ value: f32,
+ },
+ Named(&'a str),
+}
+
+impl<'a> DotString<'a> for Color<'a> {
+ fn dot_string(&self) -> Cow<'a, str> {
+ match self {
+ Color::RGB { red, green, blue } => {
+ format!("#{:02x?}{:02x?}{:02x?}", red, green, blue).into()
+ }
+ Color::RGBA {
+ red,
+ green,
+ blue,
+ alpha,
+ } => {
+ format!("#{:02x?}{:02x?}{:02x?}{:02x?}", red, green, blue, alpha).into()
+ }
+ Color::HSV {
+ hue,
+ saturation,
+ value,
+ } => format!("{} {} {}", hue, saturation, value).into(),
+ Color::Named(color) => (*color).into(),
+ }
+ }
+}
+
+// The sum of the optional weightings must sum to at most 1.
+pub struct WeightedColor<'a> {
+ pub color: Color<'a>,
+
+ // TODO: constrain
+ /// Must be in range 0 <= W <= 1.
+ pub weight: Option<f32>,
+}
+
+impl<'a> DotString<'a> for WeightedColor<'a> {
+ fn dot_string(&self) -> Cow<'a, str> {
+ let mut dot_string = self.color.dot_string().to_string();
+ if let Some(weight) = &self.weight {
+ dot_string.push_str(format!(";{}", weight).as_str());
+ }
+ dot_string.into()
+ }
+}
+
+pub struct ColorList<'a> {
+ pub colors: Vec<WeightedColor<'a>>,
+}
+
+impl<'a> DotString<'a> for ColorList<'a> {
+ /// A colon-separated list of weighted color values: WC(:WC)* where each WC has the form C(;F)?
+ /// Ex: fillcolor=yellow;0.3:blue
+ fn dot_string(&self) -> Cow<'a, str> {
+ let mut dot_string = String::new();
+ let mut iter = self.colors.iter();
+ let first = iter.next();
+ if first.is_none() {
+ return dot_string.into();
+ }
+ dot_string.push_str(&first.unwrap().dot_string());
+ for weighted_color in iter {
+ dot_string.push_str(":");
+ dot_string.push_str(&weighted_color.dot_string())
+ }
+
+ dot_string.into()
+ }
+}
+
+/// Convert an element like `(i, j)` into a WeightedColor
+pub trait IntoWeightedColor<'a> {
+ fn into_weighted_color(self) -> WeightedColor<'a>;
+}
+
+impl<'a> IntoWeightedColor<'a> for &(Color<'a>, Option<f32>) {
+ fn into_weighted_color(self) -> WeightedColor<'a> {
+ let (s, t) = *self;
+ WeightedColor {
+ color: s,
+ weight: t,
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::attributes::{Color, ColorList, WeightedColor};
+ use crate::DotString;
+
+ #[test]
+ fn colorlist_dot_string() {
+ let yellow = WeightedColor {
+ color: Color::Named("yellow"),
+ weight: Some(0.3),
+ };
+
+ let blue = WeightedColor {
+ color: Color::Named("blue"),
+ weight: None,
+ };
+
+ let color_list = ColorList {
+ colors: vec![yellow, blue],
+ };
+
+ let dot_string = color_list.dot_string();
+
+ assert_eq!("yellow;0.3:blue", dot_string);
+ }
+
+ #[test]
+ fn color_rbg_dot_string() {
+ let color = Color::RGB {
+ red: 160,
+ green: 82,
+ blue: 45,
+ };
+ assert_eq!("#a0522d", color.dot_string());
+ }
+
+ #[test]
+ fn color_rbga_dot_string() {
+ let color = Color::RGBA {
+ red: 160,
+ green: 82,
+ blue: 45,
+ alpha: 10,
+ };
+ assert_eq!("#a0522d0a", color.dot_string());
+ }
+
+ #[test]
+ fn color_hsv_dot_string() {
+ let color = Color::HSV {
+ hue: 0.051,
+ saturation: 0.718,
+ value: 0.627,
+ };
+ assert_eq!("0.051 0.718 0.627", color.dot_string());
+ }
+}