Skip to main content

lightningcss/rules/
font_feature_values.rs

1//! The `@font-feature-values` rule.
2
3use super::Location;
4use crate::error::{ParserError, PrinterError};
5use crate::parser::ParserOptions;
6use crate::printer::Printer;
7use crate::properties::font::FamilyName;
8use crate::traits::{Parse, ToCss};
9use crate::values::ident::Ident;
10use crate::values::number::CSSInteger;
11#[cfg(feature = "visitor")]
12use crate::visitor::Visit;
13use cssparser::*;
14use indexmap::IndexMap;
15use smallvec::SmallVec;
16use std::fmt::Write;
17
18/// A [@font-feature-values](https://drafts.csswg.org/css-fonts/#font-feature-values) rule.
19#[derive(Debug, PartialEq, Clone)]
20#[cfg_attr(feature = "visitor", derive(Visit))]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
23#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
24pub struct FontFeatureValuesRule<'i> {
25  /// The name of the font feature values.
26  #[cfg_attr(feature = "serde", serde(borrow))]
27  pub name: Vec<FamilyName<'i>>,
28  /// The rules within the `@font-feature-values` rule.
29  pub rules: IndexMap<FontFeatureSubruleType, FontFeatureSubrule<'i>>,
30  /// The location of the rule in the source file.
31  #[cfg_attr(feature = "visitor", skip_visit)]
32  pub loc: Location,
33}
34
35impl<'i> FontFeatureValuesRule<'i> {
36  pub(crate) fn parse<'t>(
37    family_names: Vec<FamilyName<'i>>,
38    input: &mut Parser<'i, 't>,
39    loc: Location,
40    options: &ParserOptions<'i>,
41  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
42    let mut rules = IndexMap::new();
43    let mut rule_parser = FontFeatureValuesRuleParser {
44      rules: &mut rules,
45      options,
46    };
47    let mut parser = RuleBodyParser::new(input, &mut rule_parser);
48
49    while let Some(decl_or_rule) = parser.next() {
50      if let Err((err, _)) = decl_or_rule {
51        if parser.parser.options.error_recovery {
52          parser.parser.options.warn(err);
53          continue;
54        }
55        return Err(err);
56      }
57    }
58
59    Ok(FontFeatureValuesRule {
60      name: family_names,
61      rules,
62      loc,
63    })
64  }
65}
66
67struct FontFeatureValuesRuleParser<'a, 'i> {
68  rules: &'a mut IndexMap<FontFeatureSubruleType, FontFeatureSubrule<'i>>,
69  options: &'a ParserOptions<'i>,
70}
71
72impl<'a, 'i> cssparser::DeclarationParser<'i> for FontFeatureValuesRuleParser<'a, 'i> {
73  type Declaration = ();
74  type Error = ParserError<'i>;
75}
76
77impl<'a, 'i> cssparser::AtRuleParser<'i> for FontFeatureValuesRuleParser<'a, 'i> {
78  type Prelude = FontFeatureSubruleType;
79  type AtRule = ();
80  type Error = ParserError<'i>;
81
82  fn parse_prelude<'t>(
83    &mut self,
84    name: CowRcStr<'i>,
85    input: &mut Parser<'i, 't>,
86  ) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {
87    let loc = input.current_source_location();
88    FontFeatureSubruleType::parse_string(&name)
89      .map_err(|_| loc.new_custom_error(ParserError::AtRuleInvalid(name.clone().into())))
90  }
91
92  fn parse_block<'t>(
93    &mut self,
94    prelude: Self::Prelude,
95    start: &ParserState,
96    input: &mut Parser<'i, 't>,
97  ) -> Result<Self::AtRule, ParseError<'i, Self::Error>> {
98    let loc = start.source_location();
99    let mut decls = IndexMap::new();
100    let mut has_existing = false;
101    let declarations = if let Some(rule) = self.rules.get_mut(&prelude) {
102      has_existing = true;
103      &mut rule.declarations
104    } else {
105      &mut decls
106    };
107    let mut decl_parser = FontFeatureDeclarationParser { declarations };
108    let mut parser = RuleBodyParser::new(input, &mut decl_parser);
109    while let Some(decl) = parser.next() {
110      if let Err((err, _)) = decl {
111        if self.options.error_recovery {
112          self.options.warn(err);
113          continue;
114        }
115        return Err(err);
116      }
117    }
118
119    if !has_existing {
120      self.rules.insert(
121        prelude,
122        FontFeatureSubrule {
123          name: prelude,
124          declarations: decls,
125          loc: Location {
126            source_index: self.options.source_index,
127            line: loc.line,
128            column: loc.column,
129          },
130        },
131      );
132    }
133
134    Ok(())
135  }
136}
137
138impl<'a, 'i> QualifiedRuleParser<'i> for FontFeatureValuesRuleParser<'a, 'i> {
139  type Prelude = ();
140  type QualifiedRule = ();
141  type Error = ParserError<'i>;
142}
143
144impl<'a, 'i> RuleBodyItemParser<'i, (), ParserError<'i>> for FontFeatureValuesRuleParser<'a, 'i> {
145  fn parse_declarations(&self) -> bool {
146    false
147  }
148
149  fn parse_qualified(&self) -> bool {
150    false
151  }
152}
153
154impl<'i> ToCss for FontFeatureValuesRule<'i> {
155  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
156  where
157    W: std::fmt::Write,
158  {
159    #[cfg(feature = "sourcemap")]
160    dest.add_mapping(self.loc);
161    dest.write_str("@font-feature-values ")?;
162    self.name.to_css(dest)?;
163    dest.whitespace()?;
164    dest.write_char('{')?;
165    if !self.rules.is_empty() {
166      dest.newline()?;
167      for rule in self.rules.values() {
168        rule.to_css(dest)?;
169        dest.newline()?;
170      }
171    }
172    dest.write_char('}')
173  }
174}
175
176impl<'i> FontFeatureValuesRule<'i> {
177  pub(crate) fn merge(&mut self, other: &FontFeatureValuesRule<'i>) {
178    debug_assert_eq!(self.name, other.name);
179    for (prelude, rule) in &other.rules {
180      if let Some(existing) = self.rules.get_mut(prelude) {
181        existing
182          .declarations
183          .extend(rule.declarations.iter().map(|(k, v)| (k.clone(), v.clone())));
184      } else {
185        self.rules.insert(*prelude, rule.clone());
186      }
187    }
188  }
189}
190
191/// The name of the `@font-feature-values` sub-rule.
192/// font-feature-value-type = <@stylistic> | <@historical-forms> | <@styleset> | <@character-variant>
193///   | <@swash> | <@ornaments> | <@annotation>
194#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Parse, ToCss)]
195#[cfg_attr(feature = "visitor", derive(Visit))]
196#[cfg_attr(
197  feature = "serde",
198  derive(serde::Serialize, serde::Deserialize),
199  serde(rename_all = "kebab-case")
200)]
201#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
202#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
203pub enum FontFeatureSubruleType {
204  /// @stylistic = @stylistic { <declaration-list> }
205  Stylistic,
206  /// @historical-forms = @historical-forms { <declaration-list> }
207  HistoricalForms,
208  /// @styleset = @styleset { <declaration-list> }
209  Styleset,
210  /// @character-variant = @character-variant { <declaration-list> }
211  CharacterVariant,
212  /// @swash = @swash { <declaration-list> }
213  Swash,
214  /// @ornaments = @ornaments { <declaration-list> }
215  Ornaments,
216  /// @annotation = @annotation { <declaration-list> }
217  Annotation,
218}
219
220/// A sub-rule of `@font-feature-values`
221/// https://drafts.csswg.org/css-fonts/#font-feature-values-syntax
222#[derive(Debug, PartialEq, Clone)]
223#[cfg_attr(feature = "visitor", derive(Visit))]
224#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
225#[cfg_attr(
226  feature = "serde",
227  derive(serde::Serialize, serde::Deserialize),
228  serde(rename_all = "camelCase")
229)]
230#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
231pub struct FontFeatureSubrule<'i> {
232  /// The name of the `@font-feature-values` sub-rule.
233  pub name: FontFeatureSubruleType,
234  /// The declarations within the `@font-feature-values` sub-rules.
235  #[cfg_attr(feature = "serde", serde(borrow))]
236  pub declarations: IndexMap<Ident<'i>, SmallVec<[CSSInteger; 1]>>,
237  /// The location of the rule in the source file.
238  #[cfg_attr(feature = "visitor", skip_visit)]
239  pub loc: Location,
240}
241
242impl<'i> ToCss for FontFeatureSubrule<'i> {
243  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
244  where
245    W: Write,
246  {
247    #[cfg(feature = "sourcemap")]
248    dest.add_mapping(self.loc);
249    dest.write_char('@')?;
250    self.name.to_css(dest)?;
251    dest.write_char('{')?;
252    dest.indent();
253    let len = self.declarations.len();
254    for (i, (name, value)) in self.declarations.iter().enumerate() {
255      dest.newline()?;
256      name.to_css(dest)?;
257      dest.delim(':', false)?;
258
259      let mut first = true;
260      for index in value {
261        if first {
262          first = false;
263        } else {
264          dest.write_char(' ')?;
265        }
266        index.to_css(dest)?;
267      }
268
269      if i != len - 1 || !dest.minify {
270        dest.write_char(';')?;
271      }
272    }
273    dest.dedent();
274    dest.newline()?;
275    dest.write_char('}')
276  }
277}
278
279struct FontFeatureDeclarationParser<'a, 'i> {
280  declarations: &'a mut IndexMap<Ident<'i>, SmallVec<[CSSInteger; 1]>>,
281}
282
283impl<'a, 'i> cssparser::DeclarationParser<'i> for FontFeatureDeclarationParser<'a, 'i> {
284  type Declaration = ();
285  type Error = ParserError<'i>;
286
287  fn parse_value<'t>(
288    &mut self,
289    name: CowRcStr<'i>,
290    input: &mut cssparser::Parser<'i, 't>,
291    _declaration_start: &ParserState,
292  ) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {
293    let mut indices = SmallVec::new();
294    loop {
295      if let Ok(value) = CSSInteger::parse(input) {
296        indices.push(value);
297      } else {
298        break;
299      }
300    }
301
302    if indices.is_empty() {
303      return Err(input.new_custom_error(ParserError::InvalidValue));
304    }
305
306    self.declarations.insert(Ident(name.into()), indices);
307    Ok(())
308  }
309}
310
311/// Default methods reject all at rules.
312impl<'a, 'i> AtRuleParser<'i> for FontFeatureDeclarationParser<'a, 'i> {
313  type Prelude = ();
314  type AtRule = ();
315  type Error = ParserError<'i>;
316}
317
318impl<'a, 'i> QualifiedRuleParser<'i> for FontFeatureDeclarationParser<'a, 'i> {
319  type Prelude = ();
320  type QualifiedRule = ();
321  type Error = ParserError<'i>;
322}
323
324impl<'a, 'i> RuleBodyItemParser<'i, (), ParserError<'i>> for FontFeatureDeclarationParser<'a, 'i> {
325  fn parse_qualified(&self) -> bool {
326    false
327  }
328
329  fn parse_declarations(&self) -> bool {
330    true
331  }
332}