Skip to main content

css_ast/rules/
font_feature_values.rs

1use super::prelude::*;
2use crate::{FontFamilyName, UnknownAtRule};
3use css_parse::RuleVariants;
4use csskit_proc_macro::syntax;
5
6/// Represents the `@font-feature-values` at-rule, e.g. `@font-feature-values Taisho Gothic { @annotation { boxed: 1 } }`.
7///
8/// ```md
9/// <font-feature-values-rule>
10///  │├─ "@font-feature-values" ─╭─ <font-family-name> ─ "," ─╮─ <block> ─┤│
11///                              ╰────────────────────────────╯
12/// ```
13///
14/// <https://drafts.csswg.org/css-fonts/#at-ruledef-font-feature-values>
15#[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/// The descriptors allowed directly inside an [`FontFeatureValuesRule`] block.
52#[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/// <https://drafts.csswg.org/css-fonts/#descdef-font-feature-values-font-display>
90#[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/// The _feature value blocks_ - subsidiary at-rules of [`FontFeatureValuesRule`].
111///
112/// ```md
113/// <font-feature-value-type>
114///  │├─╭─ "@stylistic" ────────── <block> ─╮─┤│
115///     ├─ "@historical-forms" ─── <block> ─┤
116///     ├─ "@styleset" ─────────── <block> ─┤
117///     ├─ "@character-variant" ── <block> ─┤
118///     ├─ "@swash" ────────────── <block> ─┤
119///     ├─ "@ornaments" ────────── <block> ─┤
120///     ╰─ "@annotation" ───────── <block> ─╯
121/// ```
122///
123/// <https://drafts.csswg.org/css-fonts/#typedef-font-feature-values-font-feature-value-type>
124#[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/// The block of `@stylistic`, `@historical-forms`, `@swash`, `@ornaments` and `@annotation`, each declaration of which
199/// takes a single `<font-feature-index>`.
200#[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/// The block of `@character-variant`, each declaration of which takes one or two `<font-feature-index>`es.
208#[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/// The block of `@styleset`, each declaration of which takes any number of `<font-feature-index>`es.
216#[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/// ```text,ignore
224/// <font-feature-index> = <integer [0,∞]>
225/// ```
226#[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/// ```text,ignore
234/// <font-feature-index>{1,2}
235/// ```
236#[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/// ```text,ignore
244/// <font-feature-index>+
245/// ```
246#[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/// A _font feature value declaration_ taking a single `<font-feature-index>`.
254#[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/// A _font feature value declaration_ taking one or two `<font-feature-index>`es.
264#[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/// A _font feature value declaration_ taking any number of `<font-feature-index>`es.
274#[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		// An unknown at-rule within the block is invalid and ignored, but does not invalidate the rule.
359		assert_parse!(
360			CssAtomSet::ATOMS,
361			FontFeatureValuesRule,
362			"@font-feature-values Bongo{@swash{ornate:1}@nope{x:1}}"
363		);
364		// A syntax error in a font feature value declaration does not invalidate the block.
365		assert_parse!(CssAtomSet::ATOMS, FontFeatureValuesRule, "@font-feature-values Bongo{@swash{swishy:3 5}}");
366	}
367}