@@ -144,11 +144,134 @@ impl ToCss for Color {
144144 }
145145}
146146
147+ /// Either a number or a percentage.
148+ pub enum NumberOrPercentage {
149+ /// `<number>`.
150+ Number {
151+ /// The numeric value parsed, as a float.
152+ value : f32 ,
153+ } ,
154+ /// `<percentage>`
155+ Percentage {
156+ /// The value as a float, divided by 100 so that the nominal range is
157+ /// 0.0 to 1.0.
158+ unit_value : f32 ,
159+ } ,
160+ }
161+
162+ impl NumberOrPercentage {
163+ fn unit_value ( & self ) -> f32 {
164+ match * self {
165+ NumberOrPercentage :: Number { value } => value,
166+ NumberOrPercentage :: Percentage { unit_value } => unit_value,
167+ }
168+ }
169+ }
170+
171+ /// Either an angle or a number.
172+ pub enum AngleOrNumber {
173+ /// `<number>`.
174+ Number {
175+ /// The numeric value parsed, as a float.
176+ value : f32 ,
177+ } ,
178+ /// `<angle>`
179+ Angle {
180+ /// The value as a number of degrees.
181+ degrees : f32 ,
182+ } ,
183+ }
184+
185+ impl AngleOrNumber {
186+ fn degrees ( & self ) -> f32 {
187+ match * self {
188+ AngleOrNumber :: Number { value } => value,
189+ AngleOrNumber :: Angle { degrees } => degrees,
190+ }
191+ }
192+ }
193+
194+ /// A trait that can be used to hook into how `cssparser` parses color
195+ /// components, with the intention of implementing more complicated behavior.
196+ ///
197+ /// For example, this is used by Servo to support calc() in color.
198+ pub trait ColorComponentParser < ' i > {
199+ /// A custom error type that can be returned from the parsing functions.
200+ type Error : ' i ;
201+
202+ /// Parse an `<angle>` or `<number>`.
203+ ///
204+ /// Returns the result in degrees.
205+ fn parse_angle_or_number < ' t > (
206+ & self ,
207+ input : & mut Parser < ' i , ' t > ,
208+ ) -> Result < AngleOrNumber , ParseError < ' i , Self :: Error > > {
209+ let location = input. current_source_location ( ) ;
210+ Ok ( match * input. next ( ) ? {
211+ Token :: Number { value, .. } => AngleOrNumber :: Number { value } ,
212+ Token :: Dimension { value : v, ref unit, .. } => {
213+ let degrees = match_ignore_ascii_case ! { & * unit,
214+ "deg" => v,
215+ "grad" => v * 360. / 400. ,
216+ "rad" => v * 360. / ( 2. * PI ) ,
217+ "turn" => v * 360. ,
218+ _ => return Err ( location. new_unexpected_token_error( Token :: Ident ( unit. clone( ) ) ) ) ,
219+ } ;
220+
221+ AngleOrNumber :: Angle { degrees }
222+ }
223+ ref t => return Err ( location. new_unexpected_token_error ( t. clone ( ) ) )
224+ } )
225+ }
226+
227+ /// Parse a `<percentage>` value.
228+ ///
229+ /// Returns the result in a number from 0.0 to 1.0.
230+ fn parse_percentage < ' t > (
231+ & self ,
232+ input : & mut Parser < ' i , ' t > ,
233+ ) -> Result < f32 , ParseError < ' i , Self :: Error > > {
234+ input. expect_percentage ( ) . map_err ( From :: from)
235+ }
236+
237+ /// Parse a `<number>` value.
238+ fn parse_number < ' t > (
239+ & self ,
240+ input : & mut Parser < ' i , ' t > ,
241+ ) -> Result < f32 , ParseError < ' i , Self :: Error > > {
242+ input. expect_number ( ) . map_err ( From :: from)
243+ }
244+
245+ /// Parse a `<number>` value or a `<percentage>` value.
246+ fn parse_number_or_percentage < ' t > (
247+ & self ,
248+ input : & mut Parser < ' i , ' t > ,
249+ ) -> Result < NumberOrPercentage , ParseError < ' i , Self :: Error > > {
250+ let location = input. current_source_location ( ) ;
251+ Ok ( match * input. next ( ) ? {
252+ Token :: Number { value, .. } => NumberOrPercentage :: Number { value } ,
253+ Token :: Percentage { unit_value, .. } => NumberOrPercentage :: Percentage { unit_value } ,
254+ ref t => return Err ( location. new_unexpected_token_error ( t. clone ( ) ) )
255+ } )
256+ }
257+ }
258+
259+ struct DefaultComponentParser ;
260+ impl < ' i > ColorComponentParser < ' i > for DefaultComponentParser {
261+ type Error = ( ) ;
262+ }
263+
147264impl Color {
148265 /// Parse a <color> value, per CSS Color Module Level 3.
149266 ///
150267 /// FIXME(#2) Deprecated CSS2 System Colors are not supported yet.
151- pub fn parse < ' i , ' t > ( input : & mut Parser < ' i , ' t > ) -> Result < Color , BasicParseError < ' i > > {
268+ pub fn parse_with < ' i , ' t , ComponentParser > (
269+ component_parser : & ComponentParser ,
270+ input : & mut Parser < ' i , ' t > ,
271+ ) -> Result < Color , ParseError < ' i , ComponentParser :: Error > >
272+ where
273+ ComponentParser : ColorComponentParser < ' i > ,
274+ {
152275 // FIXME: remove clone() when lifetimes are non-lexical
153276 let location = input. current_source_location ( ) ;
154277 let token = input. next ( ) ?. clone ( ) ;
@@ -159,11 +282,19 @@ impl Color {
159282 Token :: Ident ( ref value) => parse_color_keyword ( & * value) ,
160283 Token :: Function ( ref name) => {
161284 return input. parse_nested_block ( |arguments| {
162- parse_color_function ( & * name, arguments) . map_err ( |e| e . into ( ) )
163- } ) . map_err ( ParseError :: < ( ) > :: basic ) ;
285+ parse_color_function ( component_parser , & * name, arguments)
286+ } )
164287 }
165288 _ => Err ( ( ) )
166- } . map_err ( |( ) | location. new_basic_unexpected_token_error ( token) )
289+ } . map_err ( |( ) | location. new_unexpected_token_error ( token) )
290+ }
291+
292+ /// Parse a <color> value, per CSS Color Module Level 3.
293+ pub fn parse < ' i , ' t > (
294+ input : & mut Parser < ' i , ' t > ,
295+ ) -> Result < Color , BasicParseError < ' i > > {
296+ let component_parser = DefaultComponentParser ;
297+ Self :: parse_with ( & component_parser, input) . map_err ( ParseError :: basic)
167298 }
168299
169300 /// Parse a color hash, without the leading '#' character.
@@ -195,10 +326,8 @@ impl Color {
195326 _ => Err ( ( ) )
196327 }
197328 }
198-
199329}
200330
201-
202331#[ inline]
203332fn rgb ( red : u8 , green : u8 , blue : u8 ) -> Color {
204333 rgba ( red, green, blue, 255 )
@@ -420,11 +549,18 @@ fn clamp_floor_256_f32(val: f32) -> u8 {
420549}
421550
422551#[ inline]
423- fn parse_color_function < ' i , ' t > ( name : & str , arguments : & mut Parser < ' i , ' t > ) -> Result < Color , BasicParseError < ' i > > {
552+ fn parse_color_function < ' i , ' t , ComponentParser > (
553+ component_parser : & ComponentParser ,
554+ name : & str ,
555+ arguments : & mut Parser < ' i , ' t >
556+ ) -> Result < Color , ParseError < ' i , ComponentParser :: Error > >
557+ where
558+ ComponentParser : ColorComponentParser < ' i > ,
559+ {
424560 let ( red, green, blue, uses_commas) = match_ignore_ascii_case ! { name,
425- "rgb" | "rgba" => parse_rgb_components_rgb( arguments) ?,
426- "hsl" | "hsla" => parse_rgb_components_hsl( arguments) ?,
427- _ => return Err ( arguments. new_basic_unexpected_token_error ( Token :: Ident ( name. to_owned( ) . into( ) ) ) ) ,
561+ "rgb" | "rgba" => parse_rgb_components_rgb( component_parser , arguments) ?,
562+ "hsl" | "hsla" => parse_rgb_components_hsl( component_parser , arguments) ?,
563+ _ => return Err ( arguments. new_unexpected_token_error ( Token :: Ident ( name. to_owned( ) . into( ) ) ) ) ,
428564 } ;
429565
430566 let alpha = if !arguments. is_exhausted ( ) {
@@ -433,18 +569,7 @@ fn parse_color_function<'i, 't>(name: &str, arguments: &mut Parser<'i, 't>) -> R
433569 } else {
434570 arguments. expect_delim ( '/' ) ?;
435571 } ;
436- let location = arguments. current_source_location ( ) ;
437- match * arguments. next ( ) ? {
438- Token :: Number { value : v, .. } => {
439- clamp_unit_f32 ( v)
440- }
441- Token :: Percentage { unit_value : v, .. } => {
442- clamp_unit_f32 ( v)
443- }
444- ref t => {
445- return Err ( location. new_basic_unexpected_token_error ( t. clone ( ) ) )
446- }
447- }
572+ clamp_unit_f32 ( component_parser. parse_number_or_percentage ( arguments) ?. unit_value ( ) )
448573 } else {
449574 255
450575 } ;
@@ -455,93 +580,73 @@ fn parse_color_function<'i, 't>(name: &str, arguments: &mut Parser<'i, 't>) -> R
455580
456581
457582#[ inline]
458- fn parse_rgb_components_rgb < ' i , ' t > ( arguments : & mut Parser < ' i , ' t > ) -> Result < ( u8 , u8 , u8 , bool ) , BasicParseError < ' i > > {
459- let red: u8 ;
460- let green: u8 ;
461- let blue: u8 ;
462- let mut uses_commas = false ;
463-
583+ fn parse_rgb_components_rgb < ' i , ' t , ComponentParser > (
584+ component_parser : & ComponentParser ,
585+ arguments : & mut Parser < ' i , ' t >
586+ ) -> Result < ( u8 , u8 , u8 , bool ) , ParseError < ' i , ComponentParser :: Error > >
587+ where
588+ ComponentParser : ColorComponentParser < ' i > ,
589+ {
464590 // Either integers or percentages, but all the same type.
465591 // https://drafts.csswg.org/css-color/#rgb-functions
466- // FIXME: remove .clone() when lifetimes are non-lexical.
467- let location = arguments. current_source_location ( ) ;
468- match arguments. next ( ) ?. clone ( ) {
469- Token :: Number { value : v, .. } => {
470- red = clamp_floor_256_f32 ( v) ;
471- green = clamp_floor_256_f32 ( match arguments. next ( ) ?. clone ( ) {
472- Token :: Number { value : v, .. } => v,
473- Token :: Comma => {
474- uses_commas = true ;
475- arguments. expect_number ( ) ?
476- }
477- t => return Err ( location. new_basic_unexpected_token_error ( t) )
478- } ) ;
479- if uses_commas {
480- arguments. expect_comma ( ) ?;
481- }
482- blue = clamp_floor_256_f32 ( arguments. expect_number ( ) ?) ;
592+ let ( red, is_number) = match component_parser. parse_number_or_percentage ( arguments) ? {
593+ NumberOrPercentage :: Number { value } => {
594+ ( clamp_floor_256_f32 ( value) , true )
483595 }
484- Token :: Percentage { unit_value, .. } => {
485- red = clamp_unit_f32 ( unit_value) ;
486- green = clamp_unit_f32 ( match arguments. next ( ) ?. clone ( ) {
487- Token :: Percentage { unit_value, .. } => unit_value,
488- Token :: Comma => {
489- uses_commas = true ;
490- arguments. expect_percentage ( ) ?
491- }
492- t => return Err ( location. new_basic_unexpected_token_error ( t) )
493- } ) ;
494- if uses_commas {
495- arguments. expect_comma ( ) ?;
496- }
497- blue = clamp_unit_f32 ( arguments. expect_percentage ( ) ?) ;
596+ NumberOrPercentage :: Percentage { unit_value } => {
597+ ( clamp_unit_f32 ( unit_value) , false )
498598 }
499- t => return Err ( location. new_basic_unexpected_token_error ( t) )
500599 } ;
501- return Ok ( ( red, green, blue, uses_commas) ) ;
600+
601+ let uses_commas = arguments. try ( |i| i. expect_comma ( ) ) . is_ok ( ) ;
602+
603+ let green;
604+ let blue;
605+ if is_number {
606+ green = clamp_floor_256_f32 ( component_parser. parse_number ( arguments) ?) ;
607+ if uses_commas {
608+ arguments. expect_comma ( ) ?;
609+ }
610+ blue = clamp_floor_256_f32 ( component_parser. parse_number ( arguments) ?) ;
611+ } else {
612+ green = clamp_unit_f32 ( component_parser. parse_percentage ( arguments) ?) ;
613+ if uses_commas {
614+ arguments. expect_comma ( ) ?;
615+ }
616+ blue = clamp_unit_f32 ( component_parser. parse_percentage ( arguments) ?) ;
617+ }
618+
619+ Ok ( ( red, green, blue, uses_commas) )
502620}
503621
504622#[ inline]
505- fn parse_rgb_components_hsl < ' i , ' t > ( arguments : & mut Parser < ' i , ' t > ) -> Result < ( u8 , u8 , u8 , bool ) , BasicParseError < ' i > > {
506- let mut uses_commas = false ;
623+ fn parse_rgb_components_hsl < ' i , ' t , ComponentParser > (
624+ component_parser : & ComponentParser ,
625+ arguments : & mut Parser < ' i , ' t >
626+ ) -> Result < ( u8 , u8 , u8 , bool ) , ParseError < ' i , ComponentParser :: Error > >
627+ where
628+ ComponentParser : ColorComponentParser < ' i > ,
629+ {
507630 // Hue given as an angle
508631 // https://drafts.csswg.org/css-values/#angles
509- let location = arguments. current_source_location ( ) ;
510- let hue_degrees = match * arguments. next ( ) ? {
511- Token :: Number { value : v, .. } => v,
512- Token :: Dimension { value : v, ref unit, .. } => {
513- match_ignore_ascii_case ! { & * unit,
514- "deg" => v,
515- "grad" => v * 360. / 400. ,
516- "rad" => v * 360. / ( 2. * PI ) ,
517- "turn" => v * 360. ,
518- _ => return Err ( location. new_basic_unexpected_token_error( Token :: Ident ( unit. clone( ) ) ) ) ,
519- }
520- }
521- ref t => return Err ( location. new_basic_unexpected_token_error ( t. clone ( ) ) )
522- } ;
632+ let hue_degrees = component_parser. parse_angle_or_number ( arguments) ?. degrees ( ) ;
633+
523634 // Subtract an integer before rounding, to avoid some rounding errors:
524635 let hue_normalized_degrees = hue_degrees - 360. * ( hue_degrees / 360. ) . floor ( ) ;
525636 let hue = hue_normalized_degrees / 360. ;
526637
527638 // Saturation and lightness are clamped to 0% ... 100%
528639 // https://drafts.csswg.org/css-color/#the-hsl-notation
529- let location = arguments. current_source_location ( ) ;
530- let saturation = match arguments. next ( ) ?. clone ( ) {
531- Token :: Percentage { unit_value, .. } => unit_value,
532- Token :: Comma => {
533- uses_commas = true ;
534- arguments. expect_percentage ( ) ?
535- }
536- t => return Err ( location. new_basic_unexpected_token_error ( t) )
537- } ;
640+ let uses_commas = arguments. try ( |i| i. expect_comma ( ) ) . is_ok ( ) ;
641+
642+ let saturation = component_parser. parse_percentage ( arguments) ?;
538643 let saturation = saturation. max ( 0. ) . min ( 1. ) ;
539644
540645 if uses_commas {
541646 arguments. expect_comma ( ) ?;
542647 }
543648
544- let lightness = arguments . expect_percentage ( ) ?;
649+ let lightness = component_parser . parse_percentage ( arguments ) ?;
545650 let lightness = lightness. max ( 0. ) . min ( 1. ) ;
546651
547652 // https://drafts.csswg.org/css-color/#hsl-color
0 commit comments