Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Specified color values.
use super::AllowQuirks;
use crate::color::parsing::{
self, AngleOrNumber, Color as CSSParserColor, FromParsedColor, NumberOrPercentage,
};
use crate::color::{mix::ColorInterpolationMethod, AbsoluteColor, ColorSpace};
use crate::media_queries::Device;
use crate::parser::{Parse, ParserContext};
use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue};
use crate::values::generics::color::{
ColorMixFlags, GenericCaretColor, GenericColorMix, GenericColorOrAuto,
};
use crate::values::specified::calc::CalcNode;
use crate::values::specified::Percentage;
use crate::values::{normalize, CustomIdent};
use cssparser::{color::PredefinedColorSpace, BasicParseErrorKind, ParseErrorKind, Parser, Token};
use itoa;
use std::fmt::{self, Write};
use std::io::Write as IoWrite;
use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind};
use style_traits::{SpecifiedValueInfo, ToCss, ValueParseErrorKind};
/// A specified color-mix().
pub type ColorMix = GenericColorMix<Color, Percentage>;
impl ColorMix {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
preserve_authored: PreserveAuthored,
) -> Result<Self, ParseError<'i>> {
input.expect_function_matching("color-mix")?;
input.parse_nested_block(|input| {
let interpolation = ColorInterpolationMethod::parse(context, input)?;
input.expect_comma()?;
let try_parse_percentage = |input: &mut Parser| -> Option<Percentage> {
input
.try_parse(|input| Percentage::parse_zero_to_a_hundred(context, input))
.ok()
};
let mut left_percentage = try_parse_percentage(input);
let left = Color::parse_internal(context, input, preserve_authored)?;
if left_percentage.is_none() {
left_percentage = try_parse_percentage(input);
}
input.expect_comma()?;
let mut right_percentage = try_parse_percentage(input);
let right = Color::parse_internal(context, input, preserve_authored)?;
if right_percentage.is_none() {
right_percentage = try_parse_percentage(input);
}
let right_percentage = right_percentage
.unwrap_or_else(|| Percentage::new(1.0 - left_percentage.map_or(0.5, |p| p.get())));
let left_percentage =
left_percentage.unwrap_or_else(|| Percentage::new(1.0 - right_percentage.get()));
if left_percentage.get() + right_percentage.get() <= 0.0 {
// If the percentages sum to zero, the function is invalid.
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
// Pass RESULT_IN_MODERN_SYNTAX here, because the result of the color-mix() function
// should always be in the modern color syntax to allow for out of gamut results and
// to preserve floating point precision.
Ok(ColorMix {
interpolation,
left,
left_percentage,
right,
right_percentage,
flags: ColorMixFlags::NORMALIZE_WEIGHTS | ColorMixFlags::RESULT_IN_MODERN_SYNTAX,
})
})
}
}
/// Container holding an absolute color and the text specified by an author.
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub struct Absolute {
/// The specified color.
pub color: AbsoluteColor,
/// Authored representation.
pub authored: Option<Box<str>>,
}
impl ToCss for Absolute {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
if let Some(ref authored) = self.authored {
dest.write_str(authored)
} else {
self.color.to_css(dest)
}
}
}
/// Specified color value
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub enum Color {
/// The 'currentColor' keyword
CurrentColor,
/// An absolute color.
Absolute(Box<Absolute>),
/// A system color.
#[cfg(feature = "gecko")]
System(SystemColor),
/// A color mix.
ColorMix(Box<ColorMix>),
/// A light-dark() color.
LightDark(Box<LightDark>),
/// Quirksmode-only rule for inheriting color from the body
#[cfg(feature = "gecko")]
InheritFromBodyQuirk,
}
/// A light-dark(<light-color>, <dark-color>) function.
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem, ToCss)]
#[css(function, comma)]
pub struct LightDark {
/// The <color> that is returned when using a light theme.
pub light: Color,
/// The <color> that is returned when using a dark theme.
pub dark: Color,
}
impl LightDark {
fn compute(&self, cx: &Context) -> ComputedColor {
let style_color_scheme = cx.style().get_inherited_ui().clone_color_scheme();
let dark = cx.device().is_dark_color_scheme(&style_color_scheme);
let used = if dark { &self.dark } else { &self.light };
used.to_computed_value(cx)
}
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
preserve_authored: PreserveAuthored,
) -> Result<Self, ParseError<'i>> {
let enabled =
context.chrome_rules_enabled() || static_prefs::pref!("layout.css.light-dark.enabled");
if !enabled {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
input.expect_function_matching("light-dark")?;
input.parse_nested_block(|input| {
let light = Color::parse_internal(context, input, preserve_authored)?;
input.expect_comma()?;
let dark = Color::parse_internal(context, input, preserve_authored)?;
Ok(LightDark { light, dark })
})
}
}
impl From<AbsoluteColor> for Color {
#[inline]
fn from(value: AbsoluteColor) -> Self {
Self::from_absolute_color(value)
}
}
/// System colors. A bunch of these are ad-hoc, others come from Windows:
///
///
/// Others are HTML/CSS specific. Spec is:
///
#[allow(missing_docs)]
#[cfg(feature = "gecko")]
#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
#[repr(u8)]
pub enum SystemColor {
Activeborder,
/// Background in the (active) titlebar.
Activecaption,
Appworkspace,
Background,
Buttonface,
Buttonhighlight,
Buttonshadow,
Buttontext,
Buttonborder,
/// Text color in the (active) titlebar.
Captiontext,
#[parse(aliases = "-moz-field")]
Field,
/// Used for disabled field backgrounds.
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozDisabledfield,
#[parse(aliases = "-moz-fieldtext")]
Fieldtext,
Mark,
Marktext,
/// Combobox widgets
MozComboboxtext,
MozCombobox,
Graytext,
Highlight,
Highlighttext,
Inactiveborder,
/// Background in the (inactive) titlebar.
Inactivecaption,
/// Text color in the (inactive) titlebar.
Inactivecaptiontext,
Infobackground,
Infotext,
Menu,
Menutext,
Scrollbar,
Threeddarkshadow,
Threedface,
Threedhighlight,
Threedlightshadow,
Threedshadow,
Window,
Windowframe,
Windowtext,
#[parse(aliases = "-moz-default-color")]
Canvastext,
#[parse(aliases = "-moz-default-background-color")]
Canvas,
MozDialog,
MozDialogtext,
/// Used for selected but not focused cell backgrounds.
#[parse(aliases = "-moz-html-cellhighlight")]
MozCellhighlight,
/// Used for selected but not focused cell text.
#[parse(aliases = "-moz-html-cellhighlighttext")]
MozCellhighlighttext,
/// Used for selected and focused html cell backgrounds.
Selecteditem,
/// Used for selected and focused html cell text.
Selecteditemtext,
/// Used to button text background when hovered.
MozButtonhoverface,
/// Used to button text color when hovered.
MozButtonhovertext,
/// Used for menu item backgrounds when hovered.
MozMenuhover,
/// Used for menu item backgrounds when hovered and disabled.
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozMenuhoverdisabled,
/// Used for menu item text when hovered.
MozMenuhovertext,
/// Used for menubar item text when hovered.
MozMenubarhovertext,
/// On platforms where these colors are the same as -moz-field, use
/// -moz-fieldtext as foreground color
MozEventreerow,
MozOddtreerow,
/// Used for button text when pressed.
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozButtonactivetext,
/// Used for button background when pressed.
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozButtonactiveface,
/// Used for button background when disabled.
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozButtondisabledface,
/// Colors used for the header bar (sorta like the tab bar / menubar).
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozHeaderbar,
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozHeaderbartext,
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozHeaderbarinactive,
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozHeaderbarinactivetext,
/// Foreground color of default buttons.
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozMacDefaultbuttontext,
/// Ring color around text fields and lists.
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozMacFocusring,
/// Text color of disabled text on toolbars.
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozMacDisabledtoolbartext,
/// The background of a sidebar.
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozSidebar,
/// The foreground color of a sidebar.
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozSidebartext,
/// The border color of a sidebar.
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozSidebarborder,
/// Theme accent color.
Accentcolor,
/// Foreground for the accent color.
Accentcolortext,
/// The background-color for :autofill-ed inputs.
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozAutofillBackground,
/// Hyperlink color extracted from the system, not affected by the browser.anchor_color user
/// pref.
///
/// There is no OS-specified safe background color for this text, but it is used regularly
/// within Windows and the Gnome DE on Dialog and Window colors.
#[css(skip)]
MozNativehyperlinktext,
/// As above, but visited link color.
#[css(skip)]
MozNativevisitedhyperlinktext,
#[parse(aliases = "-moz-hyperlinktext")]
Linktext,
#[parse(aliases = "-moz-activehyperlinktext")]
Activetext,
#[parse(aliases = "-moz-visitedhyperlinktext")]
Visitedtext,
/// Color of tree column headers
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozColheader,
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozColheadertext,
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozColheaderhover,
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozColheaderhovertext,
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozColheaderactive,
#[parse(condition = "ParserContext::chrome_rules_enabled")]
MozColheaderactivetext,
#[parse(condition = "ParserContext::chrome_rules_enabled")]
TextSelectDisabledBackground,
#[css(skip)]
TextSelectAttentionBackground,
#[css(skip)]
TextSelectAttentionForeground,
#[css(skip)]
TextHighlightBackground,
#[css(skip)]
TextHighlightForeground,
#[css(skip)]
IMERawInputBackground,
#[css(skip)]
IMERawInputForeground,
#[css(skip)]
IMERawInputUnderline,
#[css(skip)]
IMESelectedRawTextBackground,
#[css(skip)]
IMESelectedRawTextForeground,
#[css(skip)]
IMESelectedRawTextUnderline,
#[css(skip)]
IMEConvertedTextBackground,
#[css(skip)]
IMEConvertedTextForeground,
#[css(skip)]
IMEConvertedTextUnderline,
#[css(skip)]
IMESelectedConvertedTextBackground,
#[css(skip)]
IMESelectedConvertedTextForeground,
#[css(skip)]
IMESelectedConvertedTextUnderline,
#[css(skip)]
SpellCheckerUnderline,
#[css(skip)]
ThemedScrollbar,
#[css(skip)]
ThemedScrollbarInactive,
#[css(skip)]
ThemedScrollbarThumb,
#[css(skip)]
ThemedScrollbarThumbHover,
#[css(skip)]
ThemedScrollbarThumbActive,
#[css(skip)]
ThemedScrollbarThumbInactive,
#[css(skip)]
End, // Just for array-indexing purposes.
}
#[cfg(feature = "gecko")]
impl SystemColor {
#[inline]
fn compute(&self, cx: &Context) -> ComputedColor {
use crate::gecko::values::convert_nscolor_to_absolute_color;
use crate::gecko_bindings::bindings;
// TODO: We should avoid cloning here most likely, though it's cheap-ish.
let style_color_scheme = cx.style().get_inherited_ui().clone_color_scheme();
let color = cx.device().system_nscolor(*self, &style_color_scheme);
if color == bindings::NS_SAME_AS_FOREGROUND_COLOR {
return ComputedColor::currentcolor();
}
ComputedColor::Absolute(convert_nscolor_to_absolute_color(color))
}
}
impl FromParsedColor for Color {
fn from_current_color() -> Self {
Color::CurrentColor
}
fn from_rgba(r: u8, g: u8, b: u8, a: f32) -> Self {
AbsoluteColor::srgb_legacy(r, g, b, a).into()
}
fn from_hsl(
hue: Option<f32>,
saturation: Option<f32>,
lightness: Option<f32>,
alpha: Option<f32>,
) -> Self {
AbsoluteColor::new(ColorSpace::Hsl, hue, saturation, lightness, alpha).into()
}
fn from_hwb(
hue: Option<f32>,
whiteness: Option<f32>,
blackness: Option<f32>,
alpha: Option<f32>,
) -> Self {
AbsoluteColor::new(ColorSpace::Hwb, hue, whiteness, blackness, alpha).into()
}
fn from_lab(
lightness: Option<f32>,
a: Option<f32>,
b: Option<f32>,
alpha: Option<f32>,
) -> Self {
AbsoluteColor::new(ColorSpace::Lab, lightness, a, b, alpha).into()
}
fn from_lch(
lightness: Option<f32>,
chroma: Option<f32>,
hue: Option<f32>,
alpha: Option<f32>,
) -> Self {
AbsoluteColor::new(ColorSpace::Lch, lightness, chroma, hue, alpha).into()
}
fn from_oklab(
lightness: Option<f32>,
a: Option<f32>,
b: Option<f32>,
alpha: Option<f32>,
) -> Self {
AbsoluteColor::new(ColorSpace::Oklab, lightness, a, b, alpha).into()
}
fn from_oklch(
lightness: Option<f32>,
chroma: Option<f32>,
hue: Option<f32>,
alpha: Option<f32>,
) -> Self {
AbsoluteColor::new(ColorSpace::Oklch, lightness, chroma, hue, alpha).into()
}
fn from_color_function(
color_space: PredefinedColorSpace,
c1: Option<f32>,
c2: Option<f32>,
c3: Option<f32>,
alpha: Option<f32>,
) -> Self {
AbsoluteColor::new(color_space.into(), c1, c2, c3, alpha).into()
}
}
struct ColorParser<'a, 'b: 'a>(&'a ParserContext<'b>);
impl<'a, 'b: 'a, 'i: 'a> parsing::ColorParser<'i> for ColorParser<'a, 'b> {
type Output = Color;
type Error = StyleParseErrorKind<'i>;
fn parse_angle_or_number<'t>(
&self,
input: &mut Parser<'i, 't>,
) -> Result<AngleOrNumber, ParseError<'i>> {
use crate::values::specified::Angle;
let location = input.current_source_location();
let token = input.next()?.clone();
match token {
Token::Dimension {
value, ref unit, ..
} => {
let angle = Angle::parse_dimension(value, unit, /* from_calc = */ false);
let degrees = match angle {
Ok(angle) => angle.degrees(),
Err(()) => return Err(location.new_unexpected_token_error(token.clone())),
};
Ok(AngleOrNumber::Angle { degrees })
},
Token::Number { value, .. } => Ok(AngleOrNumber::Number { value }),
Token::Function(ref name) => {
let function = CalcNode::math_function(self.0, name, location)?;
CalcNode::parse_angle_or_number(self.0, input, function)
},
t => return Err(location.new_unexpected_token_error(t)),
}
}
fn parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>> {
Ok(Percentage::parse(self.0, input)?.get())
}
fn parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>> {
use crate::values::specified::Number;
Ok(Number::parse(self.0, input)?.get())
}
fn parse_number_or_percentage<'t>(
&self,
input: &mut Parser<'i, 't>,
) -> Result<NumberOrPercentage, ParseError<'i>> {
let location = input.current_source_location();
match *input.next()? {
Token::Number { value, .. } => Ok(NumberOrPercentage::Number { value }),
Token::Percentage { unit_value, .. } => {
Ok(NumberOrPercentage::Percentage { unit_value })
},
Token::Function(ref name) => {
let function = CalcNode::math_function(self.0, name, location)?;
CalcNode::parse_number_or_percentage(self.0, input, function)
},
ref t => return Err(location.new_unexpected_token_error(t.clone())),
}
}
}
/// Whether to preserve authored colors during parsing. That's useful only if we
/// plan to serialize the color back.
#[derive(Copy, Clone)]
enum PreserveAuthored {
No,
Yes,
}
impl Parse for Color {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Self::parse_internal(context, input, PreserveAuthored::Yes)
}
}
impl Color {
fn parse_internal<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
preserve_authored: PreserveAuthored,
) -> Result<Self, ParseError<'i>> {
let authored = match preserve_authored {
PreserveAuthored::No => None,
PreserveAuthored::Yes => {
// Currently we only store authored value for color keywords,
// because all browsers serialize those values as keywords for
// specified value.
let start = input.state();
let authored = input.expect_ident_cloned().ok();
input.reset(&start);
authored
},
};
let color_parser = ColorParser(&*context);
match input.try_parse(|i| parsing::parse_color_with(&color_parser, i)) {
Ok(mut color) => {
if let Color::Absolute(ref mut absolute) = color {
// Because we can't set the `authored` value at construction time, we have to set it
// here.
absolute.authored = authored.map(|s| s.to_ascii_lowercase().into_boxed_str());
}
Ok(color)
},
Err(e) => {
#[cfg(feature = "gecko")]
{
if let Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) {
return Ok(Color::System(system));
}
}
if let Ok(mix) = input.try_parse(|i| ColorMix::parse(context, i, preserve_authored))
{
return Ok(Color::ColorMix(Box::new(mix)));
}
if let Ok(ld) = input.try_parse(|i| LightDark::parse(context, i, preserve_authored))
{
return Ok(Color::LightDark(Box::new(ld)));
}
match e.kind {
ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(t)) => {
Err(e.location.new_custom_error(StyleParseErrorKind::ValueError(
ValueParseErrorKind::InvalidColor(t),
)))
},
_ => Err(e),
}
},
}
}
/// Returns whether a given color is valid for authors.
pub fn is_valid(context: &ParserContext, input: &mut Parser) -> bool {
input
.parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No))
.is_ok()
}
/// Tries to parse a color and compute it with a given device.
pub fn parse_and_compute(
context: &ParserContext,
input: &mut Parser,
device: Option<&Device>,
) -> Option<ComputedColor> {
use crate::error_reporting::ContextualParseError;
let start = input.position();
let result = input
.parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No));
let specified = match result {
Ok(s) => s,
Err(e) => {
if !context.error_reporting_enabled() {
return None;
}
// Ignore other kinds of errors that might be reported, such as
// ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken),
// since Gecko didn't use to report those to the error console.
//
// TODO(emilio): Revise whether we want to keep this at all, we
// use this only for canvas, this warnings are disabled by
// default and not available on OffscreenCanvas anyways...
if let ParseErrorKind::Custom(StyleParseErrorKind::ValueError(..)) = e.kind {
let location = e.location.clone();
let error = ContextualParseError::UnsupportedValue(input.slice_from(start), e);
context.log_css_error(location, error);
}
return None;
},
};
match device {
Some(device) => {
Context::for_media_query_evaluation(device, device.quirks_mode(), |context| {
specified.to_computed_color(Some(&context))
})
},
None => specified.to_computed_color(None),
}
}
}
impl ToCss for Color {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
match *self {
Color::CurrentColor => cssparser::ToCss::to_css(&CSSParserColor::CurrentColor, dest),
Color::Absolute(ref absolute) => absolute.to_css(dest),
Color::ColorMix(ref mix) => mix.to_css(dest),
Color::LightDark(ref ld) => ld.to_css(dest),
#[cfg(feature = "gecko")]
Color::System(system) => system.to_css(dest),
#[cfg(feature = "gecko")]
Color::InheritFromBodyQuirk => Ok(()),
}
}
}
impl Color {
/// Returns whether this color is allowed in forced-colors mode.
pub fn honored_in_forced_colors_mode(&self, allow_transparent: bool) -> bool {
match *self {
Self::InheritFromBodyQuirk => false,
Self::CurrentColor | Color::System(..) => true,
Self::Absolute(ref absolute) => allow_transparent && absolute.color.is_transparent(),
Self::LightDark(ref ld) => {
ld.light.honored_in_forced_colors_mode(allow_transparent) &&
ld.dark.honored_in_forced_colors_mode(allow_transparent)
},
Self::ColorMix(ref mix) => {
mix.left.honored_in_forced_colors_mode(allow_transparent) &&
mix.right.honored_in_forced_colors_mode(allow_transparent)
},
}
}
/// Returns currentcolor value.
#[inline]
pub fn currentcolor() -> Self {
Self::CurrentColor
}
/// Returns transparent value.
#[inline]
pub fn transparent() -> Self {
// We should probably set authored to "transparent", but maybe it doesn't matter.
Self::from_absolute_color(AbsoluteColor::TRANSPARENT_BLACK)
}
/// Create a color from an [`AbsoluteColor`].
pub fn from_absolute_color(color: AbsoluteColor) -> Self {
Color::Absolute(Box::new(Absolute {
color,
authored: None,
}))
}
/// Parse a color, with quirks.
///
pub fn parse_quirky<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_quirks: AllowQuirks,
) -> Result<Self, ParseError<'i>> {
input.try_parse(|i| Self::parse(context, i)).or_else(|e| {
if !allow_quirks.allowed(context.quirks_mode) {
return Err(e);
}
Color::parse_quirky_color(input).map_err(|_| e)
})
}
fn parse_hash<'i>(
bytes: &[u8],
loc: &cssparser::SourceLocation,
) -> Result<Self, ParseError<'i>> {
match cssparser::color::parse_hash_color(bytes) {
Ok((r, g, b, a)) => Ok(Self::from_rgba(r, g, b, a)),
Err(()) => Err(loc.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
}
}
/// Parse a <quirky-color> value.
///
fn parse_quirky_color<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location();
let (value, unit) = match *input.next()? {
Token::Number {
int_value: Some(integer),
..
} => (integer, None),
Token::Dimension {
int_value: Some(integer),
ref unit,
..
} => (integer, Some(unit)),
Token::Ident(ref ident) => {
if ident.len() != 3 && ident.len() != 6 {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
return Self::parse_hash(ident.as_bytes(), &location);
},
ref t => {
return Err(location.new_unexpected_token_error(t.clone()));
},
};
if value < 0 {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
let length = if value <= 9 {
1
} else if value <= 99 {
2
} else if value <= 999 {
3
} else if value <= 9999 {
4
} else if value <= 99999 {
5
} else if value <= 999999 {
6
} else {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
};
let total = length + unit.as_ref().map_or(0, |d| d.len());
if total > 6 {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
let mut serialization = [b'0'; 6];
let space_padding = 6 - total;
let mut written = space_padding;
let mut buf = itoa::Buffer::new();
let s = buf.format(value);
(&mut serialization[written..])
.write_all(s.as_bytes())
.unwrap();
written += s.len();
if let Some(unit) = unit {
written += (&mut serialization[written..])
.write(unit.as_bytes())
.unwrap();
}
debug_assert_eq!(written, 6);
Self::parse_hash(&serialization, &location)
}
}
impl Color {
/// Converts this Color into a ComputedColor.
///
/// If `context` is `None`, and the specified color requires data from
/// the context to resolve, then `None` is returned.
pub fn to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor> {
Some(match *self {
Color::CurrentColor => ComputedColor::CurrentColor,
Color::Absolute(ref absolute) => {
let mut color = absolute.color;
// Computed lightness values can not be NaN.
if matches!(
color.color_space,
ColorSpace::Lab | ColorSpace::Oklab | ColorSpace::Lch | ColorSpace::Oklch
) {
color.components.0 = normalize(color.components.0);
}
// Computed RGB and XYZ components can not be NaN.
if !color.is_legacy_syntax() && color.color_space.is_rgb_or_xyz_like() {
color.components = color.components.map(normalize);
}
color.alpha = normalize(color.alpha);
ComputedColor::Absolute(color)
},
Color::LightDark(ref ld) => ld.compute(context?),
Color::ColorMix(ref mix) => {
use crate::values::computed::percentage::Percentage;
let left = mix.left.to_computed_color(context)?;
let right = mix.right.to_computed_color(context)?;
ComputedColor::from_color_mix(GenericColorMix {
interpolation: mix.interpolation,
left,
left_percentage: Percentage(mix.left_percentage.get()),
right,
right_percentage: Percentage(mix.right_percentage.get()),
flags: mix.flags,
})
},
#[cfg(feature = "gecko")]
Color::System(system) => system.compute(context?),
#[cfg(feature = "gecko")]
Color::InheritFromBodyQuirk => {
ComputedColor::Absolute(context?.device().body_text_color())
},
})
}
}
impl ToComputedValue for Color {
type ComputedValue = ComputedColor;
fn to_computed_value(&self, context: &Context) -> ComputedColor {
self.to_computed_color(Some(context)).unwrap()
}
fn from_computed_value(computed: &ComputedColor) -> Self {
match *computed {
ComputedColor::Absolute(ref color) => Self::from_absolute_color(color.clone()),
ComputedColor::CurrentColor => Color::CurrentColor,
ComputedColor::ColorMix(ref mix) => {
Color::ColorMix(Box::new(ToComputedValue::from_computed_value(&**mix)))
},
}
}
}
impl SpecifiedValueInfo for Color {
const SUPPORTED_TYPES: u8 = CssType::COLOR;
fn collect_completion_keywords(f: KeywordsCollectFn) {
// We are not going to insert all the color names here. Caller and
// devtools should take care of them. XXX Actually, transparent
// should probably be handled that way as well.
// XXX `currentColor` should really be `currentcolor`. But let's
// keep it consistent with the old system for now.
f(&[
"rgb",
"rgba",
"hsl",
"hsla",
"hwb",
"currentColor",
"transparent",
"color-mix",
"color",
"lab",
"lch",
"oklab",
"oklch",
]);
}
}
/// Specified value for the "color" property, which resolves the `currentcolor`
/// keyword to the parent color instead of self's color.
#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
pub struct ColorPropertyValue(pub Color);
impl ToComputedValue for ColorPropertyValue {
type ComputedValue = AbsoluteColor;
#[inline]
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
let current_color = context.builder.get_parent_inherited_text().clone_color();
self.0
.to_computed_value(context)
.resolve_to_absolute(&current_color)
}
#[inline]
fn from_computed_value(computed: &Self::ComputedValue) -> Self {
ColorPropertyValue(Color::from_absolute_color(*computed).into())
}
}
impl Parse for ColorPropertyValue {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Color::parse_quirky(context, input, AllowQuirks::Yes).map(ColorPropertyValue)
}
}
/// auto | <color>
pub type ColorOrAuto = GenericColorOrAuto<Color>;
/// caret-color
pub type CaretColor = GenericCaretColor<Color>;
impl Parse for CaretColor {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
ColorOrAuto::parse(context, input).map(GenericCaretColor)
}
}
/// Various flags to represent the color-scheme property in an efficient
/// way.
#[derive(
Clone,
Copy,
Debug,
Default,
Eq,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
#[repr(C)]
#[value_info(other_values = "light,dark,only")]
pub struct ColorSchemeFlags(u8);
bitflags! {
impl ColorSchemeFlags: u8 {
/// Whether the author specified `light`.
const LIGHT = 1 << 0;
/// Whether the author specified `dark`.
const DARK = 1 << 1;
/// Whether the author specified `only`.
const ONLY = 1 << 2;
}
}
#[derive(
Clone,
Debug,
Default,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
#[repr(C)]
#[value_info(other_values = "normal")]
pub struct ColorScheme {
#[ignore_malloc_size_of = "Arc"]
idents: crate::ArcSlice<CustomIdent>,
bits: ColorSchemeFlags,
}
impl ColorScheme {
/// Returns the `normal` value.
pub fn normal() -> Self {
Self {
idents: Default::default(),
bits: ColorSchemeFlags::empty(),
}
}
/// Returns the raw bitfield.
pub fn raw_bits(&self) -> u8 {
self.bits.bits()
}
}
impl Parse for ColorScheme {
fn parse<'i, 't>(
_: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let mut idents = vec![];
let mut bits = ColorSchemeFlags::empty();
let mut location = input.current_source_location();
while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
let mut is_only = false;
match_ignore_ascii_case! { &ident,
"normal" => {
if idents.is_empty() && bits.is_empty() {
return Ok(Self::normal());
}
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
},
"light" => bits.insert(ColorSchemeFlags::LIGHT),
"dark" => bits.insert(ColorSchemeFlags::DARK),
"only" => {
if bits.intersects(ColorSchemeFlags::ONLY) {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
bits.insert(ColorSchemeFlags::ONLY);
is_only = true;
},
_ => {},
};
if is_only {
if !idents.is_empty() {
// Only is allowed either at the beginning or at the end,
// but not in the middle.
break;
}
} else {
idents.push(CustomIdent::from_ident(location, &ident, &[])?);
}
location = input.current_source_location();
}
if idents.is_empty() {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
Ok(Self {
idents: crate::ArcSlice::from_iter(idents.into_iter()),
bits,
})
}
}
impl ToCss for ColorScheme {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
if self.idents.is_empty() {
debug_assert!(self.bits.is_empty());
return dest.write_str("normal");
}
let mut first = true;
for ident in self.idents.iter() {
if !first {
dest.write_char(' ')?;
}
first = false;
ident.to_css(dest)?;
}
if self.bits.intersects(ColorSchemeFlags::ONLY) {
dest.write_str(" only")?;
}
Ok(())
}
}
#[derive(
Clone,
Copy,
Debug,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToCss,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
#[repr(u8)]
pub enum PrintColorAdjust {
/// Ignore backgrounds and darken text.
Economy,
/// Respect specified colors.
Exact,
}
#[derive(
Clone,
Copy,
Debug,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToCss,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
#[repr(u8)]
pub enum ForcedColorAdjust {
/// Adjust colors if needed.
Auto,
/// Respect specified colors.
None,
}