Skip to main content

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