├── .github ├── FUNDING.yml └── workflows │ └── rust.yml ├── examples ├── named-colors.rs └── cli.rs ├── .gitignore ├── Makefile ├── src ├── utils │ ├── helper.rs │ ├── param.rs │ └── calc.rs ├── error.rs ├── lib.rs ├── lab.rs ├── cint.rs ├── color2.rs ├── utils.rs ├── named_colors.rs ├── parser.rs └── color.rs ├── LICENSE-MIT ├── Cargo.toml ├── CHANGELOG.md ├── tests ├── named_colors.rs ├── parser_relative_color.rs ├── parser2.rs ├── parser.rs ├── color.rs ├── chromium.rs ├── chrome_android.rs └── firefox.rs ├── README.md └── LICENSE-APACHE /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: mazznoer 4 | ko_fi: mazznoer 5 | liberapay: mazznoer 6 | custom: "https://paypal.me/mazznoer" 7 | -------------------------------------------------------------------------------- /examples/named-colors.rs: -------------------------------------------------------------------------------- 1 | use csscolorparser::NAMED_COLORS; 2 | 3 | fn main() { 4 | for (name, rgb) in &NAMED_COLORS { 5 | let [r, g, b] = rgb; 6 | println!("\x1B[48;2;{r};{g};{b}m \x1B[49m {name} {rgb:?}"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | .PHONY: all check test 4 | 5 | all: check test 6 | 7 | check: 8 | cargo build --no-default-features && \ 9 | cargo clippy --no-default-features -- -D warnings && \ 10 | cargo build --all-features && \ 11 | cargo clippy --all-features -- -D warnings && \ 12 | cargo fmt --all -- --check 13 | 14 | test: 15 | cargo test --no-default-features && \ 16 | cargo test --all-features 17 | -------------------------------------------------------------------------------- /src/utils/helper.rs: -------------------------------------------------------------------------------- 1 | // Strip prefix ignore case. 2 | pub fn strip_prefix<'a>(s: &'a str, prefix: &str) -> Option<&'a str> { 3 | if prefix.len() > s.len() { 4 | return None; 5 | } 6 | let s_start = &s[..prefix.len()]; 7 | if s_start.eq_ignore_ascii_case(prefix) { 8 | Some(&s[prefix.len()..]) 9 | } else { 10 | None 11 | } 12 | } 13 | 14 | #[cfg(test)] 15 | mod t { 16 | use super::*; 17 | 18 | #[test] 19 | fn strip_prefix_() { 20 | assert_eq!(strip_prefix("rgb(77)", "rgb"), Some("(77)")); 21 | assert_eq!(strip_prefix("RGB(0,0)", "rgb"), Some("(0,0)")); 22 | assert_eq!(strip_prefix("Hsv()", "HSV"), Some("()")); 23 | 24 | assert_eq!(strip_prefix("", "rgb"), None); 25 | assert_eq!(strip_prefix("10", "rgb"), None); 26 | assert_eq!(strip_prefix("hsv(0,0)", "hsva"), None); 27 | assert_eq!(strip_prefix("hsv", "hsva"), None); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/cli.rs: -------------------------------------------------------------------------------- 1 | // Usage: 2 | // cargo r --example cli -- [color].. 3 | 4 | use csscolorparser::parse; 5 | 6 | fn main() { 7 | for arg in std::env::args().skip(1) { 8 | println!("{arg:?}"); 9 | match parse(&arg) { 10 | Ok(c) => { 11 | let [r, g, b, _] = c.to_rgba8(); 12 | let name = if let Some(s) = c.name() { s } else { "-" }; 13 | println!(" \x1B[48;2;{r};{g};{b}m \x1B[49m"); 14 | println!(" {}", c); 15 | println!(" {}", c.to_css_rgb()); 16 | println!(" {}", c.to_css_hwb()); 17 | println!(" {}", c.to_css_hsl()); 18 | println!(" {}", c.to_css_lab()); 19 | println!(" {}", c.to_css_lch()); 20 | println!(" {}", c.to_css_oklab()); 21 | println!(" {}", c.to_css_oklch()); 22 | println!(" name {}", name); 23 | } 24 | Err(e) => println!(" {e}"), 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Nor Khasyatillah 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - "**.md" 7 | pull_request: 8 | paths-ignore: 9 | - "**.md" 10 | schedule: 11 | - cron: '0 0 */5 * *' 12 | 13 | env: 14 | CARGO_TERM_COLOR: always 15 | 16 | jobs: 17 | build: 18 | strategy: 19 | matrix: 20 | os: [macos-latest, windows-latest, ubuntu-latest] 21 | runs-on: ${{ matrix.os }} 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | 26 | - name: Build 27 | run: | 28 | cargo build --verbose --no-default-features 29 | cargo build --verbose --all-features 30 | cargo build --examples --all-features 31 | - uses: taiki-e/install-action@cargo-hack 32 | - run: | 33 | cargo hack build --no-private --feature-powerset --no-dev-deps 34 | 35 | - name: Run tests 36 | run: | 37 | cargo test --verbose --no-default-features 38 | cargo test --verbose --all-features 39 | 40 | - name: Run cargo clippy 41 | run: | 42 | cargo clippy --no-default-features -- -D warnings 43 | cargo clippy --all-features -- -D warnings 44 | 45 | - name: Run cargo fmt 46 | run: | 47 | cargo fmt --all -- --check 48 | 49 | #- name: Tarpaulin code coverage 50 | # id: coverage 51 | # if: matrix.os == 'ubuntu-latest' 52 | # run: > 53 | # cargo install cargo-tarpaulin && 54 | # cargo tarpaulin -o xml 55 | 56 | #- name: Upload to codecov.io 57 | # if: matrix.os == 'ubuntu-latest' 58 | # uses: codecov/codecov-action@v3 59 | 60 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "csscolorparser" 3 | version = "0.8.1" 4 | authors = ["Nor Khasyatillah "] 5 | edition = "2018" 6 | description = "CSS color parser library" 7 | readme = "README.md" 8 | repository = "https://github.com/mazznoer/csscolorparser-rs" 9 | documentation = "https://docs.rs/csscolorparser/" 10 | license = "MIT OR Apache-2.0" 11 | keywords = ["color", "colour", "css", "parser"] 12 | categories = ["graphics", "parser-implementations", "no-std"] 13 | exclude = [ 14 | ".github/*", 15 | ] 16 | 17 | [package.metadata.docs.rs] 18 | features = ["named-colors", "rust-rgb", "cint", "serde", "std"] 19 | 20 | [features] 21 | default = ["named-colors", "std"] 22 | 23 | cint = ["dep:cint"] 24 | named-colors = ["dep:phf", "dep:uncased"] 25 | rust-rgb = ["dep:rgb"] 26 | serde = ["dep:serde"] 27 | std = ["phf?/std", "serde?/std"] 28 | 29 | [dependencies] 30 | cint = { version = "^0.3.1", optional = true } 31 | num-traits = { version = "0.2.19", default-features = false, features = ["libm"] } 32 | phf = { version = "0.13.1", optional = true, default-features = false, features = ["macros", "uncased"] } 33 | rgb = { version = "0.8.33", optional = true } 34 | serde = { version = "1.0.139", optional = true, default-features = false, features = ["derive"] } 35 | uncased = { version = "0.9.10", optional = true, default-features = false } 36 | 37 | [dev-dependencies] 38 | serde_test = "1.0.139" 39 | 40 | [[test]] 41 | name = "named_colors" 42 | required-features = ["named-colors"] 43 | 44 | [[example]] 45 | name = "cli" 46 | required-features = ["named-colors"] 47 | 48 | [[example]] 49 | name = "named-colors" 50 | required-features = ["named-colors"] 51 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use core::error::Error; 2 | use core::fmt; 3 | 4 | /// An error which can be returned when parsing a CSS color string. 5 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 6 | pub enum ParseColorError { 7 | /// A CSS color string was invalid hex format. 8 | InvalidHex, 9 | /// A CSS color string was invalid rgb format. 10 | InvalidRgb, 11 | /// A CSS color string was invalid hsl format. 12 | InvalidHsl, 13 | /// A CSS color string was invalid hwb format. 14 | InvalidHwb, 15 | /// A CSS color string was invalid hsv format. 16 | InvalidHsv, 17 | /// A CSS color string was invalid lab format. 18 | InvalidLab, 19 | /// A CSS color string was invalid lch format. 20 | InvalidLch, 21 | /// A CSS color string was invalid oklab format. 22 | InvalidOklab, 23 | /// A CSS color string was invalid oklch format. 24 | InvalidOklch, 25 | /// A CSS color string was invalid color function. 26 | InvalidFunction, 27 | /// A CSS color string was invalid unknown format. 28 | InvalidUnknown, 29 | } 30 | 31 | impl fmt::Display for ParseColorError { 32 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 33 | match *self { 34 | Self::InvalidHex => f.write_str("invalid hex format"), 35 | Self::InvalidRgb => f.write_str("invalid rgb format"), 36 | Self::InvalidHsl => f.write_str("invalid hsl format"), 37 | Self::InvalidHwb => f.write_str("invalid hwb format"), 38 | Self::InvalidHsv => f.write_str("invalid hsv format"), 39 | Self::InvalidLab => f.write_str("invalid lab format"), 40 | Self::InvalidLch => f.write_str("invalid lch format"), 41 | Self::InvalidOklab => f.write_str("invalid oklab format"), 42 | Self::InvalidOklch => f.write_str("invalid oklch format"), 43 | Self::InvalidFunction => f.write_str("invalid color function"), 44 | Self::InvalidUnknown => f.write_str("invalid unknown format"), 45 | } 46 | } 47 | } 48 | 49 | impl Error for ParseColorError {} 50 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased](https://github.com/mazznoer/csscolorparser-rs/compare/v0.8.1...HEAD) 4 | 5 | ### Changed 6 | 7 | - Parse `none` value in absolute color format. 8 | - Changed `impl Display` for `Color` to hex color format. 9 | 10 | ## [0.8.1](https://github.com/mazznoer/csscolorparser-rs/compare/v0.8.0...v0.8.1) 11 | 12 | ### Changed 13 | 14 | - Improvements in parser code. 15 | - improvements of `calc()` parser for relative color format. 16 | 17 | ### Fixed 18 | 19 | - Require `phf` only if needed. 20 | 21 | ## [0.8.0](https://github.com/mazznoer/csscolorparser-rs/compare/v0.7.2...v0.8.0) 22 | 23 | ### Added 24 | 25 | - Support `no_std`. 26 | - Support parsing relative color format. 27 | 28 | ### Changed 29 | 30 | - Support for parsing lab format is always enabled now. Remove the `lab` cargo feature. 31 | - Using `phf::OrderedMap` and `uncased` to store named colors. 32 | 33 | ## [0.7.2](https://github.com/mazznoer/csscolorparser-rs/compare/v0.7.1...v0.7.2) 34 | 35 | ### Added 36 | 37 | - `Color::to_oklcha()` 38 | - `Color::to_css_hex()` 39 | - `Color::to_css_rgb()` 40 | - `Color::to_css_hsl()` 41 | - `Color::to_css_hwb()` 42 | - `Color::to_css_lab()` 43 | - `Color::to_css_lch()` 44 | - `Color::to_css_oklab()` 45 | - `Color::to_css_oklch()` 46 | 47 | ### Changed 48 | 49 | - Deprecate `Color::to_hex_string()` and `Color::to_rgb_string()` 50 | 51 | ## [0.7.1](https://github.com/mazznoer/csscolorparser-rs/compare/v0.7.0...v0.7.1) 52 | 53 | ### Changed 54 | 55 | - Remove some unnecessary allocations on parser code. 56 | 57 | ## [0.7.0](https://github.com/mazznoer/csscolorparser-rs/compare/v0.6.2...v0.7.0) 58 | 59 | ### Added 60 | 61 | - `Color::from_oklcha()` 62 | - Support parsing `oklab()` and `oklch()` color format. 63 | - `Color::{from,to}_{laba,lcha}()` 64 | 65 | ### Changed 66 | 67 | - `f64` -> `f32` 68 | - Return type for `Color::to_{hsva,hsla,hwba,lab,lch,oklaba,linear_rgba}()` changed from tuple to array. 69 | - Deprecate `Color::{from,to}_{lab,lch}()`, use `Color::{from,to}_{laba,lcha}()` instead. 70 | - `NAMED_COLORS` is now public 71 | 72 | ### Removed 73 | 74 | ### Fixed 75 | 76 | - Fix parsing `lab()` and `lch()` color format. 77 | - Update `oklab` formula. 78 | 79 | -------------------------------------------------------------------------------- /tests/named_colors.rs: -------------------------------------------------------------------------------- 1 | use csscolorparser::{parse, Color, NAMED_COLORS}; 2 | 3 | #[test] 4 | fn named_colors() { 5 | let skip_list = ["aqua", "cyan", "fuchsia", "magenta"]; 6 | 7 | for (&name, &rgb) in NAMED_COLORS.entries() { 8 | let c = parse(name.as_str()).unwrap(); 9 | assert_eq!(c.to_rgba8()[0..3], rgb); 10 | 11 | if skip_list.contains(&name.as_str()) 12 | || name.as_str().contains("gray") 13 | || name.as_str().contains("grey") 14 | { 15 | continue; 16 | } 17 | assert_eq!(c.name(), Some(name.as_str())); 18 | 19 | let [r, g, b] = rgb; 20 | let c = Color::from_rgba8(r, g, b, 255); 21 | assert_eq!(c.name(), Some(name.as_str())); 22 | } 23 | 24 | // Case-insensitive tests 25 | 26 | macro_rules! cmp { 27 | ($a:expr, $b:expr) => { 28 | assert_eq!(parse($a).unwrap().to_rgba8(), parse($b).unwrap().to_rgba8()); 29 | }; 30 | } 31 | 32 | cmp!("red", "RED"); 33 | cmp!("red", "Red"); 34 | cmp!("skyblue", "SKYBLUE"); 35 | cmp!("skyblue", "SkyBlue"); 36 | 37 | // Hex 38 | 39 | #[rustfmt::skip] 40 | let test_data = [ 41 | ("aliceblue", "#f0f8ff"), 42 | ("bisque", "#ffe4c4"), 43 | ("black", "#000000"), 44 | ("chartreuse", "#7fff00"), 45 | ("coral", "#ff7f50"), 46 | ("crimson", "#dc143c"), 47 | ("dodgerblue", "#1e90ff"), 48 | ("firebrick", "#b22222"), 49 | ("gold", "#ffd700"), 50 | ("hotpink", "#ff69b4"), 51 | ("indigo", "#4b0082"), 52 | ("lavender", "#e6e6fa"), 53 | ("lime", "#00ff00"), 54 | ("plum", "#dda0dd"), 55 | ("red", "#ff0000"), 56 | ("salmon", "#fa8072"), 57 | ("skyblue", "#87ceeb"), 58 | ("tomato", "#ff6347"), 59 | ("violet", "#ee82ee"), 60 | ("yellowgreen", "#9acd32"), 61 | ]; 62 | 63 | for (name, hex) in test_data { 64 | let c = csscolorparser::parse(name).unwrap(); 65 | assert_eq!(c.to_css_hex(), hex); 66 | 67 | let c = csscolorparser::parse(hex).unwrap(); 68 | assert_eq!(c.name(), Some(name)); 69 | } 70 | 71 | // Colors without names 72 | 73 | let test_data = [ 74 | Color::new(0.7, 0.8, 0.9, 1.0), 75 | Color::new(1.0, 0.5, 0.0, 1.0), 76 | Color::from_rgba8(0, 50, 100, 255), 77 | ]; 78 | for c in test_data { 79 | assert!(c.name().is_none()); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Overview 2 | //! 3 | //! Rust library for parsing CSS color string as defined in the W3C's [CSS Color Module Level 4](https://www.w3.org/TR/css-color-4/). 4 | //! 5 | //! ## Supported Color Format 6 | //! 7 | //! * [Named colors](https://www.w3.org/TR/css-color-4/#named-colors) 8 | //! * RGB hexadecimal (with and without `#` prefix) 9 | //! + Short format `#rgb` 10 | //! + Short format with alpha `#rgba` 11 | //! + Long format `#rrggbb` 12 | //! + Long format with alpha `#rrggbbaa` 13 | //! * `rgb()` and `rgba()` 14 | //! * `hsl()` and `hsla()` 15 | //! * `hwb()` 16 | //! * `lab()` 17 | //! * `lch()` 18 | //! * `oklab()` 19 | //! * `oklch()` 20 | //! * `hwba()`, `hsv()`, `hsva()` - not in CSS standard. 21 | //! 22 | //! ## Examples 23 | //! 24 | //! Using [`csscolorparser::parse()`](fn.parse.html) function. 25 | //! 26 | //! ```rust 27 | //! # fn main() -> Result<(), Box> { 28 | //! let c = csscolorparser::parse("rgb(100%,0%,0%)")?; 29 | //! 30 | //! assert_eq!(c.to_array(), [1.0, 0.0, 0.0, 1.0]); 31 | //! assert_eq!(c.to_rgba8(), [255, 0, 0, 255]); 32 | //! assert_eq!(c.to_css_hex(), "#ff0000"); 33 | //! assert_eq!(c.to_css_rgb(), "rgb(255 0 0)"); 34 | //! # Ok(()) 35 | //! # } 36 | //! ``` 37 | //! 38 | //! Using `parse()` method on `&str`. 39 | //! 40 | //! ```rust 41 | //! use csscolorparser::Color; 42 | //! # fn main() -> Result<(), Box> { 43 | //! 44 | //! let c: Color = "#ff00007f".parse()?; 45 | //! 46 | //! assert_eq!(c.to_rgba8(), [255, 0, 0, 127]); 47 | //! assert_eq!(c.to_css_hex(), "#ff00007f"); 48 | //! # Ok(()) 49 | //! # } 50 | //! ``` 51 | //! 52 | //! ## Default Feature 53 | //! 54 | //! * `std`: Using the standard library. 55 | //! * `named-colors`: Enables parsing from [named colors](https://www.w3.org/TR/css-color-4/#named-colors). 56 | //! 57 | //! ## Optional Features 58 | //! 59 | //! * `rust-rgb`: Enables converting from [`rgb`](https://crates.io/crates/rgb) crate types into `Color`. 60 | //! * `cint`: Enables converting [`cint`](https://crates.io/crates/cint) crate types to and from `Color`. 61 | //! * `serde`: Enables serializing (into HEX string) and deserializing (from any supported string color format) using [`serde`](https://serde.rs/) framework. 62 | 63 | #![forbid(unsafe_code)] 64 | #![warn(missing_docs)] 65 | #![no_std] 66 | 67 | #[cfg(feature = "std")] 68 | extern crate std; 69 | 70 | extern crate alloc; 71 | 72 | mod color; 73 | mod color2; 74 | pub use color::Color; 75 | 76 | mod error; 77 | pub use error::ParseColorError; 78 | 79 | mod parser; 80 | pub use parser::parse; 81 | 82 | #[cfg(feature = "named-colors")] 83 | mod named_colors; 84 | #[cfg(feature = "named-colors")] 85 | pub use named_colors::NAMED_COLORS; 86 | 87 | #[cfg(feature = "cint")] 88 | mod cint; 89 | 90 | mod lab; 91 | 92 | mod utils; 93 | -------------------------------------------------------------------------------- /src/lab.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "std"))] 2 | use num_traits::float::Float; 3 | 4 | // Constants for D65 white point (normalized to Y=1.0) 5 | const D65_X: f32 = 0.95047; 6 | const D65_Y: f32 = 1.0; 7 | const D65_Z: f32 = 1.08883; 8 | 9 | const DELTA: f32 = 6.0 / 29.0; 10 | const DELTA2: f32 = DELTA * DELTA; 11 | const DELTA3: f32 = DELTA2 * DELTA; 12 | 13 | // Helper function for LAB to XYZ conversion 14 | const fn lab_to_xyz(l: f32, a: f32, b: f32) -> [f32; 3] { 15 | let fy = (l + 16.0) / 116.0; 16 | let fx = fy + a / 500.0; 17 | let fz = fy - b / 200.0; 18 | 19 | const fn lab_f(t: f32) -> f32 { 20 | if t > DELTA { 21 | t * t * t 22 | } else { 23 | (t - 16.0 / 116.0) * 3.0 * DELTA2 24 | } 25 | } 26 | 27 | let x = D65_X * lab_f(fx); 28 | let y = D65_Y * lab_f(fy); 29 | let z = D65_Z * lab_f(fz); 30 | [x, y, z] 31 | } 32 | 33 | #[allow(clippy::excessive_precision)] 34 | // Helper function for XYZ to linear RGB conversion 35 | const fn xyz_to_linear_rgb(x: f32, y: f32, z: f32) -> [f32; 3] { 36 | // sRGB matrix (D65) 37 | let r = 3.2404542 * x - 1.5371385 * y - 0.4985314 * z; 38 | let g = -0.9692660 * x + 1.8760108 * y + 0.0415560 * z; 39 | let b = 0.0556434 * x - 0.2040259 * y + 1.0572252 * z; 40 | [r, g, b] 41 | } 42 | 43 | #[allow(clippy::excessive_precision)] 44 | // Helper function for linear RGB to XYZ conversion 45 | const fn linear_rgb_to_xyz(r: f32, g: f32, b: f32) -> [f32; 3] { 46 | // Inverse sRGB matrix (D65) 47 | let x = 0.4124564 * r + 0.3575761 * g + 0.1804375 * b; 48 | let y = 0.2126729 * r + 0.7151522 * g + 0.0721750 * b; 49 | let z = 0.0193339 * r + 0.1191920 * g + 0.9503041 * b; 50 | [x, y, z] 51 | } 52 | 53 | // Helper function for XYZ to LAB conversion 54 | fn xyz_to_lab(x: f32, y: f32, z: f32) -> [f32; 3] { 55 | let lab_f = |t: f32| -> f32 { 56 | if t > DELTA3 { 57 | t.cbrt() 58 | } else { 59 | (t / (3.0 * DELTA2)) + (4.0 / 29.0) 60 | } 61 | }; 62 | 63 | let fx = lab_f(x / D65_X); 64 | let fy = lab_f(y / D65_Y); 65 | let fz = lab_f(z / D65_Z); 66 | 67 | let l = 116.0 * fy - 16.0; 68 | let a = 500.0 * (fx - fy); 69 | let b = 200.0 * (fy - fz); 70 | 71 | [l, a, b] 72 | } 73 | 74 | // Convert CIELAB (L*a*b*) to linear RGB 75 | // L: [0, 100], a: [-128, 127], b: [-128, 127] 76 | // Returns RGB in [0, 1] range 77 | pub(crate) const fn lab_to_linear_rgb(l: f32, a: f32, b: f32) -> [f32; 3] { 78 | let [x, y, z] = lab_to_xyz(l, a, b); 79 | xyz_to_linear_rgb(x, y, z) 80 | } 81 | 82 | // Convert linear RGB to CIELAB (L*a*b*) 83 | // RGB components in [0, 1] range 84 | // Returns [L, a, b] with L: [0, 100], a: [-128, 127], b: [-128, 127] 85 | pub(crate) fn linear_rgb_to_lab(r: f32, g: f32, b: f32) -> [f32; 3] { 86 | let [x, y, z] = linear_rgb_to_xyz(r, g, b); 87 | xyz_to_lab(x, y, z) 88 | } 89 | -------------------------------------------------------------------------------- /src/cint.rs: -------------------------------------------------------------------------------- 1 | use crate::Color; 2 | use cint::{Alpha, ColorInterop, EncodedSrgb}; 3 | 4 | impl ColorInterop for Color { 5 | type CintTy = Alpha>; 6 | } 7 | 8 | impl From for EncodedSrgb { 9 | fn from(c: Color) -> Self { 10 | let Color { r, g, b, a: _ } = c; 11 | EncodedSrgb { r, g, b } 12 | } 13 | } 14 | 15 | impl From> for Color { 16 | fn from(c: EncodedSrgb) -> Self { 17 | let EncodedSrgb { r, g, b } = c; 18 | Self::new(r, g, b, 1.0) 19 | } 20 | } 21 | 22 | impl From for EncodedSrgb { 23 | fn from(c: Color) -> Self { 24 | let Color { r, g, b, a: _ } = c; 25 | let (r, g, b) = (r as f64, g as f64, b as f64); 26 | EncodedSrgb { r, g, b } 27 | } 28 | } 29 | 30 | impl From> for Color { 31 | fn from(c: EncodedSrgb) -> Self { 32 | let EncodedSrgb { r, g, b } = c; 33 | let (r, g, b) = (r as f32, g as f32, b as f32); 34 | Self::new(r, g, b, 1.0) 35 | } 36 | } 37 | 38 | impl From for Alpha> { 39 | fn from(c: Color) -> Self { 40 | let Color { r, g, b, a } = c; 41 | Alpha { 42 | color: EncodedSrgb { r, g, b }, 43 | alpha: a, 44 | } 45 | } 46 | } 47 | 48 | impl From>> for Color { 49 | fn from(c: Alpha>) -> Self { 50 | let Alpha { 51 | color: EncodedSrgb { r, g, b }, 52 | alpha, 53 | } = c; 54 | Self::new(r, g, b, alpha) 55 | } 56 | } 57 | 58 | impl From for Alpha> { 59 | fn from(c: Color) -> Self { 60 | let Color { r, g, b, a } = c; 61 | let (r, g, b, alpha) = (r as f64, g as f64, b as f64, a as f64); 62 | Alpha { 63 | color: EncodedSrgb { r, g, b }, 64 | alpha, 65 | } 66 | } 67 | } 68 | 69 | impl From>> for Color { 70 | fn from(c: Alpha>) -> Self { 71 | let Alpha { 72 | color: EncodedSrgb { r, g, b }, 73 | alpha, 74 | } = c; 75 | let (r, g, b, alpha) = (r as f32, g as f32, b as f32, alpha as f32); 76 | Self::new(r, g, b, alpha) 77 | } 78 | } 79 | 80 | impl From for EncodedSrgb { 81 | fn from(c: Color) -> Self { 82 | let [r, g, b, _] = c.to_rgba8(); 83 | EncodedSrgb { r, g, b } 84 | } 85 | } 86 | 87 | impl From> for Color { 88 | fn from(c: EncodedSrgb) -> Self { 89 | let EncodedSrgb { r, g, b } = c; 90 | Self::from_rgba8(r, g, b, 255) 91 | } 92 | } 93 | 94 | impl From for Alpha> { 95 | fn from(c: Color) -> Self { 96 | let [r, g, b, alpha] = c.to_rgba8(); 97 | Alpha { 98 | color: EncodedSrgb { r, g, b }, 99 | alpha, 100 | } 101 | } 102 | } 103 | 104 | impl From>> for Color { 105 | fn from(c: Alpha>) -> Self { 106 | let Alpha { 107 | color: EncodedSrgb { r, g, b }, 108 | alpha, 109 | } = c; 110 | Self::from_rgba8(r, g, b, alpha) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Rust CSS Color Parser Library 3 |

4 | 5 |

6 | License 7 | crates.io 8 | Documentation 9 | Build Status 10 | Total Downloads 11 |

12 | 13 |

14 | 15 | DocumentationChangelogFeatures 16 | 17 |

18 | 19 |
20 | 21 | [Rust](https://www.rust-lang.org/) library for parsing CSS color string as defined in the W3C's [CSS Color Module Level 4](https://www.w3.org/TR/css-color-4/). 22 | 23 | ## Supported Color Format 24 | 25 | ### Absolute Color 26 | 27 | * [Named colors](https://www.w3.org/TR/css-color-4/#named-colors) 28 | * RGB hexadecimal (with and without `#` prefix) 29 | + Short format `#rgb` 30 | + Short format with alpha `#rgba` 31 | + Long format `#rrggbb` 32 | + Long format with alpha `#rrggbbaa` 33 | * `rgb()` and `rgba()` 34 | * `hsl()` and `hsla()` 35 | * `hwb()` 36 | * `lab()` 37 | * `lch()` 38 | * `oklab()` 39 | * `oklch()` 40 | * `hwba()`, `hsv()`, `hsva()` - not in CSS standard. 41 | 42 | ### Relative Color 43 | 44 | Example: 45 | 46 | ```text 47 | rgb(from red r g calc(b + 20)) 48 | rgb(from gold calc(((r + g) + b) / 3) 127 127) 49 | hwb(from #bad455 calc(h + 35) w b) 50 | hsl(from purple h s l / 0.5) 51 | ``` 52 | 53 | #### Relative Color Format Limitations 54 | 55 | Doesn't support percentage. 56 | 57 | `calc()` only support the following expression: 58 | 59 | ```[OPERAND] [OPERATOR] [OPERAND]``` 60 | 61 | `OPERAND` can be a number, a variable (`r`, `g`, `b`, `alpha` etc. depends on color function) or another expression wrapped in parenthesis. 62 | `OPERATOR` is one of `+`, `-`, `*` or `/`. 63 | 64 | ##### Not Supported 65 | 66 | ``` 67 | rgb(from #bad455 100% g b) 68 | rgb(from #bad455 r g b / 50%) 69 | rgb(from #bad455 calc(r+g-30) 90 b) 70 | ``` 71 | 72 | ##### OK 73 | 74 | ``` 75 | rgb(from #bad455 255 g b) 76 | rgb(from #bad455 r g b / 0.5) 77 | rgb(from #bad455 calc(r+15) 90 b) 78 | rgb(from #bad455 calc((r+g)-30) 90 b) 79 | hwb(from rgb(from rgb(100% 0% 50%) r g 75) calc(h+25) w b) 80 | ``` 81 | 82 | ## Usage 83 | 84 | Add this to your `Cargo.toml` 85 | 86 | ```toml 87 | csscolorparser = "0.8" 88 | ``` 89 | 90 | ## Examples 91 | 92 | Using `csscolorparser::parse()` function. 93 | 94 | ```rust 95 | let c = csscolorparser::parse("rgb(100%,0%,0%)")?; 96 | 97 | assert_eq!(c.to_array(), [1.0, 0.0, 0.0, 1.0]); 98 | assert_eq!(c.to_rgba8(), [255, 0, 0, 255]); 99 | assert_eq!(c.to_css_hex(), "#ff0000"); 100 | assert_eq!(c.to_css_rgb(), "rgb(255 0 0)"); 101 | assert_eq!(c.name(), Some("red")); 102 | ``` 103 | 104 | Using `parse()` method on `&str`. 105 | 106 | ```rust 107 | use csscolorparser::Color; 108 | 109 | let c: Color = "#ff00007f".parse()?; 110 | 111 | assert_eq!(c.to_rgba8(), [255, 0, 0, 127]); 112 | assert_eq!(c.to_css_hex(), "#ff00007f"); 113 | ``` 114 | 115 | ## Features 116 | 117 | ### Default 118 | 119 | * __std__: Using the standard library. 120 | * __named-colors__: Enables parsing from [named colors](https://www.w3.org/TR/css-color-4/#named-colors). 121 | 122 | Default features can be disabled using `default-features = false`. 123 | 124 | ### Optional 125 | 126 | * __rust-rgb__: Enables converting from [`rgb`](https://crates.io/crates/rgb) crate types into `Color`. 127 | * __cint__: Enables converting [`cint`](https://crates.io/crates/cint) crate types to and from `Color`. 128 | * __serde__: Enables serializing (into HEX string) and deserializing (from any supported string color format) using [`serde`](https://serde.rs/) framework. 129 | 130 | ## Similar Projects 131 | 132 | * [csscolorparser](https://github.com/mazznoer/csscolorparser) (Go) 133 | * [csscolorparser](https://github.com/deanm/css-color-parser-js) (Javascript) 134 | 135 | -------------------------------------------------------------------------------- /tests/parser_relative_color.rs: -------------------------------------------------------------------------------- 1 | use csscolorparser::parse; 2 | 3 | #[test] 4 | fn parser() { 5 | let test_data = [ 6 | ["rgb(FROM #abcdef g B r / Alpha)", "#cdefab"], 7 | [ 8 | "rgb(from rgb(from #bad455 g calc(b + 23) r / alpha) b r calc(g - 23))", 9 | "#bad455", 10 | ], 11 | // --- 12 | ["rgb(from #bad455 r g b)", "#bad455"], 13 | ["rgb(from #bad455 b r g / alpha)", "#55bad4"], 14 | ["rgb(from #bad455 255 0 90)", "#ff005a"], 15 | ["rgb(from #bad455 r g b / 0.2)", "#bad45533"], 16 | ["rgb(from #bad455 r g b / calc(alpha / 2))", "#bad45580"], 17 | [ 18 | "rgb(from #bad455 calc(r + 10) calc(g - 15) calc(b * 0.75))", 19 | "#c4c540", 20 | ], 21 | ["rgb(from #bad455 calc((r + g) / 2) b g)", "#c755d4"], 22 | [ 23 | "rgb(from #bad455 127 100 calc(((r + g) + b) / 3))", 24 | "#7f64a1", 25 | ], 26 | // --- 27 | ["hwb(from #bad455 h w b)", "#bad455"], 28 | ["hwb(from #bad455 h b w)", "#90aa2b"], 29 | ["hwb(from #bad455 0 15 10)", "#e62626"], 30 | [ 31 | "hwb(from #bad455 calc(h + 90) calc(w - 5) calc(b + 10))", 32 | "#48bb99", 33 | ], 34 | // --- 35 | ["hsl(from #bad455 h s l)", "#bad455"], 36 | ["hsl(from #bad455 90 50 65)", "#a6d279"], 37 | ["hsl(from #bad455 h l s)", "#bbd45c"], 38 | ["hsl(from #bad455 calc(h - 45) calc(s + 9) l)", "#de8e4b"], 39 | // --- 40 | ["oklab(from #bad455 l a b)", "#bad455"], 41 | ["oklab(from #bad455 l b a)", "#fe9ff5"], 42 | ["oklab(from #bad455 0.75 -0.2 0.23)", "#60ce00"], 43 | ["oklab(from #bad455 calc(l * 0.7) a b)", "#708500"], 44 | // --- 45 | ["oklch(from #bad455 l c h)", "#bad455"], 46 | ["oklch(from #bad455 0.75 0.1 170)", "#66c3a4"], 47 | /*[ 48 | "oklch(from #bad455 calc(l * 1.5) c calc(h + 180))", 49 | "#ffe7ff", 50 | ],*/ 51 | [ 52 | "oklch(from #bad455 calc(l - 0.15) calc(c * 0.7) h)", 53 | "#8fa150", 54 | ], 55 | // --- 56 | ["lab(from #bad455 l a b)", "#bad455"], 57 | ["lab(from #bad455 l a b / calc(alpha / 2))", "#bad45580"], 58 | // --- 59 | ["lch(from #bad455 l c h)", "#bad455"], 60 | ["lch(from #bad455 l c h / calc(alpha * 0.5))", "#bad45580"], 61 | ]; 62 | for [s, hex] in test_data { 63 | assert_eq!(parse(s).unwrap().to_css_hex(), hex, "{:?}", s); 64 | } 65 | 66 | let test_data = [ 67 | "#ffffff", 68 | "#000000", 69 | "#71fe15", 70 | "#d6e3c9", 71 | "#2a7719", 72 | "#b53717", 73 | "#5b0b8d", 74 | "#aff632", 75 | "#65ec8d", 76 | "#d35493", 77 | "#289e5f", 78 | "#b46152", 79 | "#e0afee", 80 | "#ac2be4", 81 | "#233490", 82 | "#1afbc5", 83 | "#e41755", 84 | "#e052ee", 85 | "#4d1b5e", 86 | "#230cde", 87 | "#f8a243", 88 | "#a130d1", 89 | "#b38373", 90 | "#6b9fa203", 91 | "#0e5e0be6", 92 | "#84f9a716", 93 | "#48651550", 94 | "#1adc2cf4", 95 | "#c191a31c", 96 | "#a25518c5", 97 | "#cb33f2c9", 98 | "#89b21d36", 99 | "#cbb97f3e", 100 | ]; 101 | for hex in test_data { 102 | let p = [ 103 | format!("rgb(from {hex} r g b)"), 104 | format!("hwb(from {hex} h w b / alpha)"), 105 | format!("hsl(from {hex} h s l)"), 106 | format!("hsv(from {hex} h s v)"), 107 | format!("lab(from {hex} l a b)"), 108 | format!("lch(from {hex} l c h)"), 109 | format!("oklab(from {hex} l a b)"), 110 | format!("oklch(from {hex} l c h)"), 111 | ]; 112 | for s in p { 113 | let c = parse(&s); 114 | assert!(c.is_ok(), "{:?}", s); 115 | assert_eq!(hex, c.unwrap().to_css_hex()); 116 | } 117 | } 118 | } 119 | 120 | #[test] 121 | fn invalid() { 122 | let test_data = [ 123 | "rgb(from)", 124 | "rgb(from #f00)", 125 | "rgb(from #abx 255 0 0)", 126 | "rgb(from #f00 r g)", 127 | "rgb(from #f00 r g b 0.5)", 128 | "hwb(from #f00 h w b alpha)", 129 | "rgb(from #f00 r g b / alpha 10)", 130 | "hsl(from #f00 h s x)", 131 | "rgb(from hwb(from hsv(90 0.5 v) h w b) 0 0 0)", 132 | // non ascii 133 | "rgb(ā #f00 r g b)", 134 | "rgb(from â r g b)", 135 | "rgb(from #f00 æ g b)", 136 | "rgb(from #f00 r g b / æ)", 137 | "rgb(from #f00 r calc(ã+15) b)", 138 | "rgb(from #f00 calc(1* (r-æ)) g b)", 139 | "rgb(from #f00 r g b / 1 ã)", 140 | "rgbà(from #f00 r g b)", 141 | "æç(from #f00 r g b)", 142 | ]; 143 | for s in test_data { 144 | assert!(parse(s).is_err(), "{:?}", s); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/color2.rs: -------------------------------------------------------------------------------- 1 | // Color deprecated methods 2 | 3 | use crate::Color; 4 | 5 | use alloc::string::String; 6 | 7 | #[cfg(not(feature = "std"))] 8 | use num_traits::float::Float; 9 | 10 | impl Color { 11 | #[deprecated = "Use [new](#method.new) instead."] 12 | /// Arguments: 13 | /// 14 | /// * `r`: Red value [0..1] 15 | /// * `g`: Green value [0..1] 16 | /// * `b`: Blue value [0..1] 17 | pub fn from_rgb(r: f32, g: f32, b: f32) -> Self { 18 | Self { r, g, b, a: 1.0 } 19 | } 20 | 21 | #[deprecated = "Use [new](#method.new) instead."] 22 | /// Arguments: 23 | /// 24 | /// * `r`: Red value [0..1] 25 | /// * `g`: Green value [0..1] 26 | /// * `b`: Blue value [0..1] 27 | /// * `a`: Alpha value [0..1] 28 | pub fn from_rgba(r: f32, g: f32, b: f32, a: f32) -> Self { 29 | Self { r, g, b, a } 30 | } 31 | 32 | #[deprecated = "Use [from_rgba8](#method.from_rgba8) instead."] 33 | /// Arguments: 34 | /// 35 | /// * `r`: Red value [0..255] 36 | /// * `g`: Green value [0..255] 37 | /// * `b`: Blue value [0..255] 38 | pub fn from_rgb_u8(r: u8, g: u8, b: u8) -> Self { 39 | Self { 40 | r: r as f32 / 255.0, 41 | g: g as f32 / 255.0, 42 | b: b as f32 / 255.0, 43 | a: 1.0, 44 | } 45 | } 46 | 47 | #[deprecated = "Use [from_rgba8](#method.from_rgba8) instead."] 48 | /// Arguments: 49 | /// 50 | /// * `r`: Red value [0..255] 51 | /// * `g`: Green value [0..255] 52 | /// * `b`: Blue value [0..255] 53 | /// * `a`: Alpha value [0..255] 54 | pub fn from_rgba_u8(r: u8, g: u8, b: u8, a: u8) -> Self { 55 | Self { 56 | r: r as f32 / 255.0, 57 | g: g as f32 / 255.0, 58 | b: b as f32 / 255.0, 59 | a: a as f32 / 255.0, 60 | } 61 | } 62 | 63 | #[deprecated = "Use [from_linear_rgba](#method.from_linear_rgba) instead."] 64 | /// Arguments: 65 | /// 66 | /// * `r`: Red value [0..1] 67 | /// * `g`: Green value [0..1] 68 | /// * `b`: Blue value [0..1] 69 | pub fn from_linear_rgb(r: f32, g: f32, b: f32) -> Self { 70 | Self::from_linear_rgba(r, g, b, 1.0) 71 | } 72 | 73 | #[deprecated = "Use [from_linear_rgba8](#method.from_linear_rgba8) instead."] 74 | /// Arguments: 75 | /// 76 | /// * `r`: Red value [0..255] 77 | /// * `g`: Green value [0..255] 78 | /// * `b`: Blue value [0..255] 79 | pub fn from_linear_rgb_u8(r: u8, g: u8, b: u8) -> Self { 80 | Self::from_linear_rgba(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0) 81 | } 82 | 83 | #[deprecated = "Use [from_linear_rgba8](#method.from_linear_rgba8) instead."] 84 | /// Arguments: 85 | /// 86 | /// * `r`: Red value [0..255] 87 | /// * `g`: Green value [0..255] 88 | /// * `b`: Blue value [0..255] 89 | /// * `a`: Alpha value [0..255] 90 | pub fn from_linear_rgba_u8(r: u8, g: u8, b: u8, a: u8) -> Self { 91 | Self::from_linear_rgba( 92 | r as f32 / 255.0, 93 | g as f32 / 255.0, 94 | b as f32 / 255.0, 95 | a as f32 / 255.0, 96 | ) 97 | } 98 | 99 | #[deprecated = "Use [from_hsva](#method.from_hsva) instead."] 100 | /// Arguments: 101 | /// 102 | /// * `h`: Hue angle [0..360] 103 | /// * `s`: Saturation [0..1] 104 | /// * `v`: Value [0..1] 105 | pub fn from_hsv(h: f32, s: f32, v: f32) -> Self { 106 | Self::from_hsva(h, s, v, 1.0) 107 | } 108 | 109 | #[deprecated = "Use [from_hsla](#method.from_hsla) instead."] 110 | /// Arguments: 111 | /// 112 | /// * `h`: Hue angle [0..360] 113 | /// * `s`: Saturation [0..1] 114 | /// * `l`: Lightness [0..1] 115 | pub fn from_hsl(h: f32, s: f32, l: f32) -> Self { 116 | Self::from_hsla(h, s, l, 1.0) 117 | } 118 | 119 | #[deprecated = "Use [from_hwba](#method.from_hwba) instead."] 120 | /// Arguments: 121 | /// 122 | /// * `h`: Hue angle [0..360] 123 | /// * `w`: Whiteness [0..1] 124 | /// * `b`: Blackness [0..1] 125 | pub fn from_hwb(h: f32, w: f32, b: f32) -> Self { 126 | Self::from_hwba(h, w, b, 1.0) 127 | } 128 | 129 | #[deprecated = "Use [from_oklaba](#method.from_oklaba) instead."] 130 | /// Arguments: 131 | /// 132 | /// * `l`: Perceived lightness 133 | /// * `a`: How green/red the color is 134 | /// * `b`: How blue/yellow the color is 135 | pub fn from_oklab(l: f32, a: f32, b: f32) -> Self { 136 | Self::from_oklaba(l, a, b, 1.0) 137 | } 138 | 139 | #[deprecated = "Use [from_laba](#method.from_laba) instead."] 140 | /// Arguments: 141 | /// 142 | /// * `l`: Lightness 143 | /// * `a`: Distance along the `a` axis 144 | /// * `b`: Distance along the `b` axis 145 | /// * `alpha`: Alpha [0..1] 146 | pub fn from_lab(l: f32, a: f32, b: f32, alpha: f32) -> Self { 147 | Self::from_laba(l, a, b, alpha) 148 | } 149 | 150 | #[deprecated = "Use [to_laba](#method.to_laba) instead."] 151 | /// Returns: `[l, a, b, alpha]` 152 | pub fn to_lab(&self) -> [f32; 4] { 153 | self.to_laba() 154 | } 155 | 156 | #[deprecated = "Use [from_lcha](#method.from_lcha) instead."] 157 | /// Arguments: 158 | /// 159 | /// * `l`: Lightness 160 | /// * `c`: Chroma 161 | /// * `h`: Hue angle in radians 162 | /// * `alpha`: Alpha [0..1] 163 | pub fn from_lch(l: f32, c: f32, h: f32, alpha: f32) -> Self { 164 | Self::from_lcha(l, c, h, alpha) 165 | } 166 | 167 | #[deprecated = "Use [to_lcha](#method.to_lcha) instead."] 168 | /// Returns: `[l, c, h, alpha]` 169 | pub fn to_lch(&self) -> [f32; 4] { 170 | self.to_lcha() 171 | } 172 | 173 | #[deprecated] 174 | /// Returns: `(r, g, b, a)` 175 | /// 176 | /// * Red, green, blue and alpha in the range [0..1] 177 | pub fn rgba(&self) -> (f32, f32, f32, f32) { 178 | (self.r, self.g, self.b, self.a) 179 | } 180 | 181 | #[deprecated = "Use [to_rgba8](#method.to_rgba8) instead."] 182 | /// Returns: `(r, g, b, a)` 183 | /// 184 | /// * Red, green, blue and alpha in the range [0..255] 185 | pub fn rgba_u8(&self) -> (u8, u8, u8, u8) { 186 | ( 187 | (self.r * 255.0).round() as u8, 188 | (self.g * 255.0).round() as u8, 189 | (self.b * 255.0).round() as u8, 190 | (self.a * 255.0).round() as u8, 191 | ) 192 | } 193 | 194 | // --- Since version 0.7.2 195 | 196 | #[deprecated = "Use [to_css_hex](#method.to_css_hex) instead."] 197 | /// Get the RGB hexadecimal color string. 198 | pub fn to_hex_string(&self) -> String { 199 | self.to_css_hex() 200 | } 201 | 202 | #[deprecated = "Use [to_css_rgb](#method.to_css_rgb) instead."] 203 | /// Get the CSS `rgb()` format string. 204 | pub fn to_rgb_string(&self) -> String { 205 | self.to_css_rgb() 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | mod calc; 2 | mod helper; 3 | mod param; 4 | 5 | pub use calc::*; 6 | pub use helper::*; 7 | pub use param::*; 8 | 9 | use core::f32::consts::{PI, TAU}; 10 | 11 | const PI_3: f32 = PI * 3.0; 12 | 13 | #[cfg(not(feature = "std"))] 14 | use num_traits::float::Float; 15 | 16 | #[allow(clippy::excessive_precision)] 17 | pub(crate) fn oklab_to_linear_rgb(l: f32, a: f32, b: f32) -> [f32; 3] { 18 | let l_ = (l + 0.3963377774 * a + 0.2158037573 * b).powi(3); 19 | let m_ = (l - 0.1055613458 * a - 0.0638541728 * b).powi(3); 20 | let s_ = (l - 0.0894841775 * a - 1.2914855480 * b).powi(3); 21 | let r = 4.0767416621 * l_ - 3.3077115913 * m_ + 0.2309699292 * s_; 22 | let g = -1.2684380046 * l_ + 2.6097574011 * m_ - 0.3413193965 * s_; 23 | let b = -0.0041960863 * l_ - 0.7034186147 * m_ + 1.7076147010 * s_; 24 | [r, g, b] 25 | } 26 | 27 | #[allow(clippy::excessive_precision)] 28 | pub(crate) fn linear_rgb_to_oklab(r: f32, g: f32, b: f32) -> [f32; 3] { 29 | let l_ = (0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b).cbrt(); 30 | let m_ = (0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b).cbrt(); 31 | let s_ = (0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b).cbrt(); 32 | let l = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_; 33 | let a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_; 34 | let b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_; 35 | [l, a, b] 36 | } 37 | 38 | pub(crate) const fn hue_to_rgb(n1: f32, n2: f32, h: f32) -> f32 { 39 | let h = modulo(h, 6.0); 40 | 41 | if h < 1.0 { 42 | return n1 + ((n2 - n1) * h); 43 | } 44 | 45 | if h < 3.0 { 46 | return n2; 47 | } 48 | 49 | if h < 4.0 { 50 | return n1 + ((n2 - n1) * (4.0 - h)); 51 | } 52 | 53 | n1 54 | } 55 | 56 | // h = 0..360 57 | // s, l = 0..1 58 | // r, g, b = 0..1 59 | pub(crate) const fn hsl_to_rgb(h: f32, s: f32, l: f32) -> [f32; 3] { 60 | if s == 0.0 { 61 | return [l, l, l]; 62 | } 63 | 64 | let n2 = if l < 0.5 { 65 | l * (1.0 + s) 66 | } else { 67 | l + s - (l * s) 68 | }; 69 | 70 | let n1 = 2.0 * l - n2; 71 | let h = h / 60.0; 72 | let r = hue_to_rgb(n1, n2, h + 2.0); 73 | let g = hue_to_rgb(n1, n2, h); 74 | let b = hue_to_rgb(n1, n2, h - 2.0); 75 | [r, g, b] 76 | } 77 | 78 | pub(crate) const fn hwb_to_rgb(hue: f32, white: f32, black: f32) -> [f32; 3] { 79 | if white + black >= 1.0 { 80 | let l = white / (white + black); 81 | return [l, l, l]; 82 | } 83 | 84 | let [r, g, b] = hsl_to_rgb(hue, 1.0, 0.5); 85 | let r = r * (1.0 - white - black) + white; 86 | let g = g * (1.0 - white - black) + white; 87 | let b = b * (1.0 - white - black) + white; 88 | [r, g, b] 89 | } 90 | 91 | #[allow(clippy::float_cmp)] 92 | pub(crate) const fn hsv_to_hsl(h: f32, s: f32, v: f32) -> [f32; 3] { 93 | let l = (2.0 - s) * v / 2.0; 94 | 95 | let s = if l != 0.0 { 96 | if l == 1.0 { 97 | 0.0 98 | } else if l < 0.5 { 99 | s * v / (l * 2.0) 100 | } else { 101 | s * v / (2.0 - l * 2.0) 102 | } 103 | } else { 104 | s 105 | }; 106 | 107 | [h, s, l] 108 | } 109 | 110 | pub(crate) const fn hsv_to_rgb(h: f32, s: f32, v: f32) -> [f32; 3] { 111 | let [h, s, l] = hsv_to_hsl(h, s, v); 112 | hsl_to_rgb(h, s, l) 113 | } 114 | 115 | #[allow(clippy::float_cmp)] 116 | pub(crate) const fn rgb_to_hsv(r: f32, g: f32, b: f32) -> [f32; 3] { 117 | let v = r.max(g.max(b)); 118 | let d = v - r.min(g.min(b)); 119 | 120 | if d == 0.0 { 121 | return [0.0, 0.0, v]; 122 | } 123 | 124 | let s = d / v; 125 | let dr = (v - r) / d; 126 | let dg = (v - g) / d; 127 | let db = (v - b) / d; 128 | 129 | let h = if r == v { 130 | db - dg 131 | } else if g == v { 132 | 2.0 + dr - db 133 | } else { 134 | 4.0 + dg - dr 135 | }; 136 | 137 | let h = (h * 60.0) % 360.0; 138 | [normalize_angle(h), s, v] 139 | } 140 | 141 | #[allow(clippy::float_cmp)] 142 | pub(crate) const fn rgb_to_hsl(r: f32, g: f32, b: f32) -> [f32; 3] { 143 | let min = r.min(g.min(b)); 144 | let max = r.max(g.max(b)); 145 | let l = (max + min) / 2.0; 146 | 147 | if min == max { 148 | return [0.0, 0.0, l]; 149 | } 150 | 151 | let d = max - min; 152 | 153 | let s = if l < 0.5 { 154 | d / (max + min) 155 | } else { 156 | d / (2.0 - max - min) 157 | }; 158 | 159 | let dr = (max - r) / d; 160 | let dg = (max - g) / d; 161 | let db = (max - b) / d; 162 | 163 | let h = if r == max { 164 | db - dg 165 | } else if g == max { 166 | 2.0 + dr - db 167 | } else { 168 | 4.0 + dg - dr 169 | }; 170 | 171 | let h = (h * 60.0) % 360.0; 172 | [normalize_angle(h), s, l] 173 | } 174 | 175 | pub(crate) const fn rgb_to_hwb(r: f32, g: f32, b: f32) -> [f32; 3] { 176 | let [hue, _, _] = rgb_to_hsl(r, g, b); 177 | let white = r.min(g.min(b)); 178 | let black = 1.0 - r.max(g.max(b)); 179 | [hue, white, black] 180 | } 181 | 182 | #[inline] 183 | pub(crate) const fn normalize_angle(t: f32) -> f32 { 184 | ((t % 360.0) + 360.0) % 360.0 185 | } 186 | 187 | #[inline] 188 | pub(crate) const fn interp_angle(a0: f32, a1: f32, t: f32) -> f32 { 189 | let delta = (((a1 - a0) % 360.0) + 540.0) % 360.0 - 180.0; 190 | (a0 + t * delta + 360.0) % 360.0 191 | } 192 | 193 | #[inline] 194 | pub(crate) const fn interp_angle_rad(a0: f32, a1: f32, t: f32) -> f32 { 195 | let delta = (((a1 - a0) % TAU) + PI_3) % TAU - PI; 196 | (a0 + t * delta + TAU) % TAU 197 | } 198 | 199 | #[inline] 200 | pub(crate) const fn modulo(x: f32, n: f32) -> f32 { 201 | (x % n + n) % n 202 | } 203 | 204 | // Map t from range [a, b] to range [c, d] 205 | #[inline] 206 | pub(crate) const fn remap(t: f32, a: f32, b: f32, c: f32, d: f32) -> f32 { 207 | (t - a) * ((d - c) / (b - a)) + c 208 | } 209 | 210 | #[cfg(test)] 211 | mod t { 212 | use super::*; 213 | 214 | #[test] 215 | fn normalize_angle_() { 216 | let data = [ 217 | (0.0, 0.0), 218 | (360.0, 0.0), 219 | (720.0, 0.0), 220 | (400.0, 40.0), 221 | (1155.0, 75.0), 222 | (-360.0, 0.0), 223 | (-90.0, 270.0), 224 | (-765.0, 315.0), 225 | ]; 226 | for (x, expected) in data { 227 | let c = normalize_angle(x); 228 | assert_eq!(expected, c); 229 | } 230 | } 231 | 232 | #[test] 233 | fn interp_angle_() { 234 | let data = [ 235 | ((0.0, 360.0, 0.5), 0.0), 236 | ((360.0, 90.0, 0.0), 0.0), 237 | ((360.0, 90.0, 0.5), 45.0), 238 | ((360.0, 90.0, 1.0), 90.0), 239 | ]; 240 | for ((a, b, t), expected) in data { 241 | let v = interp_angle(a, b, t); 242 | assert_eq!(expected, v); 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /tests/parser2.rs: -------------------------------------------------------------------------------- 1 | use csscolorparser::parse; 2 | 3 | #[test] 4 | fn hex() { 5 | let test_data = [ 6 | "#71fe15", 7 | "#d6e3c9", 8 | "#2a7719", 9 | "#b53717", 10 | "#5b0b8d", 11 | "#aff632", 12 | "#65ec8d", 13 | "#d35493", 14 | "#289e5f", 15 | "#b46152", 16 | "#e0afee", 17 | "#ac2be4", 18 | "#233490", 19 | "#1afbc5", 20 | "#e41755", 21 | "#e052ee", 22 | "#4d1b5e", 23 | "#230cde", 24 | "#f8a243", 25 | "#a130d1", 26 | "#b38373", 27 | "#6b9fa203", 28 | "#0e5e0be6", 29 | "#84f9a716", 30 | "#48651550", 31 | "#1adc2cf4", 32 | "#c191a31c", 33 | "#a25518c5", 34 | "#cb33f2c9", 35 | "#89b21d36", 36 | "#cbb97f3e", 37 | ]; 38 | for s in test_data { 39 | let c = parse(s).unwrap(); 40 | assert_eq!(s, c.to_css_hex()); 41 | } 42 | } 43 | 44 | #[test] 45 | fn rgb() { 46 | let test_data = [ 47 | "rgb(71 175 99)", 48 | "rgb(170 203 72)", 49 | "rgb(45 232 237)", 50 | "rgb(119 1 124)", 51 | "rgb(243 93 86)", 52 | "rgb(223 25 119)", 53 | "rgb(6 44 133)", 54 | "rgb(167 240 237)", 55 | "rgb(97 71 129)", 56 | "rgb(125 68 93)", 57 | "rgb(139 187 62)", 58 | "rgb(100 51 80)", 59 | "rgb(27 249 123)", 60 | "rgb(230 63 99)", 61 | "rgb(241 34 4)", 62 | "rgb(149 222 185)", 63 | "rgb(3 129 213)", 64 | "rgb(88 220 108)", 65 | "rgb(199 169 6)", 66 | "rgb(54 70 163)", 67 | "rgb(90 42 106)", 68 | ]; 69 | for s in test_data { 70 | let c = parse(s).unwrap(); 71 | assert_eq!(s, c.to_css_rgb()); 72 | } 73 | } 74 | 75 | #[test] 76 | fn hsl() { 77 | let test_data = [ 78 | "hsl(0 48% 83%)", 79 | "hsl(17 73% 13%)", 80 | "hsl(35 40% 84%)", 81 | "hsl(53 88% 21%)", 82 | "hsl(71 11% 45%)", 83 | "hsl(89 12% 89%)", 84 | "hsl(107 49% 68%)", 85 | "hsl(125 96% 72%)", 86 | "hsl(143 15% 92%)", 87 | "hsl(161 80% 93%)", 88 | "hsl(179 45% 76%)", 89 | "hsl(197 99% 84%)", 90 | "hsl(215 33% 15%)", 91 | "hsl(233 69% 59%)", 92 | "hsl(251 34% 46%)", 93 | "hsl(269 43% 18%)", 94 | "hsl(287 89% 69%)", 95 | "hsl(305 87% 36%)", 96 | "hsl(323 97% 26%)", 97 | "hsl(341 61% 66%)", 98 | "hsl(359 15% 74%)", 99 | ]; 100 | for s in test_data { 101 | let c = parse(s).unwrap(); 102 | assert_eq!(s, c.to_css_hsl()); 103 | } 104 | } 105 | 106 | #[test] 107 | fn hwb() { 108 | let test_data = [ 109 | "hwb(0 87% 0%)", 110 | "hwb(17 0% 23%)", 111 | "hwb(35 0% 7%)", 112 | "hwb(53 66% 0%)", 113 | "hwb(71 0% 66%)", 114 | "hwb(89 22% 0%)", 115 | "hwb(107 0% 2%)", 116 | "hwb(125 51% 0%)", 117 | "hwb(143 10% 0%)", 118 | "hwb(161 0% 76%)", 119 | "hwb(179 72% 0%)", 120 | "hwb(197 0% 60%)", 121 | "hwb(215 0% 39%)", 122 | "hwb(233 0% 18%)", 123 | "hwb(251 0% 3%)", 124 | "hwb(269 57% 0%)", 125 | "hwb(287 21% 0%)", 126 | "hwb(305 15% 0%)", 127 | "hwb(323 55% 0%)", 128 | "hwb(341 0% 72%)", 129 | "hwb(359 0% 2%)", 130 | ]; 131 | for s in test_data { 132 | let c = parse(s).unwrap(); 133 | assert_eq!(s, c.to_css_hwb()); 134 | } 135 | } 136 | 137 | #[test] 138 | fn oklab() { 139 | let test_data = [ 140 | "oklab(0.623 0.019 -0.359)", 141 | "oklab(0.362 -0.314 -0.035)", 142 | "oklab(0.804 0.166 -0.072)", 143 | "oklab(0.832 0.089 0.265)", 144 | "oklab(0.681 0.038 -0.3)", 145 | "oklab(0.117 -0.192 0.24)", 146 | "oklab(0.651 -0.241 -0.158)", 147 | "oklab(0.421 -0.248 0.053)", 148 | "oklab(0.923 -0.119 -0.288)", 149 | "oklab(0.811 -0.295 0.347)", 150 | "oklab(0.485 -0.368 0.066)", 151 | "oklab(0.905 0.13 -0.163)", 152 | "oklab(0.778 -0.001 0.4)", 153 | "oklab(0.672 0.136 -0.03)", 154 | "oklab(0.926 0.281 0.279)", 155 | "oklab(0.247 0.155 0.379)", 156 | "oklab(0.503 0.042 0.202)", 157 | "oklab(0.792 -0.34 -0.372)", 158 | "oklab(0.877 -0.13 0.222)", 159 | "oklab(0.898 -0.068 -0.239)", 160 | "oklab(0.725 -0.343 -0.352)", 161 | ]; 162 | for s in test_data { 163 | let c = parse(s).unwrap(); 164 | assert_eq!(s, c.to_css_oklab()); 165 | } 166 | } 167 | 168 | #[test] 169 | fn oklch() { 170 | let test_data = [ 171 | "oklch(0.284 0.132 0)", 172 | "oklch(0.314 0.136 17)", 173 | "oklch(0.935 0.398 35)", 174 | "oklch(0.729 0.175 53)", 175 | "oklch(0.157 0.29 71)", 176 | "oklch(0.266 0.365 89)", 177 | "oklch(0.12 0.225 107)", 178 | "oklch(0.532 0.274 125)", 179 | "oklch(0.571 0.201 143)", 180 | "oklch(0.948 0.217 161)", 181 | "oklch(0.501 0.2 179)", 182 | "oklch(0.184 0.308 197)", 183 | "oklch(0.308 0.273 215)", 184 | "oklch(0.874 0.143 233)", 185 | "oklch(0.544 0.186 251)", 186 | "oklch(0.144 0.255 269)", 187 | "oklch(0.997 0.327 287)", 188 | "oklch(0.544 0.22 305)", 189 | "oklch(0.578 0.203 323)", 190 | "oklch(0.819 0.343 341)", 191 | "oklch(0.497 0.188 359)", 192 | ]; 193 | for s in test_data { 194 | let c = parse(s).unwrap(); 195 | assert_eq!(s, c.to_css_oklch()); 196 | } 197 | } 198 | 199 | #[test] 200 | fn lab() { 201 | let test_data = [ 202 | "lab(57.32 70.93 101.73)", 203 | "lab(19.94 -109.64 -111.1)", 204 | "lab(54.5 -21.31 -64.68)", 205 | "lab(10.25 27.72 90.4)", 206 | "lab(33.83 105.64 37.89)", 207 | "lab(83.56 108.72 89.22)", 208 | "lab(40.01 105.35 -85.3)", 209 | "lab(30.1 21.78 -92.17)", 210 | "lab(99.19 0.44 93.11)", 211 | "lab(93.32 55.85 -9.14)", 212 | "lab(78.31 -23.69 -27.56)", 213 | "lab(42.64 -22.56 -45.68)", 214 | "lab(69.27 113.33 37.39)", 215 | "lab(24.6 -37.2 88.99)", 216 | "lab(72.6 -41.31 11.88)", 217 | "lab(12.44 -6.94 69.89)", 218 | "lab(71.23 -91.08 31.29)", 219 | "lab(5.18 65.67 63.53)", 220 | "lab(18.7 19.92 67.2)", 221 | "lab(14.62 71.89 57.13)", 222 | "lab(76.47 77.56 -107.61)", 223 | ]; 224 | for s in test_data { 225 | let c = parse(s).unwrap(); 226 | assert_eq!(s, c.to_css_lab()); 227 | } 228 | } 229 | 230 | #[test] 231 | fn lch() { 232 | let test_data = [ 233 | "lch(16.4 138.03 0)", 234 | "lch(7.88 52.89 17.95)", 235 | "lch(19.43 25.84 35.9)", 236 | "lch(73.85 45.9 53.85)", 237 | "lch(72.85 126.69 71.8)", 238 | "lch(42.26 71.09 89.75)", 239 | "lch(99.21 108.15 107.7)", 240 | "lch(13.05 38.12 125.65)", 241 | "lch(46.73 30.27 143.6)", 242 | "lch(33.88 90.43 161.55)", 243 | "lch(89.29 23.68 179.5)", 244 | "lch(20.69 14.49 197.45)", 245 | "lch(64.73 27.25 215.4)", 246 | "lch(61.08 70.57 233.35)", 247 | "lch(40.84 141.03 251.3)", 248 | "lch(7.45 91.95 269.25)", 249 | "lch(53.33 83.53 287.2)", 250 | "lch(26.3 52.41 305.15)", 251 | "lch(33.6 42.99 323.1)", 252 | "lch(90.17 91 341.05)", 253 | "lch(33.83 10.5 359)", 254 | ]; 255 | for s in test_data { 256 | let c = parse(s).unwrap(); 257 | assert_eq!(s, c.to_css_lch()); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/utils/param.rs: -------------------------------------------------------------------------------- 1 | pub struct ParamParser<'a> { 2 | s: &'a str, 3 | idx: usize, 4 | } 5 | 6 | impl<'a> ParamParser<'a> { 7 | pub fn new(s: &'a str) -> Self { 8 | Self { s, idx: 0 } 9 | } 10 | 11 | // Returns `&str` from current index until space, comma, or slash is found. 12 | // Ignore space, comma, or slash inside parentheses. 13 | // Returns `None` if value not found. 14 | pub fn value(&mut self) -> Option<&'a str> { 15 | if self.is_end() { 16 | return None; 17 | } 18 | 19 | match self.s.as_bytes()[self.idx] { 20 | b' ' => return None, 21 | b',' => return None, 22 | b'/' => return None, 23 | _ => (), 24 | } 25 | 26 | let start = self.idx; 27 | 28 | // parenthesis depth 29 | let mut nesting = 0i32; 30 | 31 | while self.idx < self.s.len() { 32 | let ch = self.s.as_bytes()[self.idx]; 33 | match ch { 34 | b'(' => { 35 | nesting += 1; 36 | self.idx += 1; 37 | } 38 | b')' => { 39 | if nesting > 0 { 40 | nesting -= 1; 41 | } 42 | self.idx += 1; 43 | } 44 | b' ' | b',' | b'/' => { 45 | if nesting == 0 { 46 | // delimiter is *outside* parentheses 47 | break; 48 | } 49 | self.idx += 1; 50 | } 51 | _ => self.idx += 1, 52 | } 53 | } 54 | 55 | Some(&self.s[start..self.idx]) 56 | } 57 | 58 | // Consume one or more spaces. 59 | // Returns true if space is found, false otherwise. 60 | pub fn space(&mut self) -> bool { 61 | let mut found = false; 62 | while self.idx < self.s.len() && self.s.as_bytes()[self.idx] == b' ' { 63 | self.idx += 1; 64 | found = true; 65 | } 66 | found 67 | } 68 | 69 | // Consume one or more spaces, or single comma. 70 | // Spaces is allowed around comma. 71 | // Returns true if one of them is found, false otherwise. 72 | pub fn comma_or_space(&mut self) -> bool { 73 | let mut found_comma = false; 74 | let mut found_space = false; 75 | 76 | while self.idx < self.s.len() { 77 | let ch = self.s.as_bytes()[self.idx]; 78 | match ch { 79 | b' ' => { 80 | found_space = true; 81 | self.idx += 1; 82 | } 83 | b',' => { 84 | if found_comma { 85 | break; 86 | } 87 | found_comma = true; 88 | self.idx += 1; 89 | } 90 | _ => { 91 | break; 92 | } 93 | } 94 | } 95 | 96 | found_comma || found_space 97 | } 98 | 99 | // Consume single comma or single slash. 100 | // Spaces is allowed around comma or slash. 101 | // Returns true if one of them is found, false otherwise. 102 | pub fn comma_or_slash(&mut self) -> bool { 103 | let mut found = false; 104 | 105 | while self.idx < self.s.len() { 106 | let ch = self.s.as_bytes()[self.idx]; 107 | match ch { 108 | b' ' => { 109 | self.idx += 1; 110 | } 111 | b',' | b'/' => { 112 | if found { 113 | break; 114 | } 115 | found = true; 116 | self.idx += 1; 117 | } 118 | _ => { 119 | break; 120 | } 121 | } 122 | } 123 | 124 | found 125 | } 126 | 127 | // Consume a single slash. Spaces is allowed around slash. 128 | // Returns true if a slash is found, false otherwise. 129 | pub fn slash(&mut self) -> bool { 130 | let mut found = false; 131 | 132 | while self.idx < self.s.len() { 133 | let ch = self.s.as_bytes()[self.idx]; 134 | match ch { 135 | b' ' => { 136 | self.idx += 1; 137 | } 138 | b'/' => { 139 | if found { 140 | break; 141 | } 142 | found = true; 143 | self.idx += 1; 144 | } 145 | _ => { 146 | break; 147 | } 148 | } 149 | } 150 | 151 | found 152 | } 153 | 154 | // Returns true is we finished reading the str. 155 | pub fn is_end(&self) -> bool { 156 | self.idx >= self.s.len() 157 | } 158 | } 159 | 160 | #[cfg(test)] 161 | mod t { 162 | use super::*; 163 | 164 | #[test] 165 | fn param_parser() { 166 | let s = " "; 167 | let mut p = ParamParser::new(s); 168 | assert_eq!(p.is_end(), false); 169 | assert!(p.space()); 170 | assert_eq!(p.space(), false); 171 | assert!(p.is_end()); 172 | 173 | let s = "abc "; 174 | let mut p = ParamParser::new(s); 175 | assert_eq!(p.space(), false); 176 | assert_eq!(p.is_end(), false); 177 | assert_eq!(p.value(), Some("abc")); 178 | assert!(p.space()); 179 | assert!(p.is_end()); 180 | 181 | let s = ",, , "; 182 | let mut p = ParamParser::new(s); 183 | assert!(p.comma_or_space()); 184 | assert!(p.comma_or_space()); 185 | assert!(p.comma_or_space()); 186 | assert_eq!(p.comma_or_space(), false); 187 | assert!(p.is_end()); 188 | 189 | let s = "97,ab/5 / 10.7 "; 190 | let mut p = ParamParser::new(s); 191 | assert_eq!(p.slash(), false); 192 | assert_eq!(p.value(), Some("97")); 193 | assert_eq!(p.slash(), false); 194 | assert!(p.comma_or_space()); 195 | assert_eq!(p.value(), Some("ab")); 196 | assert!(p.slash()); 197 | assert_eq!(p.value(), Some("5")); 198 | assert!(p.slash()); 199 | assert_eq!(p.value(), Some("10.7")); 200 | assert!(p.space()); 201 | assert!(p.is_end()); 202 | 203 | let s = " ab(1 2,3),45 , xy cd / 10"; 204 | let mut p = ParamParser::new(s); 205 | assert_eq!(p.value(), None); 206 | assert!(p.space()); 207 | assert_eq!(p.space(), false); 208 | 209 | assert_eq!(p.value(), Some("ab(1 2,3)")); 210 | assert!(p.comma_or_space()); 211 | 212 | assert_eq!(p.value(), Some("45")); 213 | assert!(p.comma_or_space()); 214 | assert_eq!(p.value(), Some("xy")); 215 | assert!(p.comma_or_space()); 216 | assert_eq!(p.value(), Some("cd")); 217 | assert!(p.comma_or_slash()); 218 | assert_eq!(p.is_end(), false); 219 | assert_eq!(p.value(), Some("10")); 220 | assert_eq!(p.space(), false); 221 | assert_eq!(p.value(), None); 222 | assert!(p.is_end()); 223 | 224 | let s = "2.53/9,dog cat,fx(1 2 (56, 78))"; 225 | let mut p = ParamParser::new(s); 226 | assert_eq!(p.value(), Some("2.53")); 227 | assert!(p.comma_or_slash()); 228 | assert_eq!(p.value(), Some("9")); 229 | assert!(p.comma_or_space()); 230 | assert_eq!(p.value(), Some("dog")); 231 | assert!(p.space()); 232 | assert_eq!(p.value(), Some("cat")); 233 | assert!(p.comma_or_slash()); 234 | assert_eq!(p.value(), Some("fx(1 2 (56, 78))")); 235 | assert_eq!(p.comma_or_slash(), false); 236 | assert!(p.is_end()); 237 | 238 | let s = ") ( (9)) ("; 239 | let mut p = ParamParser::new(s); 240 | assert_eq!(p.value(), Some(")")); 241 | assert!(p.space()); 242 | assert_eq!(p.value(), Some("( (9))")); 243 | assert!(p.space()); 244 | assert_eq!(p.value(), Some("(")); 245 | assert!(p.is_end()); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/named_colors.rs: -------------------------------------------------------------------------------- 1 | use uncased::UncasedStr; 2 | 3 | /// Named colors defined in . 4 | pub static NAMED_COLORS: phf::OrderedMap<&'static UncasedStr, [u8; 3]> = phf::phf_ordered_map! { 5 | UncasedStr::new("aliceblue") => [240, 248, 255], 6 | UncasedStr::new("antiquewhite") => [250, 235, 215], 7 | UncasedStr::new("aqua") => [0, 255, 255], 8 | UncasedStr::new("aquamarine") => [127, 255, 212], 9 | UncasedStr::new("azure") => [240, 255, 255], 10 | UncasedStr::new("beige") => [245, 245, 220], 11 | UncasedStr::new("bisque") => [255, 228, 196], 12 | UncasedStr::new("black") => [0, 0, 0], 13 | UncasedStr::new("blanchedalmond") => [255, 235, 205], 14 | UncasedStr::new("blue") => [0, 0, 255], 15 | UncasedStr::new("blueviolet") => [138, 43, 226], 16 | UncasedStr::new("brown") => [165, 42, 42], 17 | UncasedStr::new("burlywood") => [222, 184, 135], 18 | UncasedStr::new("cadetblue") => [95, 158, 160], 19 | UncasedStr::new("chartreuse") => [127, 255, 0], 20 | UncasedStr::new("chocolate") => [210, 105, 30], 21 | UncasedStr::new("coral") => [255, 127, 80], 22 | UncasedStr::new("cornflowerblue") => [100, 149, 237], 23 | UncasedStr::new("cornsilk") => [255, 248, 220], 24 | UncasedStr::new("crimson") => [220, 20, 60], 25 | UncasedStr::new("cyan") => [0, 255, 255], 26 | UncasedStr::new("darkblue") => [0, 0, 139], 27 | UncasedStr::new("darkcyan") => [0, 139, 139], 28 | UncasedStr::new("darkgoldenrod") => [184, 134, 11], 29 | UncasedStr::new("darkgray") => [169, 169, 169], 30 | UncasedStr::new("darkgreen") => [0, 100, 0], 31 | UncasedStr::new("darkgrey") => [169, 169, 169], 32 | UncasedStr::new("darkkhaki") => [189, 183, 107], 33 | UncasedStr::new("darkmagenta") => [139, 0, 139], 34 | UncasedStr::new("darkolivegreen") => [85, 107, 47], 35 | UncasedStr::new("darkorange") => [255, 140, 0], 36 | UncasedStr::new("darkorchid") => [153, 50, 204], 37 | UncasedStr::new("darkred") => [139, 0, 0], 38 | UncasedStr::new("darksalmon") => [233, 150, 122], 39 | UncasedStr::new("darkseagreen") => [143, 188, 143], 40 | UncasedStr::new("darkslateblue") => [72, 61, 139], 41 | UncasedStr::new("darkslategray") => [47, 79, 79], 42 | UncasedStr::new("darkslategrey") => [47, 79, 79], 43 | UncasedStr::new("darkturquoise") => [0, 206, 209], 44 | UncasedStr::new("darkviolet") => [148, 0, 211], 45 | UncasedStr::new("deeppink") => [255, 20, 147], 46 | UncasedStr::new("deepskyblue") => [0, 191, 255], 47 | UncasedStr::new("dimgray") => [105, 105, 105], 48 | UncasedStr::new("dimgrey") => [105, 105, 105], 49 | UncasedStr::new("dodgerblue") => [30, 144, 255], 50 | UncasedStr::new("firebrick") => [178, 34, 34], 51 | UncasedStr::new("floralwhite") => [255, 250, 240], 52 | UncasedStr::new("forestgreen") => [34, 139, 34], 53 | UncasedStr::new("fuchsia") => [255, 0, 255], 54 | UncasedStr::new("gainsboro") => [220, 220, 220], 55 | UncasedStr::new("ghostwhite") => [248, 248, 255], 56 | UncasedStr::new("gold") => [255, 215, 0], 57 | UncasedStr::new("goldenrod") => [218, 165, 32], 58 | UncasedStr::new("gray") => [128, 128, 128], 59 | UncasedStr::new("green") => [0, 128, 0], 60 | UncasedStr::new("greenyellow") => [173, 255, 47], 61 | UncasedStr::new("grey") => [128, 128, 128], 62 | UncasedStr::new("honeydew") => [240, 255, 240], 63 | UncasedStr::new("hotpink") => [255, 105, 180], 64 | UncasedStr::new("indianred") => [205, 92, 92], 65 | UncasedStr::new("indigo") => [75, 0, 130], 66 | UncasedStr::new("ivory") => [255, 255, 240], 67 | UncasedStr::new("khaki") => [240, 230, 140], 68 | UncasedStr::new("lavender") => [230, 230, 250], 69 | UncasedStr::new("lavenderblush") => [255, 240, 245], 70 | UncasedStr::new("lawngreen") => [124, 252, 0], 71 | UncasedStr::new("lemonchiffon") => [255, 250, 205], 72 | UncasedStr::new("lightblue") => [173, 216, 230], 73 | UncasedStr::new("lightcoral") => [240, 128, 128], 74 | UncasedStr::new("lightcyan") => [224, 255, 255], 75 | UncasedStr::new("lightgoldenrodyellow") => [250, 250, 210], 76 | UncasedStr::new("lightgray") => [211, 211, 211], 77 | UncasedStr::new("lightgreen") => [144, 238, 144], 78 | UncasedStr::new("lightgrey") => [211, 211, 211], 79 | UncasedStr::new("lightpink") => [255, 182, 193], 80 | UncasedStr::new("lightsalmon") => [255, 160, 122], 81 | UncasedStr::new("lightseagreen") => [32, 178, 170], 82 | UncasedStr::new("lightskyblue") => [135, 206, 250], 83 | UncasedStr::new("lightslategray") => [119, 136, 153], 84 | UncasedStr::new("lightslategrey") => [119, 136, 153], 85 | UncasedStr::new("lightsteelblue") => [176, 196, 222], 86 | UncasedStr::new("lightyellow") => [255, 255, 224], 87 | UncasedStr::new("lime") => [0, 255, 0], 88 | UncasedStr::new("limegreen") => [50, 205, 50], 89 | UncasedStr::new("linen") => [250, 240, 230], 90 | UncasedStr::new("magenta") => [255, 0, 255], 91 | UncasedStr::new("maroon") => [128, 0, 0], 92 | UncasedStr::new("mediumaquamarine") => [102, 205, 170], 93 | UncasedStr::new("mediumblue") => [0, 0, 205], 94 | UncasedStr::new("mediumorchid") => [186, 85, 211], 95 | UncasedStr::new("mediumpurple") => [147, 112, 219], 96 | UncasedStr::new("mediumseagreen") => [60, 179, 113], 97 | UncasedStr::new("mediumslateblue") => [123, 104, 238], 98 | UncasedStr::new("mediumspringgreen") => [0, 250, 154], 99 | UncasedStr::new("mediumturquoise") => [72, 209, 204], 100 | UncasedStr::new("mediumvioletred") => [199, 21, 133], 101 | UncasedStr::new("midnightblue") => [25, 25, 112], 102 | UncasedStr::new("mintcream") => [245, 255, 250], 103 | UncasedStr::new("mistyrose") => [255, 228, 225], 104 | UncasedStr::new("moccasin") => [255, 228, 181], 105 | UncasedStr::new("navajowhite") => [255, 222, 173], 106 | UncasedStr::new("navy") => [0, 0, 128], 107 | UncasedStr::new("oldlace") => [253, 245, 230], 108 | UncasedStr::new("olive") => [128, 128, 0], 109 | UncasedStr::new("olivedrab") => [107, 142, 35], 110 | UncasedStr::new("orange") => [255, 165, 0], 111 | UncasedStr::new("orangered") => [255, 69, 0], 112 | UncasedStr::new("orchid") => [218, 112, 214], 113 | UncasedStr::new("palegoldenrod") => [238, 232, 170], 114 | UncasedStr::new("palegreen") => [152, 251, 152], 115 | UncasedStr::new("paleturquoise") => [175, 238, 238], 116 | UncasedStr::new("palevioletred") => [219, 112, 147], 117 | UncasedStr::new("papayawhip") => [255, 239, 213], 118 | UncasedStr::new("peachpuff") => [255, 218, 185], 119 | UncasedStr::new("peru") => [205, 133, 63], 120 | UncasedStr::new("pink") => [255, 192, 203], 121 | UncasedStr::new("plum") => [221, 160, 221], 122 | UncasedStr::new("powderblue") => [176, 224, 230], 123 | UncasedStr::new("purple") => [128, 0, 128], 124 | UncasedStr::new("rebeccapurple") => [102, 51, 153], 125 | UncasedStr::new("red") => [255, 0, 0], 126 | UncasedStr::new("rosybrown") => [188, 143, 143], 127 | UncasedStr::new("royalblue") => [65, 105, 225], 128 | UncasedStr::new("saddlebrown") => [139, 69, 19], 129 | UncasedStr::new("salmon") => [250, 128, 114], 130 | UncasedStr::new("sandybrown") => [244, 164, 96], 131 | UncasedStr::new("seagreen") => [46, 139, 87], 132 | UncasedStr::new("seashell") => [255, 245, 238], 133 | UncasedStr::new("sienna") => [160, 82, 45], 134 | UncasedStr::new("silver") => [192, 192, 192], 135 | UncasedStr::new("skyblue") => [135, 206, 235], 136 | UncasedStr::new("slateblue") => [106, 90, 205], 137 | UncasedStr::new("slategray") => [112, 128, 144], 138 | UncasedStr::new("slategrey") => [112, 128, 144], 139 | UncasedStr::new("snow") => [255, 250, 250], 140 | UncasedStr::new("springgreen") => [0, 255, 127], 141 | UncasedStr::new("steelblue") => [70, 130, 180], 142 | UncasedStr::new("tan") => [210, 180, 140], 143 | UncasedStr::new("teal") => [0, 128, 128], 144 | UncasedStr::new("thistle") => [216, 191, 216], 145 | UncasedStr::new("tomato") => [255, 99, 71], 146 | UncasedStr::new("turquoise") => [64, 224, 208], 147 | UncasedStr::new("violet") => [238, 130, 238], 148 | UncasedStr::new("wheat") => [245, 222, 179], 149 | UncasedStr::new("white") => [255, 255, 255], 150 | UncasedStr::new("whitesmoke") => [245, 245, 245], 151 | UncasedStr::new("yellow") => [255, 255, 0], 152 | UncasedStr::new("yellowgreen") => [154, 205, 50], 153 | }; 154 | -------------------------------------------------------------------------------- /tests/parser.rs: -------------------------------------------------------------------------------- 1 | use csscolorparser::{parse, Color}; 2 | 3 | #[test] 4 | fn parser() { 5 | let test_data = [ 6 | ("transparent", [0, 0, 0, 0]), 7 | ("TRANSPARENT", [0, 0, 0, 0]), 8 | ("#ff00ff64", [255, 0, 255, 100]), 9 | ("ff00ff64", [255, 0, 255, 100]), 10 | ("rgb(247,179,99)", [247, 179, 99, 255]), 11 | ("rgb(50% 50% 50%)", [128, 128, 128, 255]), 12 | ("rgb(247,179,99,0.37)", [247, 179, 99, 94]), 13 | ("hsl(270 0% 50%)", [128, 128, 128, 255]), 14 | ("hwb(0 50% 50%)", [128, 128, 128, 255]), 15 | ("hsv(0 0% 50%)", [128, 128, 128, 255]), 16 | ("hsv(0 0% 100%)", [255, 255, 255, 255]), 17 | ("hsv(0 0% 19%)", [48, 48, 48, 255]), 18 | ]; 19 | 20 | for (s, expected) in test_data { 21 | let a = parse(s).unwrap().to_rgba8(); 22 | let b = s.parse::().unwrap().to_rgba8(); 23 | let c = Color::from_html(s).unwrap().to_rgba8(); 24 | assert_eq!(expected, a); 25 | assert_eq!(expected, b); 26 | assert_eq!(expected, c); 27 | } 28 | 29 | let test_data = [ 30 | ("lab(0% 0 0)", [0, 0, 0, 255]), 31 | ("lab(100% 0 0)", [255, 255, 255, 255]), 32 | ("lab(0% 0 0 / 0.5)", [0, 0, 0, 128]), 33 | ("lch(0% 0 0)", [0, 0, 0, 255]), 34 | ("lch(100% 0 0)", [255, 255, 255, 255]), 35 | ("lch(0% 0 0 / 0.5)", [0, 0, 0, 128]), 36 | ]; 37 | 38 | for (s, expected) in test_data { 39 | assert_eq!(expected, parse(s).unwrap().to_rgba8()); 40 | } 41 | } 42 | 43 | #[test] 44 | fn equal() { 45 | let test_data = [ 46 | ("transparent", "rgb(0,0,0,0%)"), 47 | ("#FF9900", "#f90"), 48 | ("#aabbccdd", "#ABCD"), 49 | ("#BAD455", "BAD455"), 50 | ("rgb(0 255 127 / 75%)", "rgb(0,255,127,0.75)"), 51 | ("hwb(180 0% 60%)", "hwb(180,0%,60%)"), 52 | ("hwb(290 30% 0%)", "hwb(290 0.3 0)"), 53 | ("hsl(180,50%,27%)", "hsl(180,0.5,0.27)"), 54 | ("rgb(255, 165, 0)", "hsl(38.824 100% 50%)"), 55 | ("#7654CD", "rgb(46.27% 32.94% 80.39%)"), 56 | //#[cfg(feature = "lab")] 57 | //("#7654CD", "lab(44.36% 36.05 -58.99)"), 58 | ]; 59 | 60 | for (a, b) in test_data { 61 | let c1 = parse(a).unwrap(); 62 | let c2 = parse(b).unwrap(); 63 | assert_eq!(c1.to_rgba8(), c2.to_rgba8(), "{:?}", [a, b]); 64 | } 65 | } 66 | 67 | #[test] 68 | fn black() { 69 | let data = [ 70 | "#000", 71 | "#000f", 72 | "#000000", 73 | "#000000ff", 74 | "000", 75 | "000f", 76 | "000000", 77 | "000000ff", 78 | "rgb(0,0,0)", 79 | "rgb(0% 0% 0%)", 80 | "rgb(0 0 0 100%)", 81 | "hsl(270,100%,0%)", 82 | "hwb(90 0% 100%)", 83 | "hwb(120deg 0% 100% 100%)", 84 | "hsv(120 100% 0%)", 85 | "oklab(0 0 0)", 86 | "oklch(0 0 180)", 87 | ]; 88 | 89 | let black = [0, 0, 0, 255]; 90 | 91 | for s in data { 92 | let c = parse(s).unwrap().to_rgba8(); 93 | assert_eq!(black, c); 94 | } 95 | } 96 | 97 | #[test] 98 | fn red() { 99 | let data = [ 100 | "#f00", 101 | "#f00f", 102 | "#ff0000", 103 | "#ff0000ff", 104 | "f00", 105 | "f00f", 106 | "ff0000", 107 | "ff0000ff", 108 | "rgb(255,0,0)", 109 | "rgb(255 0 0)", 110 | "rgb(700, -99, 0)", // clamp to 0..255 111 | "rgb(100% 0% 0%)", 112 | "rgb(200% -10% -100%)", // clamp to 0%..100% 113 | "rgb(255 0 0 100%)", 114 | " RGB ( 255 , 0 , 0 ) ", 115 | "RGB( 255 0 0 )", 116 | "hsl(0,100%,50%)", 117 | "hsl(360 100% 50%)", 118 | "hwb(0 0% 0%)", 119 | "hwb(360deg 0% 0% 100%)", 120 | "hwb(360DEG 0% 0% 100%)", 121 | "hsv(0 100% 100%)", 122 | "oklab(0.62796, 0.22486, 0.12585)", 123 | "oklch(0.62796, 0.25768, 29.23388)", 124 | ]; 125 | 126 | let red = [255, 0, 0, 255]; 127 | 128 | for s in data { 129 | let res = parse(s); 130 | assert!(res.is_ok(), "{:?}", s); 131 | let c = res.unwrap().to_rgba8(); 132 | assert_eq!(red, c); 133 | } 134 | } 135 | 136 | #[test] 137 | fn lime() { 138 | let data = [ 139 | "#0f0", 140 | "#0f0f", 141 | "#00ff00", 142 | "#00ff00ff", 143 | "0f0", 144 | "0f0f", 145 | "00ff00", 146 | "00ff00ff", 147 | "rgb(0,255,0)", 148 | "rgb(0% 100% 0%)", 149 | "rgb(0 255 0 / 100%)", 150 | "rgba(0,255,0,1)", 151 | "hsl(120,100%,50%)", 152 | "hsl(120deg 100% 50%)", 153 | "hsl(-240 100% 50%)", 154 | "hsl(-240deg 100% 50%)", 155 | "hsl(0.3333turn 100% 50%)", 156 | "hsl(0.3333TURN 100% 50%)", 157 | "hsl(133.333grad 100% 50%)", 158 | "hsl(133.333GRAD 100% 50%)", 159 | "hsl(2.0944rad 100% 50%)", 160 | "hsl(2.0944RAD 100% 50%)", 161 | "hsla(120,100%,50%,100%)", 162 | "hwb(120 0% 0%)", 163 | "hwb(480deg 0% 0% / 100%)", 164 | "hsv(120 100% 100%)", 165 | "oklab(0.86644, -0.23389, 0.1795)", 166 | "oklch(0.86644, 0.29483, 142.49535)", 167 | ]; 168 | 169 | let lime = [0, 255, 0, 255]; 170 | 171 | for s in data { 172 | let res = parse(s); 173 | assert!(res.is_ok(), "{:?}", s); 174 | let c = res.unwrap().to_rgba8(); 175 | assert_eq!(lime, c); 176 | } 177 | } 178 | 179 | #[test] 180 | fn lime_alpha() { 181 | let data = [ 182 | "#00ff0080", 183 | "00ff0080", 184 | "rgb(0,255,0,50%)", 185 | "rgb(0% 100% 0% / 0.5)", 186 | "rgba(0%,100%,0%,50%)", 187 | "hsl(120,100%,50%,0.5)", 188 | "hsl(120deg 100% 50% / 50%)", 189 | "hsla(120,100%,50%,0.5)", 190 | "hwb(120 0% 0% / 50%)", 191 | "hsv(120 100% 100% / 50%)", 192 | ]; 193 | 194 | let lime_alpha = [0, 255, 0, 128]; 195 | 196 | for s in data { 197 | let c = parse(s).unwrap().to_rgba8(); 198 | assert_eq!(lime_alpha, c); 199 | } 200 | } 201 | 202 | #[test] 203 | fn none_value() { 204 | let test_data = [ 205 | ["rgb(none none none)", "#000000"], 206 | ["hwb(none none none)", "#ff0000"], 207 | ["hsl(none none none)", "#000000"], 208 | ["lab(none none none)", "#000000"], 209 | ["lch(none none none)", "#000000"], 210 | ["oklab(none none none)", "#000000"], 211 | ["oklch(none none none)", "#000000"], 212 | ["rgb(255 0 255 / none)", "#ff00ff00"], 213 | ["rgb(90 none 210)", "#5a00d2"], 214 | ["rgb(255 none 50%)", "#ff0080"], 215 | ["rgb(none 50% 127)", "#00807f"], 216 | ["rgb(90 35% none)", "#5a5900"], 217 | ["hwb(none 60% 0%)", "#ff9999"], 218 | ["hwb(137 55% none)", "#8cffad"], 219 | ["hsl(none 45% 55%)", "#c05959"], 220 | ["lab(50% -75 none)", "#009275"], 221 | ["lch(50% 100 none)", "#ff007b"], // xxx 222 | ["oklab(60% none -0.17)", "#5a76e4"], 223 | ["oklch(60% 0.2 none)", "#d7397b"], 224 | ]; 225 | for [s, hex] in test_data { 226 | let c = parse(s); 227 | assert!(c.is_ok(), "{:?}", s); 228 | assert_eq!(c.unwrap().to_css_hex(), hex, "{:?}", s); 229 | } 230 | } 231 | 232 | #[cfg(feature = "named-colors")] 233 | #[test] 234 | fn invalid_format() { 235 | let test_data = [ 236 | "", 237 | "bloodred", 238 | "#78afzd", 239 | "#fffff", 240 | "rgb(255,0,0", 241 | "rgb(0,255,8s)", 242 | "rgb(100%,z9%,75%)", 243 | //"rgb(255,0,0%)", // mix format 244 | //"rgb(70%,30%,0)", // mix format 245 | "rgb(255 0 0 0 0)", 246 | "rgb(255 0 0 0 / 0)", 247 | "rgb(,255,0,0)", 248 | "rgb(255,0,,0)", 249 | "rgb(255,0,0,)", 250 | "rgb(255,0,0/)", 251 | "rgb(255/0,0)", 252 | "cmyk(1 0 0)", 253 | "rgba(0 0)", 254 | "hsl(90',100%,50%)", 255 | "hsl(360,70%,50%,90%,100%)", 256 | "hsl(deg 100% 50%)", 257 | "hsl(Xturn 100% 50%)", 258 | "hsl(Zgrad 100% 50%)", 259 | "hsl(180 1 x%)", 260 | //"hsl(360,0%,0)", // mix format 261 | "hsla(360)", 262 | "hwb(Xrad,50%,50%)", 263 | "hwb(270 0% 0% 0% 0%)", 264 | //"hwb(360,0,20%)", // mix format 265 | "hsv(120 100% 100% 1 50%)", 266 | "hsv(120 XXX 100%)", 267 | //"hsv(120,100%,0.5)", //mix format 268 | "lab(100%,0)", 269 | "lab(100% 0 X)", 270 | "lch(100%,0)", 271 | "lch(100% 0 X)", 272 | "oklab(0,0)", 273 | "oklab(0,0,x,0)", 274 | "oklch(0,0,0,0,0)", 275 | "oklch(0,0,0,x)", 276 | "æ", 277 | "#ß", 278 | "rgb(ß,0,0)", 279 | "\u{1F602}", 280 | "#\u{1F602}", 281 | "rgb(\u{1F602},\u{1F602},\u{1F602})", 282 | ]; 283 | 284 | for s in test_data { 285 | let c = parse(s); 286 | assert!(c.is_err(), "{:?}", s); 287 | } 288 | 289 | #[rustfmt::skip] 290 | let test_data = [ 291 | ("#78afzd", "invalid hex format"), 292 | ("rgb(xx,yy,xx)", "invalid rgb format"), 293 | ("rgb(255,0)", "invalid rgb format"), 294 | ("hsl(0,100%,2o%)", "invalid hsl format"), 295 | ("hsv(360)", "invalid hsv format"), 296 | ("hwb(270,0%,0%,x)", "invalid hwb format"), 297 | ("lab(0%)", "invalid lab format"), 298 | ("lch(0%)", "invalid lch format"), 299 | ("oklab(9 20)", "invalid oklab format"), 300 | ("oklch()", "invalid oklch format"), 301 | ("cmyk(0,0,0,0)", "invalid color function"), 302 | ("blood", "invalid unknown format"), 303 | ("rgb(255,0,0", "invalid unknown format"), 304 | ("x£", "invalid unknown format"), 305 | ("x£x", "invalid unknown format"), 306 | ("xxx£x", "invalid unknown format"), 307 | ("xxxxx£x", "invalid unknown format"), 308 | ("\u{1F602}", "invalid unknown format"), 309 | ]; 310 | 311 | for (s, err_msg) in test_data { 312 | let c = parse(s); 313 | assert_eq!(c.unwrap_err().to_string(), err_msg, "{:?}", s); 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /tests/color.rs: -------------------------------------------------------------------------------- 1 | use core::convert::TryFrom; 2 | use csscolorparser::Color; 3 | 4 | #[test] 5 | fn basic() { 6 | let c = Color::new(1.0, 0.0, 0.0, 1.0); 7 | assert_eq!(c.to_array(), [1.0, 0.0, 0.0, 1.0]); 8 | assert_eq!(c.to_rgba8(), [255, 0, 0, 255]); 9 | assert_eq!(c.to_rgba16(), [65535, 0, 0, 65535]); 10 | assert_eq!(c.to_rgba8(), [255, 0, 0, 255]); 11 | assert_eq!(c.to_css_hex(), "#ff0000"); 12 | assert_eq!(c.to_css_rgb(), "rgb(255 0 0)"); 13 | assert_eq!(c.to_string(), "#ff0000"); 14 | assert_eq!(c.to_hsva(), [0.0, 1.0, 1.0, 1.0]); 15 | assert_eq!(c.to_hsla(), [0.0, 1.0, 0.5, 1.0]); 16 | assert_eq!(c.to_hwba(), [0.0, 0.0, 0.0, 1.0]); 17 | assert_eq!(c.to_linear_rgba(), [1.0, 0.0, 0.0, 1.0]); 18 | assert_eq!(c.to_linear_rgba_u8(), [255, 0, 0, 255]); 19 | 20 | let c = Color::new(1.0, 0.0, 0.0, 0.5); 21 | assert_eq!(c.to_rgba8(), [255, 0, 0, 128]); 22 | assert_eq!(c.to_css_hex(), "#ff000080"); 23 | assert_eq!(c.to_css_rgb(), "rgb(255 0 0 / 50%)"); 24 | assert_eq!(c.to_string(), "#ff000080"); 25 | 26 | let c = Color::new(0.0, 1.0, 0.0, 1.0); 27 | assert_eq!(c.to_hsva(), [120.0, 1.0, 1.0, 1.0]); 28 | assert_eq!(c.to_hsla(), [120.0, 1.0, 0.5, 1.0]); 29 | assert_eq!(c.to_hwba(), [120.0, 0.0, 0.0, 1.0]); 30 | 31 | let c = Color::new(0.0, 0.0, 1.0, 1.0); 32 | assert_eq!(c.to_hsva(), [240.0, 1.0, 1.0, 1.0]); 33 | assert_eq!(c.to_hsla(), [240.0, 1.0, 0.5, 1.0]); 34 | assert_eq!(c.to_hwba(), [240.0, 0.0, 0.0, 1.0]); 35 | 36 | let c = Color::new(0.0, 0.0, 0.6, 1.0); 37 | assert_eq!(c.to_hsva(), [240.0, 1.0, 0.6, 1.0]); 38 | assert_eq!(c.to_hsla(), [240.0, 1.0, 0.3, 1.0]); 39 | //assert_eq!(c.to_hwba(), [240.0, 0.0, 0.4, 1.0]); 40 | 41 | let c = Color::new(0.5, 0.5, 0.5, 1.0); 42 | assert_eq!(c.to_hsva(), [0.0, 0.0, 0.5, 1.0]); 43 | assert_eq!(c.to_hsla(), [0.0, 0.0, 0.5, 1.0]); 44 | assert_eq!(c.to_hwba(), [0.0, 0.5, 0.5, 1.0]); 45 | 46 | let c = Color::from_laba(0.0, 0.0, 0.0, 1.0); 47 | assert_eq!(c.to_rgba8(), [0, 0, 0, 255]); 48 | 49 | let c = Color::from_laba(100.0, 0.0, 0.0, 1.0); 50 | assert_eq!(c.to_rgba8(), [255, 255, 255, 255]); 51 | 52 | let c = Color::from_lcha(0.0, 0.0, 0.0, 1.0); 53 | assert_eq!(c.to_rgba8(), [0, 0, 0, 255]); 54 | 55 | let c = Color::from_lcha(100.0, 0.0, 0.0, 1.0); 56 | assert_eq!(c.to_rgba8(), [255, 255, 255, 255]); 57 | 58 | assert_eq!(Color::default().to_rgba8(), [0, 0, 0, 255]); 59 | 60 | assert_eq!( 61 | Color::try_from("#f00").unwrap().to_rgba8(), 62 | [255, 0, 0, 255] 63 | ); 64 | 65 | assert_eq!( 66 | Color::from((1.0, 0.0, 0.0, 0.5)).to_rgba8(), 67 | [255, 0, 0, 128] 68 | ); 69 | assert_eq!(Color::from((1.0, 0.0, 0.0)).to_rgba8(), [255, 0, 0, 255]); 70 | 71 | assert_eq!(Color::from((255, 0, 0, 128)).to_rgba8(), [255, 0, 0, 128]); 72 | assert_eq!(Color::from((255, 0, 0)).to_rgba8(), [255, 0, 0, 255]); 73 | 74 | assert_eq!( 75 | Color::from([1.0, 0.0, 0.0, 0.5]).to_rgba8(), 76 | [255, 0, 0, 128] 77 | ); 78 | assert_eq!(Color::from([1.0, 0.0, 0.0]).to_rgba8(), [255, 0, 0, 255]); 79 | 80 | assert_eq!(Color::from([255, 0, 0, 128]).to_rgba8(), [255, 0, 0, 128]); 81 | assert_eq!(Color::from([255, 0, 0]).to_rgba8(), [255, 0, 0, 255]); 82 | 83 | assert_eq!( 84 | Color::from([0.0_f32, 1.0, 0.5, 1.0]).to_rgba8(), 85 | [0, 255, 128, 255] 86 | ); 87 | assert_eq!( 88 | Color::from([0.0_f32, 1.0, 0.5]).to_rgba8(), 89 | [0, 255, 128, 255] 90 | ); 91 | 92 | // clamping 93 | 94 | let c = Color::new(1.23, 0.5, -0.01, 1.01); 95 | assert_eq!([c.r, c.g, c.b, c.a], [1.23, 0.5, -0.01, 1.01]); 96 | assert_eq!(c.to_array(), [1.0, 0.5, 0.0, 1.0]); 97 | assert_eq!(c.to_rgba8(), [255, 128, 0, 255]); 98 | assert_eq!(c.to_rgba16(), [65535, 32768, 0, 65535]); 99 | 100 | let c = Color::new(1.23, 0.5, -0.01, 1.01).clamp(); 101 | assert_eq!([c.r, c.g, c.b, c.a], [1.0, 0.5, 0.0, 1.0]); 102 | } 103 | 104 | #[test] 105 | fn parser() { 106 | use core::str::FromStr; 107 | let test_data = ["#71fe15", "#d6e3c9", "#2a7719", "#b53717", "#5b0b8d"]; 108 | for s in test_data { 109 | let c = Color::from_str(s).unwrap(); 110 | assert_eq!(c.to_css_hex(), s); 111 | 112 | let c = Color::try_from(s).unwrap(); 113 | assert_eq!(c.to_css_hex(), s); 114 | 115 | let c = Color::try_from(s.to_string()).unwrap(); 116 | assert_eq!(c.to_css_hex(), s); 117 | } 118 | } 119 | 120 | #[test] 121 | fn convert_colors() { 122 | let colors = &[ 123 | //Color::new(1.0, 0.7, 0.1, 1.0), // 124 | Color::from_rgba8(255, 179, 26, 255), 125 | Color::from_rgba8(10, 255, 125, 0), 126 | Color::from_linear_rgba(0.1, 0.9, 1.0, 1.0), 127 | Color::from_hwba(0.0, 0.0, 0.0, 1.0), 128 | Color::from_hwba(320.0, 0.1, 0.3, 1.0), 129 | Color::from_hsva(120.0, 0.3, 0.2, 0.1), 130 | Color::from_hsla(120.0, 0.3, 0.2, 1.0), 131 | ]; 132 | for (i, col) in colors.iter().enumerate() { 133 | println!("{i} -> {}, {}", &col.to_css_hex(), &col.to_css_rgb()); 134 | 135 | let [a, b, c, d] = col.to_linear_rgba(); 136 | let x = Color::from_linear_rgba(a, b, c, d); 137 | assert_eq!(&col.to_css_hex(), &x.to_css_hex()); 138 | 139 | let [a, b, c, d] = col.to_oklaba(); 140 | let x = Color::from_oklaba(a, b, c, d); 141 | assert_eq!(&col.to_css_hex(), &x.to_css_hex()); 142 | } 143 | 144 | let data = &[ 145 | "#000000", 146 | "#ffffff", 147 | "#999999", 148 | "#7f7f7f", 149 | "#ff0000", 150 | "#fa8072", 151 | "#87ceeb", 152 | "#ff6347", 153 | "#ee82ee", 154 | "#9acd32", 155 | "#0aff7d", 156 | "#09ff7d", 157 | "#ffb31a", 158 | "#0aff7d", 159 | "#09ff7d", 160 | "#825dfa6d", 161 | "#abc5679b", 162 | ]; 163 | for s in data { 164 | let col = csscolorparser::parse(s).unwrap(); 165 | assert_eq!(s, &col.to_css_hex()); 166 | 167 | let [a, b, c, d] = col.to_rgba8(); 168 | let x = Color::from_rgba8(a, b, c, d); 169 | assert_eq!(s, &x.to_css_hex()); 170 | 171 | let [a, b, c, d] = col.to_hsva(); 172 | let x = Color::from_hsva(a, b, c, d); 173 | assert_eq!(s, &x.to_css_hex()); 174 | 175 | let [a, b, c, d] = col.to_hsla(); 176 | let x = Color::from_hsla(a, b, c, d); 177 | assert_eq!(s, &x.to_css_hex()); 178 | 179 | let [a, b, c, d] = col.to_hwba(); 180 | let x = Color::from_hwba(a, b, c, d); 181 | assert_eq!(s, &x.to_css_hex()); 182 | 183 | let [a, b, c, d] = col.to_linear_rgba(); 184 | let x = Color::from_linear_rgba(a, b, c, d); 185 | assert_eq!(s, &x.to_css_hex()); 186 | 187 | let [a, b, c, d] = col.to_oklaba(); 188 | let x = Color::from_oklaba(a, b, c, d); 189 | assert_eq!(s, &x.to_css_hex()); 190 | 191 | let [a, b, c, d] = col.to_laba(); 192 | let x = Color::from_laba(a, b, c, d); 193 | assert_eq!(s, &x.to_css_hex()); 194 | 195 | let [a, b, c, d] = col.to_lcha(); 196 | let x = Color::from_lcha(a, b, c, d); 197 | assert_eq!(s, &x.to_css_hex()); 198 | } 199 | } 200 | 201 | #[test] 202 | fn red() { 203 | let data = &[ 204 | Color::new(1.0, 0.0, 0.0, 1.0), 205 | Color::from_rgba8(255, 0, 0, 255), 206 | Color::from_linear_rgba(1.0, 0.0, 0.0, 1.0), 207 | Color::from_linear_rgba8(255, 0, 0, 255), 208 | Color::from_hsva(0.0, 1.0, 1.0, 1.0), 209 | Color::from_hsla(360.0, 1.0, 0.5, 1.0), 210 | Color::from_hwba(0.0, 0.0, 0.0, 1.0), 211 | Color::from_oklaba( 212 | 0.6279151939969809, 213 | 0.2249032308661071, 214 | 0.12580287012451802, 215 | 1.0, 216 | ), 217 | Color::from_html("#f00").unwrap(), 218 | Color::from_html("hsv(360,100%,100%)").unwrap(), 219 | ]; 220 | for c in data { 221 | assert_eq!(c.to_rgba8(), [255, 0, 0, 255]); 222 | } 223 | } 224 | 225 | #[test] 226 | fn interpolate() { 227 | let a = Color::new(0.0, 1.0, 0.0, 1.0); 228 | let b = Color::new(0.0, 0.0, 1.0, 1.0); 229 | 230 | assert_eq!(a.interpolate_rgb(&b, 0.0).to_rgba8(), [0, 255, 0, 255]); 231 | assert_eq!(a.interpolate_rgb(&b, 0.5).to_rgba8(), [0, 128, 128, 255]); 232 | assert_eq!(a.interpolate_rgb(&b, 1.0).to_rgba8(), [0, 0, 255, 255]); 233 | 234 | assert_eq!(b.interpolate_rgb(&a, 0.0).to_rgba8(), [0, 0, 255, 255]); 235 | assert_eq!(b.interpolate_rgb(&a, 0.5).to_rgba8(), [0, 128, 128, 255]); 236 | assert_eq!(b.interpolate_rgb(&a, 1.0).to_rgba8(), [0, 255, 0, 255]); 237 | 238 | assert_eq!( 239 | a.interpolate_linear_rgb(&b, 0.0).to_rgba8(), 240 | [0, 255, 0, 255] 241 | ); 242 | assert_eq!( 243 | a.interpolate_linear_rgb(&b, 0.5).to_rgba8(), 244 | [0, 188, 188, 255] 245 | ); 246 | assert_eq!( 247 | a.interpolate_linear_rgb(&b, 1.0).to_rgba8(), 248 | [0, 0, 255, 255] 249 | ); 250 | 251 | assert_eq!(a.interpolate_hsv(&b, 0.0).to_rgba8(), [0, 255, 0, 255]); 252 | assert_eq!(a.interpolate_hsv(&b, 0.5).to_rgba8(), [0, 255, 255, 255]); 253 | assert_eq!(a.interpolate_hsv(&b, 1.0).to_rgba8(), [0, 0, 255, 255]); 254 | 255 | assert_eq!(a.interpolate_oklab(&b, 0.0).to_rgba8(), [0, 255, 0, 255]); 256 | assert_eq!(a.interpolate_oklab(&b, 0.5).to_rgba8(), [0, 170, 191, 255]); 257 | assert_eq!(a.interpolate_oklab(&b, 1.0).to_rgba8(), [0, 0, 255, 255]); 258 | 259 | assert_eq!(a.interpolate_lab(&b, 0.0).to_rgba8(), [0, 255, 0, 255]); 260 | assert_eq!(a.interpolate_lab(&b, 1.0).to_rgba8(), [0, 0, 255, 255]); 261 | 262 | assert_eq!(a.interpolate_lch(&b, 0.0).to_rgba8(), [0, 255, 0, 255]); 263 | assert_eq!(a.interpolate_lch(&b, 1.0).to_rgba8(), [0, 0, 255, 255]); 264 | } 265 | 266 | #[test] 267 | fn const_fn() { 268 | #![allow(dead_code)] 269 | 270 | const C: Color = Color::new(1.0, 0.0, 0.0, 1.0); 271 | const _: Color = Color::from_rgba8(255, 0, 0, 255); 272 | const _: Color = Color::from_hsla(0.0, 1.0, 1.0, 1.0); 273 | const _: Color = Color::from_hsva(0.0, 1.0, 1.0, 1.0); 274 | const _: Color = Color::from_hwba(0.0, 0.0, 0.3, 1.0); 275 | 276 | const _: Color = C.clamp(); 277 | 278 | const _: [f32; 4] = C.to_array(); 279 | const _: [u8; 4] = C.to_rgba8(); 280 | const _: [u16; 4] = C.to_rgba16(); 281 | const _: [f32; 4] = C.to_hsla(); 282 | const _: [f32; 4] = C.to_hsva(); 283 | const _: [f32; 4] = C.to_hwba(); 284 | 285 | const A: Color = Color::new(0.0, 0.0, 0.0, 1.0); 286 | 287 | const _: Color = A.interpolate_rgb(&C, 0.75); 288 | const _: Color = A.interpolate_hsv(&C, 0.75); 289 | } 290 | -------------------------------------------------------------------------------- /src/utils/calc.rs: -------------------------------------------------------------------------------- 1 | use super::strip_prefix; 2 | 3 | struct CalcParser<'a> { 4 | s: &'a str, 5 | idx: usize, 6 | } 7 | 8 | impl<'a> CalcParser<'a> { 9 | fn new(s: &'a str) -> Self { 10 | Self { s, idx: 0 } 11 | } 12 | 13 | // Returns everything until operator is found. 14 | // Ignore operator inside parentheses. 15 | fn operand(&mut self) -> Option<&'a str> { 16 | if self.is_end() { 17 | return None; 18 | } 19 | 20 | let start = self.idx; 21 | 22 | match self.s.as_bytes()[self.idx] { 23 | b'-' => self.idx += 1, 24 | b'+' => return None, 25 | b'*' => return None, 26 | b'/' => return None, 27 | _ => (), 28 | } 29 | 30 | // parenthesis depth 31 | let mut nesting = 0i32; 32 | 33 | while self.idx < self.s.len() { 34 | let ch = self.s.as_bytes()[self.idx]; 35 | match ch { 36 | b'(' => { 37 | nesting += 1; 38 | self.idx += 1; 39 | } 40 | b')' => { 41 | if nesting > 0 { 42 | nesting -= 1; 43 | } 44 | self.idx += 1; 45 | } 46 | b'+' | b'-' | b'*' | b'/' | b' ' => { 47 | if nesting == 0 { 48 | // operator is *outside* parentheses 49 | break; 50 | } 51 | self.idx += 1; 52 | } 53 | _ => self.idx += 1, 54 | } 55 | } 56 | 57 | Some(&self.s[start..self.idx]) 58 | } 59 | 60 | // Returns first operator found. Skip spaces. 61 | fn operator(&mut self) -> Option { 62 | if self.is_end() { 63 | return None; 64 | } 65 | 66 | let ch = self.s.as_bytes()[self.idx]; 67 | match ch { 68 | b'+' | b'-' | b'*' | b'/' => { 69 | self.idx += 1; 70 | Some(ch) 71 | } 72 | _ => None, 73 | } 74 | } 75 | 76 | fn is_end(&mut self) -> bool { 77 | // Consume all spaces until other character is found. 78 | while self.idx < self.s.len() && self.s.as_bytes()[self.idx] == b' ' { 79 | self.idx += 1; 80 | } 81 | self.idx >= self.s.len() 82 | } 83 | 84 | fn parse(&mut self) -> Option<(&str, u8, &str)> { 85 | if let (Some(va), Some(op), Some(vb), true) = ( 86 | self.operand(), 87 | self.operator(), 88 | self.operand(), 89 | self.is_end(), 90 | ) { 91 | Some((va, op, vb)) 92 | } else { 93 | None 94 | } 95 | } 96 | } 97 | 98 | pub fn parse_values(values: [&str; 4], variables: [(&str, f32); 4]) -> Option<[f32; 4]> { 99 | let parse_v = |s: &str| -> Option { 100 | if let Ok(value) = s.parse() { 101 | return Some(value); 102 | }; 103 | for (var, value) in variables { 104 | if s.eq_ignore_ascii_case(var) { 105 | return Some(value); 106 | } 107 | } 108 | None 109 | }; 110 | 111 | let mut result = [0.0; 4]; 112 | let mut i = 0; 113 | 114 | for s in values { 115 | if let Some(t) = parse_v(s) { 116 | result[i] = t; 117 | i += 1; 118 | continue; 119 | } 120 | 121 | if let Some(s) = strip_prefix(s, "calc") { 122 | if let Some(t) = parse_calc(s, &parse_v) { 123 | result[i] = t; 124 | i += 1; 125 | continue; 126 | } 127 | } 128 | 129 | return None; 130 | } 131 | 132 | Some(result) 133 | } 134 | 135 | fn parse_calc(s: &str, f: &F) -> Option 136 | where 137 | F: Fn(&str) -> Option, 138 | { 139 | if let Some(s) = s.strip_prefix('(') { 140 | if let Some(s) = s.strip_suffix(')') { 141 | let mut p = CalcParser::new(s); 142 | let (va, op, vb) = p.parse()?; 143 | 144 | let va = if let Some(v) = f(va) { 145 | v 146 | } else if let Some(v) = parse_calc(va, f) { 147 | v 148 | } else { 149 | return None; 150 | }; 151 | 152 | let vb = if let Some(v) = f(vb) { 153 | v 154 | } else if let Some(v) = parse_calc(vb, f) { 155 | v 156 | } else { 157 | return None; 158 | }; 159 | 160 | match op { 161 | b'+' => return Some(va + vb), 162 | b'-' => return Some(va - vb), 163 | b'*' => return Some(va * vb), 164 | b'/' => { 165 | if vb == 0.0 { 166 | return None; 167 | } 168 | return Some(va / vb); 169 | } 170 | _ => unreachable!(), 171 | } 172 | } 173 | } 174 | 175 | None 176 | } 177 | 178 | #[cfg(test)] 179 | mod t { 180 | use super::*; 181 | 182 | #[test] 183 | fn calc_parser() { 184 | let s = "78+0.573"; 185 | let mut p = CalcParser::new(s); 186 | assert_eq!(p.operator(), None); 187 | assert_eq!(p.operand(), Some("78")); 188 | assert_eq!(p.operand(), None); 189 | assert_eq!(p.operator(), Some(b'+')); 190 | assert_eq!(p.operator(), None); 191 | assert_eq!(p.operand(), Some("0.573")); 192 | assert_eq!(p.operator(), None); 193 | assert_eq!(p.operand(), None); 194 | assert!(p.is_end()); 195 | assert_eq!(p.parse(), None); 196 | 197 | #[rustfmt::skip] 198 | let test_data = [ 199 | ( 200 | "78+0.573", 201 | ("78", b'+', "0.573"), 202 | ), 203 | ( 204 | "g-100", 205 | ("g", b'-', "100"), 206 | ), 207 | ( 208 | " 9 * alpha ", 209 | ("9", b'*', "alpha"), 210 | ), 211 | ( 212 | "alpha/2", 213 | ("alpha", b'/', "2"), 214 | ), 215 | ( 216 | "-360+-55.07", 217 | ("-360", b'+', "-55.07"), 218 | ), 219 | ( 220 | "-7--5", 221 | ("-7", b'-', "-5"), 222 | ), 223 | ( 224 | "h+(4*0.75)", 225 | ("h", b'+', "(4*0.75)"), 226 | ), 227 | ( 228 | "(0.35*r) / (alpha - 10)", 229 | ("(0.35*r)", b'/', "(alpha - 10)"), 230 | ), 231 | ]; 232 | for (s, expected) in test_data { 233 | let mut p = CalcParser::new(s); 234 | assert_eq!(p.parse(), Some(expected), "{:?}", s); 235 | assert!(p.is_end(), "{:?}", s); 236 | } 237 | 238 | #[rustfmt::skip] 239 | let invalids = [ 240 | "", 241 | " ", 242 | "5", 243 | "g+", 244 | "-", 245 | "7---3", 246 | "*3+2", 247 | "4+5/", 248 | ]; 249 | for s in invalids { 250 | let mut p = CalcParser::new(s); 251 | assert_eq!(p.parse(), None, "{:?}", s); 252 | } 253 | } 254 | 255 | #[test] 256 | fn parse_calc_() { 257 | fn f(s: &str) -> Option { 258 | s.parse().ok() 259 | } 260 | 261 | let test_data = [ 262 | ("(1+3.7)", 4.7), 263 | ("( 0.35 - -0.5 )", 0.85), 264 | ("(2.0*(7-5))", 4.0), 265 | ("((5*10) / (7+3))", 5.0), 266 | ("(0.5 * (5 + (7 * (9 - (3 * (1 + 1))))))", 13.0), 267 | ]; 268 | for (s, expected) in test_data { 269 | assert_eq!(parse_calc(s, &f), Some(expected), "{:?}", s); 270 | } 271 | 272 | let invalids = [ 273 | "", 274 | "5", 275 | "g", 276 | "1+7", 277 | "()", 278 | "(())", 279 | "(())", 280 | "(()+(1*5))", 281 | "(9)", 282 | "(4/0)", 283 | "(1-8", 284 | "7+0.3)", 285 | "(5+(3*2)", 286 | "((5-1)", 287 | "((1+2))", 288 | "(5+(1+2/3))", 289 | "(4+5(1*3))", 290 | "((1+2)1*5)", 291 | ]; 292 | for s in invalids { 293 | assert_eq!(parse_calc(s, &f), None, "{:?}", s); 294 | } 295 | } 296 | 297 | #[test] 298 | fn parse_values_() { 299 | fn parse_value(s: &str, variables: [(&str, f32); 4]) -> Option { 300 | if let Some([t, ..]) = parse_values([s; 4], variables) { 301 | return Some(t); 302 | } 303 | None 304 | } 305 | 306 | let vars = [("r", 255.0), ("g", 127.0), ("b", 0.0), ("alpha", 0.5)]; 307 | let test_data = [ 308 | // simple value 309 | ("130", 130.0), 310 | ("-0.5", -0.5), 311 | ("g", 127.0), 312 | // calc() simple 313 | ("calc(4+5.5)", 9.5), 314 | ("calc( 10 - 7 )", 3.0), 315 | ("CALC(2.5 *2)", 5.0), 316 | ("CaLc(21.0/ 3)", 7.0), 317 | ("calc(r-55)", 200.0), 318 | ("calc(10 + g)", 137.0), 319 | ("calc(alpha*1.5)", 0.75), 320 | // calc() negative number 321 | ("calc(-97+-18)", -115.0), 322 | ("calc( -1 * -45)", 45.0), 323 | ("calc(100--35)", 135.0), 324 | ("calc(100 - -35)", 135.0), 325 | // calc() recursive 326 | ("calc(1.5*(4/2))", 3.0), 327 | ("calc( ( 19 + 6 ) / 5 )", 5.0), 328 | ("calc((2/(1.5+0.5)) - (0.75 - 0.25))", 0.5), 329 | ("calc((r + g) / 2)", 191.0), 330 | ]; 331 | for (s, expected) in test_data { 332 | assert_eq!(parse_value(s, vars), Some(expected), "{:?}", s); 333 | } 334 | 335 | let invalids = [ 336 | "", 337 | "7x", 338 | "h", 339 | "(4+5)", 340 | "cal(4+5)", 341 | "calcs(4+5)", 342 | "calc()", 343 | "calc(-)", 344 | "calc(5)", 345 | "calc(+5)", 346 | "calc(b)", 347 | "calc(g-)", 348 | "calc(5+1-4)", 349 | "calc(1 * 7 +)", 350 | "calc(5 + (1.5))", 351 | "calc(5 + (1.5 * 2 / 3))", 352 | "calc(5 + (2 - ab))", 353 | ]; 354 | for s in invalids { 355 | assert_eq!(parse_value(s, vars), None, "{:?}", s); 356 | } 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020 Nor Khasyatillah 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /tests/chromium.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn random_colors() { 3 | // The color string is randomly generated, then parsed using Chromium 87.0.4280.66 4 | let test_data = [ 5 | ("#9F2", [153, 255, 34, 255]), 6 | ("#919211", [145, 146, 17, 255]), 7 | ("#DF1A94", [223, 26, 148, 255]), 8 | ("#0E9665", [14, 150, 101, 255]), 9 | ("#D39", [221, 51, 153, 255]), 10 | ("#6EF1BB", [110, 241, 187, 255]), 11 | ("#5F2452", [95, 36, 82, 255]), 12 | ("#8E6", [136, 238, 102, 255]), 13 | ("#09BB", [0, 153, 187, 187]), 14 | ("#A51", [170, 85, 17, 255]), 15 | ("#E1FCF3", [225, 252, 243, 255]), 16 | ("#02E3", [0, 34, 238, 51]), 17 | ("#E6B2", [238, 102, 187, 34]), 18 | ("#8CEA3820", [140, 234, 56, 32]), 19 | ("#2B8", [34, 187, 136, 255]), 20 | ("#195", [17, 153, 85, 255]), 21 | ("#4B6", [68, 187, 102, 255]), 22 | ("#9AE", [153, 170, 238, 255]), 23 | ("#9D39E0", [157, 57, 224, 255]), 24 | ("#66E12CAC", [102, 225, 44, 172]), 25 | ("#27EB", [34, 119, 238, 187]), 26 | ("#9C12", [153, 204, 17, 34]), 27 | ("#86113903", [134, 17, 57, 3]), 28 | ("#B1C6ED", [177, 198, 237, 255]), 29 | ("#79D", [119, 153, 221, 255]), 30 | ("#D3F8B137", [211, 248, 177, 55]), 31 | ("#E2858DF2", [226, 133, 141, 242]), 32 | ("#B0DBA6", [176, 219, 166, 255]), 33 | ("#F7F", [255, 119, 255, 255]), 34 | ("#97C9", [153, 119, 204, 153]), 35 | ("#F06", [255, 0, 102, 255]), 36 | ("#243B", [34, 68, 51, 187]), 37 | ("#20DA", [34, 0, 221, 170]), 38 | ("#13C364B8", [19, 195, 100, 184]), 39 | ("#E515", [238, 85, 17, 85]), 40 | ("#A11C", [170, 17, 17, 204]), 41 | ("#488", [68, 136, 136, 255]), 42 | ("#5EE", [85, 238, 238, 255]), 43 | ("#5AB8A1", [90, 184, 161, 255]), 44 | ("#9A9AB8", [154, 154, 184, 255]), 45 | ("#EA9E", [238, 170, 153, 238]), 46 | ("#551AB14D", [85, 26, 177, 77]), 47 | ("#D1D5", [221, 17, 221, 85]), 48 | ("#3C4", [51, 204, 68, 255]), 49 | ("#B615FED3", [182, 21, 254, 211]), 50 | ("#FC7", [255, 204, 119, 255]), 51 | ("#4B10D2", [75, 16, 210, 255]), 52 | ("#2188EC", [33, 136, 236, 255]), 53 | ("#A68F5F9F", [166, 143, 95, 159]), 54 | ("#4670E4", [70, 112, 228, 255]), 55 | ("rgb(56.467,186.479,125.521)", [56, 186, 126, 255]), 56 | ("rgb(227.181,102.976,142.108)", [227, 103, 142, 255]), 57 | ("rgb(30.436,119.528,76.579)", [30, 120, 77, 255]), 58 | ("rgb(93.103,251.965,52.385)", [93, 252, 52, 255]), 59 | ("rgb(161.891,261.654,114.275)", [162, 255, 114, 255]), 60 | ("rgb(235.921,88.153,244.002)", [236, 88, 244, 255]), 61 | ("rgb(185.204,3.571,140.660,1.079)", [185, 4, 141, 255]), 62 | ("rgb(141.230,184.483,37.035)", [141, 184, 37, 255]), 63 | ("rgb(37.685,82.003,192.789,0.283)", [38, 82, 193, 72]), 64 | ("rgb(45.740,9.845,259.876)", [46, 10, 255, 255]), 65 | ("rgb(86.411,188.879,237.442,0.876)", [86, 189, 237, 223]), 66 | ("rgb(156.438,191.727,52.916)", [156, 192, 53, 255]), 67 | ("rgb(216.423,5.520,180.029,0.589)", [216, 6, 180, 150]), 68 | ("rgb(92.120,247.582,38.473)", [92, 248, 38, 255]), 69 | ("rgb(191.287,191.154,217.583)", [191, 191, 218, 255]), 70 | ("rgb(12.653,152.639,193.900)", [13, 153, 194, 255]), 71 | ("rgb(202.973,184.215,144.797)", [203, 184, 145, 255]), 72 | ("rgb(136.571,182.362,162.541,0.148)", [137, 182, 163, 38]), 73 | ("rgb(242.371,-9.883,94.380)", [242, 0, 94, 255]), 74 | ("rgb(225.423,48.220,245.090,0.771)", [225, 48, 245, 197]), 75 | ("rgb(120.844,198.795,84.888)", [121, 199, 85, 255]), 76 | ("rgb(164.768,213.433,79.101,0.592)", [165, 213, 79, 151]), 77 | ("rgb(38.678,179.795,181.240,0.668)", [39, 180, 181, 170]), 78 | ("rgb(20.568,-4.233,125.669)", [21, 0, 126, 255]), 79 | ("rgb(16.521,145.769,228.928,1.141)", [17, 146, 229, 255]), 80 | ("rgb(167.249,179.288,124.859,0.518)", [167, 179, 125, 132]), 81 | ("rgb(89.103,46.835,144.170)", [89, 47, 144, 255]), 82 | ("rgb(135.687,86.916,220.479,0.668)", [136, 87, 220, 170]), 83 | ("rgb(162.398,-6.010,110.044,0.753)", [162, 0, 110, 192]), 84 | ("rgb(232.790,116.598,58.135)", [233, 117, 58, 255]), 85 | ("rgb(151.480,198.087,129.953,0.205)", [151, 198, 130, 52]), 86 | ("rgb(220.157,183.680,73.321)", [220, 184, 73, 255]), 87 | ("rgb(182.469,204.897,82.469,0.901)", [182, 205, 82, 230]), 88 | ("rgb(4.167,34.557,42.727,0.701)", [4, 35, 43, 179]), 89 | ("rgb(43.038,100.728,14.607,1.052)", [43, 101, 15, 255]), 90 | ("rgb(0.045,150.822,2.053)", [0, 151, 2, 255]), 91 | ("rgb(252.789,220.105,226.842)", [253, 220, 227, 255]), 92 | ("rgb(158.071,262.551,21.134,1.191)", [158, 255, 21, 255]), 93 | ("rgb(233.762,23.326,169.395)", [234, 23, 169, 255]), 94 | ("rgb(231.402,104.070,-9.010,1.065)", [231, 104, 0, 255]), 95 | ("rgb(179.325,103.168,150.187,0.284)", [179, 103, 150, 72]), 96 | ("rgb(139.902,99.310,145.976)", [140, 99, 146, 255]), 97 | ("rgb(50.983,172.151,210.545,0.455)", [51, 172, 211, 116]), 98 | ("rgb(17.199,167.421,194.514,0.667)", [17, 167, 195, 170]), 99 | ("rgb(66.992,32.529,37.688,0.161)", [67, 33, 38, 41]), 100 | ("rgb(224.304,84.063,222.623)", [224, 84, 223, 255]), 101 | ("rgb(203.817,45.691,5.316)", [204, 46, 5, 255]), 102 | ("rgb(154.775,257.630,253.717)", [155, 255, 254, 255]), 103 | ("rgb(113.736,190.662,180.949)", [114, 191, 181, 255]), 104 | ("rgb(198.089,-9.253,145.769)", [198, 0, 146, 255]), 105 | ("rgb(36.705%,17.074%,105.740%,-0.093)", [94, 44, 255, 0]), 106 | ("rgb(36.517%,9.269%,20.384%,-0.136)", [93, 24, 52, 0]), 107 | ("rgb(43.466%,65.604%,55.038%)", [111, 167, 140, 255]), 108 | ("rgb(48.285%,63.684%,56.161%)", [123, 162, 143, 255]), 109 | ("rgb(-4.882%,-3.380%,43.553%)", [0, 0, 111, 255]), 110 | ("rgb(27.307%,75.448%,88.504%)", [70, 192, 226, 255]), 111 | ("rgb(69.694%,59.503%,75.063%)", [178, 152, 191, 255]), 112 | ("rgb(90.960%,12.457%,12.447%)", [232, 32, 32, 255]), 113 | ("rgb(-4.622%,36.627%,-8.178%)", [0, 93, 0, 255]), 114 | ("rgb(87.787%,20.447%,43.423%)", [224, 52, 111, 255]), 115 | ("rgb(67.308%,86.285%,72.392%,0.038)", [172, 220, 185, 10]), 116 | ("rgb(103.478%,75.442%,82.578%,0.746)", [255, 192, 211, 190]), 117 | ("rgb(94.011%,90.970%,79.718%)", [240, 232, 203, 255]), 118 | ("rgb(101.439%,74.849%,45.099%)", [255, 191, 115, 255]), 119 | ("rgb(89.327%,31.033%,44.547%)", [228, 79, 114, 255]), 120 | ("rgb(14.834%,42.672%,105.987%,0.244)", [38, 109, 255, 62]), 121 | ("rgb(21.958%,36.904%,78.661%)", [56, 94, 201, 255]), 122 | ("rgb(-6.690%,87.366%,71.478%)", [0, 223, 182, 255]), 123 | ("rgb(28.576%,39.953%,66.600%)", [73, 102, 170, 255]), 124 | ("rgb(40.806%,108.938%,47.153%,1.186)", [104, 255, 120, 255]), 125 | ("rgb(56.260%,70.797%,1.857%)", [143, 181, 5, 255]), 126 | ("rgb(83.108%,-8.247%,-6.553%,0.081)", [212, 0, 0, 21]), 127 | ("rgb(74.833%,49.474%,93.795%,0.274)", [191, 126, 239, 70]), 128 | ("rgb(68.953%,0.784%,100.538%,0.815)", [176, 2, 255, 208]), 129 | ("rgb(82.811%,4.286%,-7.748%)", [211, 11, 0, 255]), 130 | ("rgb(60.598%,108.140%,92.018%)", [155, 255, 235, 255]), 131 | ("rgb(43.708%,17.419%,-0.824%)", [111, 44, 0, 255]), 132 | ("rgb(25.794%,80.139%,104.639%)", [66, 204, 255, 255]), 133 | ("rgb(38.556%,11.224%,-1.615%)", [98, 29, 0, 255]), 134 | ("rgb(16.455%,48.941%,54.732%,0.187)", [42, 125, 140, 48]), 135 | ("rgb(80.390%,-9.653%,33.214%)", [205, 0, 85, 255]), 136 | ("rgb(79.032%,-1.482%,102.516%)", [202, 0, 255, 255]), 137 | ("rgb(84.366%,31.407%,87.727%)", [215, 80, 224, 255]), 138 | ("rgb(75.236%,101.526%,15.918%)", [192, 255, 41, 255]), 139 | ("rgb(41.238%,3.048%,1.219%)", [105, 8, 3, 255]), 140 | ("rgb(100.594%,5.613%,69.223%,0.190)", [255, 14, 177, 48]), 141 | ("rgb(50.523%,91.169%,-9.786%,-0.006)", [129, 232, 0, 0]), 142 | ("rgb(64.531%,35.250%,34.396%,0.566)", [165, 90, 88, 144]), 143 | ("rgb(29.348%,73.790%,37.770%)", [75, 188, 96, 255]), 144 | ("rgb(33.014%,25.582%,90.210%)", [84, 65, 230, 255]), 145 | ("rgb(-5.318%,-3.605%,13.564%)", [0, 0, 35, 255]), 146 | ("rgb(29.700%,52.430%,-9.889%)", [76, 134, 0, 255]), 147 | ("rgb(11.953%,5.255%,59.970%)", [30, 13, 153, 255]), 148 | ("rgb(76.719%,30.732%,61.515%,0.289)", [196, 78, 157, 74]), 149 | ("rgb(100.532%,96.866%,9.823%,0.475)", [255, 247, 25, 121]), 150 | ("rgb(25.194%,36.877%,67.434%,0.940)", [64, 94, 172, 240]), 151 | ("rgb(6.368%,90.828%,14.714%,0.304)", [16, 232, 38, 78]), 152 | ("rgb(90.081%,-4.819%,37.658%)", [230, 0, 96, 255]), 153 | ("rgb(-1.297%,9.539%,70.162%)", [0, 24, 179, 255]), 154 | ("rgb(47.324%,8.645%,-0.478%)", [121, 22, 0, 255]), 155 | ("hsl(0.069turn,21.307%,-3.478%,1.007)", [0, 0, 0, 255]), 156 | ("hsl(72.142,4.735%,-7.464%,1.042)", [0, 0, 0, 255]), 157 | ("hsl(0.890turn,49.391%,38.567%)", [147, 50, 114, 255]), 158 | ("hsl(3.845grad,68.172%,-6.573%)", [0, 0, 0, 255]), 159 | ("hsl(193.837,21.934%,21.649%)", [43, 62, 67, 255]), 160 | ("hsl(162.965deg,6.690%,44.810%)", [107, 122, 118, 255]), 161 | ("hsl(0.468turn,100.941%,37.582%)", [0, 192, 155, 255]), 162 | ("hsl(0.058turn,51.911%,48.034%,-0.122)", [186, 103, 59, 0]), 163 | ("hsl(120.939,66.051%,77.958%,0.145)", [162, 236, 163, 37]), 164 | ("hsl(72.452deg,63.319%,84.331%)", [230, 240, 190, 255]), 165 | ("hsl(155.120grad,106.379%,58.737%)", [45, 255, 113, 255]), 166 | ("hsl(5.240rad,-4.171%,32.487%)", [83, 83, 83, 255]), 167 | ("hsl(4.565rad,-3.467%,20.547%)", [52, 52, 52, 255]), 168 | ("hsl(295.291,7.475%,3.742%)", [10, 9, 10, 255]), 169 | ("hsl(4.287rad,96.686%,41.460%,1.012)", [23, 4, 208, 255]), 170 | ("hsl(0.283turn,95.539%,97.779%,1.064)", [247, 255, 244, 255]), 171 | ("hsl(2.638rad,37.279%,-9.931%)", [0, 0, 0, 255]), 172 | ("hsl(0.295turn,21.487%,35.668%,0.217)", [80, 110, 71, 55]), 173 | ("hsl(60.183deg,41.823%,22.086%)", [80, 80, 33, 255]), 174 | ("hsl(-0.021rad,64.948%,7.915%)", [33, 7, 8, 255]), 175 | ("hsl(121.677deg,15.914%,19.922%,-0.019)", [43, 59, 43, 0]), 176 | ("hsl(2.829rad,-9.457%,77.556%,-0.068)", [198, 198, 198, 0]), 177 | ("hsl(259.505grad,93.623%,67.416%)", [94, 111, 250, 255]), 178 | ("hsl(0.404rad,-4.398%,104.641%)", [255, 255, 255, 255]), 179 | ("hsl(0.840turn,76.343%,94.714%,0.063)", [252, 231, 251, 16]), 180 | ("hsl(49.836grad,83.982%,3.411%)", [16, 12, 1, 255]), 181 | ("hsl(0.018turn,107.944%,20.884%)", [107, 12, 0, 255]), 182 | ("hsl(0.375turn,22.837%,73.947%)", [173, 204, 181, 255]), 183 | ("hsl(331.324deg,84.224%,61.278%,0.830)", [239, 73, 153, 212]), 184 | ("hsl(2.062rad,60.636%,71.565%)", [141, 226, 139, 255]), 185 | ("hsl(91.718grad,48.490%,44.280%)", [127, 168, 58, 255]), 186 | ( 187 | "hsl(255.829deg,62.751%,107.894%,-0.045)", 188 | [255, 255, 255, 0], 189 | ), 190 | ("hsl(98.567grad,-6.159%,27.281%)", [70, 70, 70, 255]), 191 | ("hsl(1.517rad,83.487%,29.247%)", [81, 137, 12, 255]), 192 | ("hsl(83.494deg,99.500%,61.584%)", [178, 255, 60, 255]), 193 | ("hsl(400.564grad,42.181%,-6.001%,1.145)", [0, 0, 0, 255]), 194 | ("hsl(334.198deg,80.826%,90.951%,0.042)", [251, 213, 229, 11]), 195 | ("hsl(154.176,15.036%,62.757%,0.815)", [146, 174, 162, 208]), 196 | ("hsl(0.029rad,57.145%,72.419%)", [225, 147, 144, 255]), 197 | ("hsl(266.629,25.394%,-1.463%,0.584)", [0, 0, 0, 149]), 198 | ("hsl(5.873rad,105.561%,14.063%)", [72, 0, 28, 255]), 199 | ("hsl(5.731rad,38.296%,105.210%,0.659)", [255, 255, 255, 168]), 200 | ("hsl(327.273,65.108%,71.932%)", [230, 137, 188, 255]), 201 | ("hsl(89.405,36.983%,94.707%)", [242, 246, 237, 255]), 202 | ("hsl(0.925turn,78.035%,97.489%)", [254, 244, 248, 255]), 203 | ("hsl(0.000turn,56.799%,105.320%)", [255, 255, 255, 255]), 204 | ("hsl(2.276rad,107.756%,74.291%,0.290)", [124, 255, 147, 74]), 205 | ("hsl(215.619,25.313%,100.025%,-0.116)", [255, 255, 255, 0]), 206 | ("hsl(41.239,30.040%,7.348%)", [24, 21, 13, 255]), 207 | ("hsl(5.794rad,109.895%,71.423%)", [255, 109, 177, 255]), 208 | ]; 209 | for (s, expected) in test_data { 210 | let rgba = csscolorparser::parse(s).unwrap().to_rgba8(); 211 | assert_eq!(expected, rgba); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /tests/chrome_android.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn random_colors() { 3 | // The color string is randomly generated, then parsed using Chrome 87.0 on Android 4 | let test_data = [ 5 | ("#6946", [102, 153, 68, 102]), 6 | ("#4F0", [68, 255, 0, 255]), 7 | ("#75C57A", [117, 197, 122, 255]), 8 | ("#4E0A70", [78, 10, 112, 255]), 9 | ("#A195", [170, 17, 153, 85]), 10 | ("#4A4786", [74, 71, 134, 255]), 11 | ("#FC0", [255, 204, 0, 255]), 12 | ("#517FE4", [81, 127, 228, 255]), 13 | ("#1594A4", [21, 148, 164, 255]), 14 | ("#1B574A", [27, 87, 74, 255]), 15 | ("#9445D48C", [148, 69, 212, 140]), 16 | ("#BC9", [187, 204, 153, 255]), 17 | ("#9E3C6B", [158, 60, 107, 255]), 18 | ("#E92C2E5B", [233, 44, 46, 91]), 19 | ("#563B", [85, 102, 51, 187]), 20 | ("#FAD87DAA", [250, 216, 125, 170]), 21 | ("#B11", [187, 17, 17, 255]), 22 | ("#817B", [136, 17, 119, 187]), 23 | ("#2DD", [34, 221, 221, 255]), 24 | ("#7403B61A", [116, 3, 182, 26]), 25 | ("#02ABBC2F", [2, 171, 188, 47]), 26 | ("#B9A1D823", [185, 161, 216, 35]), 27 | ("#180", [17, 136, 0, 255]), 28 | ("#AB8D15A1", [171, 141, 21, 161]), 29 | ("#B701", [187, 119, 0, 17]), 30 | ("#6DEBD9", [109, 235, 217, 255]), 31 | ("#A09", [170, 0, 153, 255]), 32 | ("#84A2CB", [132, 162, 203, 255]), 33 | ("#361501", [54, 21, 1, 255]), 34 | ("#8F20", [136, 255, 34, 0]), 35 | ("#9EC963", [158, 201, 99, 255]), 36 | ("#B2B50F", [178, 181, 15, 255]), 37 | ("#B038", [187, 0, 51, 136]), 38 | ("#E769EB", [231, 105, 235, 255]), 39 | ("#C4A", [204, 68, 170, 255]), 40 | ("#9E5", [153, 238, 85, 255]), 41 | ("#FF0", [255, 255, 0, 255]), 42 | ("#6DF", [102, 221, 255, 255]), 43 | ("#A41", [170, 68, 17, 255]), 44 | ("#596F0B", [89, 111, 11, 255]), 45 | ("#1A6", [17, 170, 102, 255]), 46 | ("#BB9B0EA9", [187, 155, 14, 169]), 47 | ("#85A", [136, 85, 170, 255]), 48 | ("#938B", [153, 51, 136, 187]), 49 | ("#F89", [255, 136, 153, 255]), 50 | ("#447591", [68, 117, 145, 255]), 51 | ("#8CE6", [136, 204, 238, 102]), 52 | ("#E1528097", [225, 82, 128, 151]), 53 | ("#7BCE11", [123, 206, 17, 255]), 54 | ("#40AECEB7", [64, 174, 206, 183]), 55 | ("rgb(93.170,64.269,73.499)", [93, 64, 73, 255]), 56 | ("rgb(7.929,61.287,229.755,-0.172)", [8, 61, 230, 0]), 57 | ("rgb(59.947,234.441,231.856)", [60, 234, 232, 255]), 58 | ("rgb(40.839,233.232,263.280)", [41, 233, 255, 255]), 59 | ("rgb(146.043,242.067,228.262,1.148)", [146, 242, 228, 255]), 60 | ("rgb(40.800,31.415,141.897,0.721)", [41, 31, 142, 184]), 61 | ("rgb(252.237,162.173,239.848,1.159)", [252, 162, 240, 255]), 62 | ("rgb(187.805,86.185,253.946,1.088)", [188, 86, 254, 255]), 63 | ("rgb(20.891,82.288,149.768)", [21, 82, 150, 255]), 64 | ("rgb(132.275,143.495,131.475)", [132, 143, 131, 255]), 65 | ("rgb(106.238,145.684,129.067)", [106, 146, 129, 255]), 66 | ("rgb(6.092,125.387,194.562,0.176)", [6, 125, 195, 45]), 67 | ("rgb(215.571,177.198,41.277)", [216, 177, 41, 255]), 68 | ("rgb(226.868,90.077,42.413,0.458)", [227, 90, 42, 117]), 69 | ("rgb(79.828,63.604,4.446,-0.071)", [80, 64, 4, 0]), 70 | ("rgb(182.752,129.166,77.250,0.955)", [183, 129, 77, 244]), 71 | ("rgb(214.852,23.610,245.474)", [215, 24, 245, 255]), 72 | ("rgb(132.858,192.196,34.316)", [133, 192, 34, 255]), 73 | ("rgb(222.322,136.431,72.692)", [222, 136, 73, 255]), 74 | ("rgb(263.504,94.097,7.834)", [255, 94, 8, 255]), 75 | ("rgb(254.248,34.996,26.785)", [254, 35, 27, 255]), 76 | ("rgb(154.676,75.369,247.392)", [155, 75, 247, 255]), 77 | ("rgb(98.048,245.136,84.628)", [98, 245, 85, 255]), 78 | ("rgb(257.612,4.746,247.485,0.894)", [255, 5, 247, 228]), 79 | ("rgb(108.478,219.046,146.111)", [108, 219, 146, 255]), 80 | ("rgb(87.414,185.873,26.154,0.536)", [87, 186, 26, 137]), 81 | ("rgb(91.980,117.789,56.497)", [92, 118, 56, 255]), 82 | ("rgb(134.494,228.709,63.294,0.649)", [134, 229, 63, 165]), 83 | ("rgb(44.674,131.163,35.602)", [45, 131, 36, 255]), 84 | ("rgb(127.390,73.029,27.948,0.963)", [127, 73, 28, 246]), 85 | ("rgb(187.426,125.312,219.397)", [187, 125, 219, 255]), 86 | ("rgb(254.713,264.153,258.329)", [255, 255, 255, 255]), 87 | ("rgb(244.510,207.326,178.902,0.339)", [245, 207, 179, 86]), 88 | ("rgb(26.199,-9.612,231.652,0.662)", [26, 0, 232, 169]), 89 | ("rgb(45.304,89.336,172.582)", [45, 89, 173, 255]), 90 | ("rgb(191.387,107.530,36.480)", [191, 108, 36, 255]), 91 | ("rgb(100.149,52.445,27.521,0.677)", [100, 52, 28, 173]), 92 | ("rgb(51.050,84.982,166.808)", [51, 85, 167, 255]), 93 | ("rgb(37.221,239.178,59.749)", [37, 239, 60, 255]), 94 | ("rgb(92.024,78.623,56.212,0.765)", [92, 79, 56, 195]), 95 | ("rgb(61.206,222.657,-6.863,0.526)", [61, 223, 0, 134]), 96 | ("rgb(233.507,205.865,171.023,0.637)", [234, 206, 171, 162]), 97 | ("rgb(135.288,90.821,193.045)", [135, 91, 193, 255]), 98 | ("rgb(18.590,144.907,124.470)", [19, 145, 124, 255]), 99 | ("rgb(241.835,158.227,224.189)", [242, 158, 224, 255]), 100 | ("rgb(184.917,157.120,206.682,0.283)", [185, 157, 207, 72]), 101 | ("rgb(185.019,101.395,60.572)", [185, 101, 61, 255]), 102 | ("rgb(67.718,1.945,198.884)", [68, 2, 199, 255]), 103 | ("rgb(129.479,142.689,149.387,0.186)", [129, 143, 149, 47]), 104 | ("rgb(253.673,-2.343,196.555,-0.144)", [254, 0, 197, 0]), 105 | ("rgb(98.168%,7.203%,51.225%)", [250, 18, 131, 255]), 106 | ("rgb(96.170%,77.147%,85.996%)", [245, 197, 219, 255]), 107 | ("rgb(44.834%,53.204%,-9.634%,0.188)", [114, 136, 0, 48]), 108 | ("rgb(-7.920%,77.363%,-5.832%)", [0, 197, 0, 255]), 109 | ("rgb(81.003%,65.363%,-9.457%)", [207, 167, 0, 255]), 110 | ("rgb(85.894%,7.216%,-7.468%)", [219, 18, 0, 255]), 111 | ("rgb(100.659%,-3.397%,-0.802%,0.330)", [255, 0, 0, 84]), 112 | ("rgb(32.614%,63.257%,55.861%,-0.179)", [83, 161, 142, 0]), 113 | ("rgb(17.267%,78.342%,106.706%)", [44, 200, 255, 255]), 114 | ("rgb(-2.465%,70.281%,64.300%)", [0, 179, 164, 255]), 115 | ("rgb(74.917%,92.472%,35.277%)", [191, 236, 90, 255]), 116 | ("rgb(25.250%,103.119%,9.820%,1.073)", [64, 255, 25, 255]), 117 | ("rgb(16.308%,73.992%,41.494%)", [42, 189, 106, 255]), 118 | ("rgb(33.416%,64.317%,2.900%)", [85, 164, 7, 255]), 119 | ("rgb(86.321%,-7.134%,95.066%,0.407)", [220, 0, 242, 104]), 120 | ("rgb(72.337%,85.660%,37.990%)", [184, 218, 97, 255]), 121 | ("rgb(60.830%,102.371%,64.532%)", [155, 255, 165, 255]), 122 | ("rgb(22.944%,45.301%,57.417%,0.233)", [59, 116, 146, 59]), 123 | ("rgb(-8.796%,77.305%,55.175%)", [0, 197, 141, 255]), 124 | ("rgb(62.273%,88.630%,40.361%,-0.133)", [159, 226, 103, 0]), 125 | ("rgb(9.002%,9.048%,55.050%)", [23, 23, 140, 255]), 126 | ("rgb(39.705%,64.215%,33.386%)", [101, 164, 85, 255]), 127 | ("rgb(79.062%,9.806%,-0.228%)", [202, 25, 0, 255]), 128 | ("rgb(12.557%,26.742%,81.062%,0.187)", [32, 68, 207, 48]), 129 | ("rgb(48.037%,44.658%,94.883%,0.104)", [122, 114, 242, 27]), 130 | ("rgb(70.643%,18.209%,5.847%)", [180, 46, 15, 255]), 131 | ("rgb(17.439%,107.090%,-4.975%,0.301)", [44, 255, 0, 77]), 132 | ("rgb(36.867%,63.947%,53.503%)", [94, 163, 136, 255]), 133 | ("rgb(58.049%,108.306%,41.125%,-0.083)", [148, 255, 105, 0]), 134 | ("rgb(53.613%,-2.382%,20.660%,0.375)", [137, 0, 53, 96]), 135 | ("rgb(17.281%,0.850%,86.809%)", [44, 2, 221, 255]), 136 | ("rgb(28.877%,108.291%,22.159%,0.048)", [74, 255, 57, 12]), 137 | ("rgb(67.469%,33.982%,29.863%)", [172, 87, 76, 255]), 138 | ("rgb(12.841%,42.108%,77.364%)", [33, 107, 197, 255]), 139 | ("rgb(-6.254%,104.573%,54.338%,0.326)", [0, 255, 139, 83]), 140 | ("rgb(23.335%,-7.262%,32.061%)", [60, 0, 82, 255]), 141 | ("rgb(33.559%,104.368%,82.602%)", [86, 255, 211, 255]), 142 | ("rgb(51.030%,84.331%,22.085%)", [130, 215, 56, 255]), 143 | ("rgb(-7.688%,-0.346%,109.870%,0.492)", [0, 0, 255, 125]), 144 | ("rgb(37.791%,66.140%,-2.511%)", [96, 169, 0, 255]), 145 | ("rgb(14.877%,-9.937%,98.026%,0.391)", [38, 0, 250, 100]), 146 | ("rgb(68.965%,54.114%,79.671%,0.786)", [176, 138, 203, 200]), 147 | ("rgb(32.699%,84.074%,12.438%)", [83, 214, 32, 255]), 148 | ("rgb(61.109%,37.962%,9.726%)", [156, 97, 25, 255]), 149 | ("rgb(50.551%,21.936%,91.162%,0.379)", [129, 56, 232, 97]), 150 | ("rgb(12.006%,50.391%,84.702%)", [31, 128, 216, 255]), 151 | ("rgb(58.866%,36.294%,44.703%)", [150, 93, 114, 255]), 152 | ("rgb(73.712%,35.422%,91.533%)", [188, 90, 233, 255]), 153 | ("rgb(35.268%,82.428%,99.633%)", [90, 210, 254, 255]), 154 | ("rgb(77.313%,92.131%,3.558%)", [197, 235, 9, 255]), 155 | ("hsl(191.596grad,43.986%,97.620%)", [246, 252, 251, 255]), 156 | ("hsl(79.060deg,8.776%,101.675%)", [255, 255, 255, 255]), 157 | ( 158 | "hsl(218.222deg,30.111%,58.967%,0.650)", 159 | [119, 142, 182, 166], 160 | ), 161 | ("hsl(-0.032turn,90.086%,72.510%)", [248, 122, 146, 255]), 162 | ("hsl(21.358deg,34.513%,70.780%)", [206, 173, 155, 255]), 163 | ("hsl(248.704deg,41.071%,94.868%,-0.194)", [238, 237, 247, 0]), 164 | ("hsl(0.315turn,88.238%,77.402%,0.394)", [158, 248, 147, 100]), 165 | ("hsl(1.126rad,23.412%,92.759%)", [240, 241, 232, 255]), 166 | ("hsl(221.926,39.531%,81.028%)", [187, 199, 226, 255]), 167 | ("hsl(275.876deg,77.777%,21.408%)", [63, 12, 97, 255]), 168 | ("hsl(0.223turn,6.827%,45.074%)", [117, 123, 107, 255]), 169 | ("hsl(88.794deg,91.390%,9.152%)", [24, 45, 2, 255]), 170 | ("hsl(322.793grad,7.388%,26.361%,0.283)", [71, 62, 72, 72]), 171 | ("hsl(320.912grad,89.287%,50.117%)", [199, 14, 241, 255]), 172 | ("hsl(89.001grad,-6.397%,14.927%)", [38, 38, 38, 255]), 173 | ("hsl(0.315turn,61.008%,39.837%)", [53, 164, 40, 255]), 174 | ("hsl(3.187rad,10.620%,1.837%,0.852)", [4, 5, 5, 217]), 175 | ("hsl(15.726,68.485%,92.900%,0.955)", [249, 231, 224, 244]), 176 | ("hsl(288.124deg,102.249%,26.487%)", [108, 0, 135, 255]), 177 | ( 178 | "hsl(66.360deg,17.120%,104.832%,0.867)", 179 | [255, 255, 255, 221], 180 | ), 181 | ("hsl(199.194deg,105.374%,33.202%)", [0, 115, 169, 255]), 182 | ("hsl(337.002grad,71.223%,56.730%)", [223, 66, 215, 255]), 183 | ("hsl(359.117,89.688%,25.416%,0.329)", [123, 7, 8, 84]), 184 | ("hsl(0.210rad,50.484%,31.972%)", [123, 57, 40, 255]), 185 | ("hsl(-3.642,101.576%,101.667%)", [255, 255, 255, 255]), 186 | ("hsl(0.904turn,97.186%,99.315%)", [255, 252, 254, 255]), 187 | ("hsl(0.123turn,64.031%,76.579%)", [234, 213, 157, 255]), 188 | ("hsl(165.918grad,2.876%,-4.821%,-0.128)", [0, 0, 0, 0]), 189 | ("hsl(47.128grad,86.109%,42.237%)", [200, 146, 15, 255]), 190 | ("hsl(357.805,66.392%,90.933%,0.084)", [247, 217, 218, 21]), 191 | ("hsl(59.582deg,72.571%,70.685%)", [234, 234, 126, 255]), 192 | ("hsl(219.957grad,88.355%,34.566%)", [10, 119, 166, 255]), 193 | ("hsl(184.649,78.952%,88.831%,0.234)", [204, 246, 249, 60]), 194 | ("hsl(243.323,11.432%,-6.552%)", [0, 0, 0, 255]), 195 | ("hsl(2.873rad,97.021%,7.421%)", [1, 37, 28, 255]), 196 | ("hsl(-5.237grad,32.508%,7.772%,0.700)", [26, 13, 14, 179]), 197 | ("hsl(0.065turn,74.685%,70.874%,-0.026)", [236, 169, 125, 0]), 198 | ("hsl(209.534grad,-3.497%,28.848%)", [74, 74, 74, 255]), 199 | ("hsl(120.884,48.350%,6.898%)", [9, 26, 9, 255]), 200 | ("hsl(327.151grad,8.405%,32.912%)", [90, 77, 91, 255]), 201 | ("hsl(3.470rad,38.504%,41.877%)", [66, 122, 148, 255]), 202 | ("hsl(5.489rad,88.555%,44.840%,0.832)", [216, 13, 167, 212]), 203 | ("hsl(0.295turn,34.256%,11.399%)", [24, 39, 19, 255]), 204 | ( 205 | "hsl(70.479deg,86.200%,102.072%,0.789)", 206 | [255, 255, 255, 201], 207 | ), 208 | ("hsl(77.957grad,53.345%,16.670%)", [58, 65, 20, 255]), 209 | ("hsl(5.562rad,11.968%,109.436%)", [255, 255, 255, 255]), 210 | ("hsl(404.677grad,103.003%,9.650%)", [49, 3, 0, 255]), 211 | ("hsl(2.715rad,89.884%,15.064%,-0.083)", [4, 73, 45, 0]), 212 | ("hsl(235.834,104.274%,12.569%)", [0, 4, 64, 255]), 213 | ("hsl(5.068rad,5.213%,83.391%)", [214, 210, 215, 255]), 214 | ]; 215 | for (s, expected) in test_data { 216 | let rgba = csscolorparser::parse(s).unwrap().to_rgba8(); 217 | assert_eq!(expected, rgba); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /tests/firefox.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn random_colors() { 3 | // The color string is randomly generated, then parsed using Mozilla Firefox 84.0.2 4 | let test_data = [ 5 | ("#30758CE9", [48, 117, 140, 233]), 6 | ("#91F76A89", [145, 247, 106, 137]), 7 | ("#CF0206", [207, 2, 6, 255]), 8 | ("#5BD6AC", [91, 214, 172, 255]), 9 | ("#86AFB7FB", [134, 175, 183, 251]), 10 | ("#562", [85, 102, 34, 255]), 11 | ("#67030D", [103, 3, 13, 255]), 12 | ("#52DE306C", [82, 222, 48, 108]), 13 | ("#3425", [51, 68, 34, 85]), 14 | ("#3CF775", [60, 247, 117, 255]), 15 | ("#46D6DAC3", [70, 214, 218, 195]), 16 | ("#5AE", [85, 170, 238, 255]), 17 | ("#6900", [102, 153, 0, 0]), 18 | ("#266A6B69", [38, 106, 107, 105]), 19 | ("#D49C7F", [212, 156, 127, 255]), 20 | ("#29CEDDF2", [41, 206, 221, 242]), 21 | ("#E99", [238, 153, 153, 255]), 22 | ("#462FCA15", [70, 47, 202, 21]), 23 | ("#5ECAB8", [94, 202, 184, 255]), 24 | ("#663A77D6", [102, 58, 119, 214]), 25 | ("#4952", [68, 153, 85, 34]), 26 | ("#427273", [66, 114, 115, 255]), 27 | ("#9866", [153, 136, 102, 102]), 28 | ("#7206AC1C", [114, 6, 172, 28]), 29 | ("#03E9", [0, 51, 238, 153]), 30 | ("#8AB779", [138, 183, 121, 255]), 31 | ("#B6EFB9", [182, 239, 185, 255]), 32 | ("#735E8A", [115, 94, 138, 255]), 33 | ("#F45", [255, 68, 85, 255]), 34 | ("#E8E9", [238, 136, 238, 153]), 35 | ("#67B9FB", [103, 185, 251, 255]), 36 | ("#846A1C", [132, 106, 28, 255]), 37 | ("#DFF17C", [223, 241, 124, 255]), 38 | ("#5CB7", [85, 204, 187, 119]), 39 | ("#895191", [137, 81, 145, 255]), 40 | ("#B05ACA00", [176, 90, 202, 0]), 41 | ("#303B", [51, 0, 51, 187]), 42 | ("#E02D", [238, 0, 34, 221]), 43 | ("#BD202974", [189, 32, 41, 116]), 44 | ("#54F5D0", [84, 245, 208, 255]), 45 | ("#52F24795", [82, 242, 71, 149]), 46 | ("#2BC5", [34, 187, 204, 85]), 47 | ("#8AA7", [136, 170, 170, 119]), 48 | ("#53F55F", [83, 245, 95, 255]), 49 | ("#A4DA", [170, 68, 221, 170]), 50 | ("#CE3E", [204, 238, 51, 238]), 51 | ("#9F7", [153, 255, 119, 255]), 52 | ("#85418C", [133, 65, 140, 255]), 53 | ("#D467", [221, 68, 102, 119]), 54 | ("#D83", [221, 136, 51, 255]), 55 | ("rgb(144.894,221.944,15.338,1.042)", [145, 222, 15, 255]), 56 | ("rgb(172.519,104.262,195.385)", [173, 104, 195, 255]), 57 | ("rgb(197.660,22.336,220.613)", [198, 22, 221, 255]), 58 | ("rgb(172.049,149.391,83.459)", [172, 149, 83, 255]), 59 | ("rgb(15.808,118.246,48.177)", [16, 118, 48, 255]), 60 | ("rgb(110.224,15.299,118.110)", [110, 15, 118, 255]), 61 | ("rgb(61.657,132.868,241.810)", [62, 133, 242, 255]), 62 | ("rgb(109.629,130.088,177.988,0.263)", [110, 130, 178, 67]), 63 | ("rgb(230.646,136.749,248.448,0.049)", [231, 137, 248, 12]), 64 | ("rgb(-7.621,134.924,115.641)", [0, 135, 116, 255]), 65 | ("rgb(26.009,108.413,240.710)", [26, 108, 241, 255]), 66 | ("rgb(21.680,232.551,168.863)", [22, 233, 169, 255]), 67 | ("rgb(111.999,67.957,205.788,1.105)", [112, 68, 206, 255]), 68 | ("rgb(63.877,198.919,221.669,-0.010)", [64, 199, 222, 0]), 69 | ("rgb(142.226,135.238,260.621)", [142, 135, 255, 255]), 70 | ("rgb(59.970,110.510,182.595,-0.069)", [60, 111, 183, 0]), 71 | ("rgb(-2.286,254.610,235.853)", [0, 255, 236, 255]), 72 | ("rgb(-7.958,193.044,235.101)", [0, 193, 235, 255]), 73 | ("rgb(256.609,159.324,257.998)", [255, 159, 255, 255]), 74 | ("rgb(109.388,91.292,241.384)", [109, 91, 241, 255]), 75 | ("rgb(30.402,200.071,253.355)", [30, 200, 253, 255]), 76 | ("rgb(254.429,18.713,262.391)", [254, 19, 255, 255]), 77 | ("rgb(58.353,74.252,20.484,0.240)", [58, 74, 20, 61]), 78 | ("rgb(175.711,57.423,190.797)", [176, 57, 191, 255]), 79 | ("rgb(258.310,48.529,209.761)", [255, 49, 210, 255]), 80 | ("rgb(42.267,221.449,78.031)", [42, 221, 78, 255]), 81 | ("rgb(35.229,195.388,257.837)", [35, 195, 255, 255]), 82 | ("rgb(-1.089,245.407,-3.070,0.320)", [0, 245, 0, 82]), 83 | ("rgb(62.510,251.003,18.592)", [63, 251, 19, 255]), 84 | ("rgb(30.376,73.291,147.311)", [30, 73, 147, 255]), 85 | ("rgb(129.732,260.759,31.529)", [130, 255, 32, 255]), 86 | ("rgb(197.675,25.346,77.167,-0.005)", [198, 25, 77, 0]), 87 | ("rgb(132.191,40.666,142.643)", [132, 41, 143, 255]), 88 | ("rgb(72.049,262.173,213.300)", [72, 255, 213, 255]), 89 | ("rgb(168.366,214.806,197.234)", [168, 215, 197, 255]), 90 | ("rgb(233.079,33.094,211.204)", [233, 33, 211, 255]), 91 | ("rgb(241.575,256.514,243.212,0.915)", [242, 255, 243, 233]), 92 | ("rgb(192.030,70.554,5.442)", [192, 71, 5, 255]), 93 | ("rgb(214.761,181.138,1.594)", [215, 181, 2, 255]), 94 | ("rgb(137.246,159.092,175.243,0.412)", [137, 159, 175, 105]), 95 | ("rgb(-6.852,94.340,165.562,1.115)", [0, 94, 166, 255]), 96 | ("rgb(27.688,165.299,256.621)", [28, 165, 255, 255]), 97 | ("rgb(240.314,99.632,-3.909,0.214)", [240, 100, 0, 55]), 98 | ("rgb(62.626,96.898,45.764,0.580)", [63, 97, 46, 148]), 99 | ("rgb(101.295,61.291,109.920)", [101, 61, 110, 255]), 100 | ("rgb(250.755,243.673,73.583)", [251, 244, 74, 255]), 101 | ("rgb(87.908,173.653,236.399,0.100)", [88, 174, 236, 26]), 102 | ("rgb(1.276,63.625,76.657)", [1, 64, 77, 255]), 103 | ("rgb(182.622,81.426,107.665)", [183, 81, 108, 255]), 104 | ("rgb(97.666,126.808,202.343)", [98, 127, 202, 255]), 105 | ("rgb(19.077%,96.844%,107.049%)", [49, 247, 255, 255]), 106 | ("rgb(60.527%,20.266%,24.231%)", [154, 52, 62, 255]), 107 | ("rgb(-8.659%,45.149%,39.844%)", [0, 115, 102, 255]), 108 | ("rgb(72.862%,88.329%,41.681%)", [186, 225, 106, 255]), 109 | ("rgb(71.619%,55.033%,109.121%)", [183, 140, 255, 255]), 110 | ("rgb(8.320%,59.542%,86.342%,0.535)", [21, 152, 220, 136]), 111 | ("rgb(71.371%,30.476%,87.487%)", [182, 78, 223, 255]), 112 | ("rgb(72.932%,55.921%,36.855%)", [186, 143, 94, 255]), 113 | ("rgb(102.716%,16.386%,95.450%)", [255, 42, 243, 255]), 114 | ("rgb(31.319%,7.413%,83.436%,1.067)", [80, 19, 213, 255]), 115 | ("rgb(30.471%,106.110%,14.952%)", [78, 255, 38, 255]), 116 | ("rgb(73.746%,53.000%,70.974%)", [188, 135, 181, 255]), 117 | ("rgb(53.236%,76.453%,51.996%)", [136, 195, 133, 255]), 118 | ("rgb(74.430%,-9.053%,40.591%,0.372)", [190, 0, 104, 95]), 119 | ("rgb(63.771%,56.927%,61.386%,0.867)", [163, 145, 157, 221]), 120 | ("rgb(82.193%,76.039%,85.366%)", [210, 194, 218, 255]), 121 | ("rgb(-8.062%,84.997%,52.049%)", [0, 217, 133, 255]), 122 | ("rgb(87.040%,18.561%,51.447%)", [222, 47, 131, 255]), 123 | ("rgb(-0.383%,-7.852%,59.846%,0.606)", [0, 0, 153, 155]), 124 | ("rgb(-5.271%,29.630%,24.221%)", [0, 76, 62, 255]), 125 | ("rgb(37.412%,-9.639%,28.126%)", [95, 0, 72, 255]), 126 | ("rgb(49.021%,98.510%,38.101%,0.498)", [125, 251, 97, 127]), 127 | ("rgb(32.297%,10.427%,9.294%,1.078)", [82, 27, 24, 255]), 128 | ("rgb(66.507%,95.944%,5.022%,0.846)", [170, 245, 13, 216]), 129 | ("rgb(61.872%,37.070%,49.019%)", [158, 95, 125, 255]), 130 | ("rgb(71.738%,44.537%,91.829%)", [183, 114, 234, 255]), 131 | ("rgb(103.206%,32.133%,45.463%)", [255, 82, 116, 255]), 132 | ("rgb(72.780%,94.460%,32.319%)", [186, 241, 82, 255]), 133 | ("rgb(1.399%,0.124%,59.130%,0.668)", [4, 0, 151, 170]), 134 | ("rgb(14.442%,4.097%,14.401%,-0.092)", [37, 10, 37, 0]), 135 | ("rgb(104.800%,58.989%,13.037%)", [255, 150, 33, 255]), 136 | ("rgb(98.126%,78.825%,105.629%,0.673)", [250, 201, 255, 172]), 137 | ("rgb(53.607%,90.566%,100.766%,1.127)", [137, 231, 255, 255]), 138 | ("rgb(35.961%,2.549%,12.719%)", [92, 6, 32, 255]), 139 | ("rgb(47.649%,104.780%,20.425%,0.936)", [122, 255, 52, 239]), 140 | ("rgb(19.942%,54.172%,31.089%,1.077)", [51, 138, 79, 255]), 141 | ("rgb(70.314%,108.126%,96.525%,0.513)", [179, 255, 246, 131]), 142 | ("rgb(49.348%,-1.456%,15.051%)", [126, 0, 38, 255]), 143 | ("rgb(13.047%,34.342%,99.376%)", [33, 88, 253, 255]), 144 | ("rgb(21.879%,37.037%,34.846%)", [56, 94, 89, 255]), 145 | ("rgb(18.765%,3.997%,17.193%,0.598)", [48, 10, 44, 152]), 146 | ("rgb(54.629%,55.177%,51.436%,-0.123)", [139, 141, 131, 0]), 147 | ("rgb(8.356%,-0.583%,74.565%)", [21, 0, 190, 255]), 148 | ("rgb(2.435%,74.014%,16.351%,0.580)", [6, 189, 42, 148]), 149 | ("rgb(25.820%,80.885%,85.077%)", [66, 206, 217, 255]), 150 | ("rgb(-4.774%,54.466%,34.484%,1.139)", [0, 139, 88, 255]), 151 | ("rgb(24.928%,80.365%,48.506%)", [64, 205, 124, 255]), 152 | ("rgb(43.466%,12.333%,73.637%,0.170)", [111, 31, 188, 43]), 153 | ("rgb(26.659%,24.469%,51.155%,0.592)", [68, 62, 130, 151]), 154 | ("rgb(102.749%,16.226%,13.440%,0.542)", [255, 41, 34, 138]), 155 | ( 156 | "hsl(157.572grad,22.057%,82.352%,0.748)", 157 | [200, 220, 207, 191], 158 | ), 159 | ("hsl(359.534grad,99.302%,24.820%)", [126, 0, 77, 255]), 160 | ("hsl(89.381deg,0.820%,2.238%,1.184)", [6, 6, 6, 255]), 161 | ("hsl(3.435rad,9.931%,1.830%,0.688)", [4, 5, 5, 175]), 162 | ("hsl(269.781deg,72.977%,16.749%,0.919)", [42, 12, 74, 234]), 163 | ("hsl(0.231turn,25.156%,-8.804%)", [0, 0, 0, 255]), 164 | ("hsl(362.732grad,71.384%,89.611%)", [247, 210, 231, 255]), 165 | ("hsl(8.019deg,22.561%,21.216%)", [66, 45, 42, 255]), 166 | ("hsl(80.737grad,88.920%,-0.182%)", [0, 0, 0, 255]), 167 | ("hsl(3.901rad,104.403%,100.043%)", [255, 255, 255, 255]), 168 | ("hsl(127.668deg,38.344%,95.249%)", [238, 248, 239, 255]), 169 | ("hsl(276.662grad,32.442%,77.971%)", [186, 181, 217, 255]), 170 | ("hsl(5.491rad,65.601%,107.670%)", [255, 255, 255, 255]), 171 | ("hsl(92.909grad,102.247%,91.380%)", [238, 255, 211, 255]), 172 | ("hsl(236.020,98.201%,19.861%,-0.065)", [1, 8, 100, 0]), 173 | ("hsl(356.944grad,18.231%,70.131%)", [193, 165, 183, 255]), 174 | ("hsl(347.581grad,91.415%,35.258%,0.479)", [172, 8, 137, 122]), 175 | ("hsl(200.437deg,81.639%,56.948%,0.017)", [56, 174, 235, 4]), 176 | ("hsl(96.598,-1.219%,80.187%)", [204, 204, 204, 255]), 177 | ("hsl(162.463,35.025%,9.764%,1.000)", [16, 34, 29, 255]), 178 | ("hsl(97.785grad,42.878%,49.733%,0.540)", [130, 181, 72, 138]), 179 | ("hsl(0.243turn,61.733%,36.754%)", [99, 152, 36, 255]), 180 | ("hsl(81.865grad,93.755%,100.434%)", [255, 255, 255, 255]), 181 | ("hsl(0.846turn,12.520%,78.873%,-0.121)", [208, 194, 207, 0]), 182 | ("hsl(1.003turn,-3.924%,48.828%)", [125, 125, 125, 255]), 183 | ("hsl(262.728deg,25.894%,50.198%)", [120, 95, 161, 255]), 184 | ("hsl(187.679,4.225%,90.298%,0.974)", [229, 231, 231, 248]), 185 | ("hsl(0.301turn,-6.189%,34.167%)", [87, 87, 87, 255]), 186 | ("hsl(0.268turn,91.972%,89.764%)", [224, 253, 205, 255]), 187 | ("hsl(247.074grad,78.542%,14.539%)", [8, 25, 66, 255]), 188 | ("hsl(46.282grad,37.538%,92.244%)", [243, 238, 228, 255]), 189 | ("hsl(0.391turn,4.084%,67.843%)", [170, 176, 172, 255]), 190 | ("hsl(-0.088turn,61.468%,95.887%)", [251, 238, 245, 255]), 191 | ("hsl(0.386turn,59.601%,19.961%,-0.102)", [21, 81, 40, 0]), 192 | ("hsl(310.087,43.917%,93.640%)", [246, 232, 244, 255]), 193 | ("hsl(59.767grad,17.209%,-3.545%)", [0, 0, 0, 255]), 194 | ("hsl(-7.789grad,91.197%,40.446%)", [197, 9, 31, 255]), 195 | ("hsl(99.724deg,44.247%,91.630%,0.575)", [231, 243, 224, 147]), 196 | ("hsl(0.129rad,100.953%,88.130%,0.208)", [255, 202, 194, 53]), 197 | ("hsl(28.333grad,92.866%,94.350%,0.225)", [254, 239, 227, 57]), 198 | ("hsl(6.278rad,13.724%,109.460%,0.388)", [255, 255, 255, 99]), 199 | ("hsl(0.526turn,79.719%,57.535%)", [60, 206, 233, 255]), 200 | ("hsl(299.379,49.286%,95.788%)", [249, 239, 250, 255]), 201 | ("hsl(198.214,22.384%,26.807%,0.047)", [53, 74, 84, 12]), 202 | ("hsl(140.760,-9.660%,9.079%)", [23, 23, 23, 255]), 203 | ("hsl(0.342turn,38.150%,26.553%,0.570)", [42, 94, 45, 145]), 204 | ("hsl(235.559deg,-1.789%,81.781%)", [209, 209, 209, 255]), 205 | ("hsl(-0.400grad,100.928%,31.366%,-0.121)", [160, 0, 1, 0]), 206 | ("hsl(41.989grad,-8.040%,83.940%)", [214, 214, 214, 255]), 207 | ("hsl(327.089grad,41.748%,17.920%)", [61, 27, 65, 255]), 208 | ("hwb(5.093rad 16.228% 7.107% / 0.995)", [210, 41, 237, 254]), 209 | ("hwb(0.913turn 23.380% 28.693%)", [182, 60, 123, 255]), 210 | ( 211 | "hwb(0.083turn 48.957% 19.454% / -0.055)", 212 | [205, 165, 125, 0], 213 | ), 214 | ("hwb(223.305 46.995% 33.460% / -0.095)", [120, 134, 170, 0]), 215 | ("hwb(93.679grad 14.835% 5.257%)", [159, 242, 38, 255]), 216 | ( 217 | "hwb(208.953grad 43.974% 17.786% / 49%)", 218 | [112, 197, 210, 125], 219 | ), 220 | ("hwb(168.267 8.049% 1.527%)", [21, 251, 206, 255]), 221 | ("hwb(260.943 27.267% 43.541%)", [96, 70, 144, 255]), 222 | ("hwb(0.485turn 46.539% 10.643%)", [119, 228, 218, 255]), 223 | ("hwb(212.179deg 31.054% 9.752%)", [79, 149, 230, 255]), 224 | ("hwb(-0.070turn 14.978% 9.707%)", [230, 38, 119, 255]), 225 | ("hwb(0.073turn 36.215% 43.333% / -40%)", [145, 115, 92, 0]), 226 | ("hwb(0.162turn 49.627% 28.784%)", [182, 180, 127, 255]), 227 | ("hwb(0.243turn 20.723% 13.575%)", [144, 220, 53, 255]), 228 | ("hwb(24.098deg 10.817% 49.419%)", [129, 68, 28, 255]), 229 | ("hwb(231.600deg 9.597% 14.013% / 3%)", [24, 52, 219, 8]), 230 | ("hwb(203.254deg 18.262% 3.646%)", [47, 169, 246, 255]), 231 | ("hwb(153.756deg 48.303% 33.045%)", [123, 171, 150, 255]), 232 | ("hwb(298.912grad 22.529% 13.786%)", [136, 57, 220, 255]), 233 | ("hwb(185.717deg 5.163% 31.175%)", [13, 160, 176, 255]), 234 | ("hwb(211.980 1.733% 4.655% / -52%)", [4, 116, 243, 0]), 235 | ("hwb(204.276 30.754% 10.146%)", [78, 168, 229, 255]), 236 | ( 237 | "hwb(0.953turn 48.769% 4.335% / 0.684)", 238 | [244, 124, 158, 174], 239 | ), 240 | ("hwb(2.465rad 16.911% 11.363%)", [43, 226, 108, 255]), 241 | ("hwb(58.908deg 29.545% 12.057% / 7%)", [224, 222, 75, 18]), 242 | ("hwb(6.344rad 49.227% 42.409%)", [147, 127, 126, 255]), 243 | ("hwb(0.255turn 29.724% 27.689% / 45%)", [127, 184, 76, 115]), 244 | ("hwb(328.563deg 20.347% 38.896%)", [156, 52, 106, 255]), 245 | ("hwb(2.157deg 15.367% 13.797% / 0.761)", [220, 46, 39, 194]), 246 | ("hwb(290.068grad 13.739% 49.438%)", [68, 35, 129, 255]), 247 | ("hwb(3.523 0.171% 7.495%)", [236, 14, 0, 255]), 248 | ("hwb(0.228turn 7.261% 38.296% / -28%)", [106, 157, 19, 0]), 249 | ("hwb(114.298deg 2.263% 48.814% / 115%)", [18, 131, 6, 255]), 250 | ("hwb(6.000rad 29.569% 37.733% / -90%)", [159, 75, 98, 0]), 251 | ("hwb(293.975 48.728% 30.547%)", [172, 124, 177, 255]), 252 | ("hwb(3.861rad 5.194% 22.537%)", [13, 71, 198, 255]), 253 | ("hwb(363.051grad 39.733% 20.035%)", [204, 101, 158, 255]), 254 | ("hwb(0.046turn 4.758% 4.128%)", [244, 76, 12, 255]), 255 | ("hwb(198.156 40.979% 25.203% / 47%)", [104, 165, 191, 120]), 256 | ("hwb(169.283 15.477% 3.858% / -79%)", [39, 245, 208, 0]), 257 | ("hwb(6.282rad 36.718% 12.765% / 27%)", [222, 94, 94, 69]), 258 | ("hwb(4.590rad 20.571% 19.423%)", [111, 52, 205, 255]), 259 | ("hwb(252.979grad 3.471% 13.610% / 0.626)", [9, 52, 220, 160]), 260 | ( 261 | "hwb(0.738turn 12.619% 22.873% / 0.498)", 262 | [103, 32, 197, 127], 263 | ), 264 | ("hwb(210.399grad 1.847% 38.094%)", [5, 134, 158, 255]), 265 | ("hwb(143.863 35.042% 31.195% / 0.378)", [89, 175, 124, 96]), 266 | ( 267 | "hwb(21.631grad 41.743% 24.160% / 47%)", 268 | [193, 135, 106, 120], 269 | ), 270 | ("hwb(84.952grad 41.859% 39.539%)", [141, 154, 107, 255]), 271 | ("hwb(37.442 2.103% 9.857%)", [230, 145, 5, 255]), 272 | ("hwb(135.379grad 5.905% 8.483% / 76%)", [15, 233, 22, 194]), 273 | ]; 274 | for (s, expected) in test_data { 275 | let rgba = csscolorparser::parse(s).unwrap().to_rgba8(); 276 | assert_eq!(expected, rgba); 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /src/parser.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::parse_values; 2 | use crate::utils::remap; 3 | use crate::utils::ParamParser; 4 | use crate::{Color, ParseColorError}; 5 | 6 | #[cfg(feature = "named-colors")] 7 | use crate::NAMED_COLORS; 8 | 9 | /// Parse CSS color string 10 | /// 11 | /// # Examples 12 | /// 13 | /// ``` 14 | /// # use core::error::Error; 15 | /// # fn main() -> Result<(), Box> { 16 | /// let c = csscolorparser::parse("#ff0")?; 17 | /// 18 | /// assert_eq!(c.to_array(), [1.0, 1.0, 0.0, 1.0]); 19 | /// assert_eq!(c.to_rgba8(), [255, 255, 0, 255]); 20 | /// assert_eq!(c.to_css_hex(), "#ffff00"); 21 | /// assert_eq!(c.to_css_rgb(), "rgb(255 255 0)"); 22 | /// # Ok(()) 23 | /// # } 24 | /// ``` 25 | /// 26 | /// ``` 27 | /// # use core::error::Error; 28 | /// # fn main() -> Result<(), Box> { 29 | /// let c = csscolorparser::parse("hsl(360deg,100%,50%)")?; 30 | /// 31 | /// assert_eq!(c.to_array(), [1.0, 0.0, 0.0, 1.0]); 32 | /// assert_eq!(c.to_rgba8(), [255, 0, 0, 255]); 33 | /// assert_eq!(c.to_css_hex(), "#ff0000"); 34 | /// assert_eq!(c.to_css_rgb(), "rgb(255 0 0)"); 35 | /// # Ok(()) 36 | /// # } 37 | /// ``` 38 | #[inline(never)] 39 | pub fn parse(s: &str) -> Result { 40 | let s = s.trim(); 41 | 42 | let err = match parse_abs(s) { 43 | Ok(c) => return Ok(c), 44 | Err(e @ ParseColorError::InvalidHex) => return Err(e), 45 | Err(e @ ParseColorError::InvalidFunction) => return Err(e), 46 | Err(e @ ParseColorError::InvalidUnknown) => return Err(e), 47 | Err(e) => e, 48 | }; 49 | 50 | if let (Some(idx), Some(s)) = (s.find('('), s.strip_suffix(')')) { 51 | if !s.is_ascii() { 52 | return Err(err); 53 | } 54 | 55 | let mut pp = ParamParser::new(&s[idx + 1..]); 56 | pp.space(); 57 | 58 | #[rustfmt::skip] 59 | let ( 60 | Some(from), true, 61 | Some(color), true, 62 | Some(val1), true, 63 | Some(val2), true, 64 | Some(val3), 65 | ) = ( 66 | pp.value(), pp.space(), 67 | pp.value(), pp.space(), 68 | pp.value(), pp.space(), 69 | pp.value(), pp.space(), 70 | pp.value(), 71 | ) else { 72 | return Err(err); 73 | }; 74 | 75 | if !from.eq_ignore_ascii_case("from") { 76 | return Err(err); 77 | } 78 | 79 | let Ok(color) = parse(color) else { 80 | return Err(err); 81 | }; 82 | 83 | pp.space(); 84 | 85 | let val4 = if pp.is_end() { 86 | "alpha" 87 | } else if let (true, Some(alpha), _, true) = 88 | (pp.slash(), pp.value(), pp.space(), pp.is_end()) 89 | { 90 | alpha 91 | } else { 92 | return Err(err); 93 | }; 94 | 95 | let values = [val1, val2, val3, val4]; 96 | 97 | match err { 98 | ParseColorError::InvalidRgb => { 99 | // r, g, b [0..255] 100 | // alpha [0..1] 101 | let variables = [ 102 | ("r", color.r * 255.0), 103 | ("g", color.g * 255.0), 104 | ("b", color.b * 255.0), 105 | ("alpha", color.a), 106 | ]; 107 | if let Some([r, g, b, a]) = parse_values(values, variables) { 108 | return Ok(Color::new(r / 255.0, g / 255.0, b / 255.0, a)); 109 | }; 110 | } 111 | ParseColorError::InvalidHwb => { 112 | // h [0..360] 113 | // w, b [0..100] 114 | // alpha [0..1] 115 | let [h, w, b, a] = color.to_hwba(); 116 | let variables = [("h", h), ("w", w * 100.0), ("b", b * 100.0), ("alpha", a)]; 117 | if let Some([h, w, b, a]) = parse_values(values, variables) { 118 | return Ok(Color::from_hwba(h, w / 100.0, b / 100.0, a)); 119 | }; 120 | } 121 | ParseColorError::InvalidHsl => { 122 | // h [0..360] 123 | // s, l [0..100] 124 | // alpha [0..1] 125 | let [h, s, l, a] = color.to_hsla(); 126 | let variables = [("h", h), ("s", s * 100.0), ("l", l * 100.0), ("alpha", a)]; 127 | if let Some([h, s, l, a]) = parse_values(values, variables) { 128 | return Ok(Color::from_hsla( 129 | h, 130 | (s / 100.0).clamp(0.0, 1.0), 131 | (l / 100.0).clamp(0.0, 1.0), 132 | a, 133 | )); 134 | }; 135 | } 136 | ParseColorError::InvalidHsv => { 137 | // h [0..360] 138 | // s, v [0..100] 139 | // alpha [0..1] 140 | let [h, s, v, a] = color.to_hsva(); 141 | let variables = [("h", h), ("s", s * 100.0), ("v", v * 100.0), ("alpha", a)]; 142 | if let Some([h, s, v, a]) = parse_values(values, variables) { 143 | return Ok(Color::from_hsva(h, s / 100.0, v / 100.0, a)); 144 | }; 145 | } 146 | ParseColorError::InvalidLab => { 147 | // l [0..100] 148 | // a, b [-125..125] 149 | // alpha [0..1] 150 | let [l, a, b, alpha] = color.to_laba(); 151 | let variables = [("l", l), ("a", a), ("b", b), ("alpha", alpha)]; 152 | if let Some([l, a, b, alpha]) = parse_values(values, variables) { 153 | return Ok(Color::from_laba(l.max(0.0), a, b, alpha)); 154 | }; 155 | } 156 | ParseColorError::InvalidLch => { 157 | // l [0..100] 158 | // c [0..150] 159 | // h [0..360] 160 | // alpha [0..1] 161 | let [l, c, h, a] = color.to_lcha(); 162 | let variables = [("l", l), ("c", c), ("h", h.to_degrees()), ("alpha", a)]; 163 | if let Some([l, c, h, a]) = parse_values(values, variables) { 164 | return Ok(Color::from_lcha(l.max(0.0), c.max(0.0), h.to_radians(), a)); 165 | }; 166 | } 167 | ParseColorError::InvalidOklab => { 168 | // l [0..1] 169 | // a, b [-0.4 .. 0.4] 170 | // alpha [0..1] 171 | let [l, a, b, alpha] = color.to_oklaba(); 172 | let variables = [("l", l), ("a", a), ("b", b), ("alpha", alpha)]; 173 | if let Some([l, a, b, alpha]) = parse_values(values, variables) { 174 | return Ok(Color::from_oklaba(l.max(0.0), a, b, alpha)); 175 | }; 176 | } 177 | ParseColorError::InvalidOklch => { 178 | // l [0..1] 179 | // c [0..0.4] 180 | // h [0..360] 181 | // alpha [0..1] 182 | let [l, c, h, a] = color.to_oklcha(); 183 | let variables = [("l", l), ("c", c), ("h", h.to_degrees()), ("alpha", a)]; 184 | if let Some([l, c, h, a]) = parse_values(values, variables) { 185 | return Ok(Color::from_oklcha( 186 | l.max(0.0), 187 | c.max(0.0), 188 | h.to_radians(), 189 | a, 190 | )); 191 | }; 192 | } 193 | _ => unreachable!(), 194 | } 195 | return Err(err); 196 | } 197 | 198 | unreachable!(); 199 | } 200 | 201 | fn parse_abs(s: &str) -> Result { 202 | if s.eq_ignore_ascii_case("transparent") { 203 | return Ok(Color::new(0.0, 0.0, 0.0, 0.0)); 204 | } 205 | 206 | // Hex format 207 | if let Some(s) = s.strip_prefix('#') { 208 | return parse_hex(s); 209 | } 210 | 211 | if let (Some(idx), Some(s)) = (s.find('('), s.strip_suffix(')')) { 212 | let fname = &s[..idx].trim_end(); 213 | 214 | let err = match fname { 215 | s if s.eq_ignore_ascii_case("rgb") || s.eq_ignore_ascii_case("rgba") => { 216 | ParseColorError::InvalidRgb 217 | } 218 | s if s.eq_ignore_ascii_case("hsl") || s.eq_ignore_ascii_case("hsla") => { 219 | ParseColorError::InvalidHsl 220 | } 221 | s if s.eq_ignore_ascii_case("hwb") || s.eq_ignore_ascii_case("hwba") => { 222 | ParseColorError::InvalidHwb 223 | } 224 | s if s.eq_ignore_ascii_case("hsv") || s.eq_ignore_ascii_case("hsva") => { 225 | ParseColorError::InvalidHsv 226 | } 227 | s if s.eq_ignore_ascii_case("lab") => ParseColorError::InvalidLab, 228 | s if s.eq_ignore_ascii_case("lch") => ParseColorError::InvalidLch, 229 | s if s.eq_ignore_ascii_case("oklab") => ParseColorError::InvalidOklab, 230 | s if s.eq_ignore_ascii_case("oklch") => ParseColorError::InvalidOklch, 231 | _ => return Err(ParseColorError::InvalidFunction), 232 | }; 233 | 234 | let s = &s[idx + 1..]; 235 | 236 | if !s.is_ascii() { 237 | return Err(err); 238 | } 239 | 240 | let mut pp = ParamParser::new(s); 241 | pp.space(); 242 | 243 | let (Some(val0), true, Some(val1), true, Some(val2)) = ( 244 | pp.value(), 245 | pp.comma_or_space(), 246 | pp.value(), 247 | pp.comma_or_space(), 248 | pp.value(), 249 | ) else { 250 | return Err(err); 251 | }; 252 | 253 | let is_space = pp.space(); 254 | 255 | let alpha = if pp.is_end() { 256 | 1.0 257 | } else if let (true, Some(a), _, true) = ( 258 | pp.comma_or_slash() || is_space, 259 | pp.value(), 260 | pp.space(), 261 | pp.is_end(), 262 | ) { 263 | if let Some((v, _)) = parse_percent_or_float(a) { 264 | v.clamp(0.0, 1.0) 265 | } else { 266 | return Err(err); 267 | } 268 | } else { 269 | return Err(err); 270 | }; 271 | 272 | match err { 273 | ParseColorError::InvalidRgb => { 274 | if let (Some(r), Some(g), Some(b)) = ( 275 | // red 276 | parse_percent_or_255(val0), 277 | // green 278 | parse_percent_or_255(val1), 279 | // blue 280 | parse_percent_or_255(val2), 281 | ) { 282 | return Ok(Color { 283 | r: r.clamp(0.0, 1.0), 284 | g: g.clamp(0.0, 1.0), 285 | b: b.clamp(0.0, 1.0), 286 | a: alpha, 287 | }); 288 | } 289 | } 290 | ParseColorError::InvalidHsl => { 291 | if let (Some(h), Some((s, _)), Some((l, _))) = ( 292 | // hue 293 | parse_angle(val0), 294 | // saturation 295 | parse_percent_or_float(val1), 296 | // lightness 297 | parse_percent_or_float(val2), 298 | ) { 299 | return Ok(Color::from_hsla(h, s, l, alpha)); 300 | } 301 | } 302 | ParseColorError::InvalidHwb => { 303 | if let (Some(h), Some((w, _)), Some((b, _))) = ( 304 | // hue 305 | parse_angle(val0), 306 | // whiteness 307 | parse_percent_or_float(val1), 308 | // blackness 309 | parse_percent_or_float(val2), 310 | ) { 311 | return Ok(Color::from_hwba(h, w, b, alpha)); 312 | } 313 | } 314 | ParseColorError::InvalidHsv => { 315 | if let (Some(h), Some((s, _)), Some((v, _))) = ( 316 | // hue 317 | parse_angle(val0), 318 | // saturation 319 | parse_percent_or_float(val1), 320 | // value 321 | parse_percent_or_float(val2), 322 | ) { 323 | return Ok(Color::from_hsva(h, s, v, alpha)); 324 | } 325 | } 326 | ParseColorError::InvalidLab => { 327 | if let (Some((l, l_pct)), Some((a, a_pct)), Some((b, b_pct))) = ( 328 | // lightness 329 | parse_percent_or_float(val0), 330 | // a 331 | parse_percent_or_float(val1), 332 | // b 333 | parse_percent_or_float(val2), 334 | ) { 335 | let l = if l_pct { l * 100.0 } else { l }; 336 | let a = if a_pct { 337 | remap(a, -1.0, 1.0, -125.0, 125.0) 338 | } else { 339 | a 340 | }; 341 | let b = if b_pct { 342 | remap(b, -1.0, 1.0, -125.0, 125.0) 343 | } else { 344 | b 345 | }; 346 | return Ok(Color::from_laba(l.max(0.0), a, b, alpha)); 347 | } 348 | } 349 | ParseColorError::InvalidLch => { 350 | if let (Some((l, l_pct)), Some((c, c_pct)), Some(h)) = ( 351 | // lightness 352 | parse_percent_or_float(val0), 353 | // chroma 354 | parse_percent_or_float(val1), 355 | // hue 356 | parse_angle(val2), 357 | ) { 358 | let l = if l_pct { l * 100.0 } else { l }; 359 | let c = if c_pct { c * 150.0 } else { c }; 360 | return Ok(Color::from_lcha( 361 | l.max(0.0), 362 | c.max(0.0), 363 | h.to_radians(), 364 | alpha, 365 | )); 366 | } 367 | } 368 | ParseColorError::InvalidOklab => { 369 | if let (Some((l, _)), Some((a, a_pct)), Some((b, b_pct))) = ( 370 | // lightness 371 | parse_percent_or_float(val0), 372 | // a 373 | parse_percent_or_float(val1), 374 | // b 375 | parse_percent_or_float(val2), 376 | ) { 377 | let a = if a_pct { 378 | remap(a, -1.0, 1.0, -0.4, 0.4) 379 | } else { 380 | a 381 | }; 382 | let b = if b_pct { 383 | remap(b, -1.0, 1.0, -0.4, 0.4) 384 | } else { 385 | b 386 | }; 387 | return Ok(Color::from_oklaba(l.max(0.0), a, b, alpha)); 388 | } 389 | } 390 | ParseColorError::InvalidOklch => { 391 | if let (Some((l, _)), Some((c, c_pct)), Some(h)) = ( 392 | // lightness 393 | parse_percent_or_float(val0), 394 | // chroma 395 | parse_percent_or_float(val1), 396 | // hue 397 | parse_angle(val2), 398 | ) { 399 | let c = if c_pct { c * 0.4 } else { c }; 400 | return Ok(Color::from_oklcha( 401 | l.max(0.0), 402 | c.max(0.0), 403 | h.to_radians(), 404 | alpha, 405 | )); 406 | } 407 | } 408 | _ => unreachable!(), 409 | } 410 | return Err(err); 411 | } 412 | 413 | // Hex format without prefix '#' 414 | if let Ok(c) = parse_hex(s) { 415 | return Ok(c); 416 | } 417 | 418 | // Named colors 419 | #[cfg(feature = "named-colors")] 420 | if s.len() > 2 && s.len() < 21 { 421 | if let Some([r, g, b]) = NAMED_COLORS.get(s.into()) { 422 | return Ok(Color::from_rgba8(*r, *g, *b, 255)); 423 | } 424 | } 425 | 426 | Err(ParseColorError::InvalidUnknown) 427 | } 428 | 429 | fn parse_hex(s: &str) -> Result { 430 | if !s.is_ascii() { 431 | return Err(ParseColorError::InvalidHex); 432 | } 433 | 434 | let n = s.len(); 435 | 436 | fn parse_single_digit(digit: &str) -> Result { 437 | u8::from_str_radix(digit, 16) 438 | .map(|n| (n << 4) | n) 439 | .map_err(|_| ParseColorError::InvalidHex) 440 | } 441 | 442 | if n == 3 || n == 4 { 443 | let r = parse_single_digit(&s[0..1])?; 444 | let g = parse_single_digit(&s[1..2])?; 445 | let b = parse_single_digit(&s[2..3])?; 446 | 447 | let a = if n == 4 { 448 | parse_single_digit(&s[3..4])? 449 | } else { 450 | 255 451 | }; 452 | 453 | Ok(Color::from_rgba8(r, g, b, a)) 454 | } else if n == 6 || n == 8 { 455 | let r = u8::from_str_radix(&s[0..2], 16).map_err(|_| ParseColorError::InvalidHex)?; 456 | let g = u8::from_str_radix(&s[2..4], 16).map_err(|_| ParseColorError::InvalidHex)?; 457 | let b = u8::from_str_radix(&s[4..6], 16).map_err(|_| ParseColorError::InvalidHex)?; 458 | 459 | let a = if n == 8 { 460 | u8::from_str_radix(&s[6..8], 16).map_err(|_| ParseColorError::InvalidHex)? 461 | } else { 462 | 255 463 | }; 464 | 465 | Ok(Color::from_rgba8(r, g, b, a)) 466 | } else { 467 | Err(ParseColorError::InvalidHex) 468 | } 469 | } 470 | 471 | // strip suffix ignore case 472 | fn strip_suffix<'a>(s: &'a str, suffix: &str) -> Option<&'a str> { 473 | if suffix.len() > s.len() { 474 | return None; 475 | } 476 | let s_end = &s[s.len() - suffix.len()..]; 477 | if s_end.eq_ignore_ascii_case(suffix) { 478 | Some(&s[..s.len() - suffix.len()]) 479 | } else { 480 | None 481 | } 482 | } 483 | 484 | fn parse_percent_or_float(s: &str) -> Option<(f32, bool)> { 485 | if s.eq_ignore_ascii_case("none") { 486 | return Some((0.0, false)); 487 | } 488 | s.strip_suffix('%') 489 | .and_then(|s| s.parse().ok().map(|t: f32| (t / 100.0, true))) 490 | .or_else(|| s.parse().ok().map(|t| (t, false))) 491 | } 492 | 493 | fn parse_percent_or_255(s: &str) -> Option { 494 | if s.eq_ignore_ascii_case("none") { 495 | return Some(0.0); 496 | } 497 | s.strip_suffix('%') 498 | .and_then(|s| s.parse().ok().map(|t: f32| t / 100.0)) 499 | .or_else(|| s.parse().ok().map(|t: f32| t / 255.0)) 500 | } 501 | 502 | fn parse_angle(s: &str) -> Option { 503 | if s.eq_ignore_ascii_case("none") { 504 | return Some(0.0); 505 | } 506 | strip_suffix(s, "deg") 507 | .and_then(|s| s.parse().ok()) 508 | .or_else(|| { 509 | strip_suffix(s, "grad") 510 | .and_then(|s| s.parse().ok()) 511 | .map(|t: f32| t * 360.0 / 400.0) 512 | }) 513 | .or_else(|| { 514 | strip_suffix(s, "rad") 515 | .and_then(|s| s.parse().ok()) 516 | .map(|t: f32| t.to_degrees()) 517 | }) 518 | .or_else(|| { 519 | strip_suffix(s, "turn") 520 | .and_then(|s| s.parse().ok()) 521 | .map(|t: f32| t * 360.0) 522 | }) 523 | .or_else(|| s.parse().ok()) 524 | } 525 | 526 | #[cfg(test)] 527 | mod t { 528 | use super::*; 529 | 530 | #[test] 531 | fn strip_suffix_() { 532 | assert_eq!(strip_suffix("45deg", "deg"), Some("45")); 533 | assert_eq!(strip_suffix("90DEG", "deg"), Some("90")); 534 | assert_eq!(strip_suffix("0.25turn", "turn"), Some("0.25")); 535 | assert_eq!(strip_suffix("1.0Turn", "turn"), Some("1.0")); 536 | 537 | assert_eq!(strip_suffix("", "deg"), None); 538 | assert_eq!(strip_suffix("90", "deg"), None); 539 | } 540 | 541 | #[test] 542 | fn parse_percent_or_float_() { 543 | let test_data = [ 544 | ("none", Some((0.0, false))), 545 | ("NONE", Some((0.0, false))), 546 | ("0%", Some((0.0, true))), 547 | ("100%", Some((1.0, true))), 548 | ("50%", Some((0.5, true))), 549 | ("0", Some((0.0, false))), 550 | ("1", Some((1.0, false))), 551 | ("0.5", Some((0.5, false))), 552 | ("100.0", Some((100.0, false))), 553 | ("-23.7", Some((-23.7, false))), 554 | ("%", None), 555 | ("1x", None), 556 | ]; 557 | for (s, expected) in test_data { 558 | assert_eq!(parse_percent_or_float(s), expected); 559 | } 560 | } 561 | 562 | #[test] 563 | fn parse_percent_or_255_() { 564 | let test_data = [ 565 | ("none", Some(0.0)), 566 | ("NONE", Some(0.0)), 567 | ("0%", Some(0.0)), 568 | ("100%", Some(1.0)), 569 | ("50%", Some(0.5)), 570 | ("-100%", Some(-1.0)), 571 | ("0", Some(0.0)), 572 | ("255", Some(1.0)), 573 | ("127.5", Some(0.5)), 574 | ("%", None), 575 | ("255x", None), 576 | ]; 577 | for (s, expected) in test_data { 578 | assert_eq!(parse_percent_or_255(s), expected); 579 | } 580 | } 581 | 582 | #[test] 583 | fn parse_angle_() { 584 | let test_data = [ 585 | ("none", Some(0.0)), 586 | ("NONE", Some(0.0)), 587 | ("360", Some(360.0)), 588 | ("127.356", Some(127.356)), 589 | ("+120deg", Some(120.0)), 590 | ("90deg", Some(90.0)), 591 | ("-127deg", Some(-127.0)), 592 | ("100grad", Some(90.0)), 593 | ("1.5707963267948966rad", Some(90.0)), 594 | ("0.25turn", Some(90.0)), 595 | ("-0.25turn", Some(-90.0)), 596 | ("O", None), 597 | ("Odeg", None), 598 | ("rad", None), 599 | ]; 600 | for (s, expected) in test_data { 601 | assert_eq!(parse_angle(s), expected); 602 | } 603 | } 604 | 605 | #[test] 606 | fn parse_hex_() { 607 | // case-insensitive tests 608 | macro_rules! cmp { 609 | ($a:expr, $b:expr) => { 610 | assert_eq!( 611 | parse_hex($a).unwrap().to_rgba8(), 612 | parse_hex($b).unwrap().to_rgba8() 613 | ); 614 | }; 615 | } 616 | cmp!("abc", "ABC"); 617 | cmp!("DeF", "dEf"); 618 | cmp!("f0eB", "F0Eb"); 619 | cmp!("abcdef", "ABCDEF"); 620 | cmp!("Ff03E0cB", "fF03e0Cb"); 621 | } 622 | } 623 | -------------------------------------------------------------------------------- /src/color.rs: -------------------------------------------------------------------------------- 1 | use core::convert::TryFrom; 2 | use core::fmt; 3 | use core::str::FromStr; 4 | 5 | use alloc::format; 6 | use alloc::string::String; 7 | use alloc::string::ToString; 8 | 9 | #[cfg(not(feature = "std"))] 10 | use num_traits::float::Float; 11 | 12 | #[cfg(feature = "rust-rgb")] 13 | use rgb::{RGB, RGBA}; 14 | 15 | #[cfg(feature = "serde")] 16 | use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; 17 | 18 | use crate::lab::{lab_to_linear_rgb, linear_rgb_to_lab}; 19 | use crate::utils::*; 20 | use crate::{parse, ParseColorError}; 21 | 22 | #[cfg(feature = "named-colors")] 23 | use crate::NAMED_COLORS; 24 | 25 | #[derive(Debug, Clone, PartialEq, PartialOrd)] 26 | /// The color 27 | pub struct Color { 28 | /// Red 29 | pub r: f32, 30 | /// Green 31 | pub g: f32, 32 | /// Blue 33 | pub b: f32, 34 | /// Alpha 35 | pub a: f32, 36 | } 37 | 38 | impl Color { 39 | /// Arguments: 40 | /// 41 | /// * `r`: Red value [0..1] 42 | /// * `g`: Green value [0..1] 43 | /// * `b`: Blue value [0..1] 44 | /// * `a`: Alpha value [0..1] 45 | pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self { 46 | Self { r, g, b, a } 47 | } 48 | 49 | /// Arguments: 50 | /// 51 | /// * `r`: Red value [0..255] 52 | /// * `g`: Green value [0..255] 53 | /// * `b`: Blue value [0..255] 54 | /// * `a`: Alpha value [0..255] 55 | pub const fn from_rgba8(r: u8, g: u8, b: u8, a: u8) -> Self { 56 | Self { 57 | r: r as f32 / 255.0, 58 | g: g as f32 / 255.0, 59 | b: b as f32 / 255.0, 60 | a: a as f32 / 255.0, 61 | } 62 | } 63 | 64 | /// Arguments: 65 | /// 66 | /// * `r`: Red value [0..1] 67 | /// * `g`: Green value [0..1] 68 | /// * `b`: Blue value [0..1] 69 | /// * `a`: Alpha value [0..1] 70 | pub fn from_linear_rgba(r: f32, g: f32, b: f32, a: f32) -> Self { 71 | fn from_linear(x: f32) -> f32 { 72 | if x >= 0.0031308 { 73 | return 1.055 * x.powf(1.0 / 2.4) - 0.055; 74 | } 75 | 12.92 * x 76 | } 77 | Self::new(from_linear(r), from_linear(g), from_linear(b), a) 78 | } 79 | 80 | /// Arguments: 81 | /// 82 | /// * `r`: Red value [0..255] 83 | /// * `g`: Green value [0..255] 84 | /// * `b`: Blue value [0..255] 85 | /// * `a`: Alpha value [0..255] 86 | pub fn from_linear_rgba8(r: u8, g: u8, b: u8, a: u8) -> Self { 87 | Self::from_linear_rgba( 88 | r as f32 / 255.0, 89 | g as f32 / 255.0, 90 | b as f32 / 255.0, 91 | a as f32 / 255.0, 92 | ) 93 | } 94 | 95 | /// Arguments: 96 | /// 97 | /// * `h`: Hue angle [0..360] 98 | /// * `s`: Saturation [0..1] 99 | /// * `v`: Value [0..1] 100 | /// * `a`: Alpha [0..1] 101 | pub const fn from_hsva(h: f32, s: f32, v: f32, a: f32) -> Self { 102 | let [r, g, b] = hsv_to_rgb(normalize_angle(h), s.clamp(0.0, 1.0), v.clamp(0.0, 1.0)); 103 | Self::new(r, g, b, a) 104 | } 105 | 106 | /// Arguments: 107 | /// 108 | /// * `h`: Hue angle [0..360] 109 | /// * `s`: Saturation [0..1] 110 | /// * `l`: Lightness [0..1] 111 | /// * `a`: Alpha [0..1] 112 | pub const fn from_hsla(h: f32, s: f32, l: f32, a: f32) -> Self { 113 | let [r, g, b] = hsl_to_rgb(normalize_angle(h), s.clamp(0.0, 1.0), l.clamp(0.0, 1.0)); 114 | Self::new(r, g, b, a) 115 | } 116 | 117 | /// Arguments: 118 | /// 119 | /// * `h`: Hue angle [0..360] 120 | /// * `w`: Whiteness [0..1] 121 | /// * `b`: Blackness [0..1] 122 | /// * `a`: Alpha [0..1] 123 | pub const fn from_hwba(h: f32, w: f32, b: f32, a: f32) -> Self { 124 | let [r, g, b] = hwb_to_rgb(normalize_angle(h), w.clamp(0.0, 1.0), b.clamp(0.0, 1.0)); 125 | Self::new(r, g, b, a) 126 | } 127 | 128 | /// Arguments: 129 | /// 130 | /// * `l`: Perceived lightness 131 | /// * `a`: How green/red the color is 132 | /// * `b`: How blue/yellow the color is 133 | /// * `alpha`: Alpha [0..1] 134 | pub fn from_oklaba(l: f32, a: f32, b: f32, alpha: f32) -> Self { 135 | let [r, g, b] = oklab_to_linear_rgb(l, a, b); 136 | Self::from_linear_rgba(r, g, b, alpha) 137 | } 138 | 139 | /// Arguments: 140 | /// 141 | /// * `l`: Perceived lightness 142 | /// * `c`: Chroma 143 | /// * `h`: Hue angle in radians 144 | /// * `alpha`: Alpha [0..1] 145 | pub fn from_oklcha(l: f32, c: f32, h: f32, alpha: f32) -> Self { 146 | Self::from_oklaba(l, c * h.cos(), c * h.sin(), alpha) 147 | } 148 | 149 | /// Arguments: 150 | /// 151 | /// * `l`: Lightness 152 | /// * `a`: Distance along the `a` axis 153 | /// * `b`: Distance along the `b` axis 154 | /// * `alpha`: Alpha [0..1] 155 | pub fn from_laba(l: f32, a: f32, b: f32, alpha: f32) -> Self { 156 | let [r, g, b] = lab_to_linear_rgb(l, a, b); 157 | Self::from_linear_rgba(r, g, b, alpha) 158 | } 159 | 160 | /// Arguments: 161 | /// 162 | /// * `l`: Lightness 163 | /// * `c`: Chroma 164 | /// * `h`: Hue angle in radians 165 | /// * `alpha`: Alpha [0..1] 166 | pub fn from_lcha(l: f32, c: f32, h: f32, alpha: f32) -> Self { 167 | Self::from_laba(l, c * h.cos(), c * h.sin(), alpha) 168 | } 169 | 170 | /// Create color from CSS color string. 171 | /// 172 | /// # Examples 173 | /// ``` 174 | /// use csscolorparser::Color; 175 | /// # use core::error::Error; 176 | /// # fn main() -> Result<(), Box> { 177 | /// 178 | /// let c = Color::from_html("rgb(255,0,0)")?; 179 | /// 180 | /// assert_eq!(c.to_array(), [1.0, 0.0, 0.0, 1.0]); 181 | /// assert_eq!(c.to_rgba8(), [255, 0, 0, 255]); 182 | /// assert_eq!(c.to_css_hex(), "#ff0000"); 183 | /// assert_eq!(c.to_css_rgb(), "rgb(255 0 0)"); 184 | /// # Ok(()) 185 | /// # } 186 | /// ``` 187 | pub fn from_html>(s: S) -> Result { 188 | parse(s.as_ref()) 189 | } 190 | 191 | /// Restricts R, G, B, A values to the range [0..1]. 192 | #[must_use = "method returns a new Color and does not mutate the original Color"] 193 | pub const fn clamp(&self) -> Self { 194 | Self { 195 | r: self.r.clamp(0.0, 1.0), 196 | g: self.g.clamp(0.0, 1.0), 197 | b: self.b.clamp(0.0, 1.0), 198 | a: self.a.clamp(0.0, 1.0), 199 | } 200 | } 201 | 202 | /// Returns name if there is a name for this color. 203 | /// 204 | /// **Note:** It ignores transparency (alpha value). 205 | /// 206 | /// ``` 207 | /// use csscolorparser::Color; 208 | /// 209 | /// assert_eq!(Color::from_rgba8(255, 0, 0, 255).name(), Some("red")); 210 | /// assert_eq!(Color::from_rgba8(238, 130, 238, 255).name(), Some("violet")); 211 | /// assert_eq!(Color::from_rgba8(90, 150, 200, 255).name(), None); 212 | /// ``` 213 | #[cfg(feature = "named-colors")] 214 | pub fn name(&self) -> Option<&'static str> { 215 | let rgb = &self.to_rgba8()[0..3]; 216 | for (&k, v) in NAMED_COLORS.entries() { 217 | if v == rgb { 218 | return Some(k.as_str()); 219 | } 220 | } 221 | None 222 | } 223 | 224 | /// Returns: `[r, g, b, a]` 225 | /// 226 | /// * Red, green, blue and alpha in the range [0..1] 227 | pub const fn to_array(&self) -> [f32; 4] { 228 | [ 229 | self.r.clamp(0.0, 1.0), 230 | self.g.clamp(0.0, 1.0), 231 | self.b.clamp(0.0, 1.0), 232 | self.a.clamp(0.0, 1.0), 233 | ] 234 | } 235 | 236 | /// Returns: `[r, g, b, a]` 237 | /// 238 | /// * Red, green, blue and alpha in the range [0..255] 239 | pub const fn to_rgba8(&self) -> [u8; 4] { 240 | [ 241 | (self.r * 255.0 + 0.5) as u8, 242 | (self.g * 255.0 + 0.5) as u8, 243 | (self.b * 255.0 + 0.5) as u8, 244 | (self.a * 255.0 + 0.5) as u8, 245 | ] 246 | } 247 | 248 | /// Returns: `[r, g, b, a]` 249 | /// 250 | /// * Red, green, blue and alpha in the range [0..65535] 251 | pub const fn to_rgba16(&self) -> [u16; 4] { 252 | [ 253 | (self.r * 65535.0 + 0.5) as u16, 254 | (self.g * 65535.0 + 0.5) as u16, 255 | (self.b * 65535.0 + 0.5) as u16, 256 | (self.a * 65535.0 + 0.5) as u16, 257 | ] 258 | } 259 | 260 | /// Returns: `[h, s, v, a]` 261 | /// 262 | /// * `h`: Hue angle [0..360] 263 | /// * `s`: Saturation [0..1] 264 | /// * `v`: Value [0..1] 265 | /// * `a`: Alpha [0..1] 266 | pub const fn to_hsva(&self) -> [f32; 4] { 267 | let [h, s, v] = rgb_to_hsv( 268 | self.r.clamp(0.0, 1.0), 269 | self.g.clamp(0.0, 1.0), 270 | self.b.clamp(0.0, 1.0), 271 | ); 272 | [ 273 | h, 274 | s.clamp(0.0, 1.0), 275 | v.clamp(0.0, 1.0), 276 | self.a.clamp(0.0, 1.0), 277 | ] 278 | } 279 | 280 | /// Returns: `[h, s, l, a]` 281 | /// 282 | /// * `h`: Hue angle [0..360] 283 | /// * `s`: Saturation [0..1] 284 | /// * `l`: Lightness [0..1] 285 | /// * `a`: Alpha [0..1] 286 | pub const fn to_hsla(&self) -> [f32; 4] { 287 | let [h, s, l] = rgb_to_hsl( 288 | self.r.clamp(0.0, 1.0), 289 | self.g.clamp(0.0, 1.0), 290 | self.b.clamp(0.0, 1.0), 291 | ); 292 | [ 293 | h, 294 | s.clamp(0.0, 1.0), 295 | l.clamp(0.0, 1.0), 296 | self.a.clamp(0.0, 1.0), 297 | ] 298 | } 299 | 300 | /// Returns: `[h, w, b, a]` 301 | /// 302 | /// * `h`: Hue angle [0..360] 303 | /// * `w`: Whiteness [0..1] 304 | /// * `b`: Blackness [0..1] 305 | /// * `a`: Alpha [0..1] 306 | pub const fn to_hwba(&self) -> [f32; 4] { 307 | let [h, w, b] = rgb_to_hwb( 308 | self.r.clamp(0.0, 1.0), 309 | self.g.clamp(0.0, 1.0), 310 | self.b.clamp(0.0, 1.0), 311 | ); 312 | [ 313 | h, 314 | w.clamp(0.0, 1.0), 315 | b.clamp(0.0, 1.0), 316 | self.a.clamp(0.0, 1.0), 317 | ] 318 | } 319 | 320 | /// Returns: `[r, g, b, a]` 321 | /// 322 | /// * Red, green, blue and alpha in the range [0..1] 323 | pub fn to_linear_rgba(&self) -> [f32; 4] { 324 | fn to_linear(x: f32) -> f32 { 325 | if x >= 0.04045 { 326 | return ((x + 0.055) / 1.055).powf(2.4); 327 | } 328 | x / 12.92 329 | } 330 | [ 331 | to_linear(self.r), 332 | to_linear(self.g), 333 | to_linear(self.b), 334 | self.a, 335 | ] 336 | } 337 | 338 | /// Returns: `[r, g, b, a]` 339 | /// 340 | /// * Red, green, blue and alpha in the range [0..255] 341 | pub fn to_linear_rgba_u8(&self) -> [u8; 4] { 342 | let [r, g, b, a] = self.to_linear_rgba(); 343 | [ 344 | (r * 255.0).round() as u8, 345 | (g * 255.0).round() as u8, 346 | (b * 255.0).round() as u8, 347 | (a * 255.0).round() as u8, 348 | ] 349 | } 350 | 351 | /// Returns: `[l, a, b, alpha]` 352 | pub fn to_oklaba(&self) -> [f32; 4] { 353 | let [r, g, b, _] = self.to_linear_rgba(); 354 | let [l, a, b] = linear_rgb_to_oklab(r, g, b); 355 | [l, a, b, self.a.clamp(0.0, 1.0)] 356 | } 357 | 358 | /// Returns: `[l, c, h, alpha]` 359 | pub fn to_oklcha(&self) -> [f32; 4] { 360 | let [l, a, b, alpha] = self.to_oklaba(); 361 | let c = (a * a + b * b).sqrt(); 362 | let h = b.atan2(a); 363 | [l, c, h, alpha] 364 | } 365 | 366 | /// Returns: `[l, a, b, alpha]` 367 | pub fn to_laba(&self) -> [f32; 4] { 368 | let [r, g, b, alpha] = self.to_linear_rgba(); 369 | let [l, a, b] = linear_rgb_to_lab(r, g, b); 370 | [l, a, b, alpha.clamp(0.0, 1.0)] 371 | } 372 | 373 | /// Returns: `[l, c, h, alpha]` 374 | pub fn to_lcha(&self) -> [f32; 4] { 375 | let [l, a, b, alpha] = self.to_laba(); 376 | let c = (a * a + b * b).sqrt(); 377 | let h = b.atan2(a); 378 | [l, c, h, alpha.clamp(0.0, 1.0)] 379 | } 380 | 381 | /// Get CSS RGB hexadecimal color representation 382 | pub fn to_css_hex(&self) -> String { 383 | self.to_string() 384 | } 385 | 386 | /// Get CSS `rgb()` color representation 387 | pub fn to_css_rgb(&self) -> String { 388 | let [r, g, b, _] = self.to_rgba8(); 389 | format!("rgb({r} {g} {b}{})", fmt_alpha(self.a)) 390 | } 391 | 392 | /// Get CSS `hsl()` color representation 393 | pub fn to_css_hsl(&self) -> String { 394 | let [h, s, l, alpha] = self.to_hsla(); 395 | let h = if h.is_nan() { 396 | "none".into() 397 | } else { 398 | fmt_float(h, 2) 399 | }; 400 | let s = (s * 100.0 + 0.5).floor(); 401 | let l = (l * 100.0 + 0.5).floor(); 402 | format!("hsl({h} {s}% {l}%{})", fmt_alpha(alpha)) 403 | } 404 | 405 | /// Get CSS `hwb()` color representation 406 | pub fn to_css_hwb(&self) -> String { 407 | let [h, w, b, alpha] = self.to_hwba(); 408 | let h = if h.is_nan() { 409 | "none".into() 410 | } else { 411 | fmt_float(h, 2) 412 | }; 413 | let w = (w * 100.0 + 0.5).floor(); 414 | let b = (b * 100.0 + 0.5).floor(); 415 | format!("hwb({h} {w}% {b}%{})", fmt_alpha(alpha)) 416 | } 417 | 418 | /// Get CSS `oklab()` color representation 419 | pub fn to_css_oklab(&self) -> String { 420 | let [l, a, b, alpha] = self.to_oklaba(); 421 | let l = fmt_float(l, 3); 422 | let a = fmt_float(a, 3); 423 | let b = fmt_float(b, 3); 424 | format!("oklab({l} {a} {b}{})", fmt_alpha(alpha)) 425 | } 426 | 427 | /// Get CSS `oklch()` color representation 428 | pub fn to_css_oklch(&self) -> String { 429 | let [l, c, h, alpha] = self.to_oklcha(); 430 | let l = fmt_float(l, 3); 431 | let c = fmt_float(c, 3); 432 | let h = fmt_float(normalize_angle(h.to_degrees()), 2); 433 | format!("oklch({l} {c} {h}{})", fmt_alpha(alpha)) 434 | } 435 | 436 | /// Get CSS `lab()` color representation 437 | pub fn to_css_lab(&self) -> String { 438 | let [l, a, b, alpha] = self.to_laba(); 439 | let l = fmt_float(l, 2); 440 | let a = fmt_float(a, 2); 441 | let b = fmt_float(b, 2); 442 | format!("lab({l} {a} {b}{})", fmt_alpha(alpha)) 443 | } 444 | 445 | /// Get CSS `lch()` color representation 446 | pub fn to_css_lch(&self) -> String { 447 | use core::f32::consts::PI; 448 | 449 | fn to_degrees(t: f32) -> f32 { 450 | if t > 0.0 { 451 | t / PI * 180.0 452 | } else { 453 | 360.0 - (t.abs() / PI) * 180.0 454 | } 455 | } 456 | 457 | let [l, c, h, alpha] = self.to_lcha(); 458 | let l = fmt_float(l, 2); 459 | let c = fmt_float(c, 2); 460 | let h = fmt_float(to_degrees(h), 2); 461 | format!("lch({l} {c} {h}{})", fmt_alpha(alpha)) 462 | } 463 | 464 | /// Blend this color with the other one, in the RGB color-space. `t` in the range [0..1]. 465 | pub const fn interpolate_rgb(&self, other: &Color, t: f32) -> Self { 466 | Self { 467 | r: self.r + t * (other.r - self.r), 468 | g: self.g + t * (other.g - self.g), 469 | b: self.b + t * (other.b - self.b), 470 | a: self.a + t * (other.a - self.a), 471 | } 472 | } 473 | 474 | /// Blend this color with the other one, in the linear RGB color-space. `t` in the range [0..1]. 475 | pub fn interpolate_linear_rgb(&self, other: &Color, t: f32) -> Self { 476 | let [r1, g1, b1, a1] = self.to_linear_rgba(); 477 | let [r2, g2, b2, a2] = other.to_linear_rgba(); 478 | Self::from_linear_rgba( 479 | r1 + t * (r2 - r1), 480 | g1 + t * (g2 - g1), 481 | b1 + t * (b2 - b1), 482 | a1 + t * (a2 - a1), 483 | ) 484 | } 485 | 486 | /// Blend this color with the other one, in the HSV color-space. `t` in the range [0..1]. 487 | pub const fn interpolate_hsv(&self, other: &Color, t: f32) -> Self { 488 | let [h1, s1, v1, a1] = self.to_hsva(); 489 | let [h2, s2, v2, a2] = other.to_hsva(); 490 | Self::from_hsva( 491 | interp_angle(h1, h2, t), 492 | s1 + t * (s2 - s1), 493 | v1 + t * (v2 - v1), 494 | a1 + t * (a2 - a1), 495 | ) 496 | } 497 | 498 | /// Blend this color with the other one, in the [Oklab](https://bottosson.github.io/posts/oklab/) color-space. `t` in the range [0..1]. 499 | pub fn interpolate_oklab(&self, other: &Color, t: f32) -> Self { 500 | let [l1, a1, b1, alpha1] = self.to_oklaba(); 501 | let [l2, a2, b2, alpha2] = other.to_oklaba(); 502 | Self::from_oklaba( 503 | l1 + t * (l2 - l1), 504 | a1 + t * (a2 - a1), 505 | b1 + t * (b2 - b1), 506 | alpha1 + t * (alpha2 - alpha1), 507 | ) 508 | } 509 | 510 | /// Blend this color with the other one, in the Lab color-space. `t` in the range [0..1]. 511 | pub fn interpolate_lab(&self, other: &Color, t: f32) -> Self { 512 | let [l1, a1, b1, alpha1] = self.to_laba(); 513 | let [l2, a2, b2, alpha2] = other.to_laba(); 514 | Self::from_laba( 515 | l1 + t * (l2 - l1), 516 | a1 + t * (a2 - a1), 517 | b1 + t * (b2 - b1), 518 | alpha1 + t * (alpha2 - alpha1), 519 | ) 520 | } 521 | 522 | /// Blend this color with the other one, in the LCH color-space. `t` in the range [0..1]. 523 | pub fn interpolate_lch(&self, other: &Color, t: f32) -> Self { 524 | let [l1, c1, h1, alpha1] = self.to_lcha(); 525 | let [l2, c2, h2, alpha2] = other.to_lcha(); 526 | Self::from_lcha( 527 | l1 + t * (l2 - l1), 528 | c1 + t * (c2 - c1), 529 | interp_angle_rad(h1, h2, t), 530 | alpha1 + t * (alpha2 - alpha1), 531 | ) 532 | } 533 | } 534 | 535 | impl Default for Color { 536 | fn default() -> Self { 537 | Self { 538 | r: 0.0, 539 | g: 0.0, 540 | b: 0.0, 541 | a: 1.0, 542 | } 543 | } 544 | } 545 | 546 | impl fmt::Display for Color { 547 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 548 | let [r, g, b, a] = self.to_rgba8(); 549 | if a < 255 { 550 | write!(f, "#{r:02x}{g:02x}{b:02x}{a:02x}") 551 | } else { 552 | write!(f, "#{r:02x}{g:02x}{b:02x}") 553 | } 554 | } 555 | } 556 | 557 | impl FromStr for Color { 558 | type Err = ParseColorError; 559 | 560 | fn from_str(s: &str) -> Result { 561 | parse(s) 562 | } 563 | } 564 | 565 | impl TryFrom<&str> for Color { 566 | type Error = ParseColorError; 567 | 568 | fn try_from(s: &str) -> Result { 569 | parse(s) 570 | } 571 | } 572 | 573 | impl TryFrom for Color { 574 | type Error = ParseColorError; 575 | 576 | fn try_from(s: String) -> Result { 577 | parse(s.as_ref()) 578 | } 579 | } 580 | 581 | impl From<(f32, f32, f32, f32)> for Color { 582 | fn from((r, g, b, a): (f32, f32, f32, f32)) -> Self { 583 | Self { r, g, b, a } 584 | } 585 | } 586 | 587 | impl From<(f32, f32, f32)> for Color { 588 | fn from((r, g, b): (f32, f32, f32)) -> Self { 589 | Self { r, g, b, a: 1.0 } 590 | } 591 | } 592 | 593 | impl From<[f32; 4]> for Color { 594 | fn from([r, g, b, a]: [f32; 4]) -> Self { 595 | Self { r, g, b, a } 596 | } 597 | } 598 | 599 | impl From<[f32; 3]> for Color { 600 | fn from([r, g, b]: [f32; 3]) -> Self { 601 | Self { r, g, b, a: 1.0 } 602 | } 603 | } 604 | 605 | impl From<[f64; 4]> for Color { 606 | fn from([r, g, b, a]: [f64; 4]) -> Self { 607 | Self { 608 | r: r as f32, 609 | g: g as f32, 610 | b: b as f32, 611 | a: a as f32, 612 | } 613 | } 614 | } 615 | 616 | impl From<[f64; 3]> for Color { 617 | fn from([r, g, b]: [f64; 3]) -> Self { 618 | Self { 619 | r: r as f32, 620 | g: g as f32, 621 | b: b as f32, 622 | a: 1.0, 623 | } 624 | } 625 | } 626 | 627 | impl From<(u8, u8, u8, u8)> for Color { 628 | fn from((r, g, b, a): (u8, u8, u8, u8)) -> Self { 629 | Self::from_rgba8(r, g, b, a) 630 | } 631 | } 632 | 633 | impl From<(u8, u8, u8)> for Color { 634 | fn from((r, g, b): (u8, u8, u8)) -> Self { 635 | Self::from_rgba8(r, g, b, 255) 636 | } 637 | } 638 | 639 | impl From<[u8; 4]> for Color { 640 | fn from([r, g, b, a]: [u8; 4]) -> Self { 641 | Self::from_rgba8(r, g, b, a) 642 | } 643 | } 644 | 645 | impl From<[u8; 3]> for Color { 646 | fn from([r, g, b]: [u8; 3]) -> Self { 647 | Self::from_rgba8(r, g, b, 255) 648 | } 649 | } 650 | 651 | /// Convert rust-rgb's `RGB` type into `Color`. 652 | #[cfg(feature = "rust-rgb")] 653 | impl From> for Color { 654 | fn from(item: RGB) -> Self { 655 | Self::new(item.r, item.g, item.b, 1.0) 656 | } 657 | } 658 | 659 | /// Convert rust-rgb's `RGBA` type into `Color`. 660 | #[cfg(feature = "rust-rgb")] 661 | impl From> for Color { 662 | fn from(item: RGBA) -> Self { 663 | Self::new(item.r, item.g, item.b, item.a) 664 | } 665 | } 666 | 667 | /// Implement Serde serialization into HEX string 668 | #[cfg(feature = "serde")] 669 | impl Serialize for Color { 670 | fn serialize(&self, serializer: S) -> Result { 671 | serializer.collect_str(self) 672 | } 673 | } 674 | 675 | /// Implement Serde deserialization from string 676 | #[cfg(feature = "serde")] 677 | impl<'de> Deserialize<'de> for Color { 678 | fn deserialize>(deserializer: D) -> Result { 679 | deserializer.deserialize_str(ColorVisitor) 680 | } 681 | } 682 | 683 | #[cfg(feature = "serde")] 684 | struct ColorVisitor; 685 | 686 | #[cfg(feature = "serde")] 687 | impl Visitor<'_> for ColorVisitor { 688 | type Value = Color; 689 | 690 | fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { 691 | f.write_str("a valid css color") 692 | } 693 | 694 | fn visit_str(self, v: &str) -> Result 695 | where 696 | E: serde::de::Error, 697 | { 698 | Color::from_str(v).map_err(serde::de::Error::custom) 699 | } 700 | } 701 | 702 | fn fmt_float(t: f32, precision: usize) -> String { 703 | let s = format!("{t:.precision$}"); 704 | s.trim_end_matches('0').trim_end_matches('.').to_string() 705 | } 706 | 707 | fn fmt_alpha(alpha: f32) -> String { 708 | if alpha < 1.0 { 709 | format!(" / {}%", (alpha.max(0.0) * 100.0 + 0.5).floor()) 710 | } else { 711 | "".into() 712 | } 713 | } 714 | 715 | #[cfg(test)] 716 | mod t { 717 | #[cfg(any(feature = "serde", feature = "rust-rgb"))] 718 | use super::*; 719 | 720 | #[cfg(feature = "rust-rgb")] 721 | #[test] 722 | fn rust_rgb() { 723 | let rgb = RGB::new(0.0, 0.5, 1.0); 724 | assert_eq!(Color::new(0.0, 0.5, 1.0, 1.0), Color::from(rgb)); 725 | 726 | let rgba = RGBA::new(1.0, 0.5, 0.0, 0.5); 727 | assert_eq!(Color::new(1.0, 0.5, 0.0, 0.5), Color::from(rgba)); 728 | } 729 | 730 | #[cfg(feature = "serde")] 731 | #[test] 732 | fn serde_serialize() { 733 | let test_data = [ 734 | "#000000", 735 | "#ffffff", 736 | "#71fe15", 737 | "#d6e3c9", 738 | "#ff000000", 739 | "#ffff8080", 740 | "#6b9fa203", 741 | "#0e5e0be6", 742 | "#84f9a716", 743 | ]; 744 | for s in test_data { 745 | let color = parse(s).unwrap(); 746 | serde_test::assert_ser_tokens(&color, &[serde_test::Token::Str(s)]); 747 | } 748 | } 749 | 750 | #[cfg(feature = "serde")] 751 | #[test] 752 | fn serde_deserialize() { 753 | let col = Color::new(0.0, 1.0, 0.0, 1.0); 754 | serde_test::assert_de_tokens(&col, &[serde_test::Token::Str("#00ff00")]); 755 | serde_test::assert_de_tokens(&col, &[serde_test::Token::Str("rgb(0 255 0)")]); 756 | 757 | #[cfg(feature = "named-colors")] 758 | { 759 | serde_test::assert_de_tokens(&col, &[serde_test::Token::Str("lime")]); 760 | 761 | let col = Color::new(1.0, 0.0, 0.0, 1.0); 762 | serde_test::assert_de_tokens(&col, &[serde_test::Token::Str("red")]); 763 | 764 | let col = Color::new(1.0, 1.0, 0.0, 1.0); 765 | serde_test::assert_de_tokens(&col, &[serde_test::Token::Str("yellow")]); 766 | } 767 | } 768 | } 769 | --------------------------------------------------------------------------------