Skip to content

Commit 6e8a5a4

Browse files
author
bors-servo
authored
Auto merge of #207 - servo:towards-calc-color, r=SimonSapin
color: Add a way to hook into the parsing of the components of the color functions. This way we'll be able to implement calc-in-color without duplicating a ton of code for calc() handling in Servo. Minor version bump because the API is not broken, just expanded to support providing an optional `ColorComponentParser`. <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/rust-cssparser/207) <!-- Reviewable:end -->
2 parents d71d4aa + 97890a3 commit 6e8a5a4

File tree

3 files changed

+194
-89
lines changed

3 files changed

+194
-89
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cssparser"
3-
version = "0.23.0"
3+
version = "0.23.1"
44
authors = [ "Simon Sapin <[email protected]>" ]
55

66
description = "Rust implementation of CSS Syntax Level 3"

src/color.rs

+192-87
Original file line numberDiff line numberDiff line change
@@ -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+
147264
impl 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]
203332
fn 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

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ pub use rules_and_declarations::{DeclarationParser, DeclarationListParser, parse
8989
pub use rules_and_declarations::{RuleListParser, parse_one_rule};
9090
pub use rules_and_declarations::{AtRuleType, QualifiedRuleParser, AtRuleParser};
9191
pub use from_bytes::{stylesheet_encoding, EncodingSupport};
92-
pub use color::{RGBA, Color, parse_color_keyword};
92+
pub use color::{RGBA, Color, parse_color_keyword, AngleOrNumber, NumberOrPercentage, ColorComponentParser};
9393
pub use nth::parse_nth;
9494
pub use serializer::{ToCss, CssStringWriter, serialize_identifier, serialize_string, TokenSerializationType};
9595
pub use parser::{Parser, Delimiter, Delimiters, ParserState, ParserInput};

0 commit comments

Comments
 (0)