csscolorparser-0.8.1/.cargo_vcs_info.json0000644000000001360000000000100141060ustar { "git": { "sha1": "eca67763906f398cbae5c4143c4843f7ff54b3ce" }, "path_in_vcs": "" }csscolorparser-0.8.1/.gitignore000064400000000000000000000005071046102023000146700ustar 00000000000000# Generated by Cargo # will have compiled files and executables debug/ target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk csscolorparser-0.8.1/CHANGELOG.md000064400000000000000000000033451046102023000145140ustar 00000000000000# Changelog ## [Unreleased](https://github.com/mazznoer/csscolorparser-rs/compare/v0.8.1...HEAD) ## [0.8.1](https://github.com/mazznoer/csscolorparser-rs/compare/v0.8.0...v0.8.1) ### Changed - Improvements in parser code. - improvements of `calc()` parser for relative color format. ### Fixed - Require `phf` only if needed. ## [0.8.0](https://github.com/mazznoer/csscolorparser-rs/compare/v0.7.2...v0.8.0) ### Added - Support `no_std`. - Support parsing relative color format. ### Changed - Support for parsing lab format is always enabled now. Remove the `lab` cargo feature. - Using `phf::OrderedMap` and `uncased` to store named colors. ## [0.7.2](https://github.com/mazznoer/csscolorparser-rs/compare/v0.7.1...v0.7.2) ### Added - `Color::to_oklcha()` - `Color::to_css_hex()` - `Color::to_css_rgb()` - `Color::to_css_hsl()` - `Color::to_css_hwb()` - `Color::to_css_lab()` - `Color::to_css_lch()` - `Color::to_css_oklab()` - `Color::to_css_oklch()` ### Changed - Deprecate `Color::to_hex_string()` and `Color::to_rgb_string()` ## [0.7.1](https://github.com/mazznoer/csscolorparser-rs/compare/v0.7.0...v0.7.1) ### Changed - Remove some unnecessary allocations on parser code. ## [0.7.0](https://github.com/mazznoer/csscolorparser-rs/compare/v0.6.2...v0.7.0) ### Added - `Color::from_oklcha()` - Support parsing `oklab()` and `oklch()` color format. - `Color::{from,to}_{laba,lcha}()` ### Changed - `f64` -> `f32` - Return type for `Color::to_{hsva,hsla,hwba,lab,lch,oklaba,linear_rgba}()` changed from tuple to array. - Deprecate `Color::{from,to}_{lab,lch}()`, use `Color::{from,to}_{laba,lcha}()` instead. - `NAMED_COLORS` is now public ### Removed ### Fixed - Fix parsing `lab()` and `lch()` color format. - Update `oklab` formula. csscolorparser-0.8.1/Cargo.lock0000644000000120550000000000100120640ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bytemuck" version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" [[package]] name = "cint" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a0e87cdf78571d9fbeff16861c37a006cd718d2433dc6d5b80beaae367d899a" [[package]] name = "csscolorparser" version = "0.8.1" dependencies = [ "cint", "num-traits", "phf", "rgb", "serde", "serde_test", "uncased", ] [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "libm" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", ] [[package]] name = "phf" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ "phf_macros", "phf_shared", "serde", ] [[package]] name = "phf_generator" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ "fastrand", "phf_shared", ] [[package]] name = "phf_macros" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", "syn", "uncased", ] [[package]] name = "phf_shared" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" dependencies = [ "siphasher", "uncased", ] [[package]] name = "proc-macro2" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] [[package]] name = "rgb" version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" dependencies = [ "bytemuck", ] [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_test" version = "1.0.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f901ee573cab6b3060453d2d5f0bae4e6d628c23c0a962ff9b5f1d7c8d4f1ed" dependencies = [ "serde", ] [[package]] name = "siphasher" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "syn" version = "2.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "uncased" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" dependencies = [ "version_check", ] [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" csscolorparser-0.8.1/Cargo.toml0000644000000052370000000000100121130ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "csscolorparser" version = "0.8.1" authors = ["Nor Khasyatillah "] build = false exclude = [".github/*"] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "CSS color parser library" documentation = "https://docs.rs/csscolorparser/" readme = "README.md" keywords = [ "color", "colour", "css", "parser", ] categories = [ "graphics", "parser-implementations", "no-std", ] license = "MIT OR Apache-2.0" repository = "https://github.com/mazznoer/csscolorparser-rs" [package.metadata.docs.rs] features = [ "named-colors", "rust-rgb", "cint", "serde", "std", ] [features] cint = ["dep:cint"] default = [ "named-colors", "std", ] named-colors = [ "dep:phf", "dep:uncased", ] rust-rgb = ["dep:rgb"] serde = ["dep:serde"] std = [ "phf?/std", "serde?/std", ] [lib] name = "csscolorparser" path = "src/lib.rs" [[example]] name = "cli" path = "examples/cli.rs" required-features = ["named-colors"] [[example]] name = "named-colors" path = "examples/named-colors.rs" required-features = ["named-colors"] [[test]] name = "chrome_android" path = "tests/chrome_android.rs" [[test]] name = "chromium" path = "tests/chromium.rs" [[test]] name = "color" path = "tests/color.rs" [[test]] name = "firefox" path = "tests/firefox.rs" [[test]] name = "named_colors" path = "tests/named_colors.rs" required-features = ["named-colors"] [[test]] name = "parser" path = "tests/parser.rs" [[test]] name = "parser2" path = "tests/parser2.rs" [[test]] name = "parser_relative_color" path = "tests/parser_relative_color.rs" [dependencies.cint] version = "^0.3.1" optional = true [dependencies.num-traits] version = "0.2.19" features = ["libm"] default-features = false [dependencies.phf] version = "0.13.1" features = [ "macros", "uncased", ] optional = true default-features = false [dependencies.rgb] version = "0.8.33" optional = true [dependencies.serde] version = "1.0.139" features = ["derive"] optional = true default-features = false [dependencies.uncased] version = "0.9.10" optional = true default-features = false [dev-dependencies.serde_test] version = "1.0.139" csscolorparser-0.8.1/Cargo.toml.orig000064400000000000000000000026641046102023000155750ustar 00000000000000[package] name = "csscolorparser" version = "0.8.1" authors = ["Nor Khasyatillah "] edition = "2018" description = "CSS color parser library" readme = "README.md" repository = "https://github.com/mazznoer/csscolorparser-rs" documentation = "https://docs.rs/csscolorparser/" license = "MIT OR Apache-2.0" keywords = ["color", "colour", "css", "parser"] categories = ["graphics", "parser-implementations", "no-std"] exclude = [ ".github/*", ] [package.metadata.docs.rs] features = ["named-colors", "rust-rgb", "cint", "serde", "std"] [features] default = ["named-colors", "std"] cint = ["dep:cint"] named-colors = ["dep:phf", "dep:uncased"] rust-rgb = ["dep:rgb"] serde = ["dep:serde"] std = ["phf?/std", "serde?/std"] [dependencies] cint = { version = "^0.3.1", optional = true } num-traits = { version = "0.2.19", default-features = false, features = ["libm"] } phf = { version = "0.13.1", optional = true, default-features = false, features = ["macros", "uncased"] } rgb = { version = "0.8.33", optional = true } serde = { version = "1.0.139", optional = true, default-features = false, features = ["derive"] } uncased = { version = "0.9.10", optional = true, default-features = false } [dev-dependencies] serde_test = "1.0.139" [[test]] name = "named_colors" required-features = ["named-colors"] [[example]] name = "cli" required-features = ["named-colors"] [[example]] name = "named-colors" required-features = ["named-colors"] csscolorparser-0.8.1/LICENSE-APACHE000064400000000000000000000261221046102023000146250ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020 Nor Khasyatillah Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. csscolorparser-0.8.1/LICENSE-MIT000064400000000000000000000020611046102023000143310ustar 00000000000000MIT License Copyright (c) 2020 Nor Khasyatillah Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. csscolorparser-0.8.1/Makefile000064400000000000000000000005331046102023000143370ustar 00000000000000SHELL := /bin/bash .PHONY: all check test all: check test check: cargo build --no-default-features && \ cargo clippy --no-default-features -- -D warnings && \ cargo build --all-features && \ cargo clippy --all-features -- -D warnings && \ cargo fmt --all -- --check test: cargo test --no-default-features && \ cargo test --all-features csscolorparser-0.8.1/README.md000064400000000000000000000074321046102023000141630ustar 00000000000000

Rust CSS Color Parser Library

License crates.io Documentation Build Status Total Downloads

DocumentationChangelogFeatures


[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/). ## Supported Color Format ### Absolute Color * [Named colors](https://www.w3.org/TR/css-color-4/#named-colors) * RGB hexadecimal (with and without `#` prefix) + Short format `#rgb` + Short format with alpha `#rgba` + Long format `#rrggbb` + Long format with alpha `#rrggbbaa` * `rgb()` and `rgba()` * `hsl()` and `hsla()` * `hwb()` * `lab()` * `lch()` * `oklab()` * `oklch()` * `hwba()`, `hsv()`, `hsva()` - not in CSS standard. ### Relative Color Example: ```text rgb(from red r g calc(b + 20)) rgb(from gold calc(((r + g) + b) / 3) 127 127) hwb(from #bad455 calc(h + 35) w b) hsl(from purple h s l / 0.5) ``` #### Relative Color Format Limitations Doesn't support percentage. `calc()` only support the following expression: ```[OPERAND] [OPERATOR] [OPERAND]``` `OPERAND` can be a number, a variable (`r`, `g`, `b`, `alpha` etc. depends on color function) or another expression wrapped in parenthesis. `OPERATOR` is one of `+`, `-`, `*` or `/`. ##### Not Supported ``` rgb(from #bad455 100% g b) rgb(from #bad455 r g b / 50%) rgb(from #bad455 calc(r+g-30) 90 b) ``` ##### OK ``` rgb(from #bad455 255 g b) rgb(from #bad455 r g b / 0.5) rgb(from #bad455 calc(r+15) 90 b) rgb(from #bad455 calc((r+g)-30) 90 b) hwb(from rgb(from rgb(100% 0% 50%) r g 75) calc(h+25) w b) ``` ## Usage Add this to your `Cargo.toml` ```toml csscolorparser = "0.8" ``` ## Examples Using `csscolorparser::parse()` function. ```rust let c = csscolorparser::parse("rgb(100%,0%,0%)")?; assert_eq!(c.to_array(), [1.0, 0.0, 0.0, 1.0]); assert_eq!(c.to_rgba8(), [255, 0, 0, 255]); assert_eq!(c.to_css_hex(), "#ff0000"); assert_eq!(c.to_css_rgb(), "rgb(255 0 0)"); assert_eq!(c.name(), Some("red")); ``` Using `parse()` method on `&str`. ```rust use csscolorparser::Color; let c: Color = "#ff00007f".parse()?; assert_eq!(c.to_rgba8(), [255, 0, 0, 127]); assert_eq!(c.to_css_hex(), "#ff00007f"); ``` ## Features ### Default * __std__: Using the standard library. * __named-colors__: Enables parsing from [named colors](https://www.w3.org/TR/css-color-4/#named-colors). Default features can be disabled using `default-features = false`. ### Optional * __rust-rgb__: Enables converting from [`rgb`](https://crates.io/crates/rgb) crate types into `Color`. * __cint__: Enables converting [`cint`](https://crates.io/crates/cint) crate types to and from `Color`. * __serde__: Enables serializing (into HEX string) and deserializing (from any supported string color format) using [`serde`](https://serde.rs/) framework. ## Similar Projects * [csscolorparser](https://github.com/mazznoer/csscolorparser) (Go) * [csscolorparser](https://github.com/deanm/css-color-parser-js) (Javascript) csscolorparser-0.8.1/examples/cli.rs000064400000000000000000000016631046102023000156370ustar 00000000000000// Usage: // cargo r --example cli -- [color].. use csscolorparser::parse; fn main() { for arg in std::env::args().skip(1) { println!("{arg:?}"); match parse(&arg) { Ok(c) => { let [r, g, b, _] = c.to_rgba8(); let name = if let Some(s) = c.name() { s } else { "-" }; println!(" \x1B[48;2;{r};{g};{b}m \x1B[49m"); println!(" {}", c.to_css_hex()); println!(" {}", c.to_css_rgb()); println!(" {}", c.to_css_hwb()); println!(" {}", c.to_css_hsl()); println!(" {}", c.to_css_lab()); println!(" {}", c.to_css_lch()); println!(" {}", c.to_css_oklab()); println!(" {}", c.to_css_oklch()); println!(" name {}", name); } Err(e) => println!(" {e}"), } } } csscolorparser-0.8.1/examples/named-colors.rs000064400000000000000000000003021046102023000174400ustar 00000000000000use csscolorparser::NAMED_COLORS; fn main() { for (name, rgb) in &NAMED_COLORS { let [r, g, b] = rgb; println!("\x1B[48;2;{r};{g};{b}m \x1B[49m {name} {rgb:?}"); } } csscolorparser-0.8.1/src/cint.rs000064400000000000000000000052601046102023000147730ustar 00000000000000use crate::Color; use cint::{Alpha, ColorInterop, EncodedSrgb}; impl ColorInterop for Color { type CintTy = Alpha>; } impl From for EncodedSrgb { fn from(c: Color) -> Self { let Color { r, g, b, a: _ } = c; EncodedSrgb { r, g, b } } } impl From> for Color { fn from(c: EncodedSrgb) -> Self { let EncodedSrgb { r, g, b } = c; Self::new(r, g, b, 1.0) } } impl From for EncodedSrgb { fn from(c: Color) -> Self { let Color { r, g, b, a: _ } = c; let (r, g, b) = (r as f64, g as f64, b as f64); EncodedSrgb { r, g, b } } } impl From> for Color { fn from(c: EncodedSrgb) -> Self { let EncodedSrgb { r, g, b } = c; let (r, g, b) = (r as f32, g as f32, b as f32); Self::new(r, g, b, 1.0) } } impl From for Alpha> { fn from(c: Color) -> Self { let Color { r, g, b, a } = c; Alpha { color: EncodedSrgb { r, g, b }, alpha: a, } } } impl From>> for Color { fn from(c: Alpha>) -> Self { let Alpha { color: EncodedSrgb { r, g, b }, alpha, } = c; Self::new(r, g, b, alpha) } } impl From for Alpha> { fn from(c: Color) -> Self { let Color { r, g, b, a } = c; let (r, g, b, alpha) = (r as f64, g as f64, b as f64, a as f64); Alpha { color: EncodedSrgb { r, g, b }, alpha, } } } impl From>> for Color { fn from(c: Alpha>) -> Self { let Alpha { color: EncodedSrgb { r, g, b }, alpha, } = c; let (r, g, b, alpha) = (r as f32, g as f32, b as f32, alpha as f32); Self::new(r, g, b, alpha) } } impl From for EncodedSrgb { fn from(c: Color) -> Self { let [r, g, b, _] = c.to_rgba8(); EncodedSrgb { r, g, b } } } impl From> for Color { fn from(c: EncodedSrgb) -> Self { let EncodedSrgb { r, g, b } = c; Self::from_rgba8(r, g, b, 255) } } impl From for Alpha> { fn from(c: Color) -> Self { let [r, g, b, alpha] = c.to_rgba8(); Alpha { color: EncodedSrgb { r, g, b }, alpha, } } } impl From>> for Color { fn from(c: Alpha>) -> Self { let Alpha { color: EncodedSrgb { r, g, b }, alpha, } = c; Self::from_rgba8(r, g, b, alpha) } } csscolorparser-0.8.1/src/color.rs000064400000000000000000000512151046102023000151550ustar 00000000000000use core::convert::TryFrom; use core::fmt; use core::str::FromStr; #[cfg(feature = "rust-rgb")] use rgb::{RGB, RGBA}; #[cfg(feature = "serde")] use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; use crate::lab::{lab_to_linear_rgb, linear_rgb_to_lab}; use crate::utils::*; use crate::{parse, ParseColorError}; use alloc::format; use alloc::string::String; use alloc::string::ToString; #[cfg(not(feature = "std"))] use num_traits::float::Float; #[cfg(feature = "named-colors")] use crate::NAMED_COLORS; #[derive(Debug, Clone, PartialEq, PartialOrd)] /// The color pub struct Color { /// Red pub r: f32, /// Green pub g: f32, /// Blue pub b: f32, /// Alpha pub a: f32, } impl Color { /// Arguments: /// /// * `r`: Red value [0..1] /// * `g`: Green value [0..1] /// * `b`: Blue value [0..1] /// * `a`: Alpha value [0..1] pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self { Self { r, g, b, a } } /// Arguments: /// /// * `r`: Red value [0..255] /// * `g`: Green value [0..255] /// * `b`: Blue value [0..255] /// * `a`: Alpha value [0..255] pub const fn from_rgba8(r: u8, g: u8, b: u8, a: u8) -> Self { Self { r: r as f32 / 255.0, g: g as f32 / 255.0, b: b as f32 / 255.0, a: a as f32 / 255.0, } } /// Arguments: /// /// * `r`: Red value [0..1] /// * `g`: Green value [0..1] /// * `b`: Blue value [0..1] /// * `a`: Alpha value [0..1] pub fn from_linear_rgba(r: f32, g: f32, b: f32, a: f32) -> Self { fn from_linear(x: f32) -> f32 { if x >= 0.0031308 { return 1.055 * x.powf(1.0 / 2.4) - 0.055; } 12.92 * x } Self::new(from_linear(r), from_linear(g), from_linear(b), a) } /// Arguments: /// /// * `r`: Red value [0..255] /// * `g`: Green value [0..255] /// * `b`: Blue value [0..255] /// * `a`: Alpha value [0..255] pub fn from_linear_rgba8(r: u8, g: u8, b: u8, a: u8) -> Self { Self::from_linear_rgba( r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, a as f32 / 255.0, ) } /// Arguments: /// /// * `h`: Hue angle [0..360] /// * `s`: Saturation [0..1] /// * `v`: Value [0..1] /// * `a`: Alpha [0..1] pub const fn from_hsva(h: f32, s: f32, v: f32, a: f32) -> Self { let [r, g, b] = hsv_to_rgb(normalize_angle(h), s.clamp(0.0, 1.0), v.clamp(0.0, 1.0)); Self::new(r, g, b, a) } /// Arguments: /// /// * `h`: Hue angle [0..360] /// * `s`: Saturation [0..1] /// * `l`: Lightness [0..1] /// * `a`: Alpha [0..1] pub const fn from_hsla(h: f32, s: f32, l: f32, a: f32) -> Self { let [r, g, b] = hsl_to_rgb(normalize_angle(h), s.clamp(0.0, 1.0), l.clamp(0.0, 1.0)); Self::new(r, g, b, a) } /// Arguments: /// /// * `h`: Hue angle [0..360] /// * `w`: Whiteness [0..1] /// * `b`: Blackness [0..1] /// * `a`: Alpha [0..1] pub const fn from_hwba(h: f32, w: f32, b: f32, a: f32) -> Self { let [r, g, b] = hwb_to_rgb(normalize_angle(h), w.clamp(0.0, 1.0), b.clamp(0.0, 1.0)); Self::new(r, g, b, a) } /// Arguments: /// /// * `l`: Perceived lightness /// * `a`: How green/red the color is /// * `b`: How blue/yellow the color is /// * `alpha`: Alpha [0..1] pub fn from_oklaba(l: f32, a: f32, b: f32, alpha: f32) -> Self { let [r, g, b] = oklab_to_linear_rgb(l, a, b); Self::from_linear_rgba(r, g, b, alpha) } /// Arguments: /// /// * `l`: Perceived lightness /// * `c`: Chroma /// * `h`: Hue angle in radians /// * `alpha`: Alpha [0..1] pub fn from_oklcha(l: f32, c: f32, h: f32, alpha: f32) -> Self { Self::from_oklaba(l, c * h.cos(), c * h.sin(), alpha) } /// Arguments: /// /// * `l`: Lightness /// * `a`: Distance along the `a` axis /// * `b`: Distance along the `b` axis /// * `alpha`: Alpha [0..1] pub fn from_laba(l: f32, a: f32, b: f32, alpha: f32) -> Self { let [r, g, b] = lab_to_linear_rgb(l, a, b); Self::from_linear_rgba(r, g, b, alpha) } /// Arguments: /// /// * `l`: Lightness /// * `c`: Chroma /// * `h`: Hue angle in radians /// * `alpha`: Alpha [0..1] pub fn from_lcha(l: f32, c: f32, h: f32, alpha: f32) -> Self { Self::from_laba(l, c * h.cos(), c * h.sin(), alpha) } /// Create color from CSS color string. /// /// # Examples /// ``` /// use csscolorparser::Color; /// # use core::error::Error; /// # fn main() -> Result<(), Box> { /// /// let c = Color::from_html("rgb(255,0,0)")?; /// /// assert_eq!(c.to_array(), [1.0, 0.0, 0.0, 1.0]); /// assert_eq!(c.to_rgba8(), [255, 0, 0, 255]); /// assert_eq!(c.to_css_hex(), "#ff0000"); /// assert_eq!(c.to_css_rgb(), "rgb(255 0 0)"); /// # Ok(()) /// # } /// ``` pub fn from_html>(s: S) -> Result { parse(s.as_ref()) } /// Restricts R, G, B, A values to the range [0..1]. #[must_use = "method returns a new Color and does not mutate the original Color"] pub const fn clamp(&self) -> Self { Self { r: self.r.clamp(0.0, 1.0), g: self.g.clamp(0.0, 1.0), b: self.b.clamp(0.0, 1.0), a: self.a.clamp(0.0, 1.0), } } /// Returns name if there is a name for this color. /// /// **Note:** It ignores transparency (alpha value). /// /// ``` /// use csscolorparser::Color; /// /// assert_eq!(Color::from_rgba8(255, 0, 0, 255).name(), Some("red")); /// assert_eq!(Color::from_rgba8(238, 130, 238, 255).name(), Some("violet")); /// assert_eq!(Color::from_rgba8(90, 150, 200, 255).name(), None); /// ``` #[cfg(feature = "named-colors")] pub fn name(&self) -> Option<&'static str> { let rgb = &self.to_rgba8()[0..3]; for (&k, v) in NAMED_COLORS.entries() { if v == rgb { return Some(k.as_str()); } } None } /// Returns: `[r, g, b, a]` /// /// * Red, green, blue and alpha in the range [0..1] pub const fn to_array(&self) -> [f32; 4] { [ self.r.clamp(0.0, 1.0), self.g.clamp(0.0, 1.0), self.b.clamp(0.0, 1.0), self.a.clamp(0.0, 1.0), ] } /// Returns: `[r, g, b, a]` /// /// * Red, green, blue and alpha in the range [0..255] pub const fn to_rgba8(&self) -> [u8; 4] { [ (self.r * 255.0 + 0.5) as u8, (self.g * 255.0 + 0.5) as u8, (self.b * 255.0 + 0.5) as u8, (self.a * 255.0 + 0.5) as u8, ] } /// Returns: `[r, g, b, a]` /// /// * Red, green, blue and alpha in the range [0..65535] pub const fn to_rgba16(&self) -> [u16; 4] { [ (self.r * 65535.0 + 0.5) as u16, (self.g * 65535.0 + 0.5) as u16, (self.b * 65535.0 + 0.5) as u16, (self.a * 65535.0 + 0.5) as u16, ] } /// Returns: `[h, s, v, a]` /// /// * `h`: Hue angle [0..360] /// * `s`: Saturation [0..1] /// * `v`: Value [0..1] /// * `a`: Alpha [0..1] pub const fn to_hsva(&self) -> [f32; 4] { let [h, s, v] = rgb_to_hsv( self.r.clamp(0.0, 1.0), self.g.clamp(0.0, 1.0), self.b.clamp(0.0, 1.0), ); [ h, s.clamp(0.0, 1.0), v.clamp(0.0, 1.0), self.a.clamp(0.0, 1.0), ] } /// Returns: `[h, s, l, a]` /// /// * `h`: Hue angle [0..360] /// * `s`: Saturation [0..1] /// * `l`: Lightness [0..1] /// * `a`: Alpha [0..1] pub const fn to_hsla(&self) -> [f32; 4] { let [h, s, l] = rgb_to_hsl( self.r.clamp(0.0, 1.0), self.g.clamp(0.0, 1.0), self.b.clamp(0.0, 1.0), ); [ h, s.clamp(0.0, 1.0), l.clamp(0.0, 1.0), self.a.clamp(0.0, 1.0), ] } /// Returns: `[h, w, b, a]` /// /// * `h`: Hue angle [0..360] /// * `w`: Whiteness [0..1] /// * `b`: Blackness [0..1] /// * `a`: Alpha [0..1] pub const fn to_hwba(&self) -> [f32; 4] { let [h, w, b] = rgb_to_hwb( self.r.clamp(0.0, 1.0), self.g.clamp(0.0, 1.0), self.b.clamp(0.0, 1.0), ); [ h, w.clamp(0.0, 1.0), b.clamp(0.0, 1.0), self.a.clamp(0.0, 1.0), ] } /// Returns: `[r, g, b, a]` /// /// * Red, green, blue and alpha in the range [0..1] pub fn to_linear_rgba(&self) -> [f32; 4] { fn to_linear(x: f32) -> f32 { if x >= 0.04045 { return ((x + 0.055) / 1.055).powf(2.4); } x / 12.92 } [ to_linear(self.r), to_linear(self.g), to_linear(self.b), self.a, ] } /// Returns: `[r, g, b, a]` /// /// * Red, green, blue and alpha in the range [0..255] pub fn to_linear_rgba_u8(&self) -> [u8; 4] { let [r, g, b, a] = self.to_linear_rgba(); [ (r * 255.0).round() as u8, (g * 255.0).round() as u8, (b * 255.0).round() as u8, (a * 255.0).round() as u8, ] } /// Returns: `[l, a, b, alpha]` pub fn to_oklaba(&self) -> [f32; 4] { let [r, g, b, _] = self.to_linear_rgba(); let [l, a, b] = linear_rgb_to_oklab(r, g, b); [l, a, b, self.a.clamp(0.0, 1.0)] } /// Returns: `[l, c, h, alpha]` pub fn to_oklcha(&self) -> [f32; 4] { let [l, a, b, alpha] = self.to_oklaba(); let c = (a * a + b * b).sqrt(); let h = b.atan2(a); [l, c, h, alpha] } /// Returns: `[l, a, b, alpha]` pub fn to_laba(&self) -> [f32; 4] { let [r, g, b, alpha] = self.to_linear_rgba(); let [l, a, b] = linear_rgb_to_lab(r, g, b); [l, a, b, alpha.clamp(0.0, 1.0)] } /// Returns: `[l, c, h, alpha]` pub fn to_lcha(&self) -> [f32; 4] { let [l, a, b, alpha] = self.to_laba(); let c = (a * a + b * b).sqrt(); let h = b.atan2(a); [l, c, h, alpha.clamp(0.0, 1.0)] } /// Get CSS RGB hexadecimal color representation pub fn to_css_hex(&self) -> String { let [r, g, b, a] = self.to_rgba8(); if a < 255 { format!("#{r:02x}{g:02x}{b:02x}{a:02x}") } else { format!("#{r:02x}{g:02x}{b:02x}") } } /// Get CSS `rgb()` color representation pub fn to_css_rgb(&self) -> String { let [r, g, b, _] = self.to_rgba8(); format!("rgb({r} {g} {b}{})", fmt_alpha(self.a)) } /// Get CSS `hsl()` color representation pub fn to_css_hsl(&self) -> String { let [h, s, l, alpha] = self.to_hsla(); let h = if h.is_nan() { "none".into() } else { fmt_float(h, 2) }; let s = (s * 100.0 + 0.5).floor(); let l = (l * 100.0 + 0.5).floor(); format!("hsl({h} {s}% {l}%{})", fmt_alpha(alpha)) } /// Get CSS `hwb()` color representation pub fn to_css_hwb(&self) -> String { let [h, w, b, alpha] = self.to_hwba(); let h = if h.is_nan() { "none".into() } else { fmt_float(h, 2) }; let w = (w * 100.0 + 0.5).floor(); let b = (b * 100.0 + 0.5).floor(); format!("hwb({h} {w}% {b}%{})", fmt_alpha(alpha)) } /// Get CSS `oklab()` color representation pub fn to_css_oklab(&self) -> String { let [l, a, b, alpha] = self.to_oklaba(); let l = fmt_float(l, 3); let a = fmt_float(a, 3); let b = fmt_float(b, 3); format!("oklab({l} {a} {b}{})", fmt_alpha(alpha)) } /// Get CSS `oklch()` color representation pub fn to_css_oklch(&self) -> String { let [l, c, h, alpha] = self.to_oklcha(); let l = fmt_float(l, 3); let c = fmt_float(c, 3); let h = fmt_float(normalize_angle(h.to_degrees()), 2); format!("oklch({l} {c} {h}{})", fmt_alpha(alpha)) } /// Get CSS `lab()` color representation pub fn to_css_lab(&self) -> String { let [l, a, b, alpha] = self.to_laba(); let l = fmt_float(l, 2); let a = fmt_float(a, 2); let b = fmt_float(b, 2); format!("lab({l} {a} {b}{})", fmt_alpha(alpha)) } /// Get CSS `lch()` color representation pub fn to_css_lch(&self) -> String { use core::f32::consts::PI; fn to_degrees(t: f32) -> f32 { if t > 0.0 { t / PI * 180.0 } else { 360.0 - (t.abs() / PI) * 180.0 } } let [l, c, h, alpha] = self.to_lcha(); let l = fmt_float(l, 2); let c = fmt_float(c, 2); let h = fmt_float(to_degrees(h), 2); format!("lch({l} {c} {h}{})", fmt_alpha(alpha)) } /// Blend this color with the other one, in the RGB color-space. `t` in the range [0..1]. pub const fn interpolate_rgb(&self, other: &Color, t: f32) -> Self { Self { r: self.r + t * (other.r - self.r), g: self.g + t * (other.g - self.g), b: self.b + t * (other.b - self.b), a: self.a + t * (other.a - self.a), } } /// Blend this color with the other one, in the linear RGB color-space. `t` in the range [0..1]. pub fn interpolate_linear_rgb(&self, other: &Color, t: f32) -> Self { let [r1, g1, b1, a1] = self.to_linear_rgba(); let [r2, g2, b2, a2] = other.to_linear_rgba(); Self::from_linear_rgba( r1 + t * (r2 - r1), g1 + t * (g2 - g1), b1 + t * (b2 - b1), a1 + t * (a2 - a1), ) } /// Blend this color with the other one, in the HSV color-space. `t` in the range [0..1]. pub const fn interpolate_hsv(&self, other: &Color, t: f32) -> Self { let [h1, s1, v1, a1] = self.to_hsva(); let [h2, s2, v2, a2] = other.to_hsva(); Self::from_hsva( interp_angle(h1, h2, t), s1 + t * (s2 - s1), v1 + t * (v2 - v1), a1 + t * (a2 - a1), ) } /// Blend this color with the other one, in the [Oklab](https://bottosson.github.io/posts/oklab/) color-space. `t` in the range [0..1]. pub fn interpolate_oklab(&self, other: &Color, t: f32) -> Self { let [l1, a1, b1, alpha1] = self.to_oklaba(); let [l2, a2, b2, alpha2] = other.to_oklaba(); Self::from_oklaba( l1 + t * (l2 - l1), a1 + t * (a2 - a1), b1 + t * (b2 - b1), alpha1 + t * (alpha2 - alpha1), ) } /// Blend this color with the other one, in the Lab color-space. `t` in the range [0..1]. pub fn interpolate_lab(&self, other: &Color, t: f32) -> Self { let [l1, a1, b1, alpha1] = self.to_laba(); let [l2, a2, b2, alpha2] = other.to_laba(); Self::from_laba( l1 + t * (l2 - l1), a1 + t * (a2 - a1), b1 + t * (b2 - b1), alpha1 + t * (alpha2 - alpha1), ) } /// Blend this color with the other one, in the LCH color-space. `t` in the range [0..1]. pub fn interpolate_lch(&self, other: &Color, t: f32) -> Self { let [l1, c1, h1, alpha1] = self.to_lcha(); let [l2, c2, h2, alpha2] = other.to_lcha(); Self::from_lcha( l1 + t * (l2 - l1), c1 + t * (c2 - c1), interp_angle_rad(h1, h2, t), alpha1 + t * (alpha2 - alpha1), ) } } impl Default for Color { fn default() -> Self { Self { r: 0.0, g: 0.0, b: 0.0, a: 1.0, } } } impl fmt::Display for Color { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "RGBA({},{},{},{})", self.r, self.g, self.b, self.a) } } impl FromStr for Color { type Err = ParseColorError; fn from_str(s: &str) -> Result { parse(s) } } impl TryFrom<&str> for Color { type Error = ParseColorError; fn try_from(s: &str) -> Result { parse(s) } } impl TryFrom for Color { type Error = ParseColorError; fn try_from(s: String) -> Result { parse(s.as_ref()) } } impl From<(f32, f32, f32, f32)> for Color { fn from((r, g, b, a): (f32, f32, f32, f32)) -> Self { Self { r, g, b, a } } } impl From<(f32, f32, f32)> for Color { fn from((r, g, b): (f32, f32, f32)) -> Self { Self { r, g, b, a: 1.0 } } } impl From<[f32; 4]> for Color { fn from([r, g, b, a]: [f32; 4]) -> Self { Self { r, g, b, a } } } impl From<[f32; 3]> for Color { fn from([r, g, b]: [f32; 3]) -> Self { Self { r, g, b, a: 1.0 } } } impl From<[f64; 4]> for Color { fn from([r, g, b, a]: [f64; 4]) -> Self { Self { r: r as f32, g: g as f32, b: b as f32, a: a as f32, } } } impl From<[f64; 3]> for Color { fn from([r, g, b]: [f64; 3]) -> Self { Self { r: r as f32, g: g as f32, b: b as f32, a: 1.0, } } } impl From<(u8, u8, u8, u8)> for Color { fn from((r, g, b, a): (u8, u8, u8, u8)) -> Self { Self::from_rgba8(r, g, b, a) } } impl From<(u8, u8, u8)> for Color { fn from((r, g, b): (u8, u8, u8)) -> Self { Self::from_rgba8(r, g, b, 255) } } impl From<[u8; 4]> for Color { fn from([r, g, b, a]: [u8; 4]) -> Self { Self::from_rgba8(r, g, b, a) } } impl From<[u8; 3]> for Color { fn from([r, g, b]: [u8; 3]) -> Self { Self::from_rgba8(r, g, b, 255) } } /// Convert rust-rgb's `RGB` type into `Color`. #[cfg(feature = "rust-rgb")] impl From> for Color { fn from(item: RGB) -> Self { Self::new(item.r, item.g, item.b, 1.0) } } /// Convert rust-rgb's `RGBA` type into `Color`. #[cfg(feature = "rust-rgb")] impl From> for Color { fn from(item: RGBA) -> Self { Self::new(item.r, item.g, item.b, item.a) } } /// Implement Serde serialization into HEX string #[cfg(feature = "serde")] impl Serialize for Color { fn serialize(&self, serializer: S) -> Result { serializer.serialize_str(&self.to_css_hex()) } } /// Implement Serde deserialization from string #[cfg(feature = "serde")] impl<'de> Deserialize<'de> for Color { fn deserialize>(deserializer: D) -> Result { deserializer.deserialize_str(ColorVisitor) } } #[cfg(feature = "serde")] struct ColorVisitor; #[cfg(feature = "serde")] impl Visitor<'_> for ColorVisitor { type Value = Color; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("a valid css color") } fn visit_str(self, v: &str) -> Result where E: serde::de::Error, { Color::from_str(v).map_err(serde::de::Error::custom) } } fn fmt_float(t: f32, precision: usize) -> String { let s = format!("{t:.precision$}"); s.trim_end_matches('0').trim_end_matches('.').to_string() } fn fmt_alpha(alpha: f32) -> String { if alpha < 1.0 { format!(" / {}%", (alpha.max(0.0) * 100.0 + 0.5).floor()) } else { "".into() } } #[cfg(test)] mod tests { #[cfg(any(feature = "serde", feature = "rust-rgb"))] use super::*; #[cfg(feature = "rust-rgb")] #[test] fn test_convert_rust_rgb_to_color() { let rgb = RGB::new(0.0, 0.5, 1.0); assert_eq!(Color::new(0.0, 0.5, 1.0, 1.0), Color::from(rgb)); let rgba = RGBA::new(1.0, 0.5, 0.0, 0.5); assert_eq!(Color::new(1.0, 0.5, 0.0, 0.5), Color::from(rgba)); } #[cfg(feature = "serde")] #[test] fn test_serde_serialize_to_hex() { let color = Color::new(1.0, 1.0, 0.5, 0.5); serde_test::assert_ser_tokens(&color, &[serde_test::Token::Str("#ffff8080")]); } #[cfg(all(feature = "serde", feature = "named-colors"))] #[test] fn test_serde_deserialize_from_string() { let named = Color::new(1.0, 1.0, 0.0, 1.0); serde_test::assert_de_tokens(&named, &[serde_test::Token::Str("yellow")]); let hex = Color::new(0.0, 1.0, 0.0, 1.0); serde_test::assert_de_tokens(&hex, &[serde_test::Token::Str("#00ff00ff")]); let rgb = Color::new(0.0, 1.0, 0.0, 1.0); serde_test::assert_de_tokens(&rgb, &[serde_test::Token::Str("rgba(0,255,0,1)")]); } } csscolorparser-0.8.1/src/color2.rs000064400000000000000000000135741046102023000152450ustar 00000000000000// Color deprecated methods use crate::Color; use alloc::string::String; #[cfg(not(feature = "std"))] use num_traits::float::Float; impl Color { #[deprecated = "Use [new](#method.new) instead."] /// Arguments: /// /// * `r`: Red value [0..1] /// * `g`: Green value [0..1] /// * `b`: Blue value [0..1] pub fn from_rgb(r: f32, g: f32, b: f32) -> Self { Self { r, g, b, a: 1.0 } } #[deprecated = "Use [new](#method.new) instead."] /// Arguments: /// /// * `r`: Red value [0..1] /// * `g`: Green value [0..1] /// * `b`: Blue value [0..1] /// * `a`: Alpha value [0..1] pub fn from_rgba(r: f32, g: f32, b: f32, a: f32) -> Self { Self { r, g, b, a } } #[deprecated = "Use [from_rgba8](#method.from_rgba8) instead."] /// Arguments: /// /// * `r`: Red value [0..255] /// * `g`: Green value [0..255] /// * `b`: Blue value [0..255] pub fn from_rgb_u8(r: u8, g: u8, b: u8) -> Self { Self { r: r as f32 / 255.0, g: g as f32 / 255.0, b: b as f32 / 255.0, a: 1.0, } } #[deprecated = "Use [from_rgba8](#method.from_rgba8) instead."] /// Arguments: /// /// * `r`: Red value [0..255] /// * `g`: Green value [0..255] /// * `b`: Blue value [0..255] /// * `a`: Alpha value [0..255] pub fn from_rgba_u8(r: u8, g: u8, b: u8, a: u8) -> Self { Self { r: r as f32 / 255.0, g: g as f32 / 255.0, b: b as f32 / 255.0, a: a as f32 / 255.0, } } #[deprecated = "Use [from_linear_rgba](#method.from_linear_rgba) instead."] /// Arguments: /// /// * `r`: Red value [0..1] /// * `g`: Green value [0..1] /// * `b`: Blue value [0..1] pub fn from_linear_rgb(r: f32, g: f32, b: f32) -> Self { Self::from_linear_rgba(r, g, b, 1.0) } #[deprecated = "Use [from_linear_rgba8](#method.from_linear_rgba8) instead."] /// Arguments: /// /// * `r`: Red value [0..255] /// * `g`: Green value [0..255] /// * `b`: Blue value [0..255] pub fn from_linear_rgb_u8(r: u8, g: u8, b: u8) -> Self { Self::from_linear_rgba(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0) } #[deprecated = "Use [from_linear_rgba8](#method.from_linear_rgba8) instead."] /// Arguments: /// /// * `r`: Red value [0..255] /// * `g`: Green value [0..255] /// * `b`: Blue value [0..255] /// * `a`: Alpha value [0..255] pub fn from_linear_rgba_u8(r: u8, g: u8, b: u8, a: u8) -> Self { Self::from_linear_rgba( r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, a as f32 / 255.0, ) } #[deprecated = "Use [from_hsva](#method.from_hsva) instead."] /// Arguments: /// /// * `h`: Hue angle [0..360] /// * `s`: Saturation [0..1] /// * `v`: Value [0..1] pub fn from_hsv(h: f32, s: f32, v: f32) -> Self { Self::from_hsva(h, s, v, 1.0) } #[deprecated = "Use [from_hsla](#method.from_hsla) instead."] /// Arguments: /// /// * `h`: Hue angle [0..360] /// * `s`: Saturation [0..1] /// * `l`: Lightness [0..1] pub fn from_hsl(h: f32, s: f32, l: f32) -> Self { Self::from_hsla(h, s, l, 1.0) } #[deprecated = "Use [from_hwba](#method.from_hwba) instead."] /// Arguments: /// /// * `h`: Hue angle [0..360] /// * `w`: Whiteness [0..1] /// * `b`: Blackness [0..1] pub fn from_hwb(h: f32, w: f32, b: f32) -> Self { Self::from_hwba(h, w, b, 1.0) } #[deprecated = "Use [from_oklaba](#method.from_oklaba) instead."] /// Arguments: /// /// * `l`: Perceived lightness /// * `a`: How green/red the color is /// * `b`: How blue/yellow the color is pub fn from_oklab(l: f32, a: f32, b: f32) -> Self { Self::from_oklaba(l, a, b, 1.0) } #[deprecated = "Use [from_laba](#method.from_laba) instead."] /// Arguments: /// /// * `l`: Lightness /// * `a`: Distance along the `a` axis /// * `b`: Distance along the `b` axis /// * `alpha`: Alpha [0..1] pub fn from_lab(l: f32, a: f32, b: f32, alpha: f32) -> Self { Self::from_laba(l, a, b, alpha) } #[deprecated = "Use [to_laba](#method.to_laba) instead."] /// Returns: `[l, a, b, alpha]` pub fn to_lab(&self) -> [f32; 4] { self.to_laba() } #[deprecated = "Use [from_lcha](#method.from_lcha) instead."] /// Arguments: /// /// * `l`: Lightness /// * `c`: Chroma /// * `h`: Hue angle in radians /// * `alpha`: Alpha [0..1] pub fn from_lch(l: f32, c: f32, h: f32, alpha: f32) -> Self { Self::from_lcha(l, c, h, alpha) } #[deprecated = "Use [to_lcha](#method.to_lcha) instead."] /// Returns: `[l, c, h, alpha]` pub fn to_lch(&self) -> [f32; 4] { self.to_lcha() } #[deprecated] /// Returns: `(r, g, b, a)` /// /// * Red, green, blue and alpha in the range [0..1] pub fn rgba(&self) -> (f32, f32, f32, f32) { (self.r, self.g, self.b, self.a) } #[deprecated = "Use [to_rgba8](#method.to_rgba8) instead."] /// Returns: `(r, g, b, a)` /// /// * Red, green, blue and alpha in the range [0..255] pub fn rgba_u8(&self) -> (u8, u8, u8, u8) { ( (self.r * 255.0).round() as u8, (self.g * 255.0).round() as u8, (self.b * 255.0).round() as u8, (self.a * 255.0).round() as u8, ) } // --- Since version 0.7.2 #[deprecated = "Use [to_css_hex](#method.to_css_hex) instead."] /// Get the RGB hexadecimal color string. pub fn to_hex_string(&self) -> String { self.to_css_hex() } #[deprecated = "Use [to_css_rgb](#method.to_css_rgb) instead."] /// Get the CSS `rgb()` format string. pub fn to_rgb_string(&self) -> String { self.to_css_rgb() } } csscolorparser-0.8.1/src/error.rs000064400000000000000000000035601046102023000151700ustar 00000000000000use core::error::Error; use core::fmt; /// An error which can be returned when parsing a CSS color string. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum ParseColorError { /// A CSS color string was invalid hex format. InvalidHex, /// A CSS color string was invalid rgb format. InvalidRgb, /// A CSS color string was invalid hsl format. InvalidHsl, /// A CSS color string was invalid hwb format. InvalidHwb, /// A CSS color string was invalid hsv format. InvalidHsv, /// A CSS color string was invalid lab format. InvalidLab, /// A CSS color string was invalid lch format. InvalidLch, /// A CSS color string was invalid oklab format. InvalidOklab, /// A CSS color string was invalid oklch format. InvalidOklch, /// A CSS color string was invalid color function. InvalidFunction, /// A CSS color string was invalid unknown format. InvalidUnknown, } impl fmt::Display for ParseColorError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Self::InvalidHex => f.write_str("invalid hex format"), Self::InvalidRgb => f.write_str("invalid rgb format"), Self::InvalidHsl => f.write_str("invalid hsl format"), Self::InvalidHwb => f.write_str("invalid hwb format"), Self::InvalidHsv => f.write_str("invalid hsv format"), Self::InvalidLab => f.write_str("invalid lab format"), Self::InvalidLch => f.write_str("invalid lch format"), Self::InvalidOklab => f.write_str("invalid oklab format"), Self::InvalidOklch => f.write_str("invalid oklch format"), Self::InvalidFunction => f.write_str("invalid color function"), Self::InvalidUnknown => f.write_str("invalid unknown format"), } } } impl Error for ParseColorError {} csscolorparser-0.8.1/src/lab.rs000064400000000000000000000047611046102023000146010ustar 00000000000000#[cfg(not(feature = "std"))] use num_traits::float::Float; // Constants for D65 white point (normalized to Y=1.0) const D65_X: f32 = 0.95047; const D65_Y: f32 = 1.0; const D65_Z: f32 = 1.08883; const DELTA: f32 = 6.0 / 29.0; const DELTA2: f32 = DELTA * DELTA; const DELTA3: f32 = DELTA2 * DELTA; // Helper function for LAB to XYZ conversion const fn lab_to_xyz(l: f32, a: f32, b: f32) -> [f32; 3] { let fy = (l + 16.0) / 116.0; let fx = fy + a / 500.0; let fz = fy - b / 200.0; const fn lab_f(t: f32) -> f32 { if t > DELTA { t * t * t } else { (t - 16.0 / 116.0) * 3.0 * DELTA2 } } let x = D65_X * lab_f(fx); let y = D65_Y * lab_f(fy); let z = D65_Z * lab_f(fz); [x, y, z] } #[allow(clippy::excessive_precision)] // Helper function for XYZ to linear RGB conversion const fn xyz_to_linear_rgb(x: f32, y: f32, z: f32) -> [f32; 3] { // sRGB matrix (D65) let r = 3.2404542 * x - 1.5371385 * y - 0.4985314 * z; let g = -0.9692660 * x + 1.8760108 * y + 0.0415560 * z; let b = 0.0556434 * x - 0.2040259 * y + 1.0572252 * z; [r, g, b] } #[allow(clippy::excessive_precision)] // Helper function for linear RGB to XYZ conversion const fn linear_rgb_to_xyz(r: f32, g: f32, b: f32) -> [f32; 3] { // Inverse sRGB matrix (D65) let x = 0.4124564 * r + 0.3575761 * g + 0.1804375 * b; let y = 0.2126729 * r + 0.7151522 * g + 0.0721750 * b; let z = 0.0193339 * r + 0.1191920 * g + 0.9503041 * b; [x, y, z] } // Helper function for XYZ to LAB conversion fn xyz_to_lab(x: f32, y: f32, z: f32) -> [f32; 3] { let lab_f = |t: f32| -> f32 { if t > DELTA3 { t.cbrt() } else { (t / (3.0 * DELTA2)) + (4.0 / 29.0) } }; let fx = lab_f(x / D65_X); let fy = lab_f(y / D65_Y); let fz = lab_f(z / D65_Z); let l = 116.0 * fy - 16.0; let a = 500.0 * (fx - fy); let b = 200.0 * (fy - fz); [l, a, b] } // Convert CIELAB (L*a*b*) to linear RGB // L: [0, 100], a: [-128, 127], b: [-128, 127] // Returns RGB in [0, 1] range pub(crate) const fn lab_to_linear_rgb(l: f32, a: f32, b: f32) -> [f32; 3] { let [x, y, z] = lab_to_xyz(l, a, b); xyz_to_linear_rgb(x, y, z) } // Convert linear RGB to CIELAB (L*a*b*) // RGB components in [0, 1] range // Returns [L, a, b] with L: [0, 100], a: [-128, 127], b: [-128, 127] pub(crate) fn linear_rgb_to_lab(r: f32, g: f32, b: f32) -> [f32; 3] { let [x, y, z] = linear_rgb_to_xyz(r, g, b); xyz_to_lab(x, y, z) } csscolorparser-0.8.1/src/lib.rs000064400000000000000000000045721046102023000146110ustar 00000000000000//! # Overview //! //! 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/). //! //! ## Supported Color Format //! //! * [Named colors](https://www.w3.org/TR/css-color-4/#named-colors) //! * RGB hexadecimal (with and without `#` prefix) //! + Short format `#rgb` //! + Short format with alpha `#rgba` //! + Long format `#rrggbb` //! + Long format with alpha `#rrggbbaa` //! * `rgb()` and `rgba()` //! * `hsl()` and `hsla()` //! * `hwb()` //! * `lab()` //! * `lch()` //! * `oklab()` //! * `oklch()` //! * `hwba()`, `hsv()`, `hsva()` - not in CSS standard. //! //! ## Examples //! //! Using [`csscolorparser::parse()`](fn.parse.html) function. //! //! ```rust //! # fn main() -> Result<(), Box> { //! let c = csscolorparser::parse("rgb(100%,0%,0%)")?; //! //! assert_eq!(c.to_array(), [1.0, 0.0, 0.0, 1.0]); //! assert_eq!(c.to_rgba8(), [255, 0, 0, 255]); //! assert_eq!(c.to_css_hex(), "#ff0000"); //! assert_eq!(c.to_css_rgb(), "rgb(255 0 0)"); //! # Ok(()) //! # } //! ``` //! //! Using `parse()` method on `&str`. //! //! ```rust //! use csscolorparser::Color; //! # fn main() -> Result<(), Box> { //! //! let c: Color = "#ff00007f".parse()?; //! //! assert_eq!(c.to_rgba8(), [255, 0, 0, 127]); //! assert_eq!(c.to_css_hex(), "#ff00007f"); //! # Ok(()) //! # } //! ``` //! //! ## Default Feature //! //! * `std`: Using the standard library. //! * `named-colors`: Enables parsing from [named colors](https://www.w3.org/TR/css-color-4/#named-colors). //! //! ## Optional Features //! //! * `rust-rgb`: Enables converting from [`rgb`](https://crates.io/crates/rgb) crate types into `Color`. //! * `cint`: Enables converting [`cint`](https://crates.io/crates/cint) crate types to and from `Color`. //! * `serde`: Enables serializing (into HEX string) and deserializing (from any supported string color format) using [`serde`](https://serde.rs/) framework. #![forbid(unsafe_code)] #![warn(missing_docs)] #![no_std] #[cfg(feature = "std")] extern crate std; extern crate alloc; mod color; mod color2; pub use color::Color; mod error; pub use error::ParseColorError; mod parser; pub use parser::parse; #[cfg(feature = "named-colors")] mod named_colors; #[cfg(feature = "named-colors")] pub use named_colors::NAMED_COLORS; #[cfg(feature = "cint")] mod cint; mod lab; mod utils; csscolorparser-0.8.1/src/named_colors.rs000064400000000000000000000173231046102023000165060ustar 00000000000000use uncased::UncasedStr; /// Named colors defined in . pub static NAMED_COLORS: phf::OrderedMap<&'static UncasedStr, [u8; 3]> = phf::phf_ordered_map! { UncasedStr::new("aliceblue") => [240, 248, 255], UncasedStr::new("antiquewhite") => [250, 235, 215], UncasedStr::new("aqua") => [0, 255, 255], UncasedStr::new("aquamarine") => [127, 255, 212], UncasedStr::new("azure") => [240, 255, 255], UncasedStr::new("beige") => [245, 245, 220], UncasedStr::new("bisque") => [255, 228, 196], UncasedStr::new("black") => [0, 0, 0], UncasedStr::new("blanchedalmond") => [255, 235, 205], UncasedStr::new("blue") => [0, 0, 255], UncasedStr::new("blueviolet") => [138, 43, 226], UncasedStr::new("brown") => [165, 42, 42], UncasedStr::new("burlywood") => [222, 184, 135], UncasedStr::new("cadetblue") => [95, 158, 160], UncasedStr::new("chartreuse") => [127, 255, 0], UncasedStr::new("chocolate") => [210, 105, 30], UncasedStr::new("coral") => [255, 127, 80], UncasedStr::new("cornflowerblue") => [100, 149, 237], UncasedStr::new("cornsilk") => [255, 248, 220], UncasedStr::new("crimson") => [220, 20, 60], UncasedStr::new("cyan") => [0, 255, 255], UncasedStr::new("darkblue") => [0, 0, 139], UncasedStr::new("darkcyan") => [0, 139, 139], UncasedStr::new("darkgoldenrod") => [184, 134, 11], UncasedStr::new("darkgray") => [169, 169, 169], UncasedStr::new("darkgreen") => [0, 100, 0], UncasedStr::new("darkgrey") => [169, 169, 169], UncasedStr::new("darkkhaki") => [189, 183, 107], UncasedStr::new("darkmagenta") => [139, 0, 139], UncasedStr::new("darkolivegreen") => [85, 107, 47], UncasedStr::new("darkorange") => [255, 140, 0], UncasedStr::new("darkorchid") => [153, 50, 204], UncasedStr::new("darkred") => [139, 0, 0], UncasedStr::new("darksalmon") => [233, 150, 122], UncasedStr::new("darkseagreen") => [143, 188, 143], UncasedStr::new("darkslateblue") => [72, 61, 139], UncasedStr::new("darkslategray") => [47, 79, 79], UncasedStr::new("darkslategrey") => [47, 79, 79], UncasedStr::new("darkturquoise") => [0, 206, 209], UncasedStr::new("darkviolet") => [148, 0, 211], UncasedStr::new("deeppink") => [255, 20, 147], UncasedStr::new("deepskyblue") => [0, 191, 255], UncasedStr::new("dimgray") => [105, 105, 105], UncasedStr::new("dimgrey") => [105, 105, 105], UncasedStr::new("dodgerblue") => [30, 144, 255], UncasedStr::new("firebrick") => [178, 34, 34], UncasedStr::new("floralwhite") => [255, 250, 240], UncasedStr::new("forestgreen") => [34, 139, 34], UncasedStr::new("fuchsia") => [255, 0, 255], UncasedStr::new("gainsboro") => [220, 220, 220], UncasedStr::new("ghostwhite") => [248, 248, 255], UncasedStr::new("gold") => [255, 215, 0], UncasedStr::new("goldenrod") => [218, 165, 32], UncasedStr::new("gray") => [128, 128, 128], UncasedStr::new("green") => [0, 128, 0], UncasedStr::new("greenyellow") => [173, 255, 47], UncasedStr::new("grey") => [128, 128, 128], UncasedStr::new("honeydew") => [240, 255, 240], UncasedStr::new("hotpink") => [255, 105, 180], UncasedStr::new("indianred") => [205, 92, 92], UncasedStr::new("indigo") => [75, 0, 130], UncasedStr::new("ivory") => [255, 255, 240], UncasedStr::new("khaki") => [240, 230, 140], UncasedStr::new("lavender") => [230, 230, 250], UncasedStr::new("lavenderblush") => [255, 240, 245], UncasedStr::new("lawngreen") => [124, 252, 0], UncasedStr::new("lemonchiffon") => [255, 250, 205], UncasedStr::new("lightblue") => [173, 216, 230], UncasedStr::new("lightcoral") => [240, 128, 128], UncasedStr::new("lightcyan") => [224, 255, 255], UncasedStr::new("lightgoldenrodyellow") => [250, 250, 210], UncasedStr::new("lightgray") => [211, 211, 211], UncasedStr::new("lightgreen") => [144, 238, 144], UncasedStr::new("lightgrey") => [211, 211, 211], UncasedStr::new("lightpink") => [255, 182, 193], UncasedStr::new("lightsalmon") => [255, 160, 122], UncasedStr::new("lightseagreen") => [32, 178, 170], UncasedStr::new("lightskyblue") => [135, 206, 250], UncasedStr::new("lightslategray") => [119, 136, 153], UncasedStr::new("lightslategrey") => [119, 136, 153], UncasedStr::new("lightsteelblue") => [176, 196, 222], UncasedStr::new("lightyellow") => [255, 255, 224], UncasedStr::new("lime") => [0, 255, 0], UncasedStr::new("limegreen") => [50, 205, 50], UncasedStr::new("linen") => [250, 240, 230], UncasedStr::new("magenta") => [255, 0, 255], UncasedStr::new("maroon") => [128, 0, 0], UncasedStr::new("mediumaquamarine") => [102, 205, 170], UncasedStr::new("mediumblue") => [0, 0, 205], UncasedStr::new("mediumorchid") => [186, 85, 211], UncasedStr::new("mediumpurple") => [147, 112, 219], UncasedStr::new("mediumseagreen") => [60, 179, 113], UncasedStr::new("mediumslateblue") => [123, 104, 238], UncasedStr::new("mediumspringgreen") => [0, 250, 154], UncasedStr::new("mediumturquoise") => [72, 209, 204], UncasedStr::new("mediumvioletred") => [199, 21, 133], UncasedStr::new("midnightblue") => [25, 25, 112], UncasedStr::new("mintcream") => [245, 255, 250], UncasedStr::new("mistyrose") => [255, 228, 225], UncasedStr::new("moccasin") => [255, 228, 181], UncasedStr::new("navajowhite") => [255, 222, 173], UncasedStr::new("navy") => [0, 0, 128], UncasedStr::new("oldlace") => [253, 245, 230], UncasedStr::new("olive") => [128, 128, 0], UncasedStr::new("olivedrab") => [107, 142, 35], UncasedStr::new("orange") => [255, 165, 0], UncasedStr::new("orangered") => [255, 69, 0], UncasedStr::new("orchid") => [218, 112, 214], UncasedStr::new("palegoldenrod") => [238, 232, 170], UncasedStr::new("palegreen") => [152, 251, 152], UncasedStr::new("paleturquoise") => [175, 238, 238], UncasedStr::new("palevioletred") => [219, 112, 147], UncasedStr::new("papayawhip") => [255, 239, 213], UncasedStr::new("peachpuff") => [255, 218, 185], UncasedStr::new("peru") => [205, 133, 63], UncasedStr::new("pink") => [255, 192, 203], UncasedStr::new("plum") => [221, 160, 221], UncasedStr::new("powderblue") => [176, 224, 230], UncasedStr::new("purple") => [128, 0, 128], UncasedStr::new("rebeccapurple") => [102, 51, 153], UncasedStr::new("red") => [255, 0, 0], UncasedStr::new("rosybrown") => [188, 143, 143], UncasedStr::new("royalblue") => [65, 105, 225], UncasedStr::new("saddlebrown") => [139, 69, 19], UncasedStr::new("salmon") => [250, 128, 114], UncasedStr::new("sandybrown") => [244, 164, 96], UncasedStr::new("seagreen") => [46, 139, 87], UncasedStr::new("seashell") => [255, 245, 238], UncasedStr::new("sienna") => [160, 82, 45], UncasedStr::new("silver") => [192, 192, 192], UncasedStr::new("skyblue") => [135, 206, 235], UncasedStr::new("slateblue") => [106, 90, 205], UncasedStr::new("slategray") => [112, 128, 144], UncasedStr::new("slategrey") => [112, 128, 144], UncasedStr::new("snow") => [255, 250, 250], UncasedStr::new("springgreen") => [0, 255, 127], UncasedStr::new("steelblue") => [70, 130, 180], UncasedStr::new("tan") => [210, 180, 140], UncasedStr::new("teal") => [0, 128, 128], UncasedStr::new("thistle") => [216, 191, 216], UncasedStr::new("tomato") => [255, 99, 71], UncasedStr::new("turquoise") => [64, 224, 208], UncasedStr::new("violet") => [238, 130, 238], UncasedStr::new("wheat") => [245, 222, 179], UncasedStr::new("white") => [255, 255, 255], UncasedStr::new("whitesmoke") => [245, 245, 245], UncasedStr::new("yellow") => [255, 255, 0], UncasedStr::new("yellowgreen") => [154, 205, 50], }; csscolorparser-0.8.1/src/parser.rs000064400000000000000000000537221046102023000153400ustar 00000000000000use crate::utils::parse_value; use crate::utils::remap; use crate::utils::ParamParser; use crate::{Color, ParseColorError}; #[cfg(feature = "named-colors")] use crate::NAMED_COLORS; /// Parse CSS color string /// /// # Examples /// /// ``` /// # use core::error::Error; /// # fn main() -> Result<(), Box> { /// let c = csscolorparser::parse("#ff0")?; /// /// assert_eq!(c.to_array(), [1.0, 1.0, 0.0, 1.0]); /// assert_eq!(c.to_rgba8(), [255, 255, 0, 255]); /// assert_eq!(c.to_css_hex(), "#ffff00"); /// assert_eq!(c.to_css_rgb(), "rgb(255 255 0)"); /// # Ok(()) /// # } /// ``` /// /// ``` /// # use core::error::Error; /// # fn main() -> Result<(), Box> { /// let c = csscolorparser::parse("hsl(360deg,100%,50%)")?; /// /// assert_eq!(c.to_array(), [1.0, 0.0, 0.0, 1.0]); /// assert_eq!(c.to_rgba8(), [255, 0, 0, 255]); /// assert_eq!(c.to_css_hex(), "#ff0000"); /// assert_eq!(c.to_css_rgb(), "rgb(255 0 0)"); /// # Ok(()) /// # } /// ``` #[inline(never)] pub fn parse(s: &str) -> Result { let s = s.trim(); let err = match parse_abs(s) { Ok(c) => return Ok(c), Err(e @ ParseColorError::InvalidHex) => return Err(e), Err(e @ ParseColorError::InvalidFunction) => return Err(e), Err(e @ ParseColorError::InvalidUnknown) => return Err(e), Err(e) => e, }; if let (Some(idx), Some(s)) = (s.find('('), s.strip_suffix(')')) { if !s.is_ascii() { return Err(err); } let mut pp = ParamParser::new(&s[idx + 1..]); pp.space(); #[rustfmt::skip] let ( Some(from), true, Some(color), true, Some(val1), true, Some(val2), true, Some(val3), ) = ( pp.value(), pp.space(), pp.value(), pp.space(), pp.value(), pp.space(), pp.value(), pp.space(), pp.value(), ) else { return Err(err); }; if !from.eq_ignore_ascii_case("from") { return Err(err); } let Ok(color) = parse(color) else { return Err(err); }; pp.space(); let val4 = if pp.is_end() { "alpha" } else if let (true, Some(alpha), _, true) = (pp.slash(), pp.value(), pp.space(), pp.is_end()) { alpha } else { return Err(err); }; match err { ParseColorError::InvalidRgb => { // r, g, b [0..255] // alpha [0..1] let variables = [ ("r", color.r * 255.0), ("g", color.g * 255.0), ("b", color.b * 255.0), ("alpha", color.a), ]; if let (Some(r), Some(g), Some(b), Some(a)) = ( parse_value(val1, variables), parse_value(val2, variables), parse_value(val3, variables), parse_value(val4, variables), ) { return Ok(Color::new(r / 255.0, g / 255.0, b / 255.0, a)); }; return Err(err); } ParseColorError::InvalidHwb => { // h [0..360] // w, b [0..100] // alpha [0..1] let [h, w, b, a] = color.to_hwba(); let variables = [("h", h), ("w", w * 100.0), ("b", b * 100.0), ("alpha", a)]; if let (Some(h), Some(w), Some(b), Some(a)) = ( parse_value(val1, variables), parse_value(val2, variables), parse_value(val3, variables), parse_value(val4, variables), ) { return Ok(Color::from_hwba(h, w / 100.0, b / 100.0, a)); }; return Err(err); } ParseColorError::InvalidHsl => { // h [0..360] // s, l [0..100] // alpha [0..1] let [h, s, l, a] = color.to_hsla(); let variables = [("h", h), ("s", s * 100.0), ("l", l * 100.0), ("alpha", a)]; if let (Some(h), Some(s), Some(l), Some(a)) = ( parse_value(val1, variables), parse_value(val2, variables), parse_value(val3, variables), parse_value(val4, variables), ) { return Ok(Color::from_hsla( h, (s / 100.0).clamp(0.0, 1.0), (l / 100.0).clamp(0.0, 1.0), a, )); }; return Err(err); } ParseColorError::InvalidHsv => { // h [0..360] // s, v [0..100] // alpha [0..1] let [h, s, v, a] = color.to_hsva(); let variables = [("h", h), ("s", s * 100.0), ("v", v * 100.0), ("alpha", a)]; if let (Some(h), Some(s), Some(v), Some(a)) = ( parse_value(val1, variables), parse_value(val2, variables), parse_value(val3, variables), parse_value(val4, variables), ) { return Ok(Color::from_hsva(h, s / 100.0, v / 100.0, a)); }; return Err(err); } ParseColorError::InvalidLab => { // l [0..100] // a, b [-125..125] // alpha [0..1] let [l, a, b, alpha] = color.to_laba(); let variables = [("l", l), ("a", a), ("b", b), ("alpha", alpha)]; if let (Some(l), Some(a), Some(b), Some(alpha)) = ( parse_value(val1, variables), parse_value(val2, variables), parse_value(val3, variables), parse_value(val4, variables), ) { return Ok(Color::from_laba(l.max(0.0), a, b, alpha)); }; return Err(err); } ParseColorError::InvalidLch => { // l [0..100] // c [0..150] // h [0..360] // alpha [0..1] let [l, c, h, a] = color.to_lcha(); let variables = [("l", l), ("c", c), ("h", h.to_degrees()), ("alpha", a)]; if let (Some(l), Some(c), Some(h), Some(a)) = ( parse_value(val1, variables), parse_value(val2, variables), parse_value(val3, variables), parse_value(val4, variables), ) { return Ok(Color::from_lcha(l.max(0.0), c.max(0.0), h.to_radians(), a)); }; return Err(err); } ParseColorError::InvalidOklab => { // l [0..1] // a, b [-0.4 .. 0.4] // alpha [0..1] let [l, a, b, alpha] = color.to_oklaba(); let variables = [("l", l), ("a", a), ("b", b), ("alpha", alpha)]; if let (Some(l), Some(a), Some(b), Some(alpha)) = ( parse_value(val1, variables), parse_value(val2, variables), parse_value(val3, variables), parse_value(val4, variables), ) { return Ok(Color::from_oklaba(l.max(0.0), a, b, alpha)); }; return Err(err); } ParseColorError::InvalidOklch => { // l [0..1] // c [0..0.4] // h [0..360] // alpha [0..1] let [l, c, h, a] = color.to_oklcha(); let variables = [("l", l), ("c", c), ("h", h.to_degrees()), ("alpha", a)]; if let (Some(l), Some(c), Some(h), Some(a)) = ( parse_value(val1, variables), parse_value(val2, variables), parse_value(val3, variables), parse_value(val4, variables), ) { return Ok(Color::from_oklcha( l.max(0.0), c.max(0.0), h.to_radians(), a, )); }; return Err(err); } _ => unreachable!(), } } unreachable!(); } fn parse_abs(s: &str) -> Result { if s.eq_ignore_ascii_case("transparent") { return Ok(Color::new(0.0, 0.0, 0.0, 0.0)); } // Hex format if let Some(s) = s.strip_prefix('#') { return parse_hex(s); } if let (Some(idx), Some(s)) = (s.find('('), s.strip_suffix(')')) { let fname = &s[..idx].trim_end(); let err = match fname { s if s.eq_ignore_ascii_case("rgb") || s.eq_ignore_ascii_case("rgba") => { ParseColorError::InvalidRgb } s if s.eq_ignore_ascii_case("hsl") || s.eq_ignore_ascii_case("hsla") => { ParseColorError::InvalidHsl } s if s.eq_ignore_ascii_case("hwb") || s.eq_ignore_ascii_case("hwba") => { ParseColorError::InvalidHwb } s if s.eq_ignore_ascii_case("hsv") || s.eq_ignore_ascii_case("hsva") => { ParseColorError::InvalidHsv } s if s.eq_ignore_ascii_case("lab") => ParseColorError::InvalidLab, s if s.eq_ignore_ascii_case("lch") => ParseColorError::InvalidLch, s if s.eq_ignore_ascii_case("oklab") => ParseColorError::InvalidOklab, s if s.eq_ignore_ascii_case("oklch") => ParseColorError::InvalidOklch, _ => return Err(ParseColorError::InvalidFunction), }; let s = &s[idx + 1..]; if !s.is_ascii() { return Err(err); } let mut pp = ParamParser::new(s); pp.space(); let (Some(val0), true, Some(val1), true, Some(val2)) = ( pp.value(), pp.comma_or_space(), pp.value(), pp.comma_or_space(), pp.value(), ) else { return Err(err); }; let is_space = pp.space(); let alpha = if pp.is_end() { 1.0 } else if let (true, Some(a), _, true) = ( pp.comma_or_slash() || is_space, pp.value(), pp.space(), pp.is_end(), ) { if let Some((v, _)) = parse_percent_or_float(a) { v.clamp(0.0, 1.0) } else { return Err(err); } } else { return Err(err); }; match err { ParseColorError::InvalidRgb => { if let (Some((r, r_fmt)), Some((g, g_fmt)), Some((b, b_fmt))) = ( // red parse_percent_or_255(val0), // green parse_percent_or_255(val1), // blue parse_percent_or_255(val2), ) { if r_fmt == g_fmt && g_fmt == b_fmt { return Ok(Color { r: r.clamp(0.0, 1.0), g: g.clamp(0.0, 1.0), b: b.clamp(0.0, 1.0), a: alpha, }); } } return Err(err); } ParseColorError::InvalidHsl => { if let (Some(h), Some((s, s_fmt)), Some((l, l_fmt))) = ( // hue parse_angle(val0), // saturation parse_percent_or_float(val1), // lightness parse_percent_or_float(val2), ) { if s_fmt == l_fmt { return Ok(Color::from_hsla(h, s, l, alpha)); } } return Err(err); } ParseColorError::InvalidHwb => { if let (Some(h), Some((w, w_fmt)), Some((b, b_fmt))) = ( // hue parse_angle(val0), // whiteness parse_percent_or_float(val1), // blackness parse_percent_or_float(val2), ) { if w_fmt == b_fmt { return Ok(Color::from_hwba(h, w, b, alpha)); } } return Err(err); } ParseColorError::InvalidHsv => { if let (Some(h), Some((s, s_fmt)), Some((v, v_fmt))) = ( // hue parse_angle(val0), // saturation parse_percent_or_float(val1), // value parse_percent_or_float(val2), ) { if s_fmt == v_fmt { return Ok(Color::from_hsva(h, s, v, alpha)); } } return Err(err); } ParseColorError::InvalidLab => { if let (Some((l, l_fmt)), Some((a, a_fmt)), Some((b, b_fmt))) = ( // lightness parse_percent_or_float(val0), // a parse_percent_or_float(val1), // b parse_percent_or_float(val2), ) { let l = if l_fmt { l * 100.0 } else { l }; let a = if a_fmt { remap(a, -1.0, 1.0, -125.0, 125.0) } else { a }; let b = if b_fmt { remap(b, -1.0, 1.0, -125.0, 125.0) } else { b }; return Ok(Color::from_laba(l.max(0.0), a, b, alpha)); } return Err(err); } ParseColorError::InvalidLch => { if let (Some((l, l_fmt)), Some((c, c_fmt)), Some(h)) = ( // lightness parse_percent_or_float(val0), // chroma parse_percent_or_float(val1), // hue parse_angle(val2), ) { let l = if l_fmt { l * 100.0 } else { l }; let c = if c_fmt { c * 150.0 } else { c }; return Ok(Color::from_lcha( l.max(0.0), c.max(0.0), h.to_radians(), alpha, )); } return Err(err); } ParseColorError::InvalidOklab => { if let (Some((l, _)), Some((a, a_fmt)), Some((b, b_fmt))) = ( // lightness parse_percent_or_float(val0), // a parse_percent_or_float(val1), // b parse_percent_or_float(val2), ) { let a = if a_fmt { remap(a, -1.0, 1.0, -0.4, 0.4) } else { a }; let b = if b_fmt { remap(b, -1.0, 1.0, -0.4, 0.4) } else { b }; return Ok(Color::from_oklaba(l.max(0.0), a, b, alpha)); } return Err(err); } ParseColorError::InvalidOklch => { if let (Some((l, _)), Some((c, c_fmt)), Some(h)) = ( // lightness parse_percent_or_float(val0), // chroma parse_percent_or_float(val1), // hue parse_angle(val2), ) { let c = if c_fmt { c * 0.4 } else { c }; return Ok(Color::from_oklcha( l.max(0.0), c.max(0.0), h.to_radians(), alpha, )); } return Err(err); } _ => unreachable!(), } } // Hex format without prefix '#' if let Ok(c) = parse_hex(s) { return Ok(c); } // Named colors #[cfg(feature = "named-colors")] if s.len() > 2 && s.len() < 21 { if let Some([r, g, b]) = NAMED_COLORS.get(s.into()) { return Ok(Color::from_rgba8(*r, *g, *b, 255)); } } Err(ParseColorError::InvalidUnknown) } fn parse_hex(s: &str) -> Result { if !s.is_ascii() { return Err(ParseColorError::InvalidHex); } let n = s.len(); fn parse_single_digit(digit: &str) -> Result { u8::from_str_radix(digit, 16) .map(|n| (n << 4) | n) .map_err(|_| ParseColorError::InvalidHex) } if n == 3 || n == 4 { let r = parse_single_digit(&s[0..1])?; let g = parse_single_digit(&s[1..2])?; let b = parse_single_digit(&s[2..3])?; let a = if n == 4 { parse_single_digit(&s[3..4])? } else { 255 }; Ok(Color::from_rgba8(r, g, b, a)) } else if n == 6 || n == 8 { let r = u8::from_str_radix(&s[0..2], 16).map_err(|_| ParseColorError::InvalidHex)?; let g = u8::from_str_radix(&s[2..4], 16).map_err(|_| ParseColorError::InvalidHex)?; let b = u8::from_str_radix(&s[4..6], 16).map_err(|_| ParseColorError::InvalidHex)?; let a = if n == 8 { u8::from_str_radix(&s[6..8], 16).map_err(|_| ParseColorError::InvalidHex)? } else { 255 }; Ok(Color::from_rgba8(r, g, b, a)) } else { Err(ParseColorError::InvalidHex) } } // strip suffix ignore case fn strip_suffix<'a>(s: &'a str, suffix: &str) -> Option<&'a str> { if suffix.len() > s.len() { return None; } let s_end = &s[s.len() - suffix.len()..]; if s_end.eq_ignore_ascii_case(suffix) { Some(&s[..s.len() - suffix.len()]) } else { None } } fn parse_percent_or_float(s: &str) -> Option<(f32, bool)> { s.strip_suffix('%') .and_then(|s| s.parse().ok().map(|t: f32| (t / 100.0, true))) .or_else(|| s.parse().ok().map(|t| (t, false))) } fn parse_percent_or_255(s: &str) -> Option<(f32, bool)> { s.strip_suffix('%') .and_then(|s| s.parse().ok().map(|t: f32| (t / 100.0, true))) .or_else(|| s.parse().ok().map(|t: f32| (t / 255.0, false))) } fn parse_angle(s: &str) -> Option { strip_suffix(s, "deg") .and_then(|s| s.parse().ok()) .or_else(|| { strip_suffix(s, "grad") .and_then(|s| s.parse().ok()) .map(|t: f32| t * 360.0 / 400.0) }) .or_else(|| { strip_suffix(s, "rad") .and_then(|s| s.parse().ok()) .map(|t: f32| t.to_degrees()) }) .or_else(|| { strip_suffix(s, "turn") .and_then(|s| s.parse().ok()) .map(|t: f32| t * 360.0) }) .or_else(|| s.parse().ok()) } #[cfg(test)] mod t { use super::*; #[test] fn strip_suffix_() { assert_eq!(strip_suffix("45deg", "deg"), Some("45")); assert_eq!(strip_suffix("90DEG", "deg"), Some("90")); assert_eq!(strip_suffix("0.25turn", "turn"), Some("0.25")); assert_eq!(strip_suffix("1.0Turn", "turn"), Some("1.0")); assert_eq!(strip_suffix("", "deg"), None); assert_eq!(strip_suffix("90", "deg"), None); } #[test] fn parse_percent_or_float_() { let test_data = [ ("0%", Some((0.0, true))), ("100%", Some((1.0, true))), ("50%", Some((0.5, true))), ("0", Some((0.0, false))), ("1", Some((1.0, false))), ("0.5", Some((0.5, false))), ("100.0", Some((100.0, false))), ("-23.7", Some((-23.7, false))), ("%", None), ("1x", None), ]; for (s, expected) in test_data { assert_eq!(parse_percent_or_float(s), expected); } } #[test] fn parse_percent_or_255_() { let test_data = [ ("0%", Some((0.0, true))), ("100%", Some((1.0, true))), ("50%", Some((0.5, true))), ("-100%", Some((-1.0, true))), ("0", Some((0.0, false))), ("255", Some((1.0, false))), ("127.5", Some((0.5, false))), ("%", None), ("255x", None), ]; for (s, expected) in test_data { assert_eq!(parse_percent_or_255(s), expected); } } #[test] fn parse_angle_() { let test_data = [ ("360", Some(360.0)), ("127.356", Some(127.356)), ("+120deg", Some(120.0)), ("90deg", Some(90.0)), ("-127deg", Some(-127.0)), ("100grad", Some(90.0)), ("1.5707963267948966rad", Some(90.0)), ("0.25turn", Some(90.0)), ("-0.25turn", Some(-90.0)), ("O", None), ("Odeg", None), ("rad", None), ]; for (s, expected) in test_data { assert_eq!(parse_angle(s), expected); } } #[test] fn parse_hex_() { // case-insensitive tests macro_rules! cmp { ($a:expr, $b:expr) => { assert_eq!( parse_hex($a).unwrap().to_rgba8(), parse_hex($b).unwrap().to_rgba8() ); }; } cmp!("abc", "ABC"); cmp!("DeF", "dEf"); cmp!("f0eB", "F0Eb"); cmp!("abcdef", "ABCDEF"); cmp!("Ff03E0cB", "fF03e0Cb"); } } csscolorparser-0.8.1/src/utils/calc.rs000064400000000000000000000206631046102023000161040ustar 00000000000000use super::strip_prefix; struct CalcParser<'a> { s: &'a str, idx: usize, } impl<'a> CalcParser<'a> { fn new(s: &'a str) -> Self { Self { s, idx: 0 } } // Returns everything until operator is found. // Ignore operator inside parentheses. fn operand(&mut self) -> Option<&'a str> { if self.is_end() { return None; } let start = self.idx; match self.s.as_bytes()[self.idx] { b'-' => self.idx += 1, b'+' => return None, b'*' => return None, b'/' => return None, _ => (), } // parenthesis depth let mut nesting = 0i32; while self.idx < self.s.len() { let ch = self.s.as_bytes()[self.idx]; match ch { b'(' => { nesting += 1; self.idx += 1; } b')' => { if nesting > 0 { nesting -= 1; } self.idx += 1; } b'+' | b'-' | b'*' | b'/' | b' ' => { if nesting == 0 { // operator is *outside* parentheses break; } self.idx += 1; } _ => self.idx += 1, } } Some(&self.s[start..self.idx]) } // Returns first operator found. Skip spaces. fn operator(&mut self) -> Option { if self.is_end() { return None; } let ch = self.s.as_bytes()[self.idx]; match ch { b'+' | b'-' | b'*' | b'/' => { self.idx += 1; Some(ch) } _ => None, } } fn is_end(&mut self) -> bool { // Consume all spaces until other character is found. while self.idx < self.s.len() && self.s.as_bytes()[self.idx] == b' ' { self.idx += 1; } self.idx >= self.s.len() } fn parse(&mut self) -> Option<(&str, u8, &str)> { if let (Some(va), Some(op), Some(vb), true) = ( self.operand(), self.operator(), self.operand(), self.is_end(), ) { Some((va, op, vb)) } else { None } } } pub fn parse_value(s: &str, variables: [(&str, f32); 4]) -> Option { let parse_v = |s: &str| -> Option { if let Ok(value) = s.parse() { return Some(value); }; for (var, value) in variables { if s.eq_ignore_ascii_case(var) { return Some(value); } } None }; if let Some(t) = parse_v(s) { return Some(t); } if let Some(s) = strip_prefix(s, "calc") { return parse_calc(s, &parse_v); } None } fn parse_calc(s: &str, f: &F) -> Option where F: Fn(&str) -> Option, { if let Some(s) = s.strip_prefix('(') { if let Some(s) = s.strip_suffix(')') { let mut p = CalcParser::new(s); let (va, op, vb) = p.parse()?; let va = if let Some(v) = f(va) { v } else if let Some(v) = parse_calc(va, f) { v } else { return None; }; let vb = if let Some(v) = f(vb) { v } else if let Some(v) = parse_calc(vb, f) { v } else { return None; }; match op { b'+' => return Some(va + vb), b'-' => return Some(va - vb), b'*' => return Some(va * vb), b'/' => { if vb == 0.0 { return None; } return Some(va / vb); } _ => unreachable!(), } } } None } #[cfg(test)] mod t { use super::*; #[test] fn calc_parser() { let s = "78+0.573"; let mut p = CalcParser::new(s); assert_eq!(p.operator(), None); assert_eq!(p.operand(), Some("78")); assert_eq!(p.operand(), None); assert_eq!(p.operator(), Some(b'+')); assert_eq!(p.operator(), None); assert_eq!(p.operand(), Some("0.573")); assert_eq!(p.operator(), None); assert_eq!(p.operand(), None); assert!(p.is_end()); assert_eq!(p.parse(), None); #[rustfmt::skip] let test_data = [ ( "78+0.573", ("78", b'+', "0.573"), ), ( "g-100", ("g", b'-', "100"), ), ( " 9 * alpha ", ("9", b'*', "alpha"), ), ( "alpha/2", ("alpha", b'/', "2"), ), ( "-360+-55.07", ("-360", b'+', "-55.07"), ), ( "-7--5", ("-7", b'-', "-5"), ), ( "h+(4*0.75)", ("h", b'+', "(4*0.75)"), ), ( "(0.35*r) / (alpha - 10)", ("(0.35*r)", b'/', "(alpha - 10)"), ), ]; for (s, expected) in test_data { let mut p = CalcParser::new(s); assert_eq!(p.parse(), Some(expected), "{:?}", s); assert!(p.is_end(), "{:?}", s); } #[rustfmt::skip] let invalids = [ "", " ", "5", "g+", "-", "7---3", "*3+2", "4+5/", ]; for s in invalids { let mut p = CalcParser::new(s); assert_eq!(p.parse(), None, "{:?}", s); } } #[test] fn parse_calc_() { fn f(s: &str) -> Option { s.parse().ok() } let test_data = [ ("(1+3.7)", 4.7), ("( 0.35 - -0.5 )", 0.85), ("(2.0*(7-5))", 4.0), ("((5*10) / (7+3))", 5.0), ("(0.5 * (5 + (7 * (9 - (3 * (1 + 1))))))", 13.0), ]; for (s, expected) in test_data { assert_eq!(parse_calc(s, &f), Some(expected), "{:?}", s); } let invalids = [ "", "5", "g", "1+7", "()", "(())", "(())", "(()+(1*5))", "(9)", "(4/0)", "(1-8", "7+0.3)", "(5+(3*2)", "((5-1)", "((1+2))", "(5+(1+2/3))", "(4+5(1*3))", "((1+2)1*5)", ]; for s in invalids { assert_eq!(parse_calc(s, &f), None, "{:?}", s); } } #[test] fn parse_value_() { let vars = [("r", 255.0), ("g", 127.0), ("b", 0.0), ("alpha", 0.5)]; let test_data = [ // simple value ("130", 130.0), ("-0.5", -0.5), ("g", 127.0), // calc() simple ("calc(4+5.5)", 9.5), ("calc( 10 - 7 )", 3.0), ("CALC(2.5 *2)", 5.0), ("CaLc(21.0/ 3)", 7.0), ("calc(r-55)", 200.0), ("calc(10 + g)", 137.0), ("calc(alpha*1.5)", 0.75), // calc() negative number ("calc(-97+-18)", -115.0), ("calc( -1 * -45)", 45.0), ("calc(100--35)", 135.0), ("calc(100 - -35)", 135.0), // calc() recursive ("calc(1.5*(4/2))", 3.0), ("calc( ( 19 + 6 ) / 5 )", 5.0), ("calc((2/(1.5+0.5)) - (0.75 - 0.25))", 0.5), ("calc((r + g) / 2)", 191.0), ]; for (s, expected) in test_data { assert_eq!(parse_value(s, vars), Some(expected), "{:?}", s); } let invalids = [ "", "7x", "h", "(4+5)", "cal(4+5)", "calcs(4+5)", "calc()", "calc(-)", "calc(5)", "calc(+5)", "calc(b)", "calc(g-)", "calc(5+1-4)", "calc(1 * 7 +)", "calc(5 + (1.5))", "calc(5 + (1.5 * 2 / 3))", "calc(5 + (2 - ab))", ]; for s in invalids { assert_eq!(parse_value(s, vars), None, "{:?}", s); } } } csscolorparser-0.8.1/src/utils/helper.rs000064400000000000000000000014541046102023000164560ustar 00000000000000// Strip prefix ignore case. pub fn strip_prefix<'a>(s: &'a str, prefix: &str) -> Option<&'a str> { if prefix.len() > s.len() { return None; } let s_start = &s[..prefix.len()]; if s_start.eq_ignore_ascii_case(prefix) { Some(&s[prefix.len()..]) } else { None } } #[cfg(test)] mod t { use super::*; #[test] fn strip_prefix_() { assert_eq!(strip_prefix("rgb(77)", "rgb"), Some("(77)")); assert_eq!(strip_prefix("RGB(0,0)", "rgb"), Some("(0,0)")); assert_eq!(strip_prefix("Hsv()", "HSV"), Some("()")); assert_eq!(strip_prefix("", "rgb"), None); assert_eq!(strip_prefix("10", "rgb"), None); assert_eq!(strip_prefix("hsv(0,0)", "hsva"), None); assert_eq!(strip_prefix("hsv", "hsva"), None); } } csscolorparser-0.8.1/src/utils/param.rs000064400000000000000000000155441046102023000163040ustar 00000000000000pub struct ParamParser<'a> { s: &'a str, idx: usize, } impl<'a> ParamParser<'a> { pub fn new(s: &'a str) -> Self { Self { s, idx: 0 } } // Returns `&str` from current index until space, comma, or slash is found. // Ignore space, comma, or slash inside parentheses. // Returns `None` if value not found. pub fn value(&mut self) -> Option<&'a str> { if self.is_end() { return None; } match self.s.as_bytes()[self.idx] { b' ' => return None, b',' => return None, b'/' => return None, _ => (), } let start = self.idx; // parenthesis depth let mut nesting = 0i32; while self.idx < self.s.len() { let ch = self.s.as_bytes()[self.idx]; match ch { b'(' => { nesting += 1; self.idx += 1; } b')' => { if nesting > 0 { nesting -= 1; } self.idx += 1; } b' ' | b',' | b'/' => { if nesting == 0 { // delimiter is *outside* parentheses break; } self.idx += 1; } _ => self.idx += 1, } } Some(&self.s[start..self.idx]) } // Consume one or more spaces. // Returns true if space is found, false otherwise. pub fn space(&mut self) -> bool { let mut found = false; while self.idx < self.s.len() && self.s.as_bytes()[self.idx] == b' ' { self.idx += 1; found = true; } found } // Consume one or more spaces, or single comma. // Spaces is allowed around comma. // Returns true if one of them is found, false otherwise. pub fn comma_or_space(&mut self) -> bool { let mut found_comma = false; let mut found_space = false; while self.idx < self.s.len() { let ch = self.s.as_bytes()[self.idx]; match ch { b' ' => { found_space = true; self.idx += 1; } b',' => { if found_comma { break; } found_comma = true; self.idx += 1; } _ => { break; } } } found_comma || found_space } // Consume single comma or single slash. // Spaces is allowed around comma or slash. // Returns true if one of them is found, false otherwise. pub fn comma_or_slash(&mut self) -> bool { let mut found = false; while self.idx < self.s.len() { let ch = self.s.as_bytes()[self.idx]; match ch { b' ' => { self.idx += 1; } b',' | b'/' => { if found { break; } found = true; self.idx += 1; } _ => { break; } } } found } // Consume a single slash. Spaces is allowed around slash. // Returns true if a slash is found, false otherwise. pub fn slash(&mut self) -> bool { let mut found = false; while self.idx < self.s.len() { let ch = self.s.as_bytes()[self.idx]; match ch { b' ' => { self.idx += 1; } b'/' => { if found { break; } found = true; self.idx += 1; } _ => { break; } } } found } // Returns true is we finished reading the str. pub fn is_end(&self) -> bool { self.idx >= self.s.len() } } #[cfg(test)] mod t { use super::*; #[test] fn param_parser() { let s = " "; let mut p = ParamParser::new(s); assert_eq!(p.is_end(), false); assert!(p.space()); assert_eq!(p.space(), false); assert!(p.is_end()); let s = "abc "; let mut p = ParamParser::new(s); assert_eq!(p.space(), false); assert_eq!(p.is_end(), false); assert_eq!(p.value(), Some("abc")); assert!(p.space()); assert!(p.is_end()); let s = ",, , "; let mut p = ParamParser::new(s); assert!(p.comma_or_space()); assert!(p.comma_or_space()); assert!(p.comma_or_space()); assert_eq!(p.comma_or_space(), false); assert!(p.is_end()); let s = "97,ab/5 / 10.7 "; let mut p = ParamParser::new(s); assert_eq!(p.slash(), false); assert_eq!(p.value(), Some("97")); assert_eq!(p.slash(), false); assert!(p.comma_or_space()); assert_eq!(p.value(), Some("ab")); assert!(p.slash()); assert_eq!(p.value(), Some("5")); assert!(p.slash()); assert_eq!(p.value(), Some("10.7")); assert!(p.space()); assert!(p.is_end()); let s = " ab(1 2,3),45 , xy cd / 10"; let mut p = ParamParser::new(s); assert_eq!(p.value(), None); assert!(p.space()); assert_eq!(p.space(), false); assert_eq!(p.value(), Some("ab(1 2,3)")); assert!(p.comma_or_space()); assert_eq!(p.value(), Some("45")); assert!(p.comma_or_space()); assert_eq!(p.value(), Some("xy")); assert!(p.comma_or_space()); assert_eq!(p.value(), Some("cd")); assert!(p.comma_or_slash()); assert_eq!(p.is_end(), false); assert_eq!(p.value(), Some("10")); assert_eq!(p.space(), false); assert_eq!(p.value(), None); assert!(p.is_end()); let s = "2.53/9,dog cat,fx(1 2 (56, 78))"; let mut p = ParamParser::new(s); assert_eq!(p.value(), Some("2.53")); assert!(p.comma_or_slash()); assert_eq!(p.value(), Some("9")); assert!(p.comma_or_space()); assert_eq!(p.value(), Some("dog")); assert!(p.space()); assert_eq!(p.value(), Some("cat")); assert!(p.comma_or_slash()); assert_eq!(p.value(), Some("fx(1 2 (56, 78))")); assert_eq!(p.comma_or_slash(), false); assert!(p.is_end()); let s = ") ( (9)) ("; let mut p = ParamParser::new(s); assert_eq!(p.value(), Some(")")); assert!(p.space()); assert_eq!(p.value(), Some("( (9))")); assert!(p.space()); assert_eq!(p.value(), Some("(")); assert!(p.is_end()); } } csscolorparser-0.8.1/src/utils.rs000064400000000000000000000133151046102023000151760ustar 00000000000000mod calc; mod helper; mod param; pub use calc::*; pub use helper::*; pub use param::*; use core::f32::consts::{PI, TAU}; const PI_3: f32 = PI * 3.0; #[cfg(not(feature = "std"))] use num_traits::float::Float; #[allow(clippy::excessive_precision)] pub(crate) fn oklab_to_linear_rgb(l: f32, a: f32, b: f32) -> [f32; 3] { let l_ = (l + 0.3963377774 * a + 0.2158037573 * b).powi(3); let m_ = (l - 0.1055613458 * a - 0.0638541728 * b).powi(3); let s_ = (l - 0.0894841775 * a - 1.2914855480 * b).powi(3); let r = 4.0767416621 * l_ - 3.3077115913 * m_ + 0.2309699292 * s_; let g = -1.2684380046 * l_ + 2.6097574011 * m_ - 0.3413193965 * s_; let b = -0.0041960863 * l_ - 0.7034186147 * m_ + 1.7076147010 * s_; [r, g, b] } #[allow(clippy::excessive_precision)] pub(crate) fn linear_rgb_to_oklab(r: f32, g: f32, b: f32) -> [f32; 3] { let l_ = (0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b).cbrt(); let m_ = (0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b).cbrt(); let s_ = (0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b).cbrt(); let l = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_; let a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_; let b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_; [l, a, b] } pub(crate) const fn hue_to_rgb(n1: f32, n2: f32, h: f32) -> f32 { let h = modulo(h, 6.0); if h < 1.0 { return n1 + ((n2 - n1) * h); } if h < 3.0 { return n2; } if h < 4.0 { return n1 + ((n2 - n1) * (4.0 - h)); } n1 } // h = 0..360 // s, l = 0..1 // r, g, b = 0..1 pub(crate) const fn hsl_to_rgb(h: f32, s: f32, l: f32) -> [f32; 3] { if s == 0.0 { return [l, l, l]; } let n2 = if l < 0.5 { l * (1.0 + s) } else { l + s - (l * s) }; let n1 = 2.0 * l - n2; let h = h / 60.0; let r = hue_to_rgb(n1, n2, h + 2.0); let g = hue_to_rgb(n1, n2, h); let b = hue_to_rgb(n1, n2, h - 2.0); [r, g, b] } pub(crate) const fn hwb_to_rgb(hue: f32, white: f32, black: f32) -> [f32; 3] { if white + black >= 1.0 { let l = white / (white + black); return [l, l, l]; } let [r, g, b] = hsl_to_rgb(hue, 1.0, 0.5); let r = r * (1.0 - white - black) + white; let g = g * (1.0 - white - black) + white; let b = b * (1.0 - white - black) + white; [r, g, b] } #[allow(clippy::float_cmp)] pub(crate) const fn hsv_to_hsl(h: f32, s: f32, v: f32) -> [f32; 3] { let l = (2.0 - s) * v / 2.0; let s = if l != 0.0 { if l == 1.0 { 0.0 } else if l < 0.5 { s * v / (l * 2.0) } else { s * v / (2.0 - l * 2.0) } } else { s }; [h, s, l] } pub(crate) const fn hsv_to_rgb(h: f32, s: f32, v: f32) -> [f32; 3] { let [h, s, l] = hsv_to_hsl(h, s, v); hsl_to_rgb(h, s, l) } #[allow(clippy::float_cmp)] pub(crate) const fn rgb_to_hsv(r: f32, g: f32, b: f32) -> [f32; 3] { let v = r.max(g.max(b)); let d = v - r.min(g.min(b)); if d == 0.0 { return [0.0, 0.0, v]; } let s = d / v; let dr = (v - r) / d; let dg = (v - g) / d; let db = (v - b) / d; let h = if r == v { db - dg } else if g == v { 2.0 + dr - db } else { 4.0 + dg - dr }; let h = (h * 60.0) % 360.0; [normalize_angle(h), s, v] } #[allow(clippy::float_cmp)] pub(crate) const fn rgb_to_hsl(r: f32, g: f32, b: f32) -> [f32; 3] { let min = r.min(g.min(b)); let max = r.max(g.max(b)); let l = (max + min) / 2.0; if min == max { return [0.0, 0.0, l]; } let d = max - min; let s = if l < 0.5 { d / (max + min) } else { d / (2.0 - max - min) }; let dr = (max - r) / d; let dg = (max - g) / d; let db = (max - b) / d; let h = if r == max { db - dg } else if g == max { 2.0 + dr - db } else { 4.0 + dg - dr }; let h = (h * 60.0) % 360.0; [normalize_angle(h), s, l] } pub(crate) const fn rgb_to_hwb(r: f32, g: f32, b: f32) -> [f32; 3] { let [hue, _, _] = rgb_to_hsl(r, g, b); let white = r.min(g.min(b)); let black = 1.0 - r.max(g.max(b)); [hue, white, black] } #[inline] pub(crate) const fn normalize_angle(t: f32) -> f32 { ((t % 360.0) + 360.0) % 360.0 } #[inline] pub(crate) const fn interp_angle(a0: f32, a1: f32, t: f32) -> f32 { let delta = (((a1 - a0) % 360.0) + 540.0) % 360.0 - 180.0; (a0 + t * delta + 360.0) % 360.0 } #[inline] pub(crate) const fn interp_angle_rad(a0: f32, a1: f32, t: f32) -> f32 { let delta = (((a1 - a0) % TAU) + PI_3) % TAU - PI; (a0 + t * delta + TAU) % TAU } #[inline] pub(crate) const fn modulo(x: f32, n: f32) -> f32 { (x % n + n) % n } // Map t from range [a, b] to range [c, d] #[inline] pub(crate) const fn remap(t: f32, a: f32, b: f32, c: f32, d: f32) -> f32 { (t - a) * ((d - c) / (b - a)) + c } #[cfg(test)] mod t { use super::*; #[test] fn normalize_angle_() { let data = [ (0.0, 0.0), (360.0, 0.0), (720.0, 0.0), (400.0, 40.0), (1155.0, 75.0), (-360.0, 0.0), (-90.0, 270.0), (-765.0, 315.0), ]; for (x, expected) in data { let c = normalize_angle(x); assert_eq!(expected, c); } } #[test] fn interp_angle_() { let data = [ ((0.0, 360.0, 0.5), 0.0), ((360.0, 90.0, 0.0), 0.0), ((360.0, 90.0, 0.5), 45.0), ((360.0, 90.0, 1.0), 90.0), ]; for ((a, b, t), expected) in data { let v = interp_angle(a, b, t); assert_eq!(expected, v); } } } csscolorparser-0.8.1/tests/chrome_android.rs000064400000000000000000000275331046102023000173750ustar 00000000000000#[test] fn random_colors() { // The color string is randomly generated, then parsed using Chrome 87.0 on Android let test_data = [ ("#6946", [102, 153, 68, 102]), ("#4F0", [68, 255, 0, 255]), ("#75C57A", [117, 197, 122, 255]), ("#4E0A70", [78, 10, 112, 255]), ("#A195", [170, 17, 153, 85]), ("#4A4786", [74, 71, 134, 255]), ("#FC0", [255, 204, 0, 255]), ("#517FE4", [81, 127, 228, 255]), ("#1594A4", [21, 148, 164, 255]), ("#1B574A", [27, 87, 74, 255]), ("#9445D48C", [148, 69, 212, 140]), ("#BC9", [187, 204, 153, 255]), ("#9E3C6B", [158, 60, 107, 255]), ("#E92C2E5B", [233, 44, 46, 91]), ("#563B", [85, 102, 51, 187]), ("#FAD87DAA", [250, 216, 125, 170]), ("#B11", [187, 17, 17, 255]), ("#817B", [136, 17, 119, 187]), ("#2DD", [34, 221, 221, 255]), ("#7403B61A", [116, 3, 182, 26]), ("#02ABBC2F", [2, 171, 188, 47]), ("#B9A1D823", [185, 161, 216, 35]), ("#180", [17, 136, 0, 255]), ("#AB8D15A1", [171, 141, 21, 161]), ("#B701", [187, 119, 0, 17]), ("#6DEBD9", [109, 235, 217, 255]), ("#A09", [170, 0, 153, 255]), ("#84A2CB", [132, 162, 203, 255]), ("#361501", [54, 21, 1, 255]), ("#8F20", [136, 255, 34, 0]), ("#9EC963", [158, 201, 99, 255]), ("#B2B50F", [178, 181, 15, 255]), ("#B038", [187, 0, 51, 136]), ("#E769EB", [231, 105, 235, 255]), ("#C4A", [204, 68, 170, 255]), ("#9E5", [153, 238, 85, 255]), ("#FF0", [255, 255, 0, 255]), ("#6DF", [102, 221, 255, 255]), ("#A41", [170, 68, 17, 255]), ("#596F0B", [89, 111, 11, 255]), ("#1A6", [17, 170, 102, 255]), ("#BB9B0EA9", [187, 155, 14, 169]), ("#85A", [136, 85, 170, 255]), ("#938B", [153, 51, 136, 187]), ("#F89", [255, 136, 153, 255]), ("#447591", [68, 117, 145, 255]), ("#8CE6", [136, 204, 238, 102]), ("#E1528097", [225, 82, 128, 151]), ("#7BCE11", [123, 206, 17, 255]), ("#40AECEB7", [64, 174, 206, 183]), ("rgb(93.170,64.269,73.499)", [93, 64, 73, 255]), ("rgb(7.929,61.287,229.755,-0.172)", [8, 61, 230, 0]), ("rgb(59.947,234.441,231.856)", [60, 234, 232, 255]), ("rgb(40.839,233.232,263.280)", [41, 233, 255, 255]), ("rgb(146.043,242.067,228.262,1.148)", [146, 242, 228, 255]), ("rgb(40.800,31.415,141.897,0.721)", [41, 31, 142, 184]), ("rgb(252.237,162.173,239.848,1.159)", [252, 162, 240, 255]), ("rgb(187.805,86.185,253.946,1.088)", [188, 86, 254, 255]), ("rgb(20.891,82.288,149.768)", [21, 82, 150, 255]), ("rgb(132.275,143.495,131.475)", [132, 143, 131, 255]), ("rgb(106.238,145.684,129.067)", [106, 146, 129, 255]), ("rgb(6.092,125.387,194.562,0.176)", [6, 125, 195, 45]), ("rgb(215.571,177.198,41.277)", [216, 177, 41, 255]), ("rgb(226.868,90.077,42.413,0.458)", [227, 90, 42, 117]), ("rgb(79.828,63.604,4.446,-0.071)", [80, 64, 4, 0]), ("rgb(182.752,129.166,77.250,0.955)", [183, 129, 77, 244]), ("rgb(214.852,23.610,245.474)", [215, 24, 245, 255]), ("rgb(132.858,192.196,34.316)", [133, 192, 34, 255]), ("rgb(222.322,136.431,72.692)", [222, 136, 73, 255]), ("rgb(263.504,94.097,7.834)", [255, 94, 8, 255]), ("rgb(254.248,34.996,26.785)", [254, 35, 27, 255]), ("rgb(154.676,75.369,247.392)", [155, 75, 247, 255]), ("rgb(98.048,245.136,84.628)", [98, 245, 85, 255]), ("rgb(257.612,4.746,247.485,0.894)", [255, 5, 247, 228]), ("rgb(108.478,219.046,146.111)", [108, 219, 146, 255]), ("rgb(87.414,185.873,26.154,0.536)", [87, 186, 26, 137]), ("rgb(91.980,117.789,56.497)", [92, 118, 56, 255]), ("rgb(134.494,228.709,63.294,0.649)", [134, 229, 63, 165]), ("rgb(44.674,131.163,35.602)", [45, 131, 36, 255]), ("rgb(127.390,73.029,27.948,0.963)", [127, 73, 28, 246]), ("rgb(187.426,125.312,219.397)", [187, 125, 219, 255]), ("rgb(254.713,264.153,258.329)", [255, 255, 255, 255]), ("rgb(244.510,207.326,178.902,0.339)", [245, 207, 179, 86]), ("rgb(26.199,-9.612,231.652,0.662)", [26, 0, 232, 169]), ("rgb(45.304,89.336,172.582)", [45, 89, 173, 255]), ("rgb(191.387,107.530,36.480)", [191, 108, 36, 255]), ("rgb(100.149,52.445,27.521,0.677)", [100, 52, 28, 173]), ("rgb(51.050,84.982,166.808)", [51, 85, 167, 255]), ("rgb(37.221,239.178,59.749)", [37, 239, 60, 255]), ("rgb(92.024,78.623,56.212,0.765)", [92, 79, 56, 195]), ("rgb(61.206,222.657,-6.863,0.526)", [61, 223, 0, 134]), ("rgb(233.507,205.865,171.023,0.637)", [234, 206, 171, 162]), ("rgb(135.288,90.821,193.045)", [135, 91, 193, 255]), ("rgb(18.590,144.907,124.470)", [19, 145, 124, 255]), ("rgb(241.835,158.227,224.189)", [242, 158, 224, 255]), ("rgb(184.917,157.120,206.682,0.283)", [185, 157, 207, 72]), ("rgb(185.019,101.395,60.572)", [185, 101, 61, 255]), ("rgb(67.718,1.945,198.884)", [68, 2, 199, 255]), ("rgb(129.479,142.689,149.387,0.186)", [129, 143, 149, 47]), ("rgb(253.673,-2.343,196.555,-0.144)", [254, 0, 197, 0]), ("rgb(98.168%,7.203%,51.225%)", [250, 18, 131, 255]), ("rgb(96.170%,77.147%,85.996%)", [245, 197, 219, 255]), ("rgb(44.834%,53.204%,-9.634%,0.188)", [114, 136, 0, 48]), ("rgb(-7.920%,77.363%,-5.832%)", [0, 197, 0, 255]), ("rgb(81.003%,65.363%,-9.457%)", [207, 167, 0, 255]), ("rgb(85.894%,7.216%,-7.468%)", [219, 18, 0, 255]), ("rgb(100.659%,-3.397%,-0.802%,0.330)", [255, 0, 0, 84]), ("rgb(32.614%,63.257%,55.861%,-0.179)", [83, 161, 142, 0]), ("rgb(17.267%,78.342%,106.706%)", [44, 200, 255, 255]), ("rgb(-2.465%,70.281%,64.300%)", [0, 179, 164, 255]), ("rgb(74.917%,92.472%,35.277%)", [191, 236, 90, 255]), ("rgb(25.250%,103.119%,9.820%,1.073)", [64, 255, 25, 255]), ("rgb(16.308%,73.992%,41.494%)", [42, 189, 106, 255]), ("rgb(33.416%,64.317%,2.900%)", [85, 164, 7, 255]), ("rgb(86.321%,-7.134%,95.066%,0.407)", [220, 0, 242, 104]), ("rgb(72.337%,85.660%,37.990%)", [184, 218, 97, 255]), ("rgb(60.830%,102.371%,64.532%)", [155, 255, 165, 255]), ("rgb(22.944%,45.301%,57.417%,0.233)", [59, 116, 146, 59]), ("rgb(-8.796%,77.305%,55.175%)", [0, 197, 141, 255]), ("rgb(62.273%,88.630%,40.361%,-0.133)", [159, 226, 103, 0]), ("rgb(9.002%,9.048%,55.050%)", [23, 23, 140, 255]), ("rgb(39.705%,64.215%,33.386%)", [101, 164, 85, 255]), ("rgb(79.062%,9.806%,-0.228%)", [202, 25, 0, 255]), ("rgb(12.557%,26.742%,81.062%,0.187)", [32, 68, 207, 48]), ("rgb(48.037%,44.658%,94.883%,0.104)", [122, 114, 242, 27]), ("rgb(70.643%,18.209%,5.847%)", [180, 46, 15, 255]), ("rgb(17.439%,107.090%,-4.975%,0.301)", [44, 255, 0, 77]), ("rgb(36.867%,63.947%,53.503%)", [94, 163, 136, 255]), ("rgb(58.049%,108.306%,41.125%,-0.083)", [148, 255, 105, 0]), ("rgb(53.613%,-2.382%,20.660%,0.375)", [137, 0, 53, 96]), ("rgb(17.281%,0.850%,86.809%)", [44, 2, 221, 255]), ("rgb(28.877%,108.291%,22.159%,0.048)", [74, 255, 57, 12]), ("rgb(67.469%,33.982%,29.863%)", [172, 87, 76, 255]), ("rgb(12.841%,42.108%,77.364%)", [33, 107, 197, 255]), ("rgb(-6.254%,104.573%,54.338%,0.326)", [0, 255, 139, 83]), ("rgb(23.335%,-7.262%,32.061%)", [60, 0, 82, 255]), ("rgb(33.559%,104.368%,82.602%)", [86, 255, 211, 255]), ("rgb(51.030%,84.331%,22.085%)", [130, 215, 56, 255]), ("rgb(-7.688%,-0.346%,109.870%,0.492)", [0, 0, 255, 125]), ("rgb(37.791%,66.140%,-2.511%)", [96, 169, 0, 255]), ("rgb(14.877%,-9.937%,98.026%,0.391)", [38, 0, 250, 100]), ("rgb(68.965%,54.114%,79.671%,0.786)", [176, 138, 203, 200]), ("rgb(32.699%,84.074%,12.438%)", [83, 214, 32, 255]), ("rgb(61.109%,37.962%,9.726%)", [156, 97, 25, 255]), ("rgb(50.551%,21.936%,91.162%,0.379)", [129, 56, 232, 97]), ("rgb(12.006%,50.391%,84.702%)", [31, 128, 216, 255]), ("rgb(58.866%,36.294%,44.703%)", [150, 93, 114, 255]), ("rgb(73.712%,35.422%,91.533%)", [188, 90, 233, 255]), ("rgb(35.268%,82.428%,99.633%)", [90, 210, 254, 255]), ("rgb(77.313%,92.131%,3.558%)", [197, 235, 9, 255]), ("hsl(191.596grad,43.986%,97.620%)", [246, 252, 251, 255]), ("hsl(79.060deg,8.776%,101.675%)", [255, 255, 255, 255]), ( "hsl(218.222deg,30.111%,58.967%,0.650)", [119, 142, 182, 166], ), ("hsl(-0.032turn,90.086%,72.510%)", [248, 122, 146, 255]), ("hsl(21.358deg,34.513%,70.780%)", [206, 173, 155, 255]), ("hsl(248.704deg,41.071%,94.868%,-0.194)", [238, 237, 247, 0]), ("hsl(0.315turn,88.238%,77.402%,0.394)", [158, 248, 147, 100]), ("hsl(1.126rad,23.412%,92.759%)", [240, 241, 232, 255]), ("hsl(221.926,39.531%,81.028%)", [187, 199, 226, 255]), ("hsl(275.876deg,77.777%,21.408%)", [63, 12, 97, 255]), ("hsl(0.223turn,6.827%,45.074%)", [117, 123, 107, 255]), ("hsl(88.794deg,91.390%,9.152%)", [24, 45, 2, 255]), ("hsl(322.793grad,7.388%,26.361%,0.283)", [71, 62, 72, 72]), ("hsl(320.912grad,89.287%,50.117%)", [199, 14, 241, 255]), ("hsl(89.001grad,-6.397%,14.927%)", [38, 38, 38, 255]), ("hsl(0.315turn,61.008%,39.837%)", [53, 164, 40, 255]), ("hsl(3.187rad,10.620%,1.837%,0.852)", [4, 5, 5, 217]), ("hsl(15.726,68.485%,92.900%,0.955)", [249, 231, 224, 244]), ("hsl(288.124deg,102.249%,26.487%)", [108, 0, 135, 255]), ( "hsl(66.360deg,17.120%,104.832%,0.867)", [255, 255, 255, 221], ), ("hsl(199.194deg,105.374%,33.202%)", [0, 115, 169, 255]), ("hsl(337.002grad,71.223%,56.730%)", [223, 66, 215, 255]), ("hsl(359.117,89.688%,25.416%,0.329)", [123, 7, 8, 84]), ("hsl(0.210rad,50.484%,31.972%)", [123, 57, 40, 255]), ("hsl(-3.642,101.576%,101.667%)", [255, 255, 255, 255]), ("hsl(0.904turn,97.186%,99.315%)", [255, 252, 254, 255]), ("hsl(0.123turn,64.031%,76.579%)", [234, 213, 157, 255]), ("hsl(165.918grad,2.876%,-4.821%,-0.128)", [0, 0, 0, 0]), ("hsl(47.128grad,86.109%,42.237%)", [200, 146, 15, 255]), ("hsl(357.805,66.392%,90.933%,0.084)", [247, 217, 218, 21]), ("hsl(59.582deg,72.571%,70.685%)", [234, 234, 126, 255]), ("hsl(219.957grad,88.355%,34.566%)", [10, 119, 166, 255]), ("hsl(184.649,78.952%,88.831%,0.234)", [204, 246, 249, 60]), ("hsl(243.323,11.432%,-6.552%)", [0, 0, 0, 255]), ("hsl(2.873rad,97.021%,7.421%)", [1, 37, 28, 255]), ("hsl(-5.237grad,32.508%,7.772%,0.700)", [26, 13, 14, 179]), ("hsl(0.065turn,74.685%,70.874%,-0.026)", [236, 169, 125, 0]), ("hsl(209.534grad,-3.497%,28.848%)", [74, 74, 74, 255]), ("hsl(120.884,48.350%,6.898%)", [9, 26, 9, 255]), ("hsl(327.151grad,8.405%,32.912%)", [90, 77, 91, 255]), ("hsl(3.470rad,38.504%,41.877%)", [66, 122, 148, 255]), ("hsl(5.489rad,88.555%,44.840%,0.832)", [216, 13, 167, 212]), ("hsl(0.295turn,34.256%,11.399%)", [24, 39, 19, 255]), ( "hsl(70.479deg,86.200%,102.072%,0.789)", [255, 255, 255, 201], ), ("hsl(77.957grad,53.345%,16.670%)", [58, 65, 20, 255]), ("hsl(5.562rad,11.968%,109.436%)", [255, 255, 255, 255]), ("hsl(404.677grad,103.003%,9.650%)", [49, 3, 0, 255]), ("hsl(2.715rad,89.884%,15.064%,-0.083)", [4, 73, 45, 0]), ("hsl(235.834,104.274%,12.569%)", [0, 4, 64, 255]), ("hsl(5.068rad,5.213%,83.391%)", [214, 210, 215, 255]), ]; for (s, expected) in test_data { let rgba = csscolorparser::parse(s).unwrap().to_rgba8(); assert_eq!(expected, rgba); } } csscolorparser-0.8.1/tests/chromium.rs000064400000000000000000000274311046102023000162400ustar 00000000000000#[test] fn random_colors() { // The color string is randomly generated, then parsed using Chromium 87.0.4280.66 let test_data = [ ("#9F2", [153, 255, 34, 255]), ("#919211", [145, 146, 17, 255]), ("#DF1A94", [223, 26, 148, 255]), ("#0E9665", [14, 150, 101, 255]), ("#D39", [221, 51, 153, 255]), ("#6EF1BB", [110, 241, 187, 255]), ("#5F2452", [95, 36, 82, 255]), ("#8E6", [136, 238, 102, 255]), ("#09BB", [0, 153, 187, 187]), ("#A51", [170, 85, 17, 255]), ("#E1FCF3", [225, 252, 243, 255]), ("#02E3", [0, 34, 238, 51]), ("#E6B2", [238, 102, 187, 34]), ("#8CEA3820", [140, 234, 56, 32]), ("#2B8", [34, 187, 136, 255]), ("#195", [17, 153, 85, 255]), ("#4B6", [68, 187, 102, 255]), ("#9AE", [153, 170, 238, 255]), ("#9D39E0", [157, 57, 224, 255]), ("#66E12CAC", [102, 225, 44, 172]), ("#27EB", [34, 119, 238, 187]), ("#9C12", [153, 204, 17, 34]), ("#86113903", [134, 17, 57, 3]), ("#B1C6ED", [177, 198, 237, 255]), ("#79D", [119, 153, 221, 255]), ("#D3F8B137", [211, 248, 177, 55]), ("#E2858DF2", [226, 133, 141, 242]), ("#B0DBA6", [176, 219, 166, 255]), ("#F7F", [255, 119, 255, 255]), ("#97C9", [153, 119, 204, 153]), ("#F06", [255, 0, 102, 255]), ("#243B", [34, 68, 51, 187]), ("#20DA", [34, 0, 221, 170]), ("#13C364B8", [19, 195, 100, 184]), ("#E515", [238, 85, 17, 85]), ("#A11C", [170, 17, 17, 204]), ("#488", [68, 136, 136, 255]), ("#5EE", [85, 238, 238, 255]), ("#5AB8A1", [90, 184, 161, 255]), ("#9A9AB8", [154, 154, 184, 255]), ("#EA9E", [238, 170, 153, 238]), ("#551AB14D", [85, 26, 177, 77]), ("#D1D5", [221, 17, 221, 85]), ("#3C4", [51, 204, 68, 255]), ("#B615FED3", [182, 21, 254, 211]), ("#FC7", [255, 204, 119, 255]), ("#4B10D2", [75, 16, 210, 255]), ("#2188EC", [33, 136, 236, 255]), ("#A68F5F9F", [166, 143, 95, 159]), ("#4670E4", [70, 112, 228, 255]), ("rgb(56.467,186.479,125.521)", [56, 186, 126, 255]), ("rgb(227.181,102.976,142.108)", [227, 103, 142, 255]), ("rgb(30.436,119.528,76.579)", [30, 120, 77, 255]), ("rgb(93.103,251.965,52.385)", [93, 252, 52, 255]), ("rgb(161.891,261.654,114.275)", [162, 255, 114, 255]), ("rgb(235.921,88.153,244.002)", [236, 88, 244, 255]), ("rgb(185.204,3.571,140.660,1.079)", [185, 4, 141, 255]), ("rgb(141.230,184.483,37.035)", [141, 184, 37, 255]), ("rgb(37.685,82.003,192.789,0.283)", [38, 82, 193, 72]), ("rgb(45.740,9.845,259.876)", [46, 10, 255, 255]), ("rgb(86.411,188.879,237.442,0.876)", [86, 189, 237, 223]), ("rgb(156.438,191.727,52.916)", [156, 192, 53, 255]), ("rgb(216.423,5.520,180.029,0.589)", [216, 6, 180, 150]), ("rgb(92.120,247.582,38.473)", [92, 248, 38, 255]), ("rgb(191.287,191.154,217.583)", [191, 191, 218, 255]), ("rgb(12.653,152.639,193.900)", [13, 153, 194, 255]), ("rgb(202.973,184.215,144.797)", [203, 184, 145, 255]), ("rgb(136.571,182.362,162.541,0.148)", [137, 182, 163, 38]), ("rgb(242.371,-9.883,94.380)", [242, 0, 94, 255]), ("rgb(225.423,48.220,245.090,0.771)", [225, 48, 245, 197]), ("rgb(120.844,198.795,84.888)", [121, 199, 85, 255]), ("rgb(164.768,213.433,79.101,0.592)", [165, 213, 79, 151]), ("rgb(38.678,179.795,181.240,0.668)", [39, 180, 181, 170]), ("rgb(20.568,-4.233,125.669)", [21, 0, 126, 255]), ("rgb(16.521,145.769,228.928,1.141)", [17, 146, 229, 255]), ("rgb(167.249,179.288,124.859,0.518)", [167, 179, 125, 132]), ("rgb(89.103,46.835,144.170)", [89, 47, 144, 255]), ("rgb(135.687,86.916,220.479,0.668)", [136, 87, 220, 170]), ("rgb(162.398,-6.010,110.044,0.753)", [162, 0, 110, 192]), ("rgb(232.790,116.598,58.135)", [233, 117, 58, 255]), ("rgb(151.480,198.087,129.953,0.205)", [151, 198, 130, 52]), ("rgb(220.157,183.680,73.321)", [220, 184, 73, 255]), ("rgb(182.469,204.897,82.469,0.901)", [182, 205, 82, 230]), ("rgb(4.167,34.557,42.727,0.701)", [4, 35, 43, 179]), ("rgb(43.038,100.728,14.607,1.052)", [43, 101, 15, 255]), ("rgb(0.045,150.822,2.053)", [0, 151, 2, 255]), ("rgb(252.789,220.105,226.842)", [253, 220, 227, 255]), ("rgb(158.071,262.551,21.134,1.191)", [158, 255, 21, 255]), ("rgb(233.762,23.326,169.395)", [234, 23, 169, 255]), ("rgb(231.402,104.070,-9.010,1.065)", [231, 104, 0, 255]), ("rgb(179.325,103.168,150.187,0.284)", [179, 103, 150, 72]), ("rgb(139.902,99.310,145.976)", [140, 99, 146, 255]), ("rgb(50.983,172.151,210.545,0.455)", [51, 172, 211, 116]), ("rgb(17.199,167.421,194.514,0.667)", [17, 167, 195, 170]), ("rgb(66.992,32.529,37.688,0.161)", [67, 33, 38, 41]), ("rgb(224.304,84.063,222.623)", [224, 84, 223, 255]), ("rgb(203.817,45.691,5.316)", [204, 46, 5, 255]), ("rgb(154.775,257.630,253.717)", [155, 255, 254, 255]), ("rgb(113.736,190.662,180.949)", [114, 191, 181, 255]), ("rgb(198.089,-9.253,145.769)", [198, 0, 146, 255]), ("rgb(36.705%,17.074%,105.740%,-0.093)", [94, 44, 255, 0]), ("rgb(36.517%,9.269%,20.384%,-0.136)", [93, 24, 52, 0]), ("rgb(43.466%,65.604%,55.038%)", [111, 167, 140, 255]), ("rgb(48.285%,63.684%,56.161%)", [123, 162, 143, 255]), ("rgb(-4.882%,-3.380%,43.553%)", [0, 0, 111, 255]), ("rgb(27.307%,75.448%,88.504%)", [70, 192, 226, 255]), ("rgb(69.694%,59.503%,75.063%)", [178, 152, 191, 255]), ("rgb(90.960%,12.457%,12.447%)", [232, 32, 32, 255]), ("rgb(-4.622%,36.627%,-8.178%)", [0, 93, 0, 255]), ("rgb(87.787%,20.447%,43.423%)", [224, 52, 111, 255]), ("rgb(67.308%,86.285%,72.392%,0.038)", [172, 220, 185, 10]), ("rgb(103.478%,75.442%,82.578%,0.746)", [255, 192, 211, 190]), ("rgb(94.011%,90.970%,79.718%)", [240, 232, 203, 255]), ("rgb(101.439%,74.849%,45.099%)", [255, 191, 115, 255]), ("rgb(89.327%,31.033%,44.547%)", [228, 79, 114, 255]), ("rgb(14.834%,42.672%,105.987%,0.244)", [38, 109, 255, 62]), ("rgb(21.958%,36.904%,78.661%)", [56, 94, 201, 255]), ("rgb(-6.690%,87.366%,71.478%)", [0, 223, 182, 255]), ("rgb(28.576%,39.953%,66.600%)", [73, 102, 170, 255]), ("rgb(40.806%,108.938%,47.153%,1.186)", [104, 255, 120, 255]), ("rgb(56.260%,70.797%,1.857%)", [143, 181, 5, 255]), ("rgb(83.108%,-8.247%,-6.553%,0.081)", [212, 0, 0, 21]), ("rgb(74.833%,49.474%,93.795%,0.274)", [191, 126, 239, 70]), ("rgb(68.953%,0.784%,100.538%,0.815)", [176, 2, 255, 208]), ("rgb(82.811%,4.286%,-7.748%)", [211, 11, 0, 255]), ("rgb(60.598%,108.140%,92.018%)", [155, 255, 235, 255]), ("rgb(43.708%,17.419%,-0.824%)", [111, 44, 0, 255]), ("rgb(25.794%,80.139%,104.639%)", [66, 204, 255, 255]), ("rgb(38.556%,11.224%,-1.615%)", [98, 29, 0, 255]), ("rgb(16.455%,48.941%,54.732%,0.187)", [42, 125, 140, 48]), ("rgb(80.390%,-9.653%,33.214%)", [205, 0, 85, 255]), ("rgb(79.032%,-1.482%,102.516%)", [202, 0, 255, 255]), ("rgb(84.366%,31.407%,87.727%)", [215, 80, 224, 255]), ("rgb(75.236%,101.526%,15.918%)", [192, 255, 41, 255]), ("rgb(41.238%,3.048%,1.219%)", [105, 8, 3, 255]), ("rgb(100.594%,5.613%,69.223%,0.190)", [255, 14, 177, 48]), ("rgb(50.523%,91.169%,-9.786%,-0.006)", [129, 232, 0, 0]), ("rgb(64.531%,35.250%,34.396%,0.566)", [165, 90, 88, 144]), ("rgb(29.348%,73.790%,37.770%)", [75, 188, 96, 255]), ("rgb(33.014%,25.582%,90.210%)", [84, 65, 230, 255]), ("rgb(-5.318%,-3.605%,13.564%)", [0, 0, 35, 255]), ("rgb(29.700%,52.430%,-9.889%)", [76, 134, 0, 255]), ("rgb(11.953%,5.255%,59.970%)", [30, 13, 153, 255]), ("rgb(76.719%,30.732%,61.515%,0.289)", [196, 78, 157, 74]), ("rgb(100.532%,96.866%,9.823%,0.475)", [255, 247, 25, 121]), ("rgb(25.194%,36.877%,67.434%,0.940)", [64, 94, 172, 240]), ("rgb(6.368%,90.828%,14.714%,0.304)", [16, 232, 38, 78]), ("rgb(90.081%,-4.819%,37.658%)", [230, 0, 96, 255]), ("rgb(-1.297%,9.539%,70.162%)", [0, 24, 179, 255]), ("rgb(47.324%,8.645%,-0.478%)", [121, 22, 0, 255]), ("hsl(0.069turn,21.307%,-3.478%,1.007)", [0, 0, 0, 255]), ("hsl(72.142,4.735%,-7.464%,1.042)", [0, 0, 0, 255]), ("hsl(0.890turn,49.391%,38.567%)", [147, 50, 114, 255]), ("hsl(3.845grad,68.172%,-6.573%)", [0, 0, 0, 255]), ("hsl(193.837,21.934%,21.649%)", [43, 62, 67, 255]), ("hsl(162.965deg,6.690%,44.810%)", [107, 122, 118, 255]), ("hsl(0.468turn,100.941%,37.582%)", [0, 192, 155, 255]), ("hsl(0.058turn,51.911%,48.034%,-0.122)", [186, 103, 59, 0]), ("hsl(120.939,66.051%,77.958%,0.145)", [162, 236, 163, 37]), ("hsl(72.452deg,63.319%,84.331%)", [230, 240, 190, 255]), ("hsl(155.120grad,106.379%,58.737%)", [45, 255, 113, 255]), ("hsl(5.240rad,-4.171%,32.487%)", [83, 83, 83, 255]), ("hsl(4.565rad,-3.467%,20.547%)", [52, 52, 52, 255]), ("hsl(295.291,7.475%,3.742%)", [10, 9, 10, 255]), ("hsl(4.287rad,96.686%,41.460%,1.012)", [23, 4, 208, 255]), ("hsl(0.283turn,95.539%,97.779%,1.064)", [247, 255, 244, 255]), ("hsl(2.638rad,37.279%,-9.931%)", [0, 0, 0, 255]), ("hsl(0.295turn,21.487%,35.668%,0.217)", [80, 110, 71, 55]), ("hsl(60.183deg,41.823%,22.086%)", [80, 80, 33, 255]), ("hsl(-0.021rad,64.948%,7.915%)", [33, 7, 8, 255]), ("hsl(121.677deg,15.914%,19.922%,-0.019)", [43, 59, 43, 0]), ("hsl(2.829rad,-9.457%,77.556%,-0.068)", [198, 198, 198, 0]), ("hsl(259.505grad,93.623%,67.416%)", [94, 111, 250, 255]), ("hsl(0.404rad,-4.398%,104.641%)", [255, 255, 255, 255]), ("hsl(0.840turn,76.343%,94.714%,0.063)", [252, 231, 251, 16]), ("hsl(49.836grad,83.982%,3.411%)", [16, 12, 1, 255]), ("hsl(0.018turn,107.944%,20.884%)", [107, 12, 0, 255]), ("hsl(0.375turn,22.837%,73.947%)", [173, 204, 181, 255]), ("hsl(331.324deg,84.224%,61.278%,0.830)", [239, 73, 153, 212]), ("hsl(2.062rad,60.636%,71.565%)", [141, 226, 139, 255]), ("hsl(91.718grad,48.490%,44.280%)", [127, 168, 58, 255]), ( "hsl(255.829deg,62.751%,107.894%,-0.045)", [255, 255, 255, 0], ), ("hsl(98.567grad,-6.159%,27.281%)", [70, 70, 70, 255]), ("hsl(1.517rad,83.487%,29.247%)", [81, 137, 12, 255]), ("hsl(83.494deg,99.500%,61.584%)", [178, 255, 60, 255]), ("hsl(400.564grad,42.181%,-6.001%,1.145)", [0, 0, 0, 255]), ("hsl(334.198deg,80.826%,90.951%,0.042)", [251, 213, 229, 11]), ("hsl(154.176,15.036%,62.757%,0.815)", [146, 174, 162, 208]), ("hsl(0.029rad,57.145%,72.419%)", [225, 147, 144, 255]), ("hsl(266.629,25.394%,-1.463%,0.584)", [0, 0, 0, 149]), ("hsl(5.873rad,105.561%,14.063%)", [72, 0, 28, 255]), ("hsl(5.731rad,38.296%,105.210%,0.659)", [255, 255, 255, 168]), ("hsl(327.273,65.108%,71.932%)", [230, 137, 188, 255]), ("hsl(89.405,36.983%,94.707%)", [242, 246, 237, 255]), ("hsl(0.925turn,78.035%,97.489%)", [254, 244, 248, 255]), ("hsl(0.000turn,56.799%,105.320%)", [255, 255, 255, 255]), ("hsl(2.276rad,107.756%,74.291%,0.290)", [124, 255, 147, 74]), ("hsl(215.619,25.313%,100.025%,-0.116)", [255, 255, 255, 0]), ("hsl(41.239,30.040%,7.348%)", [24, 21, 13, 255]), ("hsl(5.794rad,109.895%,71.423%)", [255, 109, 177, 255]), ]; for (s, expected) in test_data { let rgba = csscolorparser::parse(s).unwrap().to_rgba8(); assert_eq!(expected, rgba); } } csscolorparser-0.8.1/tests/color.rs000064400000000000000000000224611046102023000155310ustar 00000000000000use core::convert::TryFrom; use csscolorparser::Color; #[test] fn basic() { let c = Color::new(1.0, 0.0, 0.0, 1.0); assert_eq!(c.to_array(), [1.0, 0.0, 0.0, 1.0]); assert_eq!(c.to_rgba8(), [255, 0, 0, 255]); assert_eq!(c.to_rgba16(), [65535, 0, 0, 65535]); assert_eq!(c.to_rgba8(), [255, 0, 0, 255]); assert_eq!(c.to_css_hex(), "#ff0000"); assert_eq!(c.to_css_rgb(), "rgb(255 0 0)"); assert_eq!(c.to_string(), "RGBA(1,0,0,1)"); assert_eq!(c.to_hsva(), [0.0, 1.0, 1.0, 1.0]); assert_eq!(c.to_hsla(), [0.0, 1.0, 0.5, 1.0]); assert_eq!(c.to_hwba(), [0.0, 0.0, 0.0, 1.0]); assert_eq!(c.to_linear_rgba(), [1.0, 0.0, 0.0, 1.0]); assert_eq!(c.to_linear_rgba_u8(), [255, 0, 0, 255]); let c = Color::new(1.0, 0.0, 0.0, 0.5); assert_eq!(c.to_rgba8(), [255, 0, 0, 128]); assert_eq!(c.to_css_hex(), "#ff000080"); assert_eq!(c.to_css_rgb(), "rgb(255 0 0 / 50%)"); assert_eq!(c.to_string(), "RGBA(1,0,0,0.5)"); let c = Color::new(0.0, 1.0, 0.0, 1.0); assert_eq!(c.to_hsva(), [120.0, 1.0, 1.0, 1.0]); assert_eq!(c.to_hsla(), [120.0, 1.0, 0.5, 1.0]); assert_eq!(c.to_hwba(), [120.0, 0.0, 0.0, 1.0]); let c = Color::new(0.0, 0.0, 1.0, 1.0); assert_eq!(c.to_hsva(), [240.0, 1.0, 1.0, 1.0]); assert_eq!(c.to_hsla(), [240.0, 1.0, 0.5, 1.0]); assert_eq!(c.to_hwba(), [240.0, 0.0, 0.0, 1.0]); let c = Color::new(0.0, 0.0, 0.6, 1.0); assert_eq!(c.to_hsva(), [240.0, 1.0, 0.6, 1.0]); assert_eq!(c.to_hsla(), [240.0, 1.0, 0.3, 1.0]); //assert_eq!(c.to_hwba(), [240.0, 0.0, 0.4, 1.0]); let c = Color::new(0.5, 0.5, 0.5, 1.0); assert_eq!(c.to_hsva(), [0.0, 0.0, 0.5, 1.0]); assert_eq!(c.to_hsla(), [0.0, 0.0, 0.5, 1.0]); assert_eq!(c.to_hwba(), [0.0, 0.5, 0.5, 1.0]); let c = Color::from_laba(0.0, 0.0, 0.0, 1.0); assert_eq!(c.to_rgba8(), [0, 0, 0, 255]); let c = Color::from_laba(100.0, 0.0, 0.0, 1.0); assert_eq!(c.to_rgba8(), [255, 255, 255, 255]); let c = Color::from_lcha(0.0, 0.0, 0.0, 1.0); assert_eq!(c.to_rgba8(), [0, 0, 0, 255]); let c = Color::from_lcha(100.0, 0.0, 0.0, 1.0); assert_eq!(c.to_rgba8(), [255, 255, 255, 255]); assert_eq!(Color::default().to_rgba8(), [0, 0, 0, 255]); assert_eq!( Color::try_from("#f00").unwrap().to_rgba8(), [255, 0, 0, 255] ); assert_eq!( Color::from((1.0, 0.0, 0.0, 0.5)).to_rgba8(), [255, 0, 0, 128] ); assert_eq!(Color::from((1.0, 0.0, 0.0)).to_rgba8(), [255, 0, 0, 255]); assert_eq!(Color::from((255, 0, 0, 128)).to_rgba8(), [255, 0, 0, 128]); assert_eq!(Color::from((255, 0, 0)).to_rgba8(), [255, 0, 0, 255]); assert_eq!( Color::from([1.0, 0.0, 0.0, 0.5]).to_rgba8(), [255, 0, 0, 128] ); assert_eq!(Color::from([1.0, 0.0, 0.0]).to_rgba8(), [255, 0, 0, 255]); assert_eq!(Color::from([255, 0, 0, 128]).to_rgba8(), [255, 0, 0, 128]); assert_eq!(Color::from([255, 0, 0]).to_rgba8(), [255, 0, 0, 255]); assert_eq!( Color::from([0.0_f32, 1.0, 0.5, 1.0]).to_rgba8(), [0, 255, 128, 255] ); assert_eq!( Color::from([0.0_f32, 1.0, 0.5]).to_rgba8(), [0, 255, 128, 255] ); // clamping let c = Color::new(1.23, 0.5, -0.01, 1.01); assert_eq!([c.r, c.g, c.b, c.a], [1.23, 0.5, -0.01, 1.01]); assert_eq!(c.to_array(), [1.0, 0.5, 0.0, 1.0]); assert_eq!(c.to_rgba8(), [255, 128, 0, 255]); assert_eq!(c.to_rgba16(), [65535, 32768, 0, 65535]); let c = Color::new(1.23, 0.5, -0.01, 1.01).clamp(); assert_eq!([c.r, c.g, c.b, c.a], [1.0, 0.5, 0.0, 1.0]); } #[test] fn parser() { use core::str::FromStr; let test_data = ["#71fe15", "#d6e3c9", "#2a7719", "#b53717", "#5b0b8d"]; for s in test_data { let c = Color::from_str(s).unwrap(); assert_eq!(c.to_css_hex(), s); let c = Color::try_from(s).unwrap(); assert_eq!(c.to_css_hex(), s); let c = Color::try_from(s.to_string()).unwrap(); assert_eq!(c.to_css_hex(), s); } } #[test] fn convert_colors() { let colors = &[ //Color::new(1.0, 0.7, 0.1, 1.0), // Color::from_rgba8(255, 179, 26, 255), Color::from_rgba8(10, 255, 125, 0), Color::from_linear_rgba(0.1, 0.9, 1.0, 1.0), Color::from_hwba(0.0, 0.0, 0.0, 1.0), Color::from_hwba(320.0, 0.1, 0.3, 1.0), Color::from_hsva(120.0, 0.3, 0.2, 0.1), Color::from_hsla(120.0, 0.3, 0.2, 1.0), ]; for (i, col) in colors.iter().enumerate() { println!("{i} -> {}, {}", &col.to_css_hex(), &col.to_css_rgb()); let [a, b, c, d] = col.to_linear_rgba(); let x = Color::from_linear_rgba(a, b, c, d); assert_eq!(&col.to_css_hex(), &x.to_css_hex()); let [a, b, c, d] = col.to_oklaba(); let x = Color::from_oklaba(a, b, c, d); assert_eq!(&col.to_css_hex(), &x.to_css_hex()); } let data = &[ "#000000", "#ffffff", "#999999", "#7f7f7f", "#ff0000", "#fa8072", "#87ceeb", "#ff6347", "#ee82ee", "#9acd32", "#0aff7d", "#09ff7d", "#ffb31a", "#0aff7d", "#09ff7d", "#825dfa6d", "#abc5679b", ]; for s in data { let col = csscolorparser::parse(s).unwrap(); assert_eq!(s, &col.to_css_hex()); let [a, b, c, d] = col.to_rgba8(); let x = Color::from_rgba8(a, b, c, d); assert_eq!(s, &x.to_css_hex()); let [a, b, c, d] = col.to_hsva(); let x = Color::from_hsva(a, b, c, d); assert_eq!(s, &x.to_css_hex()); let [a, b, c, d] = col.to_hsla(); let x = Color::from_hsla(a, b, c, d); assert_eq!(s, &x.to_css_hex()); let [a, b, c, d] = col.to_hwba(); let x = Color::from_hwba(a, b, c, d); assert_eq!(s, &x.to_css_hex()); let [a, b, c, d] = col.to_linear_rgba(); let x = Color::from_linear_rgba(a, b, c, d); assert_eq!(s, &x.to_css_hex()); let [a, b, c, d] = col.to_oklaba(); let x = Color::from_oklaba(a, b, c, d); assert_eq!(s, &x.to_css_hex()); let [a, b, c, d] = col.to_laba(); let x = Color::from_laba(a, b, c, d); assert_eq!(s, &x.to_css_hex()); let [a, b, c, d] = col.to_lcha(); let x = Color::from_lcha(a, b, c, d); assert_eq!(s, &x.to_css_hex()); } } #[test] fn red() { let data = &[ Color::new(1.0, 0.0, 0.0, 1.0), Color::from_rgba8(255, 0, 0, 255), Color::from_linear_rgba(1.0, 0.0, 0.0, 1.0), Color::from_linear_rgba8(255, 0, 0, 255), Color::from_hsva(0.0, 1.0, 1.0, 1.0), Color::from_hsla(360.0, 1.0, 0.5, 1.0), Color::from_hwba(0.0, 0.0, 0.0, 1.0), Color::from_oklaba( 0.6279151939969809, 0.2249032308661071, 0.12580287012451802, 1.0, ), Color::from_html("#f00").unwrap(), Color::from_html("hsv(360,100%,100%)").unwrap(), ]; for c in data { assert_eq!(c.to_rgba8(), [255, 0, 0, 255]); } } #[test] fn interpolate() { let a = Color::new(0.0, 1.0, 0.0, 1.0); let b = Color::new(0.0, 0.0, 1.0, 1.0); assert_eq!(a.interpolate_rgb(&b, 0.0).to_rgba8(), [0, 255, 0, 255]); assert_eq!(a.interpolate_rgb(&b, 0.5).to_rgba8(), [0, 128, 128, 255]); assert_eq!(a.interpolate_rgb(&b, 1.0).to_rgba8(), [0, 0, 255, 255]); assert_eq!(b.interpolate_rgb(&a, 0.0).to_rgba8(), [0, 0, 255, 255]); assert_eq!(b.interpolate_rgb(&a, 0.5).to_rgba8(), [0, 128, 128, 255]); assert_eq!(b.interpolate_rgb(&a, 1.0).to_rgba8(), [0, 255, 0, 255]); assert_eq!( a.interpolate_linear_rgb(&b, 0.0).to_rgba8(), [0, 255, 0, 255] ); assert_eq!( a.interpolate_linear_rgb(&b, 0.5).to_rgba8(), [0, 188, 188, 255] ); assert_eq!( a.interpolate_linear_rgb(&b, 1.0).to_rgba8(), [0, 0, 255, 255] ); assert_eq!(a.interpolate_hsv(&b, 0.0).to_rgba8(), [0, 255, 0, 255]); assert_eq!(a.interpolate_hsv(&b, 0.5).to_rgba8(), [0, 255, 255, 255]); assert_eq!(a.interpolate_hsv(&b, 1.0).to_rgba8(), [0, 0, 255, 255]); assert_eq!(a.interpolate_oklab(&b, 0.0).to_rgba8(), [0, 255, 0, 255]); assert_eq!(a.interpolate_oklab(&b, 0.5).to_rgba8(), [0, 170, 191, 255]); assert_eq!(a.interpolate_oklab(&b, 1.0).to_rgba8(), [0, 0, 255, 255]); assert_eq!(a.interpolate_lab(&b, 0.0).to_rgba8(), [0, 255, 0, 255]); assert_eq!(a.interpolate_lab(&b, 1.0).to_rgba8(), [0, 0, 255, 255]); assert_eq!(a.interpolate_lch(&b, 0.0).to_rgba8(), [0, 255, 0, 255]); assert_eq!(a.interpolate_lch(&b, 1.0).to_rgba8(), [0, 0, 255, 255]); } #[test] fn const_fn() { #![allow(dead_code)] const C: Color = Color::new(1.0, 0.0, 0.0, 1.0); const _: Color = Color::from_rgba8(255, 0, 0, 255); const _: Color = Color::from_hsla(0.0, 1.0, 1.0, 1.0); const _: Color = Color::from_hsva(0.0, 1.0, 1.0, 1.0); const _: Color = Color::from_hwba(0.0, 0.0, 0.3, 1.0); const _: Color = C.clamp(); const _: [f32; 4] = C.to_array(); const _: [u8; 4] = C.to_rgba8(); const _: [u16; 4] = C.to_rgba16(); const _: [f32; 4] = C.to_hsla(); const _: [f32; 4] = C.to_hsva(); const _: [f32; 4] = C.to_hwba(); const A: Color = Color::new(0.0, 0.0, 0.0, 1.0); const _: Color = A.interpolate_rgb(&C, 0.75); const _: Color = A.interpolate_hsv(&C, 0.75); } csscolorparser-0.8.1/tests/firefox.rs000064400000000000000000000364271046102023000160640ustar 00000000000000#[test] fn random_colors() { // The color string is randomly generated, then parsed using Mozilla Firefox 84.0.2 let test_data = [ ("#30758CE9", [48, 117, 140, 233]), ("#91F76A89", [145, 247, 106, 137]), ("#CF0206", [207, 2, 6, 255]), ("#5BD6AC", [91, 214, 172, 255]), ("#86AFB7FB", [134, 175, 183, 251]), ("#562", [85, 102, 34, 255]), ("#67030D", [103, 3, 13, 255]), ("#52DE306C", [82, 222, 48, 108]), ("#3425", [51, 68, 34, 85]), ("#3CF775", [60, 247, 117, 255]), ("#46D6DAC3", [70, 214, 218, 195]), ("#5AE", [85, 170, 238, 255]), ("#6900", [102, 153, 0, 0]), ("#266A6B69", [38, 106, 107, 105]), ("#D49C7F", [212, 156, 127, 255]), ("#29CEDDF2", [41, 206, 221, 242]), ("#E99", [238, 153, 153, 255]), ("#462FCA15", [70, 47, 202, 21]), ("#5ECAB8", [94, 202, 184, 255]), ("#663A77D6", [102, 58, 119, 214]), ("#4952", [68, 153, 85, 34]), ("#427273", [66, 114, 115, 255]), ("#9866", [153, 136, 102, 102]), ("#7206AC1C", [114, 6, 172, 28]), ("#03E9", [0, 51, 238, 153]), ("#8AB779", [138, 183, 121, 255]), ("#B6EFB9", [182, 239, 185, 255]), ("#735E8A", [115, 94, 138, 255]), ("#F45", [255, 68, 85, 255]), ("#E8E9", [238, 136, 238, 153]), ("#67B9FB", [103, 185, 251, 255]), ("#846A1C", [132, 106, 28, 255]), ("#DFF17C", [223, 241, 124, 255]), ("#5CB7", [85, 204, 187, 119]), ("#895191", [137, 81, 145, 255]), ("#B05ACA00", [176, 90, 202, 0]), ("#303B", [51, 0, 51, 187]), ("#E02D", [238, 0, 34, 221]), ("#BD202974", [189, 32, 41, 116]), ("#54F5D0", [84, 245, 208, 255]), ("#52F24795", [82, 242, 71, 149]), ("#2BC5", [34, 187, 204, 85]), ("#8AA7", [136, 170, 170, 119]), ("#53F55F", [83, 245, 95, 255]), ("#A4DA", [170, 68, 221, 170]), ("#CE3E", [204, 238, 51, 238]), ("#9F7", [153, 255, 119, 255]), ("#85418C", [133, 65, 140, 255]), ("#D467", [221, 68, 102, 119]), ("#D83", [221, 136, 51, 255]), ("rgb(144.894,221.944,15.338,1.042)", [145, 222, 15, 255]), ("rgb(172.519,104.262,195.385)", [173, 104, 195, 255]), ("rgb(197.660,22.336,220.613)", [198, 22, 221, 255]), ("rgb(172.049,149.391,83.459)", [172, 149, 83, 255]), ("rgb(15.808,118.246,48.177)", [16, 118, 48, 255]), ("rgb(110.224,15.299,118.110)", [110, 15, 118, 255]), ("rgb(61.657,132.868,241.810)", [62, 133, 242, 255]), ("rgb(109.629,130.088,177.988,0.263)", [110, 130, 178, 67]), ("rgb(230.646,136.749,248.448,0.049)", [231, 137, 248, 12]), ("rgb(-7.621,134.924,115.641)", [0, 135, 116, 255]), ("rgb(26.009,108.413,240.710)", [26, 108, 241, 255]), ("rgb(21.680,232.551,168.863)", [22, 233, 169, 255]), ("rgb(111.999,67.957,205.788,1.105)", [112, 68, 206, 255]), ("rgb(63.877,198.919,221.669,-0.010)", [64, 199, 222, 0]), ("rgb(142.226,135.238,260.621)", [142, 135, 255, 255]), ("rgb(59.970,110.510,182.595,-0.069)", [60, 111, 183, 0]), ("rgb(-2.286,254.610,235.853)", [0, 255, 236, 255]), ("rgb(-7.958,193.044,235.101)", [0, 193, 235, 255]), ("rgb(256.609,159.324,257.998)", [255, 159, 255, 255]), ("rgb(109.388,91.292,241.384)", [109, 91, 241, 255]), ("rgb(30.402,200.071,253.355)", [30, 200, 253, 255]), ("rgb(254.429,18.713,262.391)", [254, 19, 255, 255]), ("rgb(58.353,74.252,20.484,0.240)", [58, 74, 20, 61]), ("rgb(175.711,57.423,190.797)", [176, 57, 191, 255]), ("rgb(258.310,48.529,209.761)", [255, 49, 210, 255]), ("rgb(42.267,221.449,78.031)", [42, 221, 78, 255]), ("rgb(35.229,195.388,257.837)", [35, 195, 255, 255]), ("rgb(-1.089,245.407,-3.070,0.320)", [0, 245, 0, 82]), ("rgb(62.510,251.003,18.592)", [63, 251, 19, 255]), ("rgb(30.376,73.291,147.311)", [30, 73, 147, 255]), ("rgb(129.732,260.759,31.529)", [130, 255, 32, 255]), ("rgb(197.675,25.346,77.167,-0.005)", [198, 25, 77, 0]), ("rgb(132.191,40.666,142.643)", [132, 41, 143, 255]), ("rgb(72.049,262.173,213.300)", [72, 255, 213, 255]), ("rgb(168.366,214.806,197.234)", [168, 215, 197, 255]), ("rgb(233.079,33.094,211.204)", [233, 33, 211, 255]), ("rgb(241.575,256.514,243.212,0.915)", [242, 255, 243, 233]), ("rgb(192.030,70.554,5.442)", [192, 71, 5, 255]), ("rgb(214.761,181.138,1.594)", [215, 181, 2, 255]), ("rgb(137.246,159.092,175.243,0.412)", [137, 159, 175, 105]), ("rgb(-6.852,94.340,165.562,1.115)", [0, 94, 166, 255]), ("rgb(27.688,165.299,256.621)", [28, 165, 255, 255]), ("rgb(240.314,99.632,-3.909,0.214)", [240, 100, 0, 55]), ("rgb(62.626,96.898,45.764,0.580)", [63, 97, 46, 148]), ("rgb(101.295,61.291,109.920)", [101, 61, 110, 255]), ("rgb(250.755,243.673,73.583)", [251, 244, 74, 255]), ("rgb(87.908,173.653,236.399,0.100)", [88, 174, 236, 26]), ("rgb(1.276,63.625,76.657)", [1, 64, 77, 255]), ("rgb(182.622,81.426,107.665)", [183, 81, 108, 255]), ("rgb(97.666,126.808,202.343)", [98, 127, 202, 255]), ("rgb(19.077%,96.844%,107.049%)", [49, 247, 255, 255]), ("rgb(60.527%,20.266%,24.231%)", [154, 52, 62, 255]), ("rgb(-8.659%,45.149%,39.844%)", [0, 115, 102, 255]), ("rgb(72.862%,88.329%,41.681%)", [186, 225, 106, 255]), ("rgb(71.619%,55.033%,109.121%)", [183, 140, 255, 255]), ("rgb(8.320%,59.542%,86.342%,0.535)", [21, 152, 220, 136]), ("rgb(71.371%,30.476%,87.487%)", [182, 78, 223, 255]), ("rgb(72.932%,55.921%,36.855%)", [186, 143, 94, 255]), ("rgb(102.716%,16.386%,95.450%)", [255, 42, 243, 255]), ("rgb(31.319%,7.413%,83.436%,1.067)", [80, 19, 213, 255]), ("rgb(30.471%,106.110%,14.952%)", [78, 255, 38, 255]), ("rgb(73.746%,53.000%,70.974%)", [188, 135, 181, 255]), ("rgb(53.236%,76.453%,51.996%)", [136, 195, 133, 255]), ("rgb(74.430%,-9.053%,40.591%,0.372)", [190, 0, 104, 95]), ("rgb(63.771%,56.927%,61.386%,0.867)", [163, 145, 157, 221]), ("rgb(82.193%,76.039%,85.366%)", [210, 194, 218, 255]), ("rgb(-8.062%,84.997%,52.049%)", [0, 217, 133, 255]), ("rgb(87.040%,18.561%,51.447%)", [222, 47, 131, 255]), ("rgb(-0.383%,-7.852%,59.846%,0.606)", [0, 0, 153, 155]), ("rgb(-5.271%,29.630%,24.221%)", [0, 76, 62, 255]), ("rgb(37.412%,-9.639%,28.126%)", [95, 0, 72, 255]), ("rgb(49.021%,98.510%,38.101%,0.498)", [125, 251, 97, 127]), ("rgb(32.297%,10.427%,9.294%,1.078)", [82, 27, 24, 255]), ("rgb(66.507%,95.944%,5.022%,0.846)", [170, 245, 13, 216]), ("rgb(61.872%,37.070%,49.019%)", [158, 95, 125, 255]), ("rgb(71.738%,44.537%,91.829%)", [183, 114, 234, 255]), ("rgb(103.206%,32.133%,45.463%)", [255, 82, 116, 255]), ("rgb(72.780%,94.460%,32.319%)", [186, 241, 82, 255]), ("rgb(1.399%,0.124%,59.130%,0.668)", [4, 0, 151, 170]), ("rgb(14.442%,4.097%,14.401%,-0.092)", [37, 10, 37, 0]), ("rgb(104.800%,58.989%,13.037%)", [255, 150, 33, 255]), ("rgb(98.126%,78.825%,105.629%,0.673)", [250, 201, 255, 172]), ("rgb(53.607%,90.566%,100.766%,1.127)", [137, 231, 255, 255]), ("rgb(35.961%,2.549%,12.719%)", [92, 6, 32, 255]), ("rgb(47.649%,104.780%,20.425%,0.936)", [122, 255, 52, 239]), ("rgb(19.942%,54.172%,31.089%,1.077)", [51, 138, 79, 255]), ("rgb(70.314%,108.126%,96.525%,0.513)", [179, 255, 246, 131]), ("rgb(49.348%,-1.456%,15.051%)", [126, 0, 38, 255]), ("rgb(13.047%,34.342%,99.376%)", [33, 88, 253, 255]), ("rgb(21.879%,37.037%,34.846%)", [56, 94, 89, 255]), ("rgb(18.765%,3.997%,17.193%,0.598)", [48, 10, 44, 152]), ("rgb(54.629%,55.177%,51.436%,-0.123)", [139, 141, 131, 0]), ("rgb(8.356%,-0.583%,74.565%)", [21, 0, 190, 255]), ("rgb(2.435%,74.014%,16.351%,0.580)", [6, 189, 42, 148]), ("rgb(25.820%,80.885%,85.077%)", [66, 206, 217, 255]), ("rgb(-4.774%,54.466%,34.484%,1.139)", [0, 139, 88, 255]), ("rgb(24.928%,80.365%,48.506%)", [64, 205, 124, 255]), ("rgb(43.466%,12.333%,73.637%,0.170)", [111, 31, 188, 43]), ("rgb(26.659%,24.469%,51.155%,0.592)", [68, 62, 130, 151]), ("rgb(102.749%,16.226%,13.440%,0.542)", [255, 41, 34, 138]), ( "hsl(157.572grad,22.057%,82.352%,0.748)", [200, 220, 207, 191], ), ("hsl(359.534grad,99.302%,24.820%)", [126, 0, 77, 255]), ("hsl(89.381deg,0.820%,2.238%,1.184)", [6, 6, 6, 255]), ("hsl(3.435rad,9.931%,1.830%,0.688)", [4, 5, 5, 175]), ("hsl(269.781deg,72.977%,16.749%,0.919)", [42, 12, 74, 234]), ("hsl(0.231turn,25.156%,-8.804%)", [0, 0, 0, 255]), ("hsl(362.732grad,71.384%,89.611%)", [247, 210, 231, 255]), ("hsl(8.019deg,22.561%,21.216%)", [66, 45, 42, 255]), ("hsl(80.737grad,88.920%,-0.182%)", [0, 0, 0, 255]), ("hsl(3.901rad,104.403%,100.043%)", [255, 255, 255, 255]), ("hsl(127.668deg,38.344%,95.249%)", [238, 248, 239, 255]), ("hsl(276.662grad,32.442%,77.971%)", [186, 181, 217, 255]), ("hsl(5.491rad,65.601%,107.670%)", [255, 255, 255, 255]), ("hsl(92.909grad,102.247%,91.380%)", [238, 255, 211, 255]), ("hsl(236.020,98.201%,19.861%,-0.065)", [1, 8, 100, 0]), ("hsl(356.944grad,18.231%,70.131%)", [193, 165, 183, 255]), ("hsl(347.581grad,91.415%,35.258%,0.479)", [172, 8, 137, 122]), ("hsl(200.437deg,81.639%,56.948%,0.017)", [56, 174, 235, 4]), ("hsl(96.598,-1.219%,80.187%)", [204, 204, 204, 255]), ("hsl(162.463,35.025%,9.764%,1.000)", [16, 34, 29, 255]), ("hsl(97.785grad,42.878%,49.733%,0.540)", [130, 181, 72, 138]), ("hsl(0.243turn,61.733%,36.754%)", [99, 152, 36, 255]), ("hsl(81.865grad,93.755%,100.434%)", [255, 255, 255, 255]), ("hsl(0.846turn,12.520%,78.873%,-0.121)", [208, 194, 207, 0]), ("hsl(1.003turn,-3.924%,48.828%)", [125, 125, 125, 255]), ("hsl(262.728deg,25.894%,50.198%)", [120, 95, 161, 255]), ("hsl(187.679,4.225%,90.298%,0.974)", [229, 231, 231, 248]), ("hsl(0.301turn,-6.189%,34.167%)", [87, 87, 87, 255]), ("hsl(0.268turn,91.972%,89.764%)", [224, 253, 205, 255]), ("hsl(247.074grad,78.542%,14.539%)", [8, 25, 66, 255]), ("hsl(46.282grad,37.538%,92.244%)", [243, 238, 228, 255]), ("hsl(0.391turn,4.084%,67.843%)", [170, 176, 172, 255]), ("hsl(-0.088turn,61.468%,95.887%)", [251, 238, 245, 255]), ("hsl(0.386turn,59.601%,19.961%,-0.102)", [21, 81, 40, 0]), ("hsl(310.087,43.917%,93.640%)", [246, 232, 244, 255]), ("hsl(59.767grad,17.209%,-3.545%)", [0, 0, 0, 255]), ("hsl(-7.789grad,91.197%,40.446%)", [197, 9, 31, 255]), ("hsl(99.724deg,44.247%,91.630%,0.575)", [231, 243, 224, 147]), ("hsl(0.129rad,100.953%,88.130%,0.208)", [255, 202, 194, 53]), ("hsl(28.333grad,92.866%,94.350%,0.225)", [254, 239, 227, 57]), ("hsl(6.278rad,13.724%,109.460%,0.388)", [255, 255, 255, 99]), ("hsl(0.526turn,79.719%,57.535%)", [60, 206, 233, 255]), ("hsl(299.379,49.286%,95.788%)", [249, 239, 250, 255]), ("hsl(198.214,22.384%,26.807%,0.047)", [53, 74, 84, 12]), ("hsl(140.760,-9.660%,9.079%)", [23, 23, 23, 255]), ("hsl(0.342turn,38.150%,26.553%,0.570)", [42, 94, 45, 145]), ("hsl(235.559deg,-1.789%,81.781%)", [209, 209, 209, 255]), ("hsl(-0.400grad,100.928%,31.366%,-0.121)", [160, 0, 1, 0]), ("hsl(41.989grad,-8.040%,83.940%)", [214, 214, 214, 255]), ("hsl(327.089grad,41.748%,17.920%)", [61, 27, 65, 255]), ("hwb(5.093rad 16.228% 7.107% / 0.995)", [210, 41, 237, 254]), ("hwb(0.913turn 23.380% 28.693%)", [182, 60, 123, 255]), ( "hwb(0.083turn 48.957% 19.454% / -0.055)", [205, 165, 125, 0], ), ("hwb(223.305 46.995% 33.460% / -0.095)", [120, 134, 170, 0]), ("hwb(93.679grad 14.835% 5.257%)", [159, 242, 38, 255]), ( "hwb(208.953grad 43.974% 17.786% / 49%)", [112, 197, 210, 125], ), ("hwb(168.267 8.049% 1.527%)", [21, 251, 206, 255]), ("hwb(260.943 27.267% 43.541%)", [96, 70, 144, 255]), ("hwb(0.485turn 46.539% 10.643%)", [119, 228, 218, 255]), ("hwb(212.179deg 31.054% 9.752%)", [79, 149, 230, 255]), ("hwb(-0.070turn 14.978% 9.707%)", [230, 38, 119, 255]), ("hwb(0.073turn 36.215% 43.333% / -40%)", [145, 115, 92, 0]), ("hwb(0.162turn 49.627% 28.784%)", [182, 180, 127, 255]), ("hwb(0.243turn 20.723% 13.575%)", [144, 220, 53, 255]), ("hwb(24.098deg 10.817% 49.419%)", [129, 68, 28, 255]), ("hwb(231.600deg 9.597% 14.013% / 3%)", [24, 52, 219, 8]), ("hwb(203.254deg 18.262% 3.646%)", [47, 169, 246, 255]), ("hwb(153.756deg 48.303% 33.045%)", [123, 171, 150, 255]), ("hwb(298.912grad 22.529% 13.786%)", [136, 57, 220, 255]), ("hwb(185.717deg 5.163% 31.175%)", [13, 160, 176, 255]), ("hwb(211.980 1.733% 4.655% / -52%)", [4, 116, 243, 0]), ("hwb(204.276 30.754% 10.146%)", [78, 168, 229, 255]), ( "hwb(0.953turn 48.769% 4.335% / 0.684)", [244, 124, 158, 174], ), ("hwb(2.465rad 16.911% 11.363%)", [43, 226, 108, 255]), ("hwb(58.908deg 29.545% 12.057% / 7%)", [224, 222, 75, 18]), ("hwb(6.344rad 49.227% 42.409%)", [147, 127, 126, 255]), ("hwb(0.255turn 29.724% 27.689% / 45%)", [127, 184, 76, 115]), ("hwb(328.563deg 20.347% 38.896%)", [156, 52, 106, 255]), ("hwb(2.157deg 15.367% 13.797% / 0.761)", [220, 46, 39, 194]), ("hwb(290.068grad 13.739% 49.438%)", [68, 35, 129, 255]), ("hwb(3.523 0.171% 7.495%)", [236, 14, 0, 255]), ("hwb(0.228turn 7.261% 38.296% / -28%)", [106, 157, 19, 0]), ("hwb(114.298deg 2.263% 48.814% / 115%)", [18, 131, 6, 255]), ("hwb(6.000rad 29.569% 37.733% / -90%)", [159, 75, 98, 0]), ("hwb(293.975 48.728% 30.547%)", [172, 124, 177, 255]), ("hwb(3.861rad 5.194% 22.537%)", [13, 71, 198, 255]), ("hwb(363.051grad 39.733% 20.035%)", [204, 101, 158, 255]), ("hwb(0.046turn 4.758% 4.128%)", [244, 76, 12, 255]), ("hwb(198.156 40.979% 25.203% / 47%)", [104, 165, 191, 120]), ("hwb(169.283 15.477% 3.858% / -79%)", [39, 245, 208, 0]), ("hwb(6.282rad 36.718% 12.765% / 27%)", [222, 94, 94, 69]), ("hwb(4.590rad 20.571% 19.423%)", [111, 52, 205, 255]), ("hwb(252.979grad 3.471% 13.610% / 0.626)", [9, 52, 220, 160]), ( "hwb(0.738turn 12.619% 22.873% / 0.498)", [103, 32, 197, 127], ), ("hwb(210.399grad 1.847% 38.094%)", [5, 134, 158, 255]), ("hwb(143.863 35.042% 31.195% / 0.378)", [89, 175, 124, 96]), ( "hwb(21.631grad 41.743% 24.160% / 47%)", [193, 135, 106, 120], ), ("hwb(84.952grad 41.859% 39.539%)", [141, 154, 107, 255]), ("hwb(37.442 2.103% 9.857%)", [230, 145, 5, 255]), ("hwb(135.379grad 5.905% 8.483% / 76%)", [15, 233, 22, 194]), ]; for (s, expected) in test_data { let rgba = csscolorparser::parse(s).unwrap().to_rgba8(); assert_eq!(expected, rgba); } } csscolorparser-0.8.1/tests/named_colors.rs000064400000000000000000000042561046102023000170620ustar 00000000000000use csscolorparser::{parse, Color, NAMED_COLORS}; #[test] fn named_colors() { let skip_list = ["aqua", "cyan", "fuchsia", "magenta"]; for (&name, &rgb) in NAMED_COLORS.entries() { let c = parse(name.as_str()).unwrap(); assert_eq!(c.to_rgba8()[0..3], rgb); if skip_list.contains(&name.as_str()) || name.as_str().contains("gray") || name.as_str().contains("grey") { continue; } assert_eq!(c.name(), Some(name.as_str())); let [r, g, b] = rgb; let c = Color::from_rgba8(r, g, b, 255); assert_eq!(c.name(), Some(name.as_str())); } // Case-insensitive tests macro_rules! cmp { ($a:expr, $b:expr) => { assert_eq!(parse($a).unwrap().to_rgba8(), parse($b).unwrap().to_rgba8()); }; } cmp!("red", "RED"); cmp!("red", "Red"); cmp!("skyblue", "SKYBLUE"); cmp!("skyblue", "SkyBlue"); // Hex #[rustfmt::skip] let test_data = [ ("aliceblue", "#f0f8ff"), ("bisque", "#ffe4c4"), ("black", "#000000"), ("chartreuse", "#7fff00"), ("coral", "#ff7f50"), ("crimson", "#dc143c"), ("dodgerblue", "#1e90ff"), ("firebrick", "#b22222"), ("gold", "#ffd700"), ("hotpink", "#ff69b4"), ("indigo", "#4b0082"), ("lavender", "#e6e6fa"), ("lime", "#00ff00"), ("plum", "#dda0dd"), ("red", "#ff0000"), ("salmon", "#fa8072"), ("skyblue", "#87ceeb"), ("tomato", "#ff6347"), ("violet", "#ee82ee"), ("yellowgreen", "#9acd32"), ]; for (name, hex) in test_data { let c = csscolorparser::parse(name).unwrap(); assert_eq!(c.to_css_hex(), hex); let c = csscolorparser::parse(hex).unwrap(); assert_eq!(c.name(), Some(name)); } // Colors without names let test_data = [ Color::new(0.7, 0.8, 0.9, 1.0), Color::new(1.0, 0.5, 0.0, 1.0), Color::from_rgba8(0, 50, 100, 255), ]; for c in test_data { assert!(c.name().is_none()); } } csscolorparser-0.8.1/tests/parser.rs000064400000000000000000000171271046102023000157120ustar 00000000000000use csscolorparser::{parse, Color}; #[test] fn parser() { let test_data = [ ("transparent", [0, 0, 0, 0]), ("TRANSPARENT", [0, 0, 0, 0]), ("#ff00ff64", [255, 0, 255, 100]), ("ff00ff64", [255, 0, 255, 100]), ("rgb(247,179,99)", [247, 179, 99, 255]), ("rgb(50% 50% 50%)", [128, 128, 128, 255]), ("rgb(247,179,99,0.37)", [247, 179, 99, 94]), ("hsl(270 0% 50%)", [128, 128, 128, 255]), ("hwb(0 50% 50%)", [128, 128, 128, 255]), ("hsv(0 0% 50%)", [128, 128, 128, 255]), ("hsv(0 0% 100%)", [255, 255, 255, 255]), ("hsv(0 0% 19%)", [48, 48, 48, 255]), ]; for (s, expected) in test_data { let a = parse(s).unwrap().to_rgba8(); let b = s.parse::().unwrap().to_rgba8(); let c = Color::from_html(s).unwrap().to_rgba8(); assert_eq!(expected, a); assert_eq!(expected, b); assert_eq!(expected, c); } let test_data = [ ("lab(0% 0 0)", [0, 0, 0, 255]), ("lab(100% 0 0)", [255, 255, 255, 255]), ("lab(0% 0 0 / 0.5)", [0, 0, 0, 128]), ("lch(0% 0 0)", [0, 0, 0, 255]), ("lch(100% 0 0)", [255, 255, 255, 255]), ("lch(0% 0 0 / 0.5)", [0, 0, 0, 128]), ]; for (s, expected) in test_data { assert_eq!(expected, parse(s).unwrap().to_rgba8()); } } #[test] fn equal() { let test_data = [ ("transparent", "rgb(0,0,0,0%)"), ("#FF9900", "#f90"), ("#aabbccdd", "#ABCD"), ("#BAD455", "BAD455"), ("rgb(0 255 127 / 75%)", "rgb(0,255,127,0.75)"), ("hwb(180 0% 60%)", "hwb(180,0%,60%)"), ("hwb(290 30% 0%)", "hwb(290 0.3 0)"), ("hsl(180,50%,27%)", "hsl(180,0.5,0.27)"), ("rgb(255, 165, 0)", "hsl(38.824 100% 50%)"), ("#7654CD", "rgb(46.27% 32.94% 80.39%)"), //#[cfg(feature = "lab")] //("#7654CD", "lab(44.36% 36.05 -58.99)"), ]; for (a, b) in test_data { let c1 = parse(a).unwrap(); let c2 = parse(b).unwrap(); assert_eq!(c1.to_rgba8(), c2.to_rgba8(), "{:?}", [a, b]); } } #[test] fn black() { let data = [ "#000", "#000f", "#000000", "#000000ff", "000", "000f", "000000", "000000ff", "rgb(0,0,0)", "rgb(0% 0% 0%)", "rgb(0 0 0 100%)", "hsl(270,100%,0%)", "hwb(90 0% 100%)", "hwb(120deg 0% 100% 100%)", "hsv(120 100% 0%)", "oklab(0 0 0)", "oklch(0 0 180)", ]; let black = [0, 0, 0, 255]; for s in data { let c = parse(s).unwrap().to_rgba8(); assert_eq!(black, c); } } #[test] fn red() { let data = [ "#f00", "#f00f", "#ff0000", "#ff0000ff", "f00", "f00f", "ff0000", "ff0000ff", "rgb(255,0,0)", "rgb(255 0 0)", "rgb(700, -99, 0)", // clamp to 0..255 "rgb(100% 0% 0%)", "rgb(200% -10% -100%)", // clamp to 0%..100% "rgb(255 0 0 100%)", " RGB ( 255 , 0 , 0 ) ", "RGB( 255 0 0 )", "hsl(0,100%,50%)", "hsl(360 100% 50%)", "hwb(0 0% 0%)", "hwb(360deg 0% 0% 100%)", "hwb(360DEG 0% 0% 100%)", "hsv(0 100% 100%)", "oklab(0.62796, 0.22486, 0.12585)", "oklch(0.62796, 0.25768, 29.23388)", ]; let red = [255, 0, 0, 255]; for s in data { let res = parse(s); assert!(res.is_ok(), "{:?}", s); let c = res.unwrap().to_rgba8(); assert_eq!(red, c); } } #[test] fn lime() { let data = [ "#0f0", "#0f0f", "#00ff00", "#00ff00ff", "0f0", "0f0f", "00ff00", "00ff00ff", "rgb(0,255,0)", "rgb(0% 100% 0%)", "rgb(0 255 0 / 100%)", "rgba(0,255,0,1)", "hsl(120,100%,50%)", "hsl(120deg 100% 50%)", "hsl(-240 100% 50%)", "hsl(-240deg 100% 50%)", "hsl(0.3333turn 100% 50%)", "hsl(0.3333TURN 100% 50%)", "hsl(133.333grad 100% 50%)", "hsl(133.333GRAD 100% 50%)", "hsl(2.0944rad 100% 50%)", "hsl(2.0944RAD 100% 50%)", "hsla(120,100%,50%,100%)", "hwb(120 0% 0%)", "hwb(480deg 0% 0% / 100%)", "hsv(120 100% 100%)", "oklab(0.86644, -0.23389, 0.1795)", "oklch(0.86644, 0.29483, 142.49535)", ]; let lime = [0, 255, 0, 255]; for s in data { let res = parse(s); assert!(res.is_ok(), "{:?}", s); let c = res.unwrap().to_rgba8(); assert_eq!(lime, c); } } #[test] fn lime_alpha() { let data = [ "#00ff0080", "00ff0080", "rgb(0,255,0,50%)", "rgb(0% 100% 0% / 0.5)", "rgba(0%,100%,0%,50%)", "hsl(120,100%,50%,0.5)", "hsl(120deg 100% 50% / 50%)", "hsla(120,100%,50%,0.5)", "hwb(120 0% 0% / 50%)", "hsv(120 100% 100% / 50%)", ]; let lime_alpha = [0, 255, 0, 128]; for s in data { let c = parse(s).unwrap().to_rgba8(); assert_eq!(lime_alpha, c); } } #[cfg(feature = "named-colors")] #[test] fn invalid_format() { let test_data = [ "", "bloodred", "#78afzd", "#fffff", "rgb(255,0,0", "rgb(0,255,8s)", "rgb(100%,z9%,75%)", "rgb(255,0,0%)", // mix format "rgb(70%,30%,0)", // mix format "rgb(255 0 0 0 0)", "rgb(255 0 0 0 / 0)", "rgb(,255,0,0)", "rgb(255,0,,0)", "rgb(255,0,0,)", "rgb(255,0,0/)", "rgb(255/0,0)", "cmyk(1 0 0)", "rgba(0 0)", "hsl(90',100%,50%)", "hsl(360,70%,50%,90%,100%)", "hsl(deg 100% 50%)", "hsl(Xturn 100% 50%)", "hsl(Zgrad 100% 50%)", "hsl(180 1 x%)", "hsl(360,0%,0)", // mix format "hsla(360)", "hwb(Xrad,50%,50%)", "hwb(270 0% 0% 0% 0%)", "hwb(360,0,20%)", // mix format "hsv(120 100% 100% 1 50%)", "hsv(120 XXX 100%)", "hsv(120,100%,0.5)", //mix format "lab(100%,0)", "lab(100% 0 X)", "lch(100%,0)", "lch(100% 0 X)", "oklab(0,0)", "oklab(0,0,x,0)", "oklch(0,0,0,0,0)", "oklch(0,0,0,x)", "æ", "#ß", "rgb(ß,0,0)", "\u{1F602}", "#\u{1F602}", "rgb(\u{1F602},\u{1F602},\u{1F602})", ]; for s in test_data { let c = parse(s); assert!(c.is_err(), "{:?}", s); } #[rustfmt::skip] let test_data = [ ("#78afzd", "invalid hex format"), ("rgb(xx,yy,xx)", "invalid rgb format"), ("rgb(255,0)", "invalid rgb format"), ("hsl(0,100%,2o%)", "invalid hsl format"), ("hsv(360)", "invalid hsv format"), ("hwb(270,0%,0%,x)", "invalid hwb format"), ("lab(0%)", "invalid lab format"), ("lch(0%)", "invalid lch format"), ("oklab(9 20)", "invalid oklab format"), ("oklch()", "invalid oklch format"), ("cmyk(0,0,0,0)", "invalid color function"), ("blood", "invalid unknown format"), ("rgb(255,0,0", "invalid unknown format"), ("x£", "invalid unknown format"), ("x£x", "invalid unknown format"), ("xxx£x", "invalid unknown format"), ("xxxxx£x", "invalid unknown format"), ("\u{1F602}", "invalid unknown format"), ]; for (s, err_msg) in test_data { let c = parse(s); assert_eq!(c.unwrap_err().to_string(), err_msg, "{:?}", s); } } csscolorparser-0.8.1/tests/parser2.rs000064400000000000000000000145261046102023000157740ustar 00000000000000use csscolorparser::parse; #[test] fn hex() { let test_data = [ "#71fe15", "#d6e3c9", "#2a7719", "#b53717", "#5b0b8d", "#aff632", "#65ec8d", "#d35493", "#289e5f", "#b46152", "#e0afee", "#ac2be4", "#233490", "#1afbc5", "#e41755", "#e052ee", "#4d1b5e", "#230cde", "#f8a243", "#a130d1", "#b38373", "#6b9fa203", "#0e5e0be6", "#84f9a716", "#48651550", "#1adc2cf4", "#c191a31c", "#a25518c5", "#cb33f2c9", "#89b21d36", "#cbb97f3e", ]; for s in test_data { let c = parse(s).unwrap(); assert_eq!(s, c.to_css_hex()); } } #[test] fn rgb() { let test_data = [ "rgb(71 175 99)", "rgb(170 203 72)", "rgb(45 232 237)", "rgb(119 1 124)", "rgb(243 93 86)", "rgb(223 25 119)", "rgb(6 44 133)", "rgb(167 240 237)", "rgb(97 71 129)", "rgb(125 68 93)", "rgb(139 187 62)", "rgb(100 51 80)", "rgb(27 249 123)", "rgb(230 63 99)", "rgb(241 34 4)", "rgb(149 222 185)", "rgb(3 129 213)", "rgb(88 220 108)", "rgb(199 169 6)", "rgb(54 70 163)", "rgb(90 42 106)", ]; for s in test_data { let c = parse(s).unwrap(); assert_eq!(s, c.to_css_rgb()); } } #[test] fn hsl() { let test_data = [ "hsl(0 48% 83%)", "hsl(17 73% 13%)", "hsl(35 40% 84%)", "hsl(53 88% 21%)", "hsl(71 11% 45%)", "hsl(89 12% 89%)", "hsl(107 49% 68%)", "hsl(125 96% 72%)", "hsl(143 15% 92%)", "hsl(161 80% 93%)", "hsl(179 45% 76%)", "hsl(197 99% 84%)", "hsl(215 33% 15%)", "hsl(233 69% 59%)", "hsl(251 34% 46%)", "hsl(269 43% 18%)", "hsl(287 89% 69%)", "hsl(305 87% 36%)", "hsl(323 97% 26%)", "hsl(341 61% 66%)", "hsl(359 15% 74%)", ]; for s in test_data { let c = parse(s).unwrap(); assert_eq!(s, c.to_css_hsl()); } } #[test] fn hwb() { let test_data = [ "hwb(0 87% 0%)", "hwb(17 0% 23%)", "hwb(35 0% 7%)", "hwb(53 66% 0%)", "hwb(71 0% 66%)", "hwb(89 22% 0%)", "hwb(107 0% 2%)", "hwb(125 51% 0%)", "hwb(143 10% 0%)", "hwb(161 0% 76%)", "hwb(179 72% 0%)", "hwb(197 0% 60%)", "hwb(215 0% 39%)", "hwb(233 0% 18%)", "hwb(251 0% 3%)", "hwb(269 57% 0%)", "hwb(287 21% 0%)", "hwb(305 15% 0%)", "hwb(323 55% 0%)", "hwb(341 0% 72%)", "hwb(359 0% 2%)", ]; for s in test_data { let c = parse(s).unwrap(); assert_eq!(s, c.to_css_hwb()); } } #[test] fn oklab() { let test_data = [ "oklab(0.623 0.019 -0.359)", "oklab(0.362 -0.314 -0.035)", "oklab(0.804 0.166 -0.072)", "oklab(0.832 0.089 0.265)", "oklab(0.681 0.038 -0.3)", "oklab(0.117 -0.192 0.24)", "oklab(0.651 -0.241 -0.158)", "oklab(0.421 -0.248 0.053)", "oklab(0.923 -0.119 -0.288)", "oklab(0.811 -0.295 0.347)", "oklab(0.485 -0.368 0.066)", "oklab(0.905 0.13 -0.163)", "oklab(0.778 -0.001 0.4)", "oklab(0.672 0.136 -0.03)", "oklab(0.926 0.281 0.279)", "oklab(0.247 0.155 0.379)", "oklab(0.503 0.042 0.202)", "oklab(0.792 -0.34 -0.372)", "oklab(0.877 -0.13 0.222)", "oklab(0.898 -0.068 -0.239)", "oklab(0.725 -0.343 -0.352)", ]; for s in test_data { let c = parse(s).unwrap(); assert_eq!(s, c.to_css_oklab()); } } #[test] fn oklch() { let test_data = [ "oklch(0.284 0.132 0)", "oklch(0.314 0.136 17)", "oklch(0.935 0.398 35)", "oklch(0.729 0.175 53)", "oklch(0.157 0.29 71)", "oklch(0.266 0.365 89)", "oklch(0.12 0.225 107)", "oklch(0.532 0.274 125)", "oklch(0.571 0.201 143)", "oklch(0.948 0.217 161)", "oklch(0.501 0.2 179)", "oklch(0.184 0.308 197)", "oklch(0.308 0.273 215)", "oklch(0.874 0.143 233)", "oklch(0.544 0.186 251)", "oklch(0.144 0.255 269)", "oklch(0.997 0.327 287)", "oklch(0.544 0.22 305)", "oklch(0.578 0.203 323)", "oklch(0.819 0.343 341)", "oklch(0.497 0.188 359)", ]; for s in test_data { let c = parse(s).unwrap(); assert_eq!(s, c.to_css_oklch()); } } #[test] fn lab() { let test_data = [ "lab(57.32 70.93 101.73)", "lab(19.94 -109.64 -111.1)", "lab(54.5 -21.31 -64.68)", "lab(10.25 27.72 90.4)", "lab(33.83 105.64 37.89)", "lab(83.56 108.72 89.22)", "lab(40.01 105.35 -85.3)", "lab(30.1 21.78 -92.17)", "lab(99.19 0.44 93.11)", "lab(93.32 55.85 -9.14)", "lab(78.31 -23.69 -27.56)", "lab(42.64 -22.56 -45.68)", "lab(69.27 113.33 37.39)", "lab(24.6 -37.2 88.99)", "lab(72.6 -41.31 11.88)", "lab(12.44 -6.94 69.89)", "lab(71.23 -91.08 31.29)", "lab(5.18 65.67 63.53)", "lab(18.7 19.92 67.2)", "lab(14.62 71.89 57.13)", "lab(76.47 77.56 -107.61)", ]; for s in test_data { let c = parse(s).unwrap(); assert_eq!(s, c.to_css_lab()); } } #[test] fn lch() { let test_data = [ "lch(16.4 138.03 0)", "lch(7.88 52.89 17.95)", "lch(19.43 25.84 35.9)", "lch(73.85 45.9 53.85)", "lch(72.85 126.69 71.8)", "lch(42.26 71.09 89.75)", "lch(99.21 108.15 107.7)", "lch(13.05 38.12 125.65)", "lch(46.73 30.27 143.6)", "lch(33.88 90.43 161.55)", "lch(89.29 23.68 179.5)", "lch(20.69 14.49 197.45)", "lch(64.73 27.25 215.4)", "lch(61.08 70.57 233.35)", "lch(40.84 141.03 251.3)", "lch(7.45 91.95 269.25)", "lch(53.33 83.53 287.2)", "lch(26.3 52.41 305.15)", "lch(33.6 42.99 323.1)", "lch(90.17 91 341.05)", "lch(33.83 10.5 359)", ]; for s in test_data { let c = parse(s).unwrap(); assert_eq!(s, c.to_css_lch()); } } csscolorparser-0.8.1/tests/parser_relative_color.rs000064400000000000000000000062371046102023000210030ustar 00000000000000use csscolorparser::parse; #[test] fn parser() { let test_data = [ ["rgb(FROM #abcdef g B r / Alpha)", "#cdefab"], [ "rgb(from rgb(from #bad455 g calc(b + 23) r / alpha) b r calc(g - 23))", "#bad455", ], // --- ["rgb(from #bad455 r g b)", "#bad455"], ["rgb(from #bad455 b r g / alpha)", "#55bad4"], ["rgb(from #bad455 255 0 90)", "#ff005a"], ["rgb(from #bad455 r g b / 0.2)", "#bad45533"], ["rgb(from #bad455 r g b / calc(alpha / 2))", "#bad45580"], [ "rgb(from #bad455 calc(r + 10) calc(g - 15) calc(b * 0.75))", "#c4c540", ], ["rgb(from #bad455 calc((r + g) / 2) b g)", "#c755d4"], [ "rgb(from #bad455 127 100 calc(((r + g) + b) / 3))", "#7f64a1", ], // --- ["hwb(from #bad455 h w b)", "#bad455"], ["hwb(from #bad455 h b w)", "#90aa2b"], ["hwb(from #bad455 0 15 10)", "#e62626"], [ "hwb(from #bad455 calc(h + 90) calc(w - 5) calc(b + 10))", "#48bb99", ], // --- ["hsl(from #bad455 h s l)", "#bad455"], ["hsl(from #bad455 90 50 65)", "#a6d279"], ["hsl(from #bad455 h l s)", "#bbd45c"], ["hsl(from #bad455 calc(h - 45) calc(s + 9) l)", "#de8e4b"], // --- ["oklab(from #bad455 l a b)", "#bad455"], ["oklab(from #bad455 l b a)", "#fe9ff5"], ["oklab(from #bad455 0.75 -0.2 0.23)", "#60ce00"], ["oklab(from #bad455 calc(l * 0.7) a b)", "#708500"], // --- ["oklch(from #bad455 l c h)", "#bad455"], ["oklch(from #bad455 0.75 0.1 170)", "#66c3a4"], /*[ "oklch(from #bad455 calc(l * 1.5) c calc(h + 180))", "#ffe7ff", ],*/ [ "oklch(from #bad455 calc(l - 0.15) calc(c * 0.7) h)", "#8fa150", ], ]; for [s, hex] in test_data { assert_eq!(parse(s).unwrap().to_css_hex(), hex, "{:?}", s); } } #[test] fn lab() { let test_data = [ ["lab(from #bad455 l a b)", "#bad455"], ["lab(from #bad455 l a b / calc(alpha / 2))", "#bad45580"], ["lch(from #bad455 l c h)", "#bad455"], ["lch(from #bad455 l c h / calc(alpha * 0.5))", "#bad45580"], ]; for [s, hex] in test_data { assert_eq!(parse(s).unwrap().to_css_hex(), hex, "{:?}", s); } } #[test] fn invalid() { let test_data = [ "rgb(from)", "rgb(from #f00)", "rgb(from #abx 255 0 0)", "rgb(from #f00 r g)", "rgb(from #f00 r g b 0.5)", "hwb(from #f00 h w b alpha)", "rgb(from #f00 r g b / alpha 10)", "hsl(from #f00 h s x)", "rgb(from hwb(from hsv(90 0.5 v) h w b) 0 0 0)", // non ascii "rgb(ā #f00 r g b)", "rgb(from â r g b)", "rgb(from #f00 æ g b)", "rgb(from #f00 r g b / æ)", "rgb(from #f00 r calc(ã+15) b)", "rgb(from #f00 calc(1* (r-æ)) g b)", "rgb(from #f00 r g b / 1 ã)", "rgbà(from #f00 r g b)", "æç(from #f00 r g b)", ]; for s in test_data { assert!(parse(s).is_err(), "{:?}", s); } }