1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5 //! Specified color values.
6
7 use super::AllowQuirks;
8 use crate::parser::{Parse, ParserContext};
9 use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue};
10 use crate::values::generics::color::{GenericCaretColor, GenericColorOrAuto};
11 use crate::values::specified::calc::CalcNode;
12 use crate::values::specified::Percentage;
13 use crate::values::CustomIdent;
14 use cssparser::{AngleOrNumber, Color as CSSParserColor, Parser, Token, RGBA};
15 use cssparser::{BasicParseErrorKind, NumberOrPercentage, ParseErrorKind};
16 use itoa;
17 use std::fmt::{self, Write};
18 use std::io::Write as IoWrite;
19 use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind};
20 use style_traits::{SpecifiedValueInfo, ToCss, ValueParseErrorKind};
21
22 /// A color space as defined in [1].
23 ///
24 /// [1]: https://drafts.csswg.org/css-color-5/#typedef-colorspace
25 #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
26 pub enum ColorSpaceKind {
27 /// The sRGB color space.
28 Srgb,
29 /// The CIEXYZ color space.
30 Xyz,
31 /// The CIELAB color space.
32 Lab,
33 /// The CIELAB color space, expressed in cylindrical coordinates.
34 Lch,
35 }
36
37 /// A hue adjuster as defined in [1].
38 ///
39 /// [1]: https://drafts.csswg.org/css-color-5/#typedef-hue-adjuster
40 #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
41 pub enum HueAdjuster {
42 /// The "shorter" angle adjustment.
43 Shorter,
44 /// The "longer" angle adjustment.
45 Longer,
46 /// The "increasing" angle adjustment.
47 Increasing,
48 /// The "decreasing" angle adjustment.
49 Decreasing,
50 /// The "specified" angle adjustment.
51 Specified,
52 }
53
54 /// A restricted version of the css `color-mix()` function, which only supports
55 /// percentages.
56 ///
57 /// https://drafts.csswg.org/css-color-5/#color-mix
58 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
59 #[allow(missing_docs)]
60 pub struct ColorMix {
61 pub color_space: ColorSpaceKind,
62 pub left: Color,
63 pub left_percentage: Percentage,
64 pub right: Color,
65 pub right_percentage: Percentage,
66 pub hue_adjuster: HueAdjuster,
67 }
68
69 // NOTE(emilio): Syntax is still a bit in-flux, since [1] doesn't seem
70 // particularly complete, and disagrees with the examples.
71 //
72 // [1]: https://github.com/w3c/csswg-drafts/commit/a4316446112f9e814668c2caff7f826f512f8fed
73 impl Parse for ColorMix {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>74 fn parse<'i, 't>(
75 context: &ParserContext,
76 input: &mut Parser<'i, 't>,
77 ) -> Result<Self, ParseError<'i>> {
78 let enabled =
79 context.chrome_rules_enabled() || static_prefs::pref!("layout.css.color-mix.enabled");
80
81 if !enabled {
82 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
83 }
84
85 let color_spaces_enabled = context.chrome_rules_enabled() ||
86 static_prefs::pref!("layout.css.color-mix.color-spaces.enabled");
87
88 input.expect_function_matching("color-mix")?;
89
90 // NOTE(emilio): This implements the syntax described here for now,
91 // might need to get updated in the future.
92 //
93 // https://github.com/w3c/csswg-drafts/issues/6066#issuecomment-789836765
94 input.parse_nested_block(|input| {
95 input.expect_ident_matching("in")?;
96 let color_space = if color_spaces_enabled {
97 ColorSpaceKind::parse(input)?
98 } else {
99 input.expect_ident_matching("srgb")?;
100 ColorSpaceKind::Srgb
101 };
102 input.expect_comma()?;
103
104 let left = Color::parse(context, input)?;
105 let left_percentage = input
106 .try_parse(|input| Percentage::parse(context, input))
107 .ok();
108
109 input.expect_comma()?;
110
111 let right = Color::parse(context, input)?;
112 let right_percentage = input
113 .try_parse(|input| Percentage::parse(context, input))
114 .unwrap_or_else(|_| {
115 Percentage::new(1.0 - left_percentage.map_or(0.5, |p| p.get()))
116 });
117
118 let left_percentage =
119 left_percentage.unwrap_or_else(|| Percentage::new(1.0 - right_percentage.get()));
120
121 let hue_adjuster = input
122 .try_parse(|input| HueAdjuster::parse(input))
123 .unwrap_or(HueAdjuster::Shorter);
124
125 Ok(ColorMix {
126 color_space,
127 left,
128 left_percentage,
129 right,
130 right_percentage,
131 hue_adjuster,
132 })
133 })
134 }
135 }
136
137 impl ToCss for ColorMix {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,138 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
139 where
140 W: Write,
141 {
142 fn can_omit(percent: &Percentage, other: &Percentage, is_left: bool) -> bool {
143 if percent.is_calc() {
144 return false;
145 }
146 if percent.get() == 0.5 {
147 return other.get() == 0.5;
148 }
149 if is_left {
150 return false;
151 }
152 (1.0 - percent.get() - other.get()).abs() <= f32::EPSILON
153 }
154
155 dest.write_str("color-mix(in ")?;
156 self.color_space.to_css(dest)?;
157 dest.write_str(", ")?;
158 self.left.to_css(dest)?;
159 if !can_omit(&self.left_percentage, &self.right_percentage, true) {
160 dest.write_str(" ")?;
161 self.left_percentage.to_css(dest)?;
162 }
163 dest.write_str(", ")?;
164 self.right.to_css(dest)?;
165 if !can_omit(&self.right_percentage, &self.left_percentage, false) {
166 dest.write_str(" ")?;
167 self.right_percentage.to_css(dest)?;
168 }
169
170 if self.hue_adjuster != HueAdjuster::Shorter {
171 dest.write_str(" ")?;
172 self.hue_adjuster.to_css(dest)?;
173 }
174
175 dest.write_str(")")
176 }
177 }
178
179 /// Specified color value
180 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
181 pub enum Color {
182 /// The 'currentColor' keyword
183 CurrentColor,
184 /// A specific RGBA color
185 Numeric {
186 /// Parsed RGBA color
187 parsed: RGBA,
188 /// Authored representation
189 authored: Option<Box<str>>,
190 },
191 /// A complex color value from computed value
192 Complex(ComputedColor),
193 /// A system color.
194 #[cfg(feature = "gecko")]
195 System(SystemColor),
196 /// A color mix.
197 ColorMix(Box<ColorMix>),
198 /// Quirksmode-only rule for inheriting color from the body
199 #[cfg(feature = "gecko")]
200 InheritFromBodyQuirk,
201 }
202
203 /// System colors. A bunch of these are ad-hoc, others come from Windows:
204 ///
205 /// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsyscolor
206 ///
207 /// Others are HTML/CSS specific. Spec is:
208 ///
209 /// https://drafts.csswg.org/css-color/#css-system-colors
210 /// https://drafts.csswg.org/css-color/#deprecated-system-colors
211 #[allow(missing_docs)]
212 #[cfg(feature = "gecko")]
213 #[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
214 #[repr(u8)]
215 pub enum SystemColor {
216 #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
217 TextSelectDisabledBackground,
218 #[css(skip)]
219 TextSelectAttentionBackground,
220 #[css(skip)]
221 TextSelectAttentionForeground,
222 #[css(skip)]
223 TextHighlightBackground,
224 #[css(skip)]
225 TextHighlightForeground,
226 #[css(skip)]
227 IMERawInputBackground,
228 #[css(skip)]
229 IMERawInputForeground,
230 #[css(skip)]
231 IMERawInputUnderline,
232 #[css(skip)]
233 IMESelectedRawTextBackground,
234 #[css(skip)]
235 IMESelectedRawTextForeground,
236 #[css(skip)]
237 IMESelectedRawTextUnderline,
238 #[css(skip)]
239 IMEConvertedTextBackground,
240 #[css(skip)]
241 IMEConvertedTextForeground,
242 #[css(skip)]
243 IMEConvertedTextUnderline,
244 #[css(skip)]
245 IMESelectedConvertedTextBackground,
246 #[css(skip)]
247 IMESelectedConvertedTextForeground,
248 #[css(skip)]
249 IMESelectedConvertedTextUnderline,
250 #[css(skip)]
251 SpellCheckerUnderline,
252 #[css(skip)]
253 ThemedScrollbar,
254 #[css(skip)]
255 ThemedScrollbarInactive,
256 #[css(skip)]
257 ThemedScrollbarThumb,
258 #[css(skip)]
259 ThemedScrollbarThumbHover,
260 #[css(skip)]
261 ThemedScrollbarThumbActive,
262 #[css(skip)]
263 ThemedScrollbarThumbInactive,
264 Activeborder,
265 /// Background in the (active) titlebar.
266 Activecaption,
267 Appworkspace,
268 Background,
269 Buttonface,
270 Buttonhighlight,
271 Buttonshadow,
272 Buttontext,
273 /// Text color in the (active) titlebar.
274 Captiontext,
275 #[parse(aliases = "-moz-field")]
276 Field,
277 /// Used for disabled field backgrounds.
278 #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
279 MozDisabledfield,
280 #[parse(aliases = "-moz-fieldtext")]
281 Fieldtext,
282
283 Graytext,
284 Highlight,
285 Highlighttext,
286 Inactiveborder,
287 /// Background in the (inactive) titlebar.
288 Inactivecaption,
289 /// Text color in the (inactive) titlebar.
290 Inactivecaptiontext,
291 Infobackground,
292 Infotext,
293 Menu,
294 Menutext,
295 Scrollbar,
296 Threeddarkshadow,
297 Threedface,
298 Threedhighlight,
299 Threedlightshadow,
300 Threedshadow,
301 Window,
302 Windowframe,
303 Windowtext,
304 MozButtondefault,
305 #[parse(aliases = "-moz-default-color")]
306 Canvastext,
307 #[parse(aliases = "-moz-default-background-color")]
308 Canvas,
309 MozDialog,
310 MozDialogtext,
311 /// Used to highlight valid regions to drop something onto.
312 MozDragtargetzone,
313 /// Used for selected but not focused cell backgrounds.
314 #[parse(aliases = "-moz-html-cellhighlight")]
315 MozCellhighlight,
316 /// Used for selected but not focused cell text.
317 #[parse(aliases = "-moz-html-cellhighlighttext")]
318 MozCellhighlighttext,
319 /// Used for selected and focused html cell backgrounds.
320 Selecteditem,
321 /// Used for selected and focused html cell text.
322 Selecteditemtext,
323 /// Used to button text background when hovered.
324 MozButtonhoverface,
325 /// Used to button text color when hovered.
326 MozButtonhovertext,
327 /// Used for menu item backgrounds when hovered.
328 MozMenuhover,
329 /// Used for menu item text when hovered.
330 MozMenuhovertext,
331 /// Used for menubar item text.
332 MozMenubartext,
333 /// Used for menubar item text when hovered.
334 MozMenubarhovertext,
335
336 /// On platforms where these colors are the same as -moz-field, use
337 /// -moz-fieldtext as foreground color
338 MozEventreerow,
339 MozOddtreerow,
340
341 /// Used for button text when pressed.
342 #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
343 MozButtonactivetext,
344
345 /// Used for button background when pressed.
346 #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
347 MozButtonactiveface,
348
349 /// Used for button background when disabled.
350 #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
351 MozButtondisabledface,
352
353 /// Background color of chrome toolbars in active windows.
354 MozMacChromeActive,
355 /// Background color of chrome toolbars in inactive windows.
356 MozMacChromeInactive,
357 /// Foreground color of default buttons.
358 MozMacDefaultbuttontext,
359 /// Ring color around text fields and lists.
360 MozMacFocusring,
361 /// Color used when mouse is over a menu item.
362 MozMacMenuselect,
363 /// Color used to do shadows on menu items.
364 MozMacMenushadow,
365 /// Color used to display text for disabled menu items.
366 MozMacMenutextdisable,
367 /// Color used to display text while mouse is over a menu item.
368 MozMacMenutextselect,
369 /// Text color of disabled text on toolbars.
370 MozMacDisabledtoolbartext,
371 /// Inactive light hightlight
372 MozMacSecondaryhighlight,
373
374 /// Font smoothing background colors needed by the Mac OS X theme, based on
375 /// -moz-appearance names.
376 MozMacVibrantTitlebarLight,
377 MozMacVibrantTitlebarDark,
378 MozMacMenupopup,
379 MozMacMenuitem,
380 MozMacActiveMenuitem,
381 MozMacSourceList,
382 MozMacSourceListSelection,
383 MozMacActiveSourceListSelection,
384 MozMacTooltip,
385
386 /// Theme accent color.
387 #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
388 MozAccentColor,
389
390 /// Foreground for the accent color.
391 #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
392 MozAccentColorForeground,
393
394 /// The background-color for :autofill-ed inputs.
395 #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
396 MozAutofillBackground,
397
398 /// Media rebar text.
399 MozWinMediatext,
400 /// Communications rebar text.
401 MozWinCommunicationstext,
402
403 /// Hyperlink color extracted from the system, not affected by the
404 /// browser.anchor_color user pref.
405 ///
406 /// There is no OS-specified safe background color for this text, but it is
407 /// used regularly within Windows and the Gnome DE on Dialog and Window
408 /// colors.
409 MozNativehyperlinktext,
410
411 /// As above, but visited link color.
412 #[css(skip)]
413 MozNativevisitedhyperlinktext,
414
415 #[parse(aliases = "-moz-hyperlinktext")]
416 Linktext,
417 #[parse(aliases = "-moz-activehyperlinktext")]
418 Activetext,
419 #[parse(aliases = "-moz-visitedhyperlinktext")]
420 Visitedtext,
421
422 /// Combobox widgets
423 MozComboboxtext,
424 MozCombobox,
425
426 /// Color of tree column headers
427 #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
428 MozColheadertext,
429 #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
430 MozColheaderhovertext,
431
432 #[css(skip)]
433 End, // Just for array-indexing purposes.
434 }
435
436 #[cfg(feature = "gecko")]
437 impl SystemColor {
438 #[inline]
compute(&self, cx: &Context) -> ComputedColor439 fn compute(&self, cx: &Context) -> ComputedColor {
440 use crate::gecko::values::convert_nscolor_to_rgba;
441 use crate::gecko_bindings::bindings;
442
443 // TODO: We should avoid cloning here most likely, though it's
444 // cheap-ish.
445 let style_color_scheme = cx.style().get_inherited_ui().clone_color_scheme();
446 let color = cx.device().system_nscolor(*self, &style_color_scheme);
447 if color == bindings::NS_SAME_AS_FOREGROUND_COLOR {
448 return ComputedColor::currentcolor();
449 }
450 ComputedColor::rgba(convert_nscolor_to_rgba(color))
451 }
452 }
453
454 impl From<RGBA> for Color {
from(value: RGBA) -> Self455 fn from(value: RGBA) -> Self {
456 Color::rgba(value)
457 }
458 }
459
460 struct ColorComponentParser<'a, 'b: 'a>(&'a ParserContext<'b>);
461 impl<'a, 'b: 'a, 'i: 'a> ::cssparser::ColorComponentParser<'i> for ColorComponentParser<'a, 'b> {
462 type Error = StyleParseErrorKind<'i>;
463
parse_angle_or_number<'t>( &self, input: &mut Parser<'i, 't>, ) -> Result<AngleOrNumber, ParseError<'i>>464 fn parse_angle_or_number<'t>(
465 &self,
466 input: &mut Parser<'i, 't>,
467 ) -> Result<AngleOrNumber, ParseError<'i>> {
468 use crate::values::specified::Angle;
469
470 let location = input.current_source_location();
471 let token = input.next()?.clone();
472 match token {
473 Token::Dimension {
474 value, ref unit, ..
475 } => {
476 let angle = Angle::parse_dimension(value, unit, /* from_calc = */ false);
477
478 let degrees = match angle {
479 Ok(angle) => angle.degrees(),
480 Err(()) => return Err(location.new_unexpected_token_error(token.clone())),
481 };
482
483 Ok(AngleOrNumber::Angle { degrees })
484 },
485 Token::Number { value, .. } => Ok(AngleOrNumber::Number { value }),
486 Token::Function(ref name) => {
487 let function = CalcNode::math_function(name, location)?;
488 CalcNode::parse_angle_or_number(self.0, input, function)
489 },
490 t => return Err(location.new_unexpected_token_error(t)),
491 }
492 }
493
parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>>494 fn parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>> {
495 Ok(Percentage::parse(self.0, input)?.get())
496 }
497
parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>>498 fn parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>> {
499 use crate::values::specified::Number;
500
501 Ok(Number::parse(self.0, input)?.get())
502 }
503
parse_number_or_percentage<'t>( &self, input: &mut Parser<'i, 't>, ) -> Result<NumberOrPercentage, ParseError<'i>>504 fn parse_number_or_percentage<'t>(
505 &self,
506 input: &mut Parser<'i, 't>,
507 ) -> Result<NumberOrPercentage, ParseError<'i>> {
508 let location = input.current_source_location();
509
510 match *input.next()? {
511 Token::Number { value, .. } => Ok(NumberOrPercentage::Number { value }),
512 Token::Percentage { unit_value, .. } => {
513 Ok(NumberOrPercentage::Percentage { unit_value })
514 },
515 Token::Function(ref name) => {
516 let function = CalcNode::math_function(name, location)?;
517 CalcNode::parse_number_or_percentage(self.0, input, function)
518 },
519 ref t => return Err(location.new_unexpected_token_error(t.clone())),
520 }
521 }
522 }
523
524 impl Parse for Color {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>525 fn parse<'i, 't>(
526 context: &ParserContext,
527 input: &mut Parser<'i, 't>,
528 ) -> Result<Self, ParseError<'i>> {
529 // Currently we only store authored value for color keywords,
530 // because all browsers serialize those values as keywords for
531 // specified value.
532 let start = input.state();
533 let authored = input.expect_ident_cloned().ok();
534 input.reset(&start);
535
536 let compontent_parser = ColorComponentParser(&*context);
537 match input.try_parse(|i| CSSParserColor::parse_with(&compontent_parser, i)) {
538 Ok(value) => Ok(match value {
539 CSSParserColor::CurrentColor => Color::CurrentColor,
540 CSSParserColor::RGBA(rgba) => Color::Numeric {
541 parsed: rgba,
542 authored: authored.map(|s| s.to_ascii_lowercase().into_boxed_str()),
543 },
544 }),
545 Err(e) => {
546 #[cfg(feature = "gecko")]
547 {
548 if let Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) {
549 return Ok(Color::System(system));
550 }
551 }
552
553 if let Ok(mix) = input.try_parse(|i| ColorMix::parse(context, i)) {
554 return Ok(Color::ColorMix(Box::new(mix)));
555 }
556
557 match e.kind {
558 ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(t)) => {
559 Err(e.location.new_custom_error(StyleParseErrorKind::ValueError(
560 ValueParseErrorKind::InvalidColor(t),
561 )))
562 },
563 _ => Err(e),
564 }
565 },
566 }
567 }
568 }
569
570 impl ToCss for Color {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,571 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
572 where
573 W: Write,
574 {
575 match *self {
576 Color::CurrentColor => CSSParserColor::CurrentColor.to_css(dest),
577 Color::Numeric {
578 authored: Some(ref authored),
579 ..
580 } => dest.write_str(authored),
581 Color::Numeric {
582 parsed: ref rgba, ..
583 } => rgba.to_css(dest),
584 // TODO: Could represent this as a color-mix() instead.
585 Color::Complex(_) => Ok(()),
586 Color::ColorMix(ref mix) => mix.to_css(dest),
587 #[cfg(feature = "gecko")]
588 Color::System(system) => system.to_css(dest),
589 #[cfg(feature = "gecko")]
590 Color::InheritFromBodyQuirk => Ok(()),
591 }
592 }
593 }
594
595 /// A wrapper of cssparser::Color::parse_hash.
596 ///
597 /// That function should never return CurrentColor, so it makes no sense to
598 /// handle a cssparser::Color here. This should really be done in cssparser
599 /// directly rather than here.
parse_hash_color(value: &[u8]) -> Result<RGBA, ()>600 fn parse_hash_color(value: &[u8]) -> Result<RGBA, ()> {
601 CSSParserColor::parse_hash(value).map(|color| match color {
602 CSSParserColor::RGBA(rgba) => rgba,
603 CSSParserColor::CurrentColor => unreachable!("parse_hash should never return currentcolor"),
604 })
605 }
606
607 impl Color {
608 /// Returns whether this color is a system color.
is_system(&self) -> bool609 pub fn is_system(&self) -> bool {
610 matches!(self, Color::System(..))
611 }
612
613 /// Returns currentcolor value.
614 #[inline]
currentcolor() -> Color615 pub fn currentcolor() -> Color {
616 Color::CurrentColor
617 }
618
619 /// Returns transparent value.
620 #[inline]
transparent() -> Color621 pub fn transparent() -> Color {
622 // We should probably set authored to "transparent", but maybe it doesn't matter.
623 Color::rgba(RGBA::transparent())
624 }
625
626 /// Returns a numeric RGBA color value.
627 #[inline]
rgba(rgba: RGBA) -> Self628 pub fn rgba(rgba: RGBA) -> Self {
629 Color::Numeric {
630 parsed: rgba,
631 authored: None,
632 }
633 }
634
635 /// Parse a color, with quirks.
636 ///
637 /// <https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk>
parse_quirky<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, allow_quirks: AllowQuirks, ) -> Result<Self, ParseError<'i>>638 pub fn parse_quirky<'i, 't>(
639 context: &ParserContext,
640 input: &mut Parser<'i, 't>,
641 allow_quirks: AllowQuirks,
642 ) -> Result<Self, ParseError<'i>> {
643 input.try_parse(|i| Self::parse(context, i)).or_else(|e| {
644 if !allow_quirks.allowed(context.quirks_mode) {
645 return Err(e);
646 }
647 Color::parse_quirky_color(input)
648 .map(Color::rgba)
649 .map_err(|_| e)
650 })
651 }
652
653 /// Parse a <quirky-color> value.
654 ///
655 /// <https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk>
parse_quirky_color<'i, 't>(input: &mut Parser<'i, 't>) -> Result<RGBA, ParseError<'i>>656 fn parse_quirky_color<'i, 't>(input: &mut Parser<'i, 't>) -> Result<RGBA, ParseError<'i>> {
657 let location = input.current_source_location();
658 let (value, unit) = match *input.next()? {
659 Token::Number {
660 int_value: Some(integer),
661 ..
662 } => (integer, None),
663 Token::Dimension {
664 int_value: Some(integer),
665 ref unit,
666 ..
667 } => (integer, Some(unit)),
668 Token::Ident(ref ident) => {
669 if ident.len() != 3 && ident.len() != 6 {
670 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
671 }
672 return parse_hash_color(ident.as_bytes()).map_err(|()| {
673 location.new_custom_error(StyleParseErrorKind::UnspecifiedError)
674 });
675 },
676 ref t => {
677 return Err(location.new_unexpected_token_error(t.clone()));
678 },
679 };
680 if value < 0 {
681 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
682 }
683 let length = if value <= 9 {
684 1
685 } else if value <= 99 {
686 2
687 } else if value <= 999 {
688 3
689 } else if value <= 9999 {
690 4
691 } else if value <= 99999 {
692 5
693 } else if value <= 999999 {
694 6
695 } else {
696 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
697 };
698 let total = length + unit.as_ref().map_or(0, |d| d.len());
699 if total > 6 {
700 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
701 }
702 let mut serialization = [b'0'; 6];
703 let space_padding = 6 - total;
704 let mut written = space_padding;
705 written += itoa::write(&mut serialization[written..], value).unwrap();
706 if let Some(unit) = unit {
707 written += (&mut serialization[written..])
708 .write(unit.as_bytes())
709 .unwrap();
710 }
711 debug_assert_eq!(written, 6);
712 parse_hash_color(&serialization)
713 .map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
714 }
715 }
716
717 impl Color {
718 /// Converts this Color into a ComputedColor.
719 ///
720 /// If `context` is `None`, and the specified color requires data from
721 /// the context to resolve, then `None` is returned.
to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor>722 pub fn to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor> {
723 Some(match *self {
724 Color::CurrentColor => ComputedColor::currentcolor(),
725 Color::Numeric { ref parsed, .. } => ComputedColor::rgba(*parsed),
726 Color::Complex(ref complex) => *complex,
727 Color::ColorMix(ref mix) => {
728 use crate::values::animated::color::Color as AnimatedColor;
729 use crate::values::animated::ToAnimatedValue;
730
731 let left = mix.left.to_computed_color(context)?.to_animated_value();
732 let right = mix.right.to_computed_color(context)?.to_animated_value();
733 ToAnimatedValue::from_animated_value(AnimatedColor::mix(
734 mix.color_space,
735 &left,
736 mix.left_percentage.get(),
737 &right,
738 mix.right_percentage.get(),
739 mix.hue_adjuster,
740 ))
741 },
742 #[cfg(feature = "gecko")]
743 Color::System(system) => system.compute(context?),
744 #[cfg(feature = "gecko")]
745 Color::InheritFromBodyQuirk => ComputedColor::rgba(context?.device().body_text_color()),
746 })
747 }
748 }
749
750 impl ToComputedValue for Color {
751 type ComputedValue = ComputedColor;
752
to_computed_value(&self, context: &Context) -> ComputedColor753 fn to_computed_value(&self, context: &Context) -> ComputedColor {
754 self.to_computed_color(Some(context)).unwrap()
755 }
756
from_computed_value(computed: &ComputedColor) -> Self757 fn from_computed_value(computed: &ComputedColor) -> Self {
758 if computed.is_numeric() {
759 return Color::rgba(computed.color);
760 }
761 if computed.is_currentcolor() {
762 return Color::currentcolor();
763 }
764 Color::Complex(*computed)
765 }
766 }
767
768 /// Specified color value for `-moz-font-smoothing-background-color`.
769 ///
770 /// This property does not support `currentcolor`. We could drop it at
771 /// parse-time, but it's not exposed to the web so it doesn't really matter.
772 ///
773 /// We resolve it to `transparent` instead.
774 #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
775 pub struct MozFontSmoothingBackgroundColor(pub Color);
776
777 impl Parse for MozFontSmoothingBackgroundColor {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>778 fn parse<'i, 't>(
779 context: &ParserContext,
780 input: &mut Parser<'i, 't>,
781 ) -> Result<Self, ParseError<'i>> {
782 Color::parse(context, input).map(MozFontSmoothingBackgroundColor)
783 }
784 }
785
786 impl ToComputedValue for MozFontSmoothingBackgroundColor {
787 type ComputedValue = RGBA;
788
to_computed_value(&self, context: &Context) -> RGBA789 fn to_computed_value(&self, context: &Context) -> RGBA {
790 self.0
791 .to_computed_value(context)
792 .to_rgba(RGBA::transparent())
793 }
794
from_computed_value(computed: &RGBA) -> Self795 fn from_computed_value(computed: &RGBA) -> Self {
796 MozFontSmoothingBackgroundColor(Color::rgba(*computed))
797 }
798 }
799
800 impl SpecifiedValueInfo for Color {
801 const SUPPORTED_TYPES: u8 = CssType::COLOR;
802
collect_completion_keywords(f: KeywordsCollectFn)803 fn collect_completion_keywords(f: KeywordsCollectFn) {
804 // We are not going to insert all the color names here. Caller and
805 // devtools should take care of them. XXX Actually, transparent
806 // should probably be handled that way as well.
807 // XXX `currentColor` should really be `currentcolor`. But let's
808 // keep it consistent with the old system for now.
809 f(&["rgb", "rgba", "hsl", "hsla", "hwb", "currentColor", "transparent"]);
810 }
811 }
812
813 /// Specified value for the "color" property, which resolves the `currentcolor`
814 /// keyword to the parent color instead of self's color.
815 #[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
816 #[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
817 pub struct ColorPropertyValue(pub Color);
818
819 impl ToComputedValue for ColorPropertyValue {
820 type ComputedValue = RGBA;
821
822 #[inline]
to_computed_value(&self, context: &Context) -> RGBA823 fn to_computed_value(&self, context: &Context) -> RGBA {
824 self.0
825 .to_computed_value(context)
826 .to_rgba(context.builder.get_parent_inherited_text().clone_color())
827 }
828
829 #[inline]
from_computed_value(computed: &RGBA) -> Self830 fn from_computed_value(computed: &RGBA) -> Self {
831 ColorPropertyValue(Color::rgba(*computed).into())
832 }
833 }
834
835 impl Parse for ColorPropertyValue {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>836 fn parse<'i, 't>(
837 context: &ParserContext,
838 input: &mut Parser<'i, 't>,
839 ) -> Result<Self, ParseError<'i>> {
840 Color::parse_quirky(context, input, AllowQuirks::Yes).map(ColorPropertyValue)
841 }
842 }
843
844 /// auto | <color>
845 pub type ColorOrAuto = GenericColorOrAuto<Color>;
846
847 /// caret-color
848 pub type CaretColor = GenericCaretColor<Color>;
849
850 impl Parse for CaretColor {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>851 fn parse<'i, 't>(
852 context: &ParserContext,
853 input: &mut Parser<'i, 't>,
854 ) -> Result<Self, ParseError<'i>> {
855 ColorOrAuto::parse(context, input).map(GenericCaretColor)
856 }
857 }
858
859 bitflags! {
860 /// Various flags to represent the color-scheme property in an efficient
861 /// way.
862 #[derive(Default, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)]
863 #[repr(C)]
864 #[value_info(other_values = "light,dark,only")]
865 pub struct ColorSchemeFlags: u8 {
866 /// Whether the author specified `light`.
867 const LIGHT = 1 << 0;
868 /// Whether the author specified `dark`.
869 const DARK = 1 << 1;
870 /// Whether the author specified `only`.
871 const ONLY = 1 << 2;
872 }
873 }
874
875 /// <https://drafts.csswg.org/css-color-adjust/#color-scheme-prop>
876 #[derive(
877 Clone,
878 Debug,
879 Default,
880 MallocSizeOf,
881 PartialEq,
882 SpecifiedValueInfo,
883 ToComputedValue,
884 ToResolvedValue,
885 ToShmem,
886 )]
887 #[repr(C)]
888 #[value_info(other_values = "normal")]
889 pub struct ColorScheme {
890 #[ignore_malloc_size_of = "Arc"]
891 idents: crate::ArcSlice<CustomIdent>,
892 bits: ColorSchemeFlags,
893 }
894
895 impl ColorScheme {
896 /// Returns the `normal` value.
normal() -> Self897 pub fn normal() -> Self {
898 Self {
899 idents: Default::default(),
900 bits: ColorSchemeFlags::empty(),
901 }
902 }
903
904 /// Returns the raw bitfield.
raw_bits(&self) -> u8905 pub fn raw_bits(&self) -> u8 {
906 self.bits.bits
907 }
908 }
909
910 impl Parse for ColorScheme {
parse<'i, 't>( _: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>911 fn parse<'i, 't>(
912 _: &ParserContext,
913 input: &mut Parser<'i, 't>,
914 ) -> Result<Self, ParseError<'i>> {
915 let mut idents = vec![];
916 let mut bits = ColorSchemeFlags::empty();
917
918 let mut location = input.current_source_location();
919 while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
920 let mut is_only = false;
921 match_ignore_ascii_case! { &ident,
922 "normal" => {
923 if idents.is_empty() && bits.is_empty() {
924 return Ok(Self::normal());
925 }
926 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
927 },
928 "light" => bits.insert(ColorSchemeFlags::LIGHT),
929 "dark" => bits.insert(ColorSchemeFlags::DARK),
930 "only" => {
931 if bits.intersects(ColorSchemeFlags::ONLY) {
932 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
933 }
934 bits.insert(ColorSchemeFlags::ONLY);
935 is_only = true;
936 },
937 _ => {},
938 };
939
940 if is_only {
941 if !idents.is_empty() {
942 // Only is allowed either at the beginning or at the end,
943 // but not in the middle.
944 break;
945 }
946 } else {
947 idents.push(CustomIdent::from_ident(location, &ident, &[])?);
948 }
949 location = input.current_source_location();
950 }
951
952 if idents.is_empty() {
953 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
954 }
955
956 Ok(Self {
957 idents: crate::ArcSlice::from_iter(idents.into_iter()),
958 bits,
959 })
960 }
961 }
962
963 impl ToCss for ColorScheme {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,964 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
965 where
966 W: Write,
967 {
968 if self.idents.is_empty() {
969 debug_assert!(self.bits.is_empty());
970 return dest.write_str("normal");
971 }
972 let mut first = true;
973 for ident in self.idents.iter() {
974 if !first {
975 dest.write_char(' ')?;
976 }
977 first = false;
978 ident.to_css(dest)?;
979 }
980 if self.bits.intersects(ColorSchemeFlags::ONLY) {
981 dest.write_str(" only")?;
982 }
983 Ok(())
984 }
985 }
986
987 /// https://drafts.csswg.org/css-color-adjust/#print-color-adjust
988 #[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem)]
989 #[repr(u8)]
990 pub enum PrintColorAdjust {
991 /// Ignore backgrounds and darken text.
992 Economy,
993 /// Respect specified colors.
994 Exact,
995 }
996