11#![ allow( dead_code) ]
22
3+ use anyhow:: bail;
34use serde:: de:: { self , Deserialize , Deserializer , Visitor } ;
45use std:: fmt;
5- use std:: num:: ParseIntError ;
66
77pub fn rgb < C : From < Rgba > > ( hex : u32 ) -> C {
88 let r = ( ( hex >> 16 ) & 0xFF ) as f32 / 255.0 ;
@@ -19,7 +19,7 @@ pub fn rgba(hex: u32) -> Rgba {
1919 Rgba { r, g, b, a }
2020}
2121
22- #[ derive( Clone , Copy , Default ) ]
22+ #[ derive( PartialEq , Clone , Copy , Default ) ]
2323pub struct Rgba {
2424 pub r : f32 ,
2525 pub g : f32 ,
@@ -70,21 +70,7 @@ impl<'de> Visitor<'de> for RgbaVisitor {
7070 }
7171
7272 fn visit_str < E : de:: Error > ( self , value : & str ) -> Result < Rgba , E > {
73- if value. len ( ) == 7 || value. len ( ) == 9 {
74- let r = u8:: from_str_radix ( & value[ 1 ..3 ] , 16 ) . unwrap ( ) as f32 / 255.0 ;
75- let g = u8:: from_str_radix ( & value[ 3 ..5 ] , 16 ) . unwrap ( ) as f32 / 255.0 ;
76- let b = u8:: from_str_radix ( & value[ 5 ..7 ] , 16 ) . unwrap ( ) as f32 / 255.0 ;
77- let a = if value. len ( ) == 9 {
78- u8:: from_str_radix ( & value[ 7 ..9 ] , 16 ) . unwrap ( ) as f32 / 255.0
79- } else {
80- 1.0
81- } ;
82- Ok ( Rgba { r, g, b, a } )
83- } else {
84- Err ( E :: custom (
85- "Bad format for RGBA. Expected #rrggbb or #rrggbbaa." ,
86- ) )
87- }
73+ Rgba :: try_from ( value) . map_err ( E :: custom)
8874 }
8975}
9076
@@ -125,19 +111,59 @@ impl From<Hsla> for Rgba {
125111}
126112
127113impl TryFrom < & ' _ str > for Rgba {
128- type Error = ParseIntError ;
114+ type Error = anyhow :: Error ;
129115
130116 fn try_from ( value : & ' _ str ) -> Result < Self , Self :: Error > {
131- let r = u8:: from_str_radix ( & value[ 1 ..3 ] , 16 ) ? as f32 / 255.0 ;
132- let g = u8:: from_str_radix ( & value[ 3 ..5 ] , 16 ) ? as f32 / 255.0 ;
133- let b = u8:: from_str_radix ( & value[ 5 ..7 ] , 16 ) ? as f32 / 255.0 ;
134- let a = if value. len ( ) > 7 {
135- u8:: from_str_radix ( & value[ 7 ..9 ] , 16 ) ? as f32 / 255.0
136- } else {
137- 1.0
117+ const RGB : usize = "rgb" . len ( ) ;
118+ const RGBA : usize = "rgba" . len ( ) ;
119+ const RRGGBB : usize = "rrggbb" . len ( ) ;
120+ const RRGGBBAA : usize = "rrggbbaa" . len ( ) ;
121+
122+ const EXPECTED_FORMATS : & ' static str = "Expected #rgb, #rgba, #rrggbb, or #rrggbbaa" ;
123+
124+ let Some ( ( "" , hex) ) = value. trim ( ) . split_once ( '#' ) else {
125+ bail ! ( "invalid RGBA hex color: '{value}'. {EXPECTED_FORMATS}" ) ;
126+ } ;
127+
128+ let ( r, g, b, a) = match hex. len ( ) {
129+ RGB | RGBA => {
130+ let r = u8:: from_str_radix ( & hex[ 0 ..1 ] , 16 ) ?;
131+ let g = u8:: from_str_radix ( & hex[ 1 ..2 ] , 16 ) ?;
132+ let b = u8:: from_str_radix ( & hex[ 2 ..3 ] , 16 ) ?;
133+ let a = if hex. len ( ) == RGBA {
134+ u8:: from_str_radix ( & hex[ 3 ..4 ] , 16 ) ?
135+ } else {
136+ 0xf
137+ } ;
138+
139+ /// Duplicates a given hex digit.
140+ /// E.g., `0xf` -> `0xff`.
141+ const fn duplicate ( value : u8 ) -> u8 {
142+ value << 4 | value
143+ }
144+
145+ ( duplicate ( r) , duplicate ( g) , duplicate ( b) , duplicate ( a) )
146+ }
147+ RRGGBB | RRGGBBAA => {
148+ let r = u8:: from_str_radix ( & hex[ 0 ..2 ] , 16 ) ?;
149+ let g = u8:: from_str_radix ( & hex[ 2 ..4 ] , 16 ) ?;
150+ let b = u8:: from_str_radix ( & hex[ 4 ..6 ] , 16 ) ?;
151+ let a = if hex. len ( ) == RRGGBBAA {
152+ u8:: from_str_radix ( & hex[ 6 ..8 ] , 16 ) ?
153+ } else {
154+ 0xff
155+ } ;
156+ ( r, g, b, a)
157+ }
158+ _ => bail ! ( "invalid RGBA hex color: '{value}'. {EXPECTED_FORMATS}" ) ,
138159 } ;
139160
140- Ok ( Rgba { r, g, b, a } )
161+ Ok ( Rgba {
162+ r : r as f32 / 255. ,
163+ g : g as f32 / 255. ,
164+ b : b as f32 / 255. ,
165+ a : a as f32 / 255. ,
166+ } )
141167 }
142168}
143169
@@ -311,3 +337,52 @@ impl<'de> Deserialize<'de> for Hsla {
311337 Ok ( Hsla :: from ( rgba) )
312338 }
313339}
340+
341+ #[ cfg( test) ]
342+ mod tests {
343+ use serde_json:: json;
344+
345+ use super :: * ;
346+
347+ #[ test]
348+ fn test_deserialize_three_value_hex_to_rgba ( ) {
349+ let actual: Rgba = serde_json:: from_value ( json ! ( "#f09" ) ) . unwrap ( ) ;
350+
351+ assert_eq ! ( actual, rgba( 0xff0099ff ) )
352+ }
353+
354+ #[ test]
355+ fn test_deserialize_four_value_hex_to_rgba ( ) {
356+ let actual: Rgba = serde_json:: from_value ( json ! ( "#f09f" ) ) . unwrap ( ) ;
357+
358+ assert_eq ! ( actual, rgba( 0xff0099ff ) )
359+ }
360+
361+ #[ test]
362+ fn test_deserialize_six_value_hex_to_rgba ( ) {
363+ let actual: Rgba = serde_json:: from_value ( json ! ( "#ff0099" ) ) . unwrap ( ) ;
364+
365+ assert_eq ! ( actual, rgba( 0xff0099ff ) )
366+ }
367+
368+ #[ test]
369+ fn test_deserialize_eight_value_hex_to_rgba ( ) {
370+ let actual: Rgba = serde_json:: from_value ( json ! ( "#ff0099ff" ) ) . unwrap ( ) ;
371+
372+ assert_eq ! ( actual, rgba( 0xff0099ff ) )
373+ }
374+
375+ #[ test]
376+ fn test_deserialize_eight_value_hex_with_padding_to_rgba ( ) {
377+ let actual: Rgba = serde_json:: from_value ( json ! ( " #f5f5f5ff " ) ) . unwrap ( ) ;
378+
379+ assert_eq ! ( actual, rgba( 0xf5f5f5ff ) )
380+ }
381+
382+ #[ test]
383+ fn test_deserialize_eight_value_hex_with_mixed_case_to_rgba ( ) {
384+ let actual: Rgba = serde_json:: from_value ( json ! ( "#DeAdbEeF" ) ) . unwrap ( ) ;
385+
386+ assert_eq ! ( actual, rgba( 0xdeadbeef ) )
387+ }
388+ }
0 commit comments