From 086c044dc34dfc0f74fbe41f4ecb402b2cd34884 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:13:33 +0200 Subject: Merging upstream version 125.0.1. Signed-off-by: Daniel Baumann --- servo/components/style/color/to_css.rs | 251 +++++++++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 servo/components/style/color/to_css.rs (limited to 'servo/components/style/color/to_css.rs') diff --git a/servo/components/style/color/to_css.rs b/servo/components/style/color/to_css.rs new file mode 100644 index 0000000000..350d8386f1 --- /dev/null +++ b/servo/components/style/color/to_css.rs @@ -0,0 +1,251 @@ +/* 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/. */ + +//! Write colors into CSS strings. + +use super::{AbsoluteColor, ColorFlags, ColorSpace}; +use crate::values::normalize; +use cssparser::color::{clamp_unit_f32, serialize_color_alpha, OPAQUE}; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ToCss}; + +/// A [`ModernComponent`] can serialize to `none`, `nan`, `infinity` and +/// floating point values. +struct ModernComponent<'a>(&'a Option); + +impl<'a> ToCss for ModernComponent<'a> { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: fmt::Write, + { + if let Some(value) = self.0 { + if value.is_finite() { + value.to_css(dest) + } else if value.is_nan() { + dest.write_str("calc(NaN)") + } else { + debug_assert!(value.is_infinite()); + if value.is_sign_negative() { + dest.write_str("calc(-infinity)") + } else { + dest.write_str("calc(infinity)") + } + } + } else { + dest.write_str("none") + } + } +} + +impl ToCss for AbsoluteColor { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + match self.color_space { + ColorSpace::Srgb if self.flags.contains(ColorFlags::IS_LEGACY_SRGB) => { + // The "none" keyword is not supported in the rgb/rgba legacy syntax. + let has_alpha = self.alpha != OPAQUE; + + dest.write_str(if has_alpha { "rgba(" } else { "rgb(" })?; + clamp_unit_f32(self.components.0).to_css(dest)?; + dest.write_str(", ")?; + clamp_unit_f32(self.components.1).to_css(dest)?; + dest.write_str(", ")?; + clamp_unit_f32(self.components.2).to_css(dest)?; + + // Legacy syntax does not allow none components. + serialize_color_alpha(dest, Some(self.alpha), true)?; + + dest.write_char(')') + }, + ColorSpace::Hsl | ColorSpace::Hwb => self.into_srgb_legacy().to_css(dest), + ColorSpace::Oklab | ColorSpace::Lab | ColorSpace::Oklch | ColorSpace::Lch => { + if let ColorSpace::Oklab | ColorSpace::Oklch = self.color_space { + dest.write_str("ok")?; + } + if let ColorSpace::Oklab | ColorSpace::Lab = self.color_space { + dest.write_str("lab(")?; + } else { + dest.write_str("lch(")?; + } + ModernComponent(&self.c0()).to_css(dest)?; + dest.write_char(' ')?; + ModernComponent(&self.c1()).to_css(dest)?; + dest.write_char(' ')?; + ModernComponent(&self.c2()).to_css(dest)?; + serialize_color_alpha(dest, self.alpha(), false)?; + dest.write_char(')') + }, + _ => { + #[cfg(debug_assertions)] + match self.color_space { + ColorSpace::Srgb => { + debug_assert!( + !self.flags.contains(ColorFlags::IS_LEGACY_SRGB), + "legacy srgb is not a color function" + ); + }, + ColorSpace::SrgbLinear | + ColorSpace::DisplayP3 | + ColorSpace::A98Rgb | + ColorSpace::ProphotoRgb | + ColorSpace::Rec2020 | + ColorSpace::XyzD50 | + ColorSpace::XyzD65 => { + // These color spaces are allowed. + }, + _ => { + unreachable!("other color spaces do not support color() syntax") + }, + }; + + dest.write_str("color(")?; + self.color_space.to_css(dest)?; + dest.write_char(' ')?; + ModernComponent(&self.c0()).to_css(dest)?; + dest.write_char(' ')?; + ModernComponent(&self.c1()).to_css(dest)?; + dest.write_char(' ')?; + ModernComponent(&self.c2()).to_css(dest)?; + + serialize_color_alpha(dest, self.alpha(), false)?; + + dest.write_char(')') + }, + } + } +} + +impl AbsoluteColor { + /// Write a string to `dest` that represents a color as an author would + /// enter it. + /// NOTE: The format of the output is NOT according to any specification, + /// but makes assumptions about the best ways that authors would want to + /// enter color values in style sheets, devtools, etc. + pub fn write_author_preferred_value(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + macro_rules! precision { + ($v:expr) => {{ + ($v * 100.0).round() / 100.0 + }}; + } + macro_rules! number { + ($c:expr) => {{ + if let Some(v) = $c.map(normalize) { + precision!(v).to_css(dest)?; + } else { + write!(dest, "none")?; + } + }}; + } + macro_rules! percentage { + ($c:expr) => {{ + if let Some(v) = $c.map(normalize) { + precision!(v).to_css(dest)?; + dest.write_char('%')?; + } else { + write!(dest, "none")?; + } + }}; + } + macro_rules! unit_percentage { + ($c:expr) => {{ + if let Some(v) = $c.map(normalize) { + precision!(v * 100.0).to_css(dest)?; + dest.write_char('%')?; + } else { + write!(dest, "none")?; + } + }}; + } + macro_rules! angle { + ($c:expr) => {{ + if let Some(v) = $c.map(normalize) { + precision!(v).to_css(dest)?; + dest.write_str("deg")?; + } else { + write!(dest, "none")?; + } + }}; + } + + match self.color_space { + ColorSpace::Srgb => { + write!(dest, "rgb(")?; + unit_percentage!(self.c0()); + dest.write_char(' ')?; + unit_percentage!(self.c1()); + dest.write_char(' ')?; + unit_percentage!(self.c2()); + serialize_color_alpha(dest, self.alpha(), false)?; + dest.write_char(')') + }, + ColorSpace::Hsl | ColorSpace::Hwb => { + dest.write_str(if self.color_space == ColorSpace::Hsl { + "hsl(" + } else { + "hwb(" + })?; + angle!(self.c0()); + dest.write_char(' ')?; + percentage!(self.c1()); + dest.write_char(' ')?; + percentage!(self.c2()); + serialize_color_alpha(dest, self.alpha(), false)?; + dest.write_char(')') + }, + ColorSpace::Lab | ColorSpace::Oklab => { + if self.color_space == ColorSpace::Oklab { + dest.write_str("ok")?; + } + dest.write_str("lab(")?; + if self.color_space == ColorSpace::Lab { + percentage!(self.c0()) + } else { + unit_percentage!(self.c0()) + } + dest.write_char(' ')?; + number!(self.c1()); + dest.write_char(' ')?; + number!(self.c2()); + serialize_color_alpha(dest, self.alpha(), false)?; + dest.write_char(')') + }, + ColorSpace::Lch | ColorSpace::Oklch => { + if self.color_space == ColorSpace::Oklch { + dest.write_str("ok")?; + } + dest.write_str("lch(")?; + number!(self.c0()); + dest.write_char(' ')?; + number!(self.c1()); + dest.write_char(' ')?; + angle!(self.c2()); + serialize_color_alpha(dest, self.alpha(), false)?; + dest.write_char(')') + }, + ColorSpace::SrgbLinear | + ColorSpace::DisplayP3 | + ColorSpace::A98Rgb | + ColorSpace::ProphotoRgb | + ColorSpace::Rec2020 | + ColorSpace::XyzD50 | + ColorSpace::XyzD65 => { + dest.write_str("color(")?; + self.color_space.to_css(dest)?; + dest.write_char(' ')?; + number!(self.c0()); + dest.write_char(' ')?; + number!(self.c1()); + dest.write_char(' ')?; + number!(self.c2()); + serialize_color_alpha(dest, self.alpha(), false)?; + dest.write_char(')') + }, + } + } +} -- cgit v1.2.3