/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#![deny(missing_docs)]
//! Parsing for CSS colors.
usesuper::{
color_function::ColorFunction,
component::{ColorComponent, ColorComponentType},
AbsoluteColor,
}; usecrate::{
parser::{Parse, ParserContext},
values::{
generics::{calc::CalcUnits, Optional},
specified::{angle::Angle as SpecifiedAngle, calc::Leaf, color::Color as SpecifiedColor},
},
}; use cssparser::{
color::{parse_hash_color, PredefinedColorSpace, OPAQUE},
match_ignore_ascii_case, CowRcStr, Parser, Token,
}; use style_traits::{ParseError, StyleParseErrorKind};
/// Returns true if the relative color syntax pref is enabled. #[inline] pubfn rcs_enabled() -> bool {
static_prefs::pref!("layout.css.relative-color-syntax.enabled")
}
/// Represents a channel keyword inside a color. #[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, PartialOrd, ToCss, ToShmem)] #[repr(u8)] pubenum ChannelKeyword { /// alpha
Alpha, /// a
A, /// b, blackness, blue
B, /// chroma
C, /// green
G, /// hue
H, /// lightness
L, /// red
R, /// saturation
S, /// whiteness
W, /// x
X, /// y
Y, /// z
Z,
}
/// Return the named color with the given name. /// /// Matching is case-insensitive in the ASCII range. /// CSS escaping (if relevant) should be resolved before calling this function. /// (For example, the value of an `Ident` token is fine.) #[inline] pubfn parse_color_keyword(ident: &str) -> Result<SpecifiedColor, ()> {
Ok(match_ignore_ascii_case! { ident, "transparent" => {
SpecifiedColor::from_absolute_color(AbsoluteColor::srgb_legacy(0u8, 0u8, 0u8, 0.0))
}, "currentcolor" => SpecifiedColor::CurrentColor,
_ => { let (r, g, b) = cssparser::color::parse_named_color(ident)?;
SpecifiedColor::from_absolute_color(AbsoluteColor::srgb_legacy(r, g, b, OPAQUE))
},
})
}
/// Parse a CSS color using the specified [`ColorParser`] and return a new color /// value on success. pubfn parse_color_with<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<SpecifiedColor, ParseError<'i>> { let location = input.current_source_location(); let token = input.next()?; match *token {
Token::Hash(ref value) | Token::IDHash(ref value) => parse_hash_color(value.as_bytes())
.map(|(r, g, b, a)| {
SpecifiedColor::from_absolute_color(AbsoluteColor::srgb_legacy(r, g, b, a))
}),
Token::Ident(ref value) => parse_color_keyword(value),
Token::Function(ref name) => { let name = name.clone(); return input.parse_nested_block(|arguments| { let color_function = parse_color_function(context, name, arguments)?;
if color_function.has_origin_color() { // Preserve the color as it was parsed.
Ok(SpecifiedColor::ColorFunction(Box::new(color_function)))
} elseiflet Ok(resolved) = color_function.resolve_to_absolute() {
Ok(SpecifiedColor::from_absolute_color(resolved))
} else { // This will only happen when the parsed color contains errors like calc units // that cannot be resolved at parse time, but will fail when trying to resolve // them, etc. This should be rare, but for now just failing the color value // makes sense.
Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
});
},
_ => Err(()),
}
.map_err(|()| location.new_unexpected_token_error(token.clone()))
}
/// Parse one of the color functions: rgba(), lab(), color(), etc. #[inline] fn parse_color_function<'i, 't>(
context: &ParserContext,
name: CowRcStr<'i>,
arguments: &mut Parser<'i, 't>,
) -> Result<ColorFunction<SpecifiedColor>, ParseError<'i>> { let origin_color = parse_origin_color(context, arguments)?; let has_origin_color = origin_color.is_some();
if has_origin_color { // Validate the channels and calc expressions by trying to resolve them against // transparent. // FIXME(emilio, bug 1925572): This could avoid cloning, or be done earlier. let abs = color.map_origin_color(|_| Some(AbsoluteColor::TRANSPARENT_BLACK)); if abs.resolve_to_absolute().is_err() { return Err(arguments.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
}
arguments.expect_exhausted()?;
Ok(color)
}
/// Parse the relative color syntax "from" syntax `from <color>`. fn parse_origin_color<'i, 't>(
context: &ParserContext,
arguments: &mut Parser<'i, 't>,
) -> Result<Option<SpecifiedColor>, ParseError<'i>> { if !rcs_enabled() { return Ok(None);
}
// Not finding the from keyword is not an error, it just means we don't // have an origin color. if arguments
.try_parse(|p| p.expect_ident_matching("from"))
.is_err()
{ return Ok(None);
}
// If the first component is not "none" and is followed by a comma, then we // are parsing the legacy syntax. Legacy syntax also doesn't support an // origin color. let is_legacy_syntax = origin_color.is_none() &&
!maybe_red.is_none() &&
arguments.try_parse(|p| p.expect_comma()).is_ok();
Ok(if is_legacy_syntax { let (green, blue) = if maybe_red.could_be_percentage() { let green = parse_percentage(context, arguments, false)?;
arguments.expect_comma()?; let blue = parse_percentage(context, arguments, false)?;
(green, blue)
} else { let green = parse_number(context, arguments, false)?;
arguments.expect_comma()?; let blue = parse_number(context, arguments, false)?;
(green, blue)
};
let alpha = parse_legacy_alpha(context, arguments)?;
ColorFunction::Rgb(origin_color.into(), maybe_red, green, blue, alpha)
} else { let green = parse_number_or_percentage(context, arguments, true)?; let blue = parse_number_or_percentage(context, arguments, true)?;
let alpha = parse_modern_alpha(context, arguments)?;
// If the hue is not "none" and is followed by a comma, then we are parsing // the legacy syntax. Legacy syntax also doesn't support an origin color. let is_legacy_syntax = origin_color.is_none() &&
!hue.is_none() &&
arguments.try_parse(|p| p.expect_comma()).is_ok();
let (saturation, lightness, alpha) = if is_legacy_syntax { let saturation = parse_percentage(context, arguments, false)?;
arguments.expect_comma()?; let lightness = parse_percentage(context, arguments, false)?; let alpha = parse_legacy_alpha(context, arguments)?;
(saturation, lightness, alpha)
} else { let saturation = parse_number_or_percentage(context, arguments, true)?; let lightness = parse_number_or_percentage(context, arguments, true)?; let alpha = parse_modern_alpha(context, arguments)?;
(saturation, lightness, alpha)
};
/// Either a percentage or a number. #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)] #[repr(u8)] pubenum NumberOrPercentageComponent { /// `<number>`.
Number(f32), /// `<percentage>` /// The value as a float, divided by 100 so that the nominal range is 0.0 to 1.0.
Percentage(f32),
}
impl NumberOrPercentageComponent { /// Return the value as a number. Percentages will be adjusted to the range /// [0..percent_basis]. pubfn to_number(&self, percentage_basis: f32) -> f32 { match *self { Self::Number(value) => value, Self::Percentage(unit_value) => unit_value * percentage_basis,
}
}
}
/// Either an angle or a number. #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)] #[repr(u8)] pubenum NumberOrAngleComponent { /// `<number>`.
Number(f32), /// `<angle>` /// The value as a number of degrees.
Angle(f32),
}
impl NumberOrAngleComponent { /// Return the angle in degrees. `NumberOrAngle::Number` is returned as /// degrees, because it is the canonical unit. pubfn degrees(&self) -> f32 { match *self { Self::Number(value) => value, Self::Angle(degrees) => degrees,
}
}
}
let value = ColorComponent::<NumberOrPercentageComponent>::parse(context, input, allow_none)?; if !value.could_be_percentage() { return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.