@@ -144,11 +144,134 @@ impl ToCss for Color {
144
144
}
145
145
}
146
146
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
+
147
264
impl Color {
148
265
/// Parse a <color> value, per CSS Color Module Level 3.
149
266
///
150
267
/// 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
+ {
152
275
// FIXME: remove clone() when lifetimes are non-lexical
153
276
let location = input. current_source_location ( ) ;
154
277
let token = input. next ( ) ?. clone ( ) ;
@@ -159,11 +282,19 @@ impl Color {
159
282
Token :: Ident ( ref value) => parse_color_keyword ( & * value) ,
160
283
Token :: Function ( ref name) => {
161
284
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
+ } )
164
287
}
165
288
_ => 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)
167
298
}
168
299
169
300
/// Parse a color hash, without the leading '#' character.
@@ -195,10 +326,8 @@ impl Color {
195
326
_ => Err ( ( ) )
196
327
}
197
328
}
198
-
199
329
}
200
330
201
-
202
331
#[ inline]
203
332
fn rgb ( red : u8 , green : u8 , blue : u8 ) -> Color {
204
333
rgba ( red, green, blue, 255 )
@@ -420,11 +549,18 @@ fn clamp_floor_256_f32(val: f32) -> u8 {
420
549
}
421
550
422
551
#[ 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
+ {
424
560
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( ) ) ) ) ,
428
564
} ;
429
565
430
566
let alpha = if !arguments. is_exhausted ( ) {
@@ -433,18 +569,7 @@ fn parse_color_function<'i, 't>(name: &str, arguments: &mut Parser<'i, 't>) -> R
433
569
} else {
434
570
arguments. expect_delim ( '/' ) ?;
435
571
} ;
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 ( ) )
448
573
} else {
449
574
255
450
575
} ;
@@ -455,93 +580,73 @@ fn parse_color_function<'i, 't>(name: &str, arguments: &mut Parser<'i, 't>) -> R
455
580
456
581
457
582
#[ 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
+ {
464
590
// Either integers or percentages, but all the same type.
465
591
// 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 )
483
595
}
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 )
498
598
}
499
- t => return Err ( location. new_basic_unexpected_token_error ( t) )
500
599
} ;
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) )
502
620
}
503
621
504
622
#[ 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
+ {
507
630
// Hue given as an angle
508
631
// 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
+
523
634
// Subtract an integer before rounding, to avoid some rounding errors:
524
635
let hue_normalized_degrees = hue_degrees - 360. * ( hue_degrees / 360. ) . floor ( ) ;
525
636
let hue = hue_normalized_degrees / 360. ;
526
637
527
638
// Saturation and lightness are clamped to 0% ... 100%
528
639
// 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) ?;
538
643
let saturation = saturation. max ( 0. ) . min ( 1. ) ;
539
644
540
645
if uses_commas {
541
646
arguments. expect_comma ( ) ?;
542
647
}
543
648
544
- let lightness = arguments . expect_percentage ( ) ?;
649
+ let lightness = component_parser . parse_percentage ( arguments ) ?;
545
650
let lightness = lightness. max ( 0. ) . min ( 1. ) ;
546
651
547
652
// https://drafts.csswg.org/css-color/#hsl-color
0 commit comments