blob: af06c487f57c8e1fa7fa0a6a377caa8f31bed9b6 [file] [log] [blame] [edit]
//! The `@font-palette-values` rule.
use super::supports::SupportsRule;
use super::{CssRule, CssRuleList, Location, MinifyContext};
use crate::error::{ParserError, PrinterError};
use crate::printer::Printer;
use crate::properties::custom::CustomProperty;
use crate::properties::font::FontFamily;
use crate::stylesheet::ParserOptions;
use crate::targets::Targets;
use crate::traits::{Parse, ToCss};
use crate::values::color::{ColorFallbackKind, CssColor};
use crate::values::ident::DashedIdent;
use crate::values::number::CSSInteger;
#[cfg(feature = "visitor")]
use crate::visitor::Visit;
use cssparser::*;
/// A [@font-palette-values](https://drafts.csswg.org/css-fonts-4/#font-palette-values) rule.
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct FontPaletteValuesRule<'i> {
/// The name of the font palette.
pub name: DashedIdent<'i>,
/// Declarations in the `@font-palette-values` rule.
#[cfg_attr(feature = "serde", serde(borrow))]
pub properties: Vec<FontPaletteValuesProperty<'i>>,
/// The location of the rule in the source file.
#[cfg_attr(feature = "visitor", skip_visit)]
pub loc: Location,
}
/// A property within an `@font-palette-values` rule.
///
/// See [FontPaletteValuesRule](FontPaletteValuesRule).
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type", content = "value", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub enum FontPaletteValuesProperty<'i> {
/// The `font-family` property.
#[cfg_attr(feature = "serde", serde(borrow))]
FontFamily(FontFamily<'i>),
/// The `base-palette` property.
BasePalette(BasePalette),
/// The `override-colors` property.
OverrideColors(Vec<OverrideColors>),
/// An unknown or unsupported property.
Custom(CustomProperty<'i>),
}
/// A value for the [base-palette](https://drafts.csswg.org/css-fonts-4/#base-palette-desc)
/// property in an `@font-palette-values` rule.
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type", content = "value", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum BasePalette {
/// A light color palette as defined within the font.
Light,
/// A dark color palette as defined within the font.
Dark,
/// A palette index within the font.
Integer(u16),
}
/// A value for the [override-colors](https://drafts.csswg.org/css-fonts-4/#override-color)
/// property in an `@font-palette-values` rule.
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub struct OverrideColors {
/// The index of the color within the palette to override.
index: u16,
/// The replacement color.
color: CssColor,
}
pub(crate) struct FontPaletteValuesDeclarationParser;
impl<'i> cssparser::DeclarationParser<'i> for FontPaletteValuesDeclarationParser {
type Declaration = FontPaletteValuesProperty<'i>;
type Error = ParserError<'i>;
fn parse_value<'t>(
&mut self,
name: CowRcStr<'i>,
input: &mut cssparser::Parser<'i, 't>,
) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {
let state = input.state();
match_ignore_ascii_case! { &name,
"font-family" => {
// https://drafts.csswg.org/css-fonts-4/#font-family-2-desc
if let Ok(font_family) = FontFamily::parse(input) {
return match font_family {
FontFamily::Generic(_) => Err(input.new_custom_error(ParserError::InvalidDeclaration)),
_ => Ok(FontPaletteValuesProperty::FontFamily(font_family))
}
}
},
"base-palette" => {
// https://drafts.csswg.org/css-fonts-4/#base-palette-desc
if let Ok(base_palette) = BasePalette::parse(input) {
return Ok(FontPaletteValuesProperty::BasePalette(base_palette))
}
},
"override-colors" => {
// https://drafts.csswg.org/css-fonts-4/#override-color
if let Ok(override_colors) = input.parse_comma_separated(OverrideColors::parse) {
return Ok(FontPaletteValuesProperty::OverrideColors(override_colors))
}
},
_ => return Err(input.new_custom_error(ParserError::InvalidDeclaration))
}
input.reset(&state);
return Ok(FontPaletteValuesProperty::Custom(CustomProperty::parse(
name.into(),
input,
&ParserOptions::default(),
)?));
}
}
/// Default methods reject all at rules.
impl<'i> AtRuleParser<'i> for FontPaletteValuesDeclarationParser {
type Prelude = ();
type AtRule = FontPaletteValuesProperty<'i>;
type Error = ParserError<'i>;
}
impl<'i> QualifiedRuleParser<'i> for FontPaletteValuesDeclarationParser {
type Prelude = ();
type QualifiedRule = FontPaletteValuesProperty<'i>;
type Error = ParserError<'i>;
}
impl<'i> RuleBodyItemParser<'i, FontPaletteValuesProperty<'i>, ParserError<'i>>
for FontPaletteValuesDeclarationParser
{
fn parse_qualified(&self) -> bool {
false
}
fn parse_declarations(&self) -> bool {
true
}
}
impl<'i> FontPaletteValuesRule<'i> {
pub(crate) fn parse<'t>(
name: DashedIdent<'i>,
input: &mut Parser<'i, 't>,
loc: Location,
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let mut decl_parser = FontPaletteValuesDeclarationParser;
let mut parser = RuleBodyParser::new(input, &mut decl_parser);
let mut properties = vec![];
while let Some(decl) = parser.next() {
if let Ok(decl) = decl {
properties.push(decl);
}
}
Ok(FontPaletteValuesRule { name, properties, loc })
}
}
impl<'i> Parse<'i> for BasePalette {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
if let Ok(i) = input.try_parse(CSSInteger::parse) {
if i.is_negative() {
return Err(input.new_custom_error(ParserError::InvalidValue));
}
return Ok(BasePalette::Integer(i as u16));
}
let location = input.current_source_location();
let ident = input.expect_ident()?;
match_ignore_ascii_case! { &*ident,
"light" => Ok(BasePalette::Light),
"dark" => Ok(BasePalette::Dark),
_ => Err(location.new_unexpected_token_error(Token::Ident(ident.clone())))
}
}
}
impl ToCss for BasePalette {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
BasePalette::Light => dest.write_str("light"),
BasePalette::Dark => dest.write_str("dark"),
BasePalette::Integer(i) => (*i as CSSInteger).to_css(dest),
}
}
}
impl<'i> Parse<'i> for OverrideColors {
fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let index = CSSInteger::parse(input)?;
if index.is_negative() {
return Err(input.new_custom_error(ParserError::InvalidValue));
}
let color = CssColor::parse(input)?;
if matches!(color, CssColor::CurrentColor) {
return Err(input.new_custom_error(ParserError::InvalidValue));
}
Ok(OverrideColors {
index: index as u16,
color,
})
}
}
impl ToCss for OverrideColors {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
(self.index as CSSInteger).to_css(dest)?;
dest.write_char(' ')?;
self.color.to_css(dest)
}
}
impl OverrideColors {
fn get_fallback(&self, kind: ColorFallbackKind) -> OverrideColors {
OverrideColors {
index: self.index,
color: self.color.get_fallback(kind),
}
}
}
impl<'i> FontPaletteValuesRule<'i> {
pub(crate) fn minify(&mut self, context: &mut MinifyContext<'_, 'i>, _: bool) {
let mut properties = Vec::with_capacity(self.properties.len());
for property in &self.properties {
match property {
FontPaletteValuesProperty::OverrideColors(override_colors) => {
// Generate color fallbacks.
let mut fallbacks = ColorFallbackKind::empty();
for o in override_colors {
fallbacks |= o.color.get_necessary_fallbacks(context.targets.current);
}
if fallbacks.contains(ColorFallbackKind::RGB) {
properties.push(FontPaletteValuesProperty::OverrideColors(
override_colors.iter().map(|o| o.get_fallback(ColorFallbackKind::RGB)).collect(),
));
}
if fallbacks.contains(ColorFallbackKind::P3) {
properties.push(FontPaletteValuesProperty::OverrideColors(
override_colors.iter().map(|o| o.get_fallback(ColorFallbackKind::P3)).collect(),
));
}
let override_colors = if fallbacks.contains(ColorFallbackKind::LAB) {
override_colors.iter().map(|o| o.get_fallback(ColorFallbackKind::P3)).collect()
} else {
override_colors.clone()
};
properties.push(FontPaletteValuesProperty::OverrideColors(override_colors));
}
_ => properties.push(property.clone()),
}
}
self.properties = properties;
}
pub(crate) fn get_fallbacks<T>(&mut self, targets: Targets) -> Vec<CssRule<'i, T>> {
// Get fallbacks for unparsed properties. These will generate @supports rules
// containing duplicate @font-palette-values rules.
let mut fallbacks = ColorFallbackKind::empty();
for property in &self.properties {
match property {
FontPaletteValuesProperty::Custom(CustomProperty { value, .. }) => {
fallbacks |= value.get_necessary_fallbacks(targets);
}
_ => {}
}
}
let mut res = Vec::new();
let lowest_fallback = fallbacks.lowest();
fallbacks.remove(lowest_fallback);
if fallbacks.contains(ColorFallbackKind::P3) {
res.push(self.get_fallback(ColorFallbackKind::P3));
}
if fallbacks.contains(ColorFallbackKind::LAB)
|| (!lowest_fallback.is_empty() && lowest_fallback != ColorFallbackKind::LAB)
{
res.push(self.get_fallback(ColorFallbackKind::LAB));
}
if !lowest_fallback.is_empty() {
for property in &mut self.properties {
match property {
FontPaletteValuesProperty::Custom(CustomProperty { value, .. }) => {
*value = value.get_fallback(lowest_fallback);
}
_ => {}
}
}
}
res
}
fn get_fallback<T>(&self, kind: ColorFallbackKind) -> CssRule<'i, T> {
let properties = self
.properties
.iter()
.map(|property| match property {
FontPaletteValuesProperty::Custom(custom) => FontPaletteValuesProperty::Custom(CustomProperty {
name: custom.name.clone(),
value: custom.value.get_fallback(kind),
}),
_ => property.clone(),
})
.collect();
CssRule::Supports(SupportsRule {
condition: kind.supports_condition(),
rules: CssRuleList(vec![CssRule::FontPaletteValues(FontPaletteValuesRule {
name: self.name.clone(),
properties,
loc: self.loc.clone(),
})]),
loc: self.loc.clone(),
})
}
}
impl<'i> ToCss for FontPaletteValuesRule<'i> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
#[cfg(feature = "sourcemap")]
dest.add_mapping(self.loc);
dest.write_str("@font-palette-values ")?;
self.name.to_css(dest)?;
dest.whitespace()?;
dest.write_char('{')?;
dest.indent();
let len = self.properties.len();
for (i, prop) in self.properties.iter().enumerate() {
dest.newline()?;
prop.to_css(dest)?;
if i != len - 1 || !dest.minify {
dest.write_char(';')?;
}
}
dest.dedent();
dest.newline()?;
dest.write_char('}')
}
}
impl<'i> ToCss for FontPaletteValuesProperty<'i> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
macro_rules! property {
($prop: literal, $value: expr) => {{
dest.write_str($prop)?;
dest.delim(':', false)?;
$value.to_css(dest)
}};
}
match self {
FontPaletteValuesProperty::FontFamily(f) => property!("font-family", f),
FontPaletteValuesProperty::BasePalette(b) => property!("base-palette", b),
FontPaletteValuesProperty::OverrideColors(o) => property!("override-colors", o),
FontPaletteValuesProperty::Custom(custom) => {
dest.write_str(custom.name.as_ref())?;
dest.delim(':', false)?;
custom.value.to_css(dest, true)
}
}
}
}