Fix `hex_to_hsla` inside the color crate (#9412)

Tobias Decking created

Previously, when `hex_to_hsla` was called with a color string using the
#rgb format, the expansion
did not add alpha values, leading to an incorrect result. This patch
fixes the issue.

In addition to that, parsing has been reworked, and the new function no
longer makes
calls to the global allocator in addition to generation much smaller
code. Parsing should
therefore be a bit more performant.

Change summary

crates/color/src/color.rs | 27 +++++++++++++--------------
1 file changed, 13 insertions(+), 14 deletions(-)

Detailed changes

crates/color/src/color.rs 🔗

@@ -58,24 +58,23 @@ pub fn hex_to_hsla(s: &str) -> Result<RGBAColor, String> {
     let hex = s.trim_start_matches('#');
 
     // Expand shorthand formats #RGB and #RGBA to #RRGGBB and #RRGGBBAA
-    let hex = match hex.len() {
-        3 => hex.chars().map(|c| c.to_string().repeat(2)).collect(),
-        4 => {
-            let (rgb, alpha) = hex.split_at(3);
-            let rgb = rgb
-                .chars()
-                .map(|c| c.to_string().repeat(2))
-                .collect::<String>();
-            let alpha = alpha.chars().next().unwrap().to_string().repeat(2);
-            format!("{}{}", rgb, alpha)
-        }
-        6 => format!("{}ff", hex), // Add alpha if missing
-        8 => hex.to_string(),      // Already in full format
+    let h = hex.as_bytes();
+    let arr: [u8; 8] = match h.len() {
+        // #RGB => #RRGGBBAA
+        3 => [h[0], h[0], h[1], h[1], h[2], h[2], b'f', b'f'],
+        // #RGBA => #RRGGBBAA
+        4 => [h[0], h[0], h[1], h[1], h[2], h[2], h[3], h[3]],
+        // #RRGGBB => #RRGGBBAA
+        6 => [h[0], h[1], h[2], h[3], h[4], h[5], b'f', b'f'],
+        // Already in #RRGGBBAA
+        8 => h.try_into().unwrap(),
         _ => return Err("Invalid hexadecimal string length".to_string()),
     };
 
+    let hex =
+        std::str::from_utf8(&arr).map_err(|_| format!("Invalid hexadecimal string: {}", s))?;
     let hex_val =
-        u32::from_str_radix(&hex, 16).map_err(|_| format!("Invalid hexadecimal string: {}", s))?;
+        u32::from_str_radix(hex, 16).map_err(|_| format!("Invalid hexadecimal string: {}", s))?;
 
     Ok(RGBAColor {
         r: ((hex_val >> 24) & 0xFF) as f32 / 255.0,