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