style/stylesheets/
font_palette_values_rule.rs

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//! The [`@font-palette-values`][font-palette-values] at-rule.
6//!
7//! [font-palette-values]: https://drafts.csswg.org/css-fonts/#font-palette-values
8
9use crate::error_reporting::ContextualParseError;
10#[cfg(feature = "gecko")]
11use crate::gecko_bindings::{
12    bindings::Gecko_AppendPaletteValueHashEntry,
13    bindings::{Gecko_SetFontPaletteBase, Gecko_SetFontPaletteOverride},
14    structs::gfx::FontPaletteValueSet,
15    structs::gfx::FontPaletteValueSet_PaletteValues_kDark,
16    structs::gfx::FontPaletteValueSet_PaletteValues_kLight,
17};
18use crate::parser::{Parse, ParserContext};
19use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
20use crate::stylesheets::font_feature_values_rule::parse_family_name_list;
21use crate::values::computed::font::FamilyName;
22use crate::values::specified::Color as SpecifiedColor;
23use crate::values::specified::NonNegativeInteger;
24use crate::values::DashedIdent;
25use cssparser::{
26    AtRuleParser, CowRcStr, DeclarationParser, Parser, ParserState, QualifiedRuleParser,
27    RuleBodyItemParser, RuleBodyParser, SourceLocation,
28};
29use selectors::parser::SelectorParseErrorKind;
30use std::fmt::{self, Write};
31use style_traits::{Comma, OneOrMoreSeparated};
32use style_traits::{CssStringWriter, CssWriter, ParseError, StyleParseErrorKind, ToCss};
33
34#[allow(missing_docs)]
35#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
36pub struct FontPaletteOverrideColor {
37    index: NonNegativeInteger,
38    color: SpecifiedColor,
39}
40
41impl Parse for FontPaletteOverrideColor {
42    fn parse<'i, 't>(
43        context: &ParserContext,
44        input: &mut Parser<'i, 't>,
45    ) -> Result<FontPaletteOverrideColor, ParseError<'i>> {
46        let index = NonNegativeInteger::parse(context, input)?;
47        let location = input.current_source_location();
48        let color = SpecifiedColor::parse(context, input)?;
49        // Only absolute colors are accepted here:
50        //   https://drafts.csswg.org/css-fonts/#override-color
51        //   https://drafts.csswg.org/css-color-5/#absolute-color
52        // so check that the specified color can be resolved without a context
53        // or currentColor value.
54        if color.resolve_to_absolute().is_some() {
55            // We store the specified color (not the resolved absolute color)
56            // because that is what the rule exposes to authors.
57            return Ok(FontPaletteOverrideColor { index, color });
58        }
59        Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
60    }
61}
62
63impl ToCss for FontPaletteOverrideColor {
64    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
65    where
66        W: fmt::Write,
67    {
68        self.index.to_css(dest)?;
69        dest.write_char(' ')?;
70        self.color.to_css(dest)
71    }
72}
73
74impl OneOrMoreSeparated for FontPaletteOverrideColor {
75    type S = Comma;
76}
77
78impl OneOrMoreSeparated for FamilyName {
79    type S = Comma;
80}
81
82#[allow(missing_docs)]
83#[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
84pub enum FontPaletteBase {
85    Light,
86    Dark,
87    Index(NonNegativeInteger),
88}
89
90/// The [`@font-palette-values`][font-palette-values] at-rule.
91///
92/// [font-palette-values]: https://drafts.csswg.org/css-fonts/#font-palette-values
93#[derive(Clone, Debug, PartialEq, ToShmem)]
94pub struct FontPaletteValuesRule {
95    /// Palette name.
96    pub name: DashedIdent,
97    /// Font family list for @font-palette-values rule.
98    /// Family names cannot contain generic families. FamilyName
99    /// also accepts only non-generic names.
100    pub family_names: Vec<FamilyName>,
101    /// The base palette.
102    pub base_palette: Option<FontPaletteBase>,
103    /// The list of override colors.
104    pub override_colors: Vec<FontPaletteOverrideColor>,
105    /// The line and column of the rule's source code.
106    pub source_location: SourceLocation,
107}
108
109impl FontPaletteValuesRule {
110    /// Creates an empty FontPaletteValuesRule with given location and name.
111    fn new(name: DashedIdent, location: SourceLocation) -> Self {
112        FontPaletteValuesRule {
113            name,
114            family_names: vec![],
115            base_palette: None,
116            override_colors: vec![],
117            source_location: location,
118        }
119    }
120
121    /// Parses a `FontPaletteValuesRule`.
122    pub fn parse(
123        context: &ParserContext,
124        input: &mut Parser,
125        name: DashedIdent,
126        location: SourceLocation,
127    ) -> Self {
128        let mut rule = FontPaletteValuesRule::new(name, location);
129        let mut parser = FontPaletteValuesDeclarationParser {
130            context,
131            rule: &mut rule,
132        };
133        let mut iter = RuleBodyParser::new(input, &mut parser);
134        while let Some(declaration) = iter.next() {
135            if let Err((error, slice)) = declaration {
136                let location = error.location;
137                let error =
138                    ContextualParseError::UnsupportedFontPaletteValuesDescriptor(slice, error);
139                context.log_css_error(location, error);
140            }
141        }
142        rule
143    }
144
145    /// Prints inside of `@font-palette-values` block.
146    fn value_to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
147    where
148        W: Write,
149    {
150        if !self.family_names.is_empty() {
151            dest.write_str("font-family: ")?;
152            self.family_names.to_css(dest)?;
153            dest.write_str("; ")?;
154        }
155        if let Some(base) = &self.base_palette {
156            dest.write_str("base-palette: ")?;
157            base.to_css(dest)?;
158            dest.write_str("; ")?;
159        }
160        if !self.override_colors.is_empty() {
161            dest.write_str("override-colors: ")?;
162            self.override_colors.to_css(dest)?;
163            dest.write_str("; ")?;
164        }
165        Ok(())
166    }
167
168    /// Convert to Gecko FontPaletteValueSet.
169    #[cfg(feature = "gecko")]
170    pub fn to_gecko_palette_value_set(&self, dest: *mut FontPaletteValueSet) {
171        for ref family in self.family_names.iter() {
172            let family = family.name.to_ascii_lowercase();
173            let palette_values = unsafe {
174                Gecko_AppendPaletteValueHashEntry(dest, family.as_ptr(), self.name.0.as_ptr())
175            };
176            if let Some(base_palette) = &self.base_palette {
177                unsafe {
178                    Gecko_SetFontPaletteBase(
179                        palette_values,
180                        match &base_palette {
181                            FontPaletteBase::Light => FontPaletteValueSet_PaletteValues_kLight,
182                            FontPaletteBase::Dark => FontPaletteValueSet_PaletteValues_kDark,
183                            FontPaletteBase::Index(i) => i.0.value() as i32,
184                        },
185                    );
186                }
187            }
188            for c in &self.override_colors {
189                // We checked at parse time that the specified color can be resolved
190                // in this way, so the unwrap() here will succeed.
191                let absolute = c.color.resolve_to_absolute().unwrap();
192                unsafe {
193                    Gecko_SetFontPaletteOverride(
194                        palette_values,
195                        c.index.0.value(),
196                        (&absolute) as *const _ as *mut _,
197                    );
198                }
199            }
200        }
201    }
202}
203
204impl ToCssWithGuard for FontPaletteValuesRule {
205    fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
206        dest.write_str("@font-palette-values ")?;
207        self.name.to_css(&mut CssWriter::new(dest))?;
208        dest.write_str(" { ")?;
209        self.value_to_css(&mut CssWriter::new(dest))?;
210        dest.write_char('}')
211    }
212}
213
214/// Parser for declarations in `FontPaletteValuesRule`.
215struct FontPaletteValuesDeclarationParser<'a> {
216    context: &'a ParserContext<'a>,
217    rule: &'a mut FontPaletteValuesRule,
218}
219
220impl<'a, 'i> AtRuleParser<'i> for FontPaletteValuesDeclarationParser<'a> {
221    type Prelude = ();
222    type AtRule = ();
223    type Error = StyleParseErrorKind<'i>;
224}
225
226impl<'a, 'i> QualifiedRuleParser<'i> for FontPaletteValuesDeclarationParser<'a> {
227    type Prelude = ();
228    type QualifiedRule = ();
229    type Error = StyleParseErrorKind<'i>;
230}
231
232fn parse_override_colors<'i, 't>(
233    context: &ParserContext,
234    input: &mut Parser<'i, 't>,
235) -> Result<Vec<FontPaletteOverrideColor>, ParseError<'i>> {
236    input.parse_comma_separated(|i| FontPaletteOverrideColor::parse(context, i))
237}
238
239impl<'a, 'b, 'i> DeclarationParser<'i> for FontPaletteValuesDeclarationParser<'a> {
240    type Declaration = ();
241    type Error = StyleParseErrorKind<'i>;
242
243    fn parse_value<'t>(
244        &mut self,
245        name: CowRcStr<'i>,
246        input: &mut Parser<'i, 't>,
247        _declaration_start: &ParserState,
248    ) -> Result<(), ParseError<'i>> {
249        match_ignore_ascii_case! { &*name,
250            "font-family" => {
251                self.rule.family_names = parse_family_name_list(self.context, input)?
252            },
253            "base-palette" => {
254                self.rule.base_palette = Some(input.parse_entirely(|i| FontPaletteBase::parse(self.context, i))?)
255            },
256            "override-colors" => {
257                self.rule.override_colors = parse_override_colors(self.context, input)?
258            },
259            _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
260        }
261        Ok(())
262    }
263}
264
265impl<'a, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>>
266    for FontPaletteValuesDeclarationParser<'a>
267{
268    fn parse_declarations(&self) -> bool {
269        true
270    }
271    fn parse_qualified(&self) -> bool {
272        false
273    }
274}