1use super::prelude::*;
2use crate::{FontFamilyName, UnknownAtRule};
3use css_parse::RuleVariants;
4use csskit_proc_macro::syntax;
5
6#[node]
16#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
18#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
19#[cfg_attr(
20 feature = "css_feature_data",
21 derive(::csskit_derives::ToCSSFeature),
22 css_feature("css.at-rules.font-feature-values")
23)]
24#[derive(csskit_derives::NodeWithMetadata)]
25#[metadata(node_kinds = AtRule, used_at_rules = FontFeatureValues)]
26pub struct FontFeatureValuesRule<'a> {
27 #[cfg_attr(feature = "visitable", visit(skip))]
28 #[atom(CssAtomSet::FontFeatureValues)]
29 pub name: T![AtKeyword],
30 pub prelude: FontFeatureValuesPrelude<'a>,
31 #[metadata(block)]
32 pub block: FontFeatureValuesRuleBlock<'a>,
33}
34
35#[node]
36#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
38#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
39#[derive(csskit_derives::NodeWithMetadata)]
40pub struct FontFeatureValuesPrelude<'a>(pub CommaSeparated<'a, FontFamilyName<'a>>);
41
42#[node]
43#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
44#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
45#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
46#[derive(csskit_derives::NodeWithMetadata)]
47pub struct FontFeatureValuesRuleBlock<'a>(
48 Block<'a, FontFeatureValuesRuleStyleValue<'a>, FontFeatureValueTypeRule<'a>, CssMetadata>,
49);
50
51#[node]
53#[derive(ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
55#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
56#[derive(csskit_derives::NodeWithMetadata)]
57pub enum FontFeatureValuesRuleStyleValue<'a> {
58 FontDisplay(FontDisplayValue),
59 Unknown(ComponentValues<'a>),
60}
61
62impl<'a> DeclarationValue<'a, CssMetadata> for FontFeatureValuesRuleStyleValue<'a> {
63 fn valid_declaration_name<I>(p: &Parser<'a, I>, c: Cursor) -> bool
64 where
65 I: Iterator<Item = Cursor> + Clone,
66 {
67 matches!(p.to_atom::<CssAtomSet>(c), CssAtomSet::FontDisplay)
68 }
69
70 fn is_unknown(&self) -> bool {
71 matches!(self, Self::Unknown(_))
72 }
73
74 fn needs_computing(&self) -> bool {
75 matches!(self, Self::Unknown(_))
76 }
77
78 fn parse_declaration_value<I>(p: &mut Parser<'a, I>, c: Cursor) -> ParserResult<Self>
79 where
80 I: Iterator<Item = Cursor> + Clone,
81 {
82 Ok(match p.to_atom::<CssAtomSet>(c) {
83 CssAtomSet::FontDisplay => Self::FontDisplay(p.parse::<FontDisplayValue>()?),
84 _ => Self::Unknown(p.parse::<ComponentValues<'a>>()?),
85 })
86 }
87}
88
89#[node]
91#[derive(
92 Parse, Peek, IntoCursor, ToSpan, ToCursors, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
93)]
94#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
95#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
96#[derive(csskit_derives::NodeWithMetadata)]
97pub enum FontDisplayValue {
98 #[atom(CssAtomSet::Auto)]
99 Auto(T![Ident]),
100 #[atom(CssAtomSet::Block)]
101 Block(T![Ident]),
102 #[atom(CssAtomSet::Swap)]
103 Swap(T![Ident]),
104 #[atom(CssAtomSet::Fallback)]
105 Fallback(T![Ident]),
106 #[atom(CssAtomSet::Optional)]
107 Optional(T![Ident]),
108}
109
110#[node]
125#[derive(ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
126#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
127#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
128pub enum FontFeatureValueTypeRule<'a> {
129 Stylistic(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], FontFeatureValueBlock<'a>),
130 HistoricalForms(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], FontFeatureValueBlock<'a>),
131 Styleset(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], FontFeatureValueListBlock<'a>),
132 CharacterVariant(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], FontFeatureValuePairBlock<'a>),
133 Swash(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], FontFeatureValueBlock<'a>),
134 Ornaments(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], FontFeatureValueBlock<'a>),
135 Annotation(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], FontFeatureValueBlock<'a>),
136 UnknownAt(UnknownAtRule<'a>),
137}
138
139impl<'a> NodeWithMetadata<CssMetadata> for FontFeatureValueTypeRule<'a> {
140 fn metadata(&self) -> CssMetadata {
141 match self {
142 Self::Stylistic(_, block)
143 | Self::HistoricalForms(_, block)
144 | Self::Swash(_, block)
145 | Self::Ornaments(_, block)
146 | Self::Annotation(_, block) => block.metadata(),
147 Self::Styleset(_, block) => block.metadata(),
148 Self::CharacterVariant(_, block) => block.metadata(),
149 Self::UnknownAt(rule) => rule.metadata(),
150 }
151 }
152}
153
154impl<'a> RuleVariants<'a> for FontFeatureValueTypeRule<'a> {
155 type DeclarationValue = FontFeatureValuesRuleStyleValue<'a>;
156 type Metadata = CssMetadata;
157
158 fn parse_at_rule<I>(p: &mut Parser<'a, I>, name: Cursor) -> ParserResult<Self>
159 where
160 I: Iterator<Item = Cursor> + Clone,
161 {
162 let at_keyword = p.parse::<T![AtKeyword]>()?;
163 Ok(match p.to_atom::<CssAtomSet>(name) {
164 CssAtomSet::Stylistic => Self::Stylistic(at_keyword, p.parse::<FontFeatureValueBlock<'a>>()?),
165 CssAtomSet::HistoricalForms => Self::HistoricalForms(at_keyword, p.parse::<FontFeatureValueBlock<'a>>()?),
166 CssAtomSet::Styleset => Self::Styleset(at_keyword, p.parse::<FontFeatureValueListBlock<'a>>()?),
167 CssAtomSet::CharacterVariant => {
168 Self::CharacterVariant(at_keyword, p.parse::<FontFeatureValuePairBlock<'a>>()?)
169 }
170 CssAtomSet::Swash => Self::Swash(at_keyword, p.parse::<FontFeatureValueBlock<'a>>()?),
171 CssAtomSet::Ornaments => Self::Ornaments(at_keyword, p.parse::<FontFeatureValueBlock<'a>>()?),
172 CssAtomSet::Annotation => Self::Annotation(at_keyword, p.parse::<FontFeatureValueBlock<'a>>()?),
173 _ => Err(Diagnostic::new(name, Diagnostic::unexpected_at_rule))?,
174 })
175 }
176
177 fn parse_unknown_at_rule<I>(p: &mut Parser<'a, I>, _: Cursor) -> ParserResult<Self>
178 where
179 I: Iterator<Item = Cursor> + Clone,
180 {
181 p.parse::<UnknownAtRule>().map(Self::UnknownAt)
182 }
183
184 fn is_unknown(&self) -> bool {
185 matches!(self, Self::UnknownAt(_))
186 }
187}
188
189impl<'a> Parse<'a> for FontFeatureValueTypeRule<'a> {
190 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
191 where
192 I: Iterator<Item = Cursor> + Clone,
193 {
194 Self::parse_rule_variants(p)
195 }
196}
197
198#[node]
201#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
202#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
203#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
204#[derive(csskit_derives::NodeWithMetadata)]
205pub struct FontFeatureValueBlock<'a>(DeclarationList<'a, FontFeatureValue<'a>, CssMetadata>);
206
207#[node]
209#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
210#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
211#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
212#[derive(csskit_derives::NodeWithMetadata)]
213pub struct FontFeatureValuePairBlock<'a>(DeclarationList<'a, FontFeatureValuePair<'a>, CssMetadata>);
214
215#[node]
217#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
218#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
219#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
220#[derive(csskit_derives::NodeWithMetadata)]
221pub struct FontFeatureValueListBlock<'a>(DeclarationList<'a, FontFeatureValueList<'a>, CssMetadata>);
222
223#[syntax(" <integer [0,∞]> ")]
227#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
228#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
229#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
230#[derive(csskit_derives::NodeWithMetadata)]
231pub struct FontFeatureIndex<'a>;
232
233#[syntax(" <integer [0,∞]>{1,2} ")]
237#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
238#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
239#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
240#[derive(csskit_derives::NodeWithMetadata)]
241pub struct FontFeatureIndexPair<'a>;
242
243#[syntax(" <integer [0,∞]>+ ")]
247#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
248#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
249#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
250#[derive(csskit_derives::NodeWithMetadata)]
251pub struct FontFeatureIndexList<'a>;
252
253#[node]
255#[derive(ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
256#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
257#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
258pub enum FontFeatureValue<'a> {
259 Index(FontFeatureIndex<'a>),
260 Unknown(ComponentValues<'a>),
261}
262
263#[node]
265#[derive(ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
266#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
267#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
268pub enum FontFeatureValuePair<'a> {
269 Index(FontFeatureIndexPair<'a>),
270 Unknown(ComponentValues<'a>),
271}
272
273#[node]
275#[derive(ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
276#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
277#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
278pub enum FontFeatureValueList<'a> {
279 Index(FontFeatureIndexList<'a>),
280 Unknown(ComponentValues<'a>),
281}
282
283macro_rules! font_feature_value {
284 ($value: ident, $index: ident) => {
285 impl<'a> DeclarationValue<'a, CssMetadata> for $value<'a> {
286 fn is_unknown(&self) -> bool {
287 matches!(self, Self::Unknown(_))
288 }
289
290 fn needs_computing(&self) -> bool {
291 matches!(self, Self::Unknown(_))
292 }
293
294 fn parse_specified_declaration_value<I>(p: &mut Parser<'a, I>, _: Cursor) -> ParserResult<Self>
295 where
296 I: Iterator<Item = Cursor> + Clone,
297 {
298 Ok(Self::Index(p.parse::<$index<'a>>()?))
299 }
300
301 fn parse_unknown_declaration_value<I>(p: &mut Parser<'a, I>, _: Cursor) -> ParserResult<Self>
302 where
303 I: Iterator<Item = Cursor> + Clone,
304 {
305 Ok(Self::Unknown(p.parse::<ComponentValues<'a>>()?))
306 }
307 }
308
309 impl<'a> NodeWithMetadata<CssMetadata> for $value<'a> {
310 fn metadata(&self) -> CssMetadata {
311 match self {
312 Self::Index(index) => index.metadata(),
313 Self::Unknown(_) => CssMetadata::default(),
314 }
315 }
316 }
317 };
318}
319
320font_feature_value!(FontFeatureValue, FontFeatureIndex);
321font_feature_value!(FontFeatureValuePair, FontFeatureIndexPair);
322font_feature_value!(FontFeatureValueList, FontFeatureIndexList);
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327 use crate::CssAtomSet;
328 use css_parse::assert_parse;
329
330 #[test]
331 fn test_writes() {
332 assert_parse!(CssAtomSet::ATOMS, FontFeatureValuesRule, "@font-feature-values Taisho Gothic{}");
333 assert_parse!(
334 CssAtomSet::ATOMS,
335 FontFeatureValuesRule,
336 "@font-feature-values Otaru Kisa{@annotation{circled:1;black-boxed:3}}"
337 );
338 assert_parse!(
339 CssAtomSet::ATOMS,
340 FontFeatureValuesRule,
341 "@font-feature-values \"Otaru Kisa\",Bongo{font-display:swap;@swash{ornate:1}}"
342 );
343 assert_parse!(
344 CssAtomSet::ATOMS,
345 FontFeatureValuesRule,
346 "@font-feature-values Mars Serif{@styleset{alt-g:1;code:4 5}}"
347 );
348 assert_parse!(
349 CssAtomSet::ATOMS,
350 FontFeatureValuesRule,
351 "@font-feature-values MM Greek{@character-variant{alpha-2:1 2;gamma:12}}"
352 );
353 assert_parse!(
354 CssAtomSet::ATOMS,
355 FontFeatureValuesRule,
356 "@font-feature-values Bongo{@stylistic{foo:1}@historical-forms{bar:2}@ornaments{baz:3}}"
357 );
358 assert_parse!(
360 CssAtomSet::ATOMS,
361 FontFeatureValuesRule,
362 "@font-feature-values Bongo{@swash{ornate:1}@nope{x:1}}"
363 );
364 assert_parse!(CssAtomSet::ATOMS, FontFeatureValuesRule, "@font-feature-values Bongo{@swash{swishy:3 5}}");
366 }
367}