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 http://mozilla.org/MPL/2.0/. */
//! General color-parsing utilities, independent on the specific color storage and parsing
//! implementation.
//!
//! For a more complete css-color implementation take a look at cssparser-color crate, or at
//! Gecko's color module.
// Allow text like <color> in docs.
#![allow(rustdoc::invalid_html_tags)]
/// The opaque alpha value of 1.0.
pub const OPAQUE: f32 = 1.0;
use crate::{BasicParseError, Parser, ToCss, Token};
use std::fmt;
/// Clamp a 0..1 number to a 0..255 range to u8.
///
/// Whilst scaling by 256 and flooring would provide
/// an equal distribution of integers to percentage inputs,
/// this is not what Gecko does so we instead multiply by 255
/// and round (adding 0.5 and flooring is equivalent to rounding)
///
/// Chrome does something similar for the alpha value, but not
/// the rgb values.
///
///
/// Clamping to 256 and rounding after would let 1.0 map to 256, and
/// `256.0_f32 as u8` is undefined behavior:
///
#[inline]
pub fn clamp_unit_f32(val: f32) -> u8 {
clamp_floor_256_f32(val * 255.)
}
/// Round and clamp a single number to a u8.
#[inline]
pub fn clamp_floor_256_f32(val: f32) -> u8 {
val.round().clamp(0., 255.) as u8
}
/// Serialize the alpha copmonent of a color according to the specification.
#[inline]
pub fn serialize_color_alpha(
dest: &mut impl fmt::Write,
alpha: Option<f32>,
legacy_syntax: bool,
) -> fmt::Result {
let alpha = match alpha {
None => return dest.write_str(" / none"),
Some(a) => a,
};
// If the alpha component is full opaque, don't emit the alpha value in CSS.
if alpha == OPAQUE {
return Ok(());
}
dest.write_str(if legacy_syntax { ", " } else { " / " })?;
// Try first with two decimal places, then with three.
let mut rounded_alpha = (alpha * 100.).round() / 100.;
if clamp_unit_f32(rounded_alpha) != clamp_unit_f32(alpha) {
rounded_alpha = (alpha * 1000.).round() / 1000.;
}
rounded_alpha.to_css(dest)
}
/// A Predefined color space specified in:
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(tag = "type"))]
pub enum PredefinedColorSpace {
Srgb,
SrgbLinear,
DisplayP3,
A98Rgb,
ProphotoRgb,
Rec2020,
XyzD50,
XyzD65,
}
impl PredefinedColorSpace {
/// Parse a PredefinedColorSpace from the given input.
pub fn parse<'i>(input: &mut Parser<'i, '_>) -> Result<Self, BasicParseError<'i>> {
let location = input.current_source_location();
let ident = input.expect_ident()?;
Ok(match_ignore_ascii_case! { ident,
"srgb" => Self::Srgb,
"srgb-linear" => Self::SrgbLinear,
"display-p3" => Self::DisplayP3,
"a98-rgb" => Self::A98Rgb,
"prophoto-rgb" => Self::ProphotoRgb,
"rec2020" => Self::Rec2020,
"xyz-d50" => Self::XyzD50,
"xyz" | "xyz-d65" => Self::XyzD65,
_ => return Err(location.new_basic_unexpected_token_error(Token::Ident(ident.clone()))),
})
}
}
impl ToCss for PredefinedColorSpace {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: fmt::Write,
{
dest.write_str(match self {
Self::Srgb => "srgb",
Self::SrgbLinear => "srgb-linear",
Self::DisplayP3 => "display-p3",
Self::A98Rgb => "a98-rgb",
Self::ProphotoRgb => "prophoto-rgb",
Self::Rec2020 => "rec2020",
Self::XyzD50 => "xyz-d50",
Self::XyzD65 => "xyz-d65",
})
}
}
/// Parse a color hash, without the leading '#' character.
#[allow(clippy::result_unit_err)]
#[inline]
pub fn parse_hash_color(value: &[u8]) -> Result<(u8, u8, u8, f32), ()> {
Ok(match value.len() {
8 => (
from_hex(value[0])? * 16 + from_hex(value[1])?,
from_hex(value[2])? * 16 + from_hex(value[3])?,
from_hex(value[4])? * 16 + from_hex(value[5])?,
(from_hex(value[6])? * 16 + from_hex(value[7])?) as f32 / 255.0,
),
6 => (
from_hex(value[0])? * 16 + from_hex(value[1])?,
from_hex(value[2])? * 16 + from_hex(value[3])?,
from_hex(value[4])? * 16 + from_hex(value[5])?,
OPAQUE,
),
4 => (
from_hex(value[0])? * 17,
from_hex(value[1])? * 17,
from_hex(value[2])? * 17,
(from_hex(value[3])? * 17) as f32 / 255.0,
),
3 => (
from_hex(value[0])? * 17,
from_hex(value[1])? * 17,
from_hex(value[2])? * 17,
OPAQUE,
),
_ => return Err(()),
})
}
ascii_case_insensitive_phf_map! {
named_colors -> (u8, u8, u8) = {
"black" => (0, 0, 0),
"silver" => (192, 192, 192),
"gray" => (128, 128, 128),
"white" => (255, 255, 255),
"maroon" => (128, 0, 0),
"red" => (255, 0, 0),
"purple" => (128, 0, 128),
"fuchsia" => (255, 0, 255),
"green" => (0, 128, 0),
"lime" => (0, 255, 0),
"olive" => (128, 128, 0),
"yellow" => (255, 255, 0),
"navy" => (0, 0, 128),
"blue" => (0, 0, 255),
"teal" => (0, 128, 128),
"aqua" => (0, 255, 255),
"aliceblue" => (240, 248, 255),
"antiquewhite" => (250, 235, 215),
"aquamarine" => (127, 255, 212),
"azure" => (240, 255, 255),
"beige" => (245, 245, 220),
"bisque" => (255, 228, 196),
"blanchedalmond" => (255, 235, 205),
"blueviolet" => (138, 43, 226),
"brown" => (165, 42, 42),
"burlywood" => (222, 184, 135),
"cadetblue" => (95, 158, 160),
"chartreuse" => (127, 255, 0),
"chocolate" => (210, 105, 30),
"coral" => (255, 127, 80),
"cornflowerblue" => (100, 149, 237),
"cornsilk" => (255, 248, 220),
"crimson" => (220, 20, 60),
"cyan" => (0, 255, 255),
"darkblue" => (0, 0, 139),
"darkcyan" => (0, 139, 139),
"darkgoldenrod" => (184, 134, 11),
"darkgray" => (169, 169, 169),
"darkgreen" => (0, 100, 0),
"darkgrey" => (169, 169, 169),
"darkkhaki" => (189, 183, 107),
"darkmagenta" => (139, 0, 139),
"darkolivegreen" => (85, 107, 47),
"darkorange" => (255, 140, 0),
"darkorchid" => (153, 50, 204),
"darkred" => (139, 0, 0),
"darksalmon" => (233, 150, 122),
"darkseagreen" => (143, 188, 143),
"darkslateblue" => (72, 61, 139),
"darkslategray" => (47, 79, 79),
"darkslategrey" => (47, 79, 79),
"darkturquoise" => (0, 206, 209),
"darkviolet" => (148, 0, 211),
"deeppink" => (255, 20, 147),
"deepskyblue" => (0, 191, 255),
"dimgray" => (105, 105, 105),
"dimgrey" => (105, 105, 105),
"dodgerblue" => (30, 144, 255),
"firebrick" => (178, 34, 34),
"floralwhite" => (255, 250, 240),
"forestgreen" => (34, 139, 34),
"gainsboro" => (220, 220, 220),
"ghostwhite" => (248, 248, 255),
"gold" => (255, 215, 0),
"goldenrod" => (218, 165, 32),
"greenyellow" => (173, 255, 47),
"grey" => (128, 128, 128),
"honeydew" => (240, 255, 240),
"hotpink" => (255, 105, 180),
"indianred" => (205, 92, 92),
"indigo" => (75, 0, 130),
"ivory" => (255, 255, 240),
"khaki" => (240, 230, 140),
"lavender" => (230, 230, 250),
"lavenderblush" => (255, 240, 245),
"lawngreen" => (124, 252, 0),
"lemonchiffon" => (255, 250, 205),
"lightblue" => (173, 216, 230),
"lightcoral" => (240, 128, 128),
"lightcyan" => (224, 255, 255),
"lightgoldenrodyellow" => (250, 250, 210),
"lightgray" => (211, 211, 211),
"lightgreen" => (144, 238, 144),
"lightgrey" => (211, 211, 211),
"lightpink" => (255, 182, 193),
"lightsalmon" => (255, 160, 122),
"lightseagreen" => (32, 178, 170),
"lightskyblue" => (135, 206, 250),
"lightslategray" => (119, 136, 153),
"lightslategrey" => (119, 136, 153),
"lightsteelblue" => (176, 196, 222),
"lightyellow" => (255, 255, 224),
"limegreen" => (50, 205, 50),
"linen" => (250, 240, 230),
"magenta" => (255, 0, 255),
"mediumaquamarine" => (102, 205, 170),
"mediumblue" => (0, 0, 205),
"mediumorchid" => (186, 85, 211),
"mediumpurple" => (147, 112, 219),
"mediumseagreen" => (60, 179, 113),
"mediumslateblue" => (123, 104, 238),
"mediumspringgreen" => (0, 250, 154),
"mediumturquoise" => (72, 209, 204),
"mediumvioletred" => (199, 21, 133),
"midnightblue" => (25, 25, 112),
"mintcream" => (245, 255, 250),
"mistyrose" => (255, 228, 225),
"moccasin" => (255, 228, 181),
"navajowhite" => (255, 222, 173),
"oldlace" => (253, 245, 230),
"olivedrab" => (107, 142, 35),
"orange" => (255, 165, 0),
"orangered" => (255, 69, 0),
"orchid" => (218, 112, 214),
"palegoldenrod" => (238, 232, 170),
"palegreen" => (152, 251, 152),
"paleturquoise" => (175, 238, 238),
"palevioletred" => (219, 112, 147),
"papayawhip" => (255, 239, 213),
"peachpuff" => (255, 218, 185),
"peru" => (205, 133, 63),
"pink" => (255, 192, 203),
"plum" => (221, 160, 221),
"powderblue" => (176, 224, 230),
"rebeccapurple" => (102, 51, 153),
"rosybrown" => (188, 143, 143),
"royalblue" => (65, 105, 225),
"saddlebrown" => (139, 69, 19),
"salmon" => (250, 128, 114),
"sandybrown" => (244, 164, 96),
"seagreen" => (46, 139, 87),
"seashell" => (255, 245, 238),
"sienna" => (160, 82, 45),
"skyblue" => (135, 206, 235),
"slateblue" => (106, 90, 205),
"slategray" => (112, 128, 144),
"slategrey" => (112, 128, 144),
"snow" => (255, 250, 250),
"springgreen" => (0, 255, 127),
"steelblue" => (70, 130, 180),
"tan" => (210, 180, 140),
"thistle" => (216, 191, 216),
"tomato" => (255, 99, 71),
"turquoise" => (64, 224, 208),
"violet" => (238, 130, 238),
"wheat" => (245, 222, 179),
"whitesmoke" => (245, 245, 245),
"yellowgreen" => (154, 205, 50),
}
}
/// Returns the named color with the given name.
#[allow(clippy::result_unit_err)]
#[inline]
pub fn parse_named_color(ident: &str) -> Result<(u8, u8, u8), ()> {
named_colors::get(ident).copied().ok_or(())
}
/// Returns an iterator over all named CSS colors.
#[inline]
pub fn all_named_colors() -> impl Iterator<Item = (&'static str, (u8, u8, u8))> {
named_colors::entries().map(|(k, v)| (*k, *v))
}
#[inline]
fn from_hex(c: u8) -> Result<u8, ()> {
match c {
b'0'..=b'9' => Ok(c - b'0'),
b'a'..=b'f' => Ok(c - b'a' + 10),
b'A'..=b'F' => Ok(c - b'A' + 10),
_ => Err(()),
}
}