Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make color parsing case insensitive #109

Merged
merged 2 commits into from
Dec 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ You can find its changes [documented below](#020-2024-12-17).

This release has an [MSRV][] of 1.82.

### Fixed

* Make color parsing case insensitive. ([#109][] by [@raphlinus][])

## [0.2.0][] (2024-12-17)

This release has an [MSRV][] of 1.82.
Expand Down Expand Up @@ -74,6 +78,7 @@ This is the initial release.
[#86]: https://github.com/linebender/color/pull/86
[#92]: https://github.com/linebender/color/pull/92
[#100]: https://github.com/linebender/color/pull/100
[#109]: https://github.com/linebender/color/pull/109

[Unreleased]: https://github.com/linebender/color/compare/v0.2.0...HEAD
[0.2.0]: https://github.com/linebender/color/releases/tag/v0.2.0
Expand Down
61 changes: 54 additions & 7 deletions color/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use core::error::Error;
use core::f64;
use core::fmt;
use core::str;
use core::str::FromStr;

use crate::{AlphaColor, ColorSpaceTag, DynamicColor, Flags, Missing, Srgb};
Expand Down Expand Up @@ -277,7 +278,7 @@ impl<'a> Parser<'a> {
match value {
Some(Value::Number(n)) => Ok(Some(n * scale)),
Some(Value::Percent(n)) => Ok(Some(n * pct_scale)),
Some(Value::Symbol("none")) => Ok(None),
Some(Value::Symbol(s)) if s.eq_ignore_ascii_case("none") => Ok(None),
_ => Err(ParseError::UnknownColorComponent),
}
}
Expand All @@ -287,9 +288,11 @@ impl<'a> Parser<'a> {
let value = self.value();
match value {
Some(Value::Number(n)) => Ok(Some(n)),
Some(Value::Symbol("none")) => Ok(None),
Some(Value::Symbol(s)) if s.eq_ignore_ascii_case("none") => Ok(None),
Some(Value::Dimension(n, dim)) => {
let scale = match dim {
let mut buf = [0; LOWERCASE_BUF_SIZE];
let dim_lc = make_lowercase(dim, &mut buf);
let scale = match dim_lc {
"deg" => 1.0,
"rad" => 180.0 / f64::consts::PI,
"grad" => 0.9,
Expand Down Expand Up @@ -429,7 +432,9 @@ impl<'a> Parser<'a> {
let Some(id) = self.ident() else {
return Err(ParseError::ExpectedColorSpaceIdentifier);
};
let cs = match id {
let mut buf = [0; LOWERCASE_BUF_SIZE];
let id_lc = make_lowercase(id, &mut buf);
let cs = match id_lc {
"srgb" => ColorSpaceTag::Srgb,
"srgb-linear" => ColorSpaceTag::LinearSrgb,
"display-p3" => ColorSpaceTag::DisplayP3,
Expand Down Expand Up @@ -478,7 +483,9 @@ pub fn parse_color_prefix(s: &str) -> Result<(usize, DynamicColor), ParseError>
}
let mut parser = Parser::new(s);
if let Some(id) = parser.ident() {
let color = match id {
let mut buf = [0; LOWERCASE_BUF_SIZE];
let id_lc = make_lowercase(id, &mut buf);
let color = match id_lc {
"rgb" | "rgba" => parser.rgb().map(set_from_named_color_space),
"lab" => parser
.lab(100.0, 1.25, ColorSpaceTag::Lab)
Expand All @@ -496,7 +503,7 @@ pub fn parse_color_prefix(s: &str) -> Result<(usize, DynamicColor), ParseError>
"hwb" => parser.hwb().map(set_from_named_color_space),
"color" => parser.color(),
_ => {
if let Some(ix) = crate::x11_colors::lookup_palette_index(id) {
if let Some(ix) = crate::x11_colors::lookup_palette_index(id_lc) {
let [r, g, b, a] = crate::x11_colors::COLORS[ix];
let mut color =
DynamicColor::from_alpha_color(AlphaColor::from_rgba8(r, g, b, a));
Expand Down Expand Up @@ -584,7 +591,8 @@ impl FromStr for ColorSpaceTag {
type Err = ParseError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
let mut buf = [0; LOWERCASE_BUF_SIZE];
match make_lowercase(s, &mut buf) {
"srgb" => Ok(Self::Srgb),
"srgb-linear" => Ok(Self::LinearSrgb),
"lab" => Ok(Self::Lab),
Expand All @@ -601,6 +609,28 @@ impl FromStr for ColorSpaceTag {
}
}

const LOWERCASE_BUF_SIZE: usize = 32;

/// If the string contains any uppercase characters, make a lowercase copy
/// in the provided buffer space.
///
/// If anything goes wrong (including the buffer size being exceeded), return
/// the original string.
fn make_lowercase<'a>(s: &'a str, buf: &'a mut [u8; LOWERCASE_BUF_SIZE]) -> &'a str {
let len = s.len();
if len <= LOWERCASE_BUF_SIZE && s.as_bytes().iter().any(|c| c.is_ascii_uppercase()) {
buf[..len].copy_from_slice(s.as_bytes());
if let Ok(s_copy) = str::from_utf8_mut(&mut buf[..len]) {
s_copy.make_ascii_lowercase();
s_copy
} else {
s
}
} else {
s
}
}

#[cfg(test)]
mod tests {
use crate::DynamicColor;
Expand Down Expand Up @@ -705,4 +735,21 @@ mod tests {
ParseError::UnknownAngleDimension,
);
}

#[test]
fn case_insensitive() {
for (c1, c2) in [
("red", "ReD"),
("lightgoldenrodyellow", "LightGoldenRodYellow"),
("rgb(102, 51, 153)", "RGB(102, 51, 153)"),
(
"color(rec2020 0.2 0.3 0.4 / 0.85)",
"CoLoR(ReC2020 0.2 0.3 0.4 / 0.85)",
),
("hwb(120deg 30% 50%)", "HwB(120DeG 30% 50%)"),
("hsl(none none none)", "HSL(NONE NONE NONE)"),
] {
assert_close_color(parse_color(c1).unwrap(), parse_color(c2).unwrap());
}
}
}
Loading