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