layout-cat 0.1.0

Box-model layout: cascade + block layout over a dom-cat tree using css-cat stylesheets. Produces a LayoutBox tree with positions and dimensions. No mut, no Rc/Arc, no interior mutability, no panics, exhaustive matches. Fourth sub-crate of a Servo-replacement webview runtime targeting Tauri.
//! Color values.

/// An RGBA color.  Components are in `0.0..=1.0`.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Color {
    r: f64,
    g: f64,
    b: f64,
    a: f64,
}

impl Eq for Color {}

impl Color {
    /// Build an RGBA color.
    #[must_use]
    pub fn rgba(r: f64, g: f64, b: f64, a: f64) -> Self {
        Self { r, g, b, a }
    }

    /// Opaque RGB.
    #[must_use]
    pub fn rgb(r: f64, g: f64, b: f64) -> Self {
        Self::rgba(r, g, b, 1.0)
    }

    /// Transparent black (the layout-tree default).
    #[must_use]
    pub fn transparent() -> Self {
        Self::rgba(0.0, 0.0, 0.0, 0.0)
    }

    /// The red channel `0..=1`.
    #[must_use]
    pub fn red(&self) -> f64 {
        self.r
    }

    /// The green channel.
    #[must_use]
    pub fn green(&self) -> f64 {
        self.g
    }

    /// The blue channel.
    #[must_use]
    pub fn blue(&self) -> f64 {
        self.b
    }

    /// The alpha channel.
    #[must_use]
    pub fn alpha(&self) -> f64 {
        self.a
    }

    /// Parse `#rrggbb` / `#rgb` / `#rrggbbaa` / `#rgba`.  Returns
    /// `None` for unrecognised input.
    #[must_use]
    pub fn from_hex(hex: &str) -> Option<Self> {
        let bytes = hex.as_bytes();
        match bytes.len() {
            3 => parse_short(bytes, false),
            4 => parse_short(bytes, true),
            6 => parse_long(bytes, false),
            8 => parse_long(bytes, true),
            _other => None,
        }
    }
}

fn parse_short(bytes: &[u8], with_alpha: bool) -> Option<Color> {
    let r = hex_digit(*bytes.first()?)?;
    let g = hex_digit(*bytes.get(1)?)?;
    let b = hex_digit(*bytes.get(2)?)?;
    let a = if with_alpha {
        hex_digit(*bytes.get(3)?)?
    } else {
        15
    };
    Some(Color::rgba(
        f64::from(r * 17) / 255.0,
        f64::from(g * 17) / 255.0,
        f64::from(b * 17) / 255.0,
        f64::from(a * 17) / 255.0,
    ))
}

fn parse_long(bytes: &[u8], with_alpha: bool) -> Option<Color> {
    let r = pair(bytes, 0)?;
    let g = pair(bytes, 2)?;
    let b = pair(bytes, 4)?;
    let a = if with_alpha { pair(bytes, 6)? } else { 255 };
    Some(Color::rgba(
        f64::from(r) / 255.0,
        f64::from(g) / 255.0,
        f64::from(b) / 255.0,
        f64::from(a) / 255.0,
    ))
}

fn pair(bytes: &[u8], idx: usize) -> Option<u8> {
    let hi = hex_digit(*bytes.get(idx)?)?;
    let lo = hex_digit(*bytes.get(idx + 1)?)?;
    Some(hi * 16 + lo)
}

fn hex_digit(b: u8) -> Option<u8> {
    match b {
        b'0'..=b'9' => Some(b - b'0'),
        b'a'..=b'f' => Some(b - b'a' + 10),
        b'A'..=b'F' => Some(b - b'A' + 10),
        _other => None,
    }
}

/// Look up a CSS named color.  v0 covers the 16 basic + a few common
/// extras; full named-color table deferred to v0.2.
#[must_use]
pub fn named(name: &str) -> Option<Color> {
    match name.to_ascii_lowercase().as_str() {
        "black" => Some(Color::rgb(0.0, 0.0, 0.0)),
        "white" => Some(Color::rgb(1.0, 1.0, 1.0)),
        "red" => Some(Color::rgb(1.0, 0.0, 0.0)),
        "green" => Some(Color::rgb(0.0, 0.5, 0.0)),
        "blue" => Some(Color::rgb(0.0, 0.0, 1.0)),
        "yellow" => Some(Color::rgb(1.0, 1.0, 0.0)),
        "cyan" | "aqua" => Some(Color::rgb(0.0, 1.0, 1.0)),
        "magenta" | "fuchsia" => Some(Color::rgb(1.0, 0.0, 1.0)),
        "silver" => Some(Color::rgb(0.75, 0.75, 0.75)),
        "gray" | "grey" => Some(Color::rgb(0.5, 0.5, 0.5)),
        "maroon" => Some(Color::rgb(0.5, 0.0, 0.0)),
        "olive" => Some(Color::rgb(0.5, 0.5, 0.0)),
        "purple" => Some(Color::rgb(0.5, 0.0, 0.5)),
        "teal" => Some(Color::rgb(0.0, 0.5, 0.5)),
        "navy" => Some(Color::rgb(0.0, 0.0, 0.5)),
        "lime" => Some(Color::rgb(0.0, 1.0, 0.0)),
        "orange" => Some(Color::rgb(1.0, 0.647, 0.0)),
        "transparent" => Some(Color::transparent()),
        _other => None,
    }
}