Skip to main content

fleuron/style/
subset.rs

1//! The CSS subset as data.
2//!
3//! A host with a style editor needs to know what the engine accepts
4//! before a sheet is sent: which properties, which values, which
5//! selectors. This module describes that from the tables the parser
6//! dispatches through, so the description and the parser cannot
7//! drift: a property is in the description because the parser reads
8//! it, and reads it because it is in the table.
9//!
10//! The `syntax` strings use CSS value-definition syntax. A bare word
11//! is a keyword, `<name>` is a type, `name()` is a function. The
12//! keywords are also listed on their own, for a host that would
13//! rather complete them than parse the grammar; where one stands in
14//! the grammar is still the grammar's to say.
15
16use serde::{Deserialize, Serialize};
17
18use crate::style::element::ELEMENTS;
19use crate::style::properties::{CounterStyle, MarginBox};
20use crate::style::sheet::{
21    COMBINATORS, COMPOUNDS, CUSTOM_PROPERTY, DECLARATION, FIRST_LINE_PROPERTIES,
22    FONT_FACE_DESCRIPTORS, MARGIN_BOX_PROPERTIES, NAMED, PAGE_PROPERTIES, PAGE_SELECTORS,
23    PAGE_SIZES, PROPERTIES, PSEUDO_CLASSES, PSEUDO_ELEMENTS, SELECTOR_LIST, Spec, UNITS, VAR,
24};
25
26/// What the engine accepts, from the version that produced it.
27#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
28pub struct Subset {
29    /// The engine version this description came from.
30    pub version: String,
31    /// What a style rule may select.
32    pub selectors: Selectors,
33    /// The shape of one declaration, in value-definition syntax.
34    pub declaration: String,
35    /// The shape of one custom property declaration, in
36    /// value-definition syntax.
37    pub custom_property: String,
38    /// The function that puts a custom property's value in a
39    /// declaration, in value-definition syntax.
40    pub var: String,
41    /// The properties a style rule may declare.
42    pub properties: Vec<Property>,
43    /// The `@page` rule.
44    pub page: Page,
45    /// The `@font-face` rule.
46    pub font_face: FontFace,
47    /// The units a `<length>` may carry.
48    pub units: Vec<String>,
49    /// The names a `<color>` may be.
50    pub color_names: Vec<String>,
51}
52
53/// The selector vocabulary.
54#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
55pub struct Selectors {
56    /// The element names, as the content tree produces them.
57    pub elements: Vec<String>,
58    /// What a compound is made of besides pseudo-classes.
59    pub compounds: Vec<Selector>,
60    /// The combinators between two compounds.
61    pub combinators: Vec<Selector>,
62    /// How selectors join in a list.
63    pub list: Selector,
64    /// The pseudo-classes, functional ones with their parentheses.
65    pub pseudo_classes: Vec<Selector>,
66    /// The pseudo-elements.
67    pub pseudo_elements: Vec<Selector>,
68    /// The properties `::first-line` takes, out of the properties a
69    /// style rule may declare.
70    pub first_line_properties: Vec<String>,
71}
72
73/// One piece of selector syntax, with a selector that uses it.
74#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
75pub struct Selector {
76    /// The syntax as it is written.
77    pub name: String,
78    /// A whole selector that parses.
79    pub example: String,
80}
81
82/// One property.
83#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
84pub struct Property {
85    /// The property name.
86    pub name: String,
87    /// Whether a child starts from the parent's value.
88    pub inherited: bool,
89    /// The values it accepts, in CSS value-definition syntax.
90    pub syntax: String,
91    /// The keywords in `syntax`, wherever they stand in it. One that
92    /// only follows another value, like `landscape` after a page
93    /// size, is not a value on its own.
94    pub keywords: Vec<String>,
95    /// Values that parse.
96    pub examples: Vec<String>,
97}
98
99/// One `@font-face` descriptor.
100#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
101pub struct Descriptor {
102    /// The descriptor name.
103    pub name: String,
104    /// The values it accepts, in CSS value-definition syntax.
105    pub syntax: String,
106    /// The keywords in `syntax`, wherever they stand in it.
107    pub keywords: Vec<String>,
108    /// Values that parse.
109    pub examples: Vec<String>,
110}
111
112/// The `@page` rule.
113#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
114pub struct Page {
115    /// What follows `@page`, in value-definition syntax: a page name,
116    /// then any of the page selectors.
117    pub prelude: String,
118    /// The page selectors, without their colon.
119    pub selectors: Vec<String>,
120    /// The properties a page body may declare.
121    pub properties: Vec<Property>,
122    /// The margin boxes a page body may open.
123    pub margin_boxes: Vec<MarginBoxDescription>,
124    /// The properties a margin box declares on top of the style
125    /// properties, which it also accepts.
126    pub margin_box_properties: Vec<Property>,
127    /// The named sheets `size` accepts, portrait.
128    pub sizes: Vec<PageSize>,
129    /// The counter styles `counter(page, ...)` accepts.
130    pub counter_styles: Vec<String>,
131}
132
133/// One margin box.
134#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
135pub struct MarginBoxDescription {
136    /// The at-rule name, without the `@`.
137    pub name: String,
138    /// Whether the engine paints it. A box it does not paint is read
139    /// and dropped.
140    pub paints: bool,
141}
142
143/// One named page size.
144#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
145pub struct PageSize {
146    /// The keyword.
147    pub name: String,
148    /// Width in points, portrait.
149    pub width: f32,
150    /// Height in points, portrait.
151    pub height: f32,
152}
153
154/// The `@font-face` rule.
155#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
156pub struct FontFace {
157    /// The descriptors a face body may declare.
158    pub descriptors: Vec<Descriptor>,
159}
160
161impl Subset {
162    /// The subset this build of the engine accepts.
163    pub fn describe() -> Subset {
164        Subset {
165            version: env!("CARGO_PKG_VERSION").to_string(),
166            selectors: Selectors {
167                elements: strings(&ELEMENTS),
168                compounds: selectors(COMPOUNDS),
169                combinators: selectors(COMBINATORS),
170                list: Selector {
171                    name: SELECTOR_LIST.0.to_string(),
172                    example: SELECTOR_LIST.1.to_string(),
173                },
174                pseudo_classes: selectors(PSEUDO_CLASSES),
175                pseudo_elements: selectors(PSEUDO_ELEMENTS),
176                first_line_properties: strings(FIRST_LINE_PROPERTIES),
177            },
178            declaration: DECLARATION.to_string(),
179            custom_property: CUSTOM_PROPERTY.to_string(),
180            var: VAR.to_string(),
181            properties: properties(PROPERTIES),
182            page: Page {
183                prelude: format!(
184                    "<name>? [ {} ]*",
185                    PAGE_SELECTORS
186                        .iter()
187                        .map(|(name, _)| format!(":{name}"))
188                        .collect::<Vec<_>>()
189                        .join(" | ")
190                ),
191                selectors: PAGE_SELECTORS
192                    .iter()
193                    .map(|(name, _)| name.to_string())
194                    .collect(),
195                properties: properties(PAGE_PROPERTIES),
196                margin_boxes: MarginBox::ALL
197                    .iter()
198                    .map(|margin_box| MarginBoxDescription {
199                        name: margin_box.keyword().to_string(),
200                        paints: margin_box.band().is_some(),
201                    })
202                    .collect(),
203                margin_box_properties: properties(MARGIN_BOX_PROPERTIES),
204                sizes: PAGE_SIZES
205                    .iter()
206                    .map(|(name, (width, height))| PageSize {
207                        name: name.to_string(),
208                        width: *width,
209                        height: *height,
210                    })
211                    .collect(),
212                counter_styles: CounterStyle::ALL
213                    .iter()
214                    .map(|style| style.keyword().to_string())
215                    .collect(),
216            },
217            font_face: FontFace {
218                descriptors: FONT_FACE_DESCRIPTORS
219                    .iter()
220                    .map(|spec| Descriptor {
221                        name: spec.name.to_string(),
222                        syntax: spec.syntax.to_string(),
223                        keywords: keywords(spec.syntax),
224                        examples: strings(spec.examples),
225                    })
226                    .collect(),
227            },
228            units: UNITS.iter().map(|(name, _)| name.to_string()).collect(),
229            color_names: NAMED.iter().map(|(name, _)| name.to_string()).collect(),
230        }
231    }
232}
233
234fn strings(values: &[&str]) -> Vec<String> {
235    values.iter().map(|value| value.to_string()).collect()
236}
237
238fn selectors(table: &[(&str, &str)]) -> Vec<Selector> {
239    table
240        .iter()
241        .map(|(name, example)| Selector {
242            name: name.to_string(),
243            example: example.to_string(),
244        })
245        .collect()
246}
247
248fn properties<D>(specs: &[Spec<D>]) -> Vec<Property> {
249    specs
250        .iter()
251        .map(|spec| Property {
252            name: spec.name.to_string(),
253            inherited: spec.inherited,
254            syntax: spec.syntax.to_string(),
255            keywords: keywords(spec.syntax),
256            examples: strings(spec.examples),
257        })
258        .collect()
259}
260
261/// The keywords in a value-definition: the identifiers that are not
262/// a type, not a function name, and not inside a function's
263/// parentheses or a multiplier's braces.
264fn keywords(syntax: &str) -> Vec<String> {
265    let mut found: Vec<String> = Vec::new();
266    let mut word = String::new();
267    let mut depth = 0u32;
268    let mut in_type = false;
269    let mut in_braces = false;
270    for c in syntax.chars() {
271        if c.is_ascii_alphanumeric() || c == '-' {
272            word.push(c);
273            continue;
274        }
275        if !word.is_empty() {
276            let keyword = !in_type
277                && !in_braces
278                && depth == 0
279                && c != '('
280                && !word.starts_with(|c: char| c.is_ascii_digit());
281            if keyword && !found.contains(&word) {
282                found.push(word.clone());
283            }
284            word.clear();
285        }
286        match c {
287            '<' => in_type = true,
288            '>' => in_type = false,
289            '{' => in_braces = true,
290            '}' => in_braces = false,
291            '(' => depth += 1,
292            ')' => depth = depth.saturating_sub(1),
293            _ => {}
294        }
295    }
296    if !word.is_empty()
297        && !in_type
298        && !in_braces
299        && depth == 0
300        && !word.starts_with(|c: char| c.is_ascii_digit())
301        && !found.contains(&word)
302    {
303        found.push(word);
304    }
305    found
306}
307
308#[cfg(test)]
309mod tests {
310    use super::*;
311    use crate::style::element::ElementTree;
312    use crate::style::properties::{Declaration, Edge};
313    use crate::style::sheet::{FaceDeclaration, MarginDeclaration, PageDeclaration};
314    use cssparser::{CowRcStr, Parser, ParserInput};
315    use std::collections::BTreeSet;
316
317    #[test]
318    fn keywords_skip_types_functions_and_multipliers() {
319        assert_eq!(
320            keywords(
321                "none | <string> | counter(page) | counter(page, <counter-style>) | string(<name>)"
322            ),
323            vec!["none"]
324        );
325        assert_eq!(
326            keywords("<length>{1,2} | <page-size> [ portrait | landscape ]?"),
327            vec!["portrait", "landscape"]
328        );
329        assert_eq!(
330            keywords("normal | bold | <number [1,1000]>"),
331            vec!["normal", "bold"]
332        );
333        assert_eq!(
334            keywords("[ <family-name> | serif | sans-serif | monospace ]#"),
335            vec!["serif", "sans-serif", "monospace"]
336        );
337        assert_eq!(
338            keywords("[ <url> format(<string>)? | local(<string>) ]#"),
339            Vec::<String>::new()
340        );
341    }
342
343    #[test]
344    fn version_is_the_crate_version() {
345        assert_eq!(Subset::describe().version, env!("CARGO_PKG_VERSION"));
346    }
347
348    #[test]
349    fn round_trips_through_json() {
350        let subset = Subset::describe();
351        let json = serde_json::to_string(&subset).unwrap();
352        let read: Subset = serde_json::from_str(&json).unwrap();
353        assert_eq!(read, subset);
354    }
355
356    /// Every example a row names reads, through that row, into a
357    /// declaration of the property the row names: a shorthand into
358    /// its longhands, a longhand into itself.
359    #[test]
360    fn examples_read_into_their_own_property() {
361        fn check<D>(specs: &[Spec<D>], property_of: fn(&D) -> &str) {
362            for spec in specs {
363                for example in spec.examples {
364                    let mut input = ParserInput::new(example);
365                    let mut parser = Parser::new(&mut input);
366                    let name = CowRcStr::from(spec.name);
367                    let declarations = spec
368                        .read(&name, &mut parser)
369                        .unwrap_or_else(|error| panic!("{}: {example}: {error:?}", spec.name));
370                    assert!(
371                        parser.expect_exhausted().is_ok(),
372                        "{}: {example} left input unread",
373                        spec.name
374                    );
375                    assert!(!declarations.is_empty(), "{}: {example}", spec.name);
376                    for declaration in &declarations {
377                        let read = property_of(declaration);
378                        assert!(
379                            longhand_of(read, spec.name),
380                            "{}: {example} read as {read}",
381                            spec.name
382                        );
383                    }
384                }
385            }
386        }
387        check(PROPERTIES, Declaration::property);
388        check(PAGE_PROPERTIES, |declaration| match declaration {
389            PageDeclaration::Size(..) => "size",
390            PageDeclaration::Margin(edge, _) => margin_of(*edge),
391            PageDeclaration::BackgroundColor(_) => "background-color",
392            PageDeclaration::BackgroundImage(_) => "background-image",
393            PageDeclaration::BackgroundRepeat(_) => "background-repeat",
394            PageDeclaration::BackgroundSize(_) => "background-size",
395            PageDeclaration::BackgroundPosition(..) => "background-position",
396            PageDeclaration::ColumnCount(_) => "column-count",
397            PageDeclaration::ColumnWidth(_) => "column-width",
398            PageDeclaration::ColumnGap(_) => "column-gap",
399            PageDeclaration::ColumnRuleWidth(_) => "column-rule-width",
400            PageDeclaration::ColumnRuleStyle(_) => "column-rule-style",
401            PageDeclaration::AlignContent(_) => "align-content",
402            PageDeclaration::Pending(pending) => &pending.property,
403        });
404        check(MARGIN_BOX_PROPERTIES, |declaration| match declaration {
405            MarginDeclaration::Content(_) => "content",
406            MarginDeclaration::Style(declaration) => declaration.property(),
407            MarginDeclaration::Pending(pending) => &pending.property,
408        });
409        check(FONT_FACE_DESCRIPTORS, |declaration| match declaration {
410            FaceDeclaration::Family(_) => "font-family",
411            FaceDeclaration::Style(_) => "font-style",
412            FaceDeclaration::Weight(_) => "font-weight",
413            FaceDeclaration::Src(_) => "src",
414        });
415    }
416
417    /// Whether `read` names a longhand of `shorthand`: its own name,
418    /// or a name carrying every word of it, in order. `border-width`
419    /// and `border-top` are both shorthands of `border-top-width`,
420    /// which is why one prefix is not enough.
421    fn longhand_of(read: &str, shorthand: &str) -> bool {
422        let mut words = read.split('-');
423        shorthand
424            .split('-')
425            .all(|word| words.any(|read| read == word))
426    }
427
428    #[test]
429    fn a_longhand_carries_every_word_of_its_shorthand() {
430        assert!(longhand_of("margin-top", "margin"));
431        assert!(longhand_of("margin-top", "margin-top"));
432        assert!(longhand_of("border-top-width", "border"));
433        assert!(longhand_of("border-top-width", "border-top"));
434        assert!(longhand_of("border-top-width", "border-width"));
435        assert!(!longhand_of("border-top-width", "border-color"));
436        assert!(!longhand_of("margin-top", "padding"));
437    }
438
439    fn margin_of(edge: Edge) -> &'static str {
440        match edge {
441            Edge::Top => "margin-top",
442            Edge::Right => "margin-right",
443            Edge::Bottom => "margin-bottom",
444            Edge::Left => "margin-left",
445        }
446    }
447
448    /// The element names the tree produces are the ones the description
449    /// lists, no more and no fewer.
450    #[test]
451    fn elements_are_the_ones_the_tree_builds() {
452        let inlines = r#"[
453            {"type": "text", "value": "a "},
454            {"type": "emphasis", "children": [{"type": "text", "value": "b"}]},
455            {"type": "strong", "children": [{"type": "text", "value": "c"}]},
456            {"type": "code", "value": "d"},
457            {"type": "link", "url": "x", "children": [{"type": "text", "value": "e"}]},
458            {"type": "strikethrough", "children": [{"type": "text", "value": "f"}]},
459            {"type": "span", "children": [{"type": "text", "value": "g"}]},
460            {"type": "note", "blocks": [{"type": "paragraph", "inlines": [
461                {"type": "text", "value": "h"}
462            ]}]}
463        ]"#;
464        let headings: Vec<String> = (1..=6)
465            .map(|level| {
466                format!(r#"{{"type": "heading", "level": {level}, "inlines": {inlines}}}"#)
467            })
468            .collect();
469        let json = format!(
470            r#"{{"metadata": {{}}, "sections": [{{"blocks": [
471                {},
472                {{"type": "paragraph", "inlines": {inlines}}},
473                {{"type": "blockquote", "blocks": [{{"type": "paragraph", "inlines": {inlines}}}]}},
474                {{"type": "code_block", "text": "fn main() {{}}"}},
475                {{"type": "thematic_break"}},
476                {{"type": "image", "url": "image.png", "alt": "alt"}},
477                {{"type": "list", "items": [{{"blocks": []}}]}},
478                {{"type": "list", "ordered": true, "items": [{{"blocks": []}}]}},
479                {{"type": "table",
480                  "head": [{{"cells": [{{"blocks": []}}]}}],
481                  "body": [{{"cells": [{{"blocks": []}}]}}]}}
482            ]}}]}}"#,
483            headings.join(",\n")
484        );
485        let mut book: crate::content::Book = serde_json::from_str(&json).unwrap();
486        book.assign_node_ids();
487        let built: BTreeSet<&str> = ElementTree::build(&book)
488            .nodes()
489            .iter()
490            .map(|node| node.name)
491            .collect();
492        let listed: BTreeSet<&str> = ELEMENTS.iter().copied().collect();
493        assert_eq!(built, listed);
494    }
495}