Skip to main content

fleuron/style/
element.rs

1//! The content tree as something selectors can match against.
2//!
3//! `selectors` walks a DOM through parent and sibling links the
4//! content tree does not have, so compilation flattens the tree once
5//! into an arena that does. Element names are the markdown vocabulary
6//! spelled the way an author writes them in CSS: `book`, `section`,
7//! `h1`…`h6`, `p`, `blockquote`, `pre`, `hr`, `img`, `ul`, `ol`,
8//! `li`, `table`, `thead`, `tbody`, `tr`, `th`, `td`, `em`, `strong`,
9//! `code`, `a`.
10//!
11//! A code block is a `pre` element. It holds text rather than
12//! inlines, so there is no `code` element inside it; `code` is an
13//! inline code span.
14//!
15//! An element carries the classes and the id the content tree gave
16//! it alongside the name.
17//!
18//! A table's header rows sit in a `thead` and its body rows in a
19//! `tbody`, as HTML puts them, so a row counts among the rows of its
20//! own group. The two groups are elements with no content node behind
21//! them: they pass on what they inherit, and nothing else of theirs
22//! reaches layout.
23//!
24//! An item of a tight list holds its text directly, as HTML writes
25//! `<li>text</li>`. The paragraph that text is in has no element of
26//! its own: its inlines are children of the `li`, and it takes the
27//! style of an anonymous box inside it. So `li > p` reaches only the
28//! paragraphs of a loose list.
29//!
30//! Text runs are not elements, the same as in CSS: they have no style
31//! of their own and never count towards `:first-child`.
32
33use std::borrow::Borrow;
34use std::fmt;
35
36use cssparser::{ToCss, serialize_identifier};
37use precomputed_hash::PrecomputedHash;
38use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
39use selectors::bloom::{BLOOM_HASH_MASK, BloomFilter};
40use selectors::matching::{ElementSelectorFlags, MatchingContext};
41use selectors::parser::{NonTSPseudoClass, PseudoElement as PseudoElementTrait, SelectorImpl};
42use selectors::{Element, OpaqueElement};
43
44use crate::content::{Alignment, Attributes, Block, Book, Inline, ListItem, NodeId, Row};
45
46/// An interned CSS identifier: element name, class, id, namespace.
47///
48/// One type serves every slot `SelectorImpl` asks for; the novel
49/// subset has no namespaces and no attribute selectors, so the
50/// distinctions the trait draws between them do not pay for separate
51/// types.
52#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
53pub struct Atom(pub String);
54
55impl From<&str> for Atom {
56    fn from(value: &str) -> Self {
57        Atom(value.to_string())
58    }
59}
60
61impl From<String> for Atom {
62    fn from(value: String) -> Self {
63        Atom(value)
64    }
65}
66
67impl Borrow<str> for Atom {
68    fn borrow(&self) -> &str {
69        &self.0
70    }
71}
72
73impl ToCss for Atom {
74    fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
75        serialize_identifier(&self.0, dest)
76    }
77}
78
79impl PrecomputedHash for Atom {
80    fn precomputed_hash(&self) -> u32 {
81        fnv(&self.0)
82    }
83}
84
85/// FNV-1a: the bloom filter needs a hash of the name itself, not of
86/// where the string is stored.
87fn fnv(name: &str) -> u32 {
88    let mut hash = 0x811c_9dc5u32;
89    for byte in name.as_bytes() {
90        hash ^= *byte as u32;
91        hash = hash.wrapping_mul(0x0100_0193);
92    }
93    hash
94}
95
96/// The novel subset has no non-tree-structural pseudo-classes:
97/// `:hover` and its relatives describe a document being interacted
98/// with, and a book is not one.
99#[derive(Clone, Debug, Eq, PartialEq)]
100pub enum PseudoClass {}
101
102impl ToCss for PseudoClass {
103    fn to_css<W: fmt::Write>(&self, _dest: &mut W) -> fmt::Result {
104        match *self {}
105    }
106}
107
108impl NonTSPseudoClass for PseudoClass {
109    type Impl = Fleuron;
110
111    fn is_active_or_hover(&self) -> bool {
112        match *self {}
113    }
114
115    fn is_user_action_state(&self) -> bool {
116        match *self {}
117    }
118}
119
120pub use crate::content::PseudoElement;
121
122impl ToCss for PseudoElement {
123    fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
124        match self {
125            PseudoElement::FirstLetter => dest.write_str("::first-letter"),
126            PseudoElement::FirstLine => dest.write_str("::first-line"),
127            PseudoElement::Before => dest.write_str("::before"),
128            PseudoElement::After => dest.write_str("::after"),
129        }
130    }
131}
132
133/// The elements that sit inside a line of text rather than making
134/// one. `::before` and `::after` generate text inside the line on
135/// these.
136pub const INLINE_ELEMENTS: [&str; 6] = ["code", "em", "strong", "a", "s", "span"];
137
138/// The blocks that `::before` and `::after` generate a box inside.
139pub const BLOCK_ELEMENTS: [&str; 16] = [
140    "section",
141    "h1",
142    "h2",
143    "h3",
144    "h4",
145    "h5",
146    "h6",
147    "p",
148    "blockquote",
149    "pre",
150    "hr",
151    "img",
152    "ul",
153    "ol",
154    "li",
155    "table",
156];
157
158impl PseudoElementTrait for PseudoElement {
159    type Impl = Fleuron;
160}
161
162/// The engine's selector flavour.
163#[derive(Clone, Debug, Eq, PartialEq)]
164pub struct Fleuron;
165
166impl SelectorImpl for Fleuron {
167    type ExtraMatchingData<'a> = std::marker::PhantomData<&'a ()>;
168    type AttrValue = Atom;
169    type Identifier = Atom;
170    type LocalName = Atom;
171    type NamespaceUrl = Atom;
172    type NamespacePrefix = Atom;
173    type BorrowedLocalName = Atom;
174    type BorrowedNamespaceUrl = Atom;
175    type NonTSPseudoClass = PseudoClass;
176    type PseudoElement = PseudoElement;
177}
178
179/// Every element name the tree can hold, in the order the content
180/// tree introduces them. A selector names one of these or matches
181/// nothing.
182pub const ELEMENTS: [&str; 30] = [
183    "book",
184    "section",
185    "notes",
186    "note",
187    "h1",
188    "h2",
189    "h3",
190    "h4",
191    "h5",
192    "h6",
193    "p",
194    "blockquote",
195    "pre",
196    "hr",
197    "img",
198    "ul",
199    "ol",
200    "li",
201    "table",
202    "thead",
203    "tbody",
204    "tr",
205    "th",
206    "td",
207    "code",
208    "em",
209    "strong",
210    "a",
211    "s",
212    "span",
213];
214
215/// The id past the last one the book assigned. Text runs hold ids
216/// and are no elements, so the elements alone do not say where the
217/// book's ids end.
218fn past_last_id(book: &Book) -> u32 {
219    book.sections
220        .last()
221        .and_then(|section| book.subtree(section.id))
222        .map(|held| held.end)
223        .unwrap_or(1)
224}
225
226/// One element: a name, an identity in the content tree, and the
227/// links a selector walks.
228#[derive(Debug)]
229pub struct ElementNode {
230    /// The element name as CSS spells it.
231    pub name: &'static str,
232    /// The content node this element stands for.
233    pub id: NodeId,
234    /// The classes and id the sheet names it by.
235    pub attributes: Attributes,
236    pub parent: Option<usize>,
237    pub previous: Option<usize>,
238    pub next: Option<usize>,
239    pub first_child: Option<usize>,
240    /// True for elements with text of their own.
241    pub has_text: bool,
242    /// The alignment the source wrote on a table cell's column, which
243    /// the cascade applies below every rule of the author's.
244    pub align: Option<Alignment>,
245}
246
247/// The content tree flattened into an arena, in document order. Index
248/// 0 is the book.
249#[derive(Debug, Default)]
250pub struct ElementTree {
251    nodes: Vec<ElementNode>,
252    /// The paragraphs that have no element of their own, each with the
253    /// `li` whose anonymous box holds it.
254    anonymous: Vec<(NodeId, usize)>,
255    /// The footnote area, which stands for the foot of every page
256    /// rather than for anything the manuscript wrote.
257    notes: NodeId,
258}
259
260impl ElementTree {
261    /// Flattens one book. Elements come out in document order, so
262    /// index order is reading order.
263    pub fn build(book: &Book) -> ElementTree {
264        let mut tree = ElementTree::default();
265        let root = tree.push(
266            "book",
267            NodeId::UNASSIGNED,
268            &Attributes::default(),
269            None,
270            false,
271        );
272        let sections: Vec<usize> = book
273            .sections
274            .iter()
275            .map(|section| {
276                let index = tree.push(
277                    "section",
278                    section.id,
279                    &section.attributes,
280                    Some(root),
281                    false,
282                );
283                let children = tree.blocks(&section.blocks, index);
284                tree.link(index, &children);
285                index
286            })
287            .collect();
288        tree.link(root, &sections);
289        tree.notes = tree.area(root, past_last_id(book));
290        tree
291    }
292
293    /// The footnote area: the box the notes of a page are set in.
294    ///
295    /// No content node stands behind it, so it takes an id past the
296    /// ones the book assigned. It is not among the children of the
297    /// book, because the sections are the children a sheet counts,
298    /// and it is the book that it inherits from.
299    fn area(&mut self, root: usize, past: u32) -> NodeId {
300        let id = NodeId::new(past);
301        self.push("notes", id, &Attributes::default(), Some(root), false);
302        id
303    }
304
305    /// The id of the footnote area.
306    pub fn notes(&self) -> NodeId {
307        self.notes
308    }
309
310    /// Every element, in document order.
311    pub fn nodes(&self) -> &[ElementNode] {
312        &self.nodes
313    }
314
315    /// The paragraphs of tight list items, each with the index of the
316    /// item that holds it.
317    pub fn anonymous(&self) -> &[(NodeId, usize)] {
318        &self.anonymous
319    }
320
321    /// A handle onto one element, for matching.
322    pub fn at(&self, index: usize) -> ElementRef<'_> {
323        ElementRef { tree: self, index }
324    }
325
326    fn blocks(&mut self, blocks: &[Block], parent: usize) -> Vec<usize> {
327        blocks
328            .iter()
329            .map(|block| match block {
330                Block::Heading {
331                    id,
332                    level,
333                    inlines,
334                    attributes,
335                    ..
336                } => {
337                    let name = match u8::from(*level) {
338                        1 => "h1",
339                        2 => "h2",
340                        3 => "h3",
341                        4 => "h4",
342                        5 => "h5",
343                        _ => "h6",
344                    };
345                    let index = self.push(name, *id, attributes, Some(parent), false);
346                    let (children, has_text) = self.inlines(inlines, index);
347                    self.link(index, &children);
348                    self.nodes[index].has_text = has_text;
349                    index
350                }
351                Block::Paragraph {
352                    id,
353                    inlines,
354                    attributes,
355                    ..
356                } => {
357                    let index = self.push("p", *id, attributes, Some(parent), false);
358                    let (children, has_text) = self.inlines(inlines, index);
359                    self.link(index, &children);
360                    self.nodes[index].has_text = has_text;
361                    index
362                }
363                Block::Blockquote {
364                    id,
365                    blocks,
366                    attributes,
367                    ..
368                } => {
369                    let index = self.push("blockquote", *id, attributes, Some(parent), false);
370                    let children = self.blocks(blocks, index);
371                    self.link(index, &children);
372                    index
373                }
374                Block::CodeBlock {
375                    id,
376                    text,
377                    attributes,
378                    ..
379                } => self.push("pre", *id, attributes, Some(parent), !text.is_empty()),
380                Block::ThematicBreak { id, attributes, .. } => {
381                    self.push("hr", *id, attributes, Some(parent), false)
382                }
383                Block::PageBreak { id, attributes, .. } => {
384                    self.push("pagebreak", *id, attributes, Some(parent), false)
385                }
386                Block::ColumnBreak { id, attributes, .. } => {
387                    self.push("columnbreak", *id, attributes, Some(parent), false)
388                }
389                Block::Image { id, attributes, .. } => {
390                    self.push("img", *id, attributes, Some(parent), false)
391                }
392                Block::List {
393                    id,
394                    ordered,
395                    tight,
396                    items,
397                    attributes,
398                    ..
399                } => {
400                    let name = if *ordered { "ol" } else { "ul" };
401                    let index = self.push(name, *id, attributes, Some(parent), false);
402                    let children = self.items(items, *tight, index);
403                    self.link(index, &children);
404                    index
405                }
406                Block::Table {
407                    id,
408                    head,
409                    body,
410                    attributes,
411                    ..
412                } => {
413                    let index = self.push("table", *id, attributes, Some(parent), false);
414                    let mut groups = Vec::new();
415                    for (group, rows, cell) in [("thead", head, "th"), ("tbody", body, "td")] {
416                        if rows.is_empty() {
417                            continue;
418                        }
419                        let at = self.push(
420                            group,
421                            NodeId::UNASSIGNED,
422                            &Attributes::default(),
423                            Some(index),
424                            false,
425                        );
426                        let children = self.rows(rows, cell, at);
427                        self.link(at, &children);
428                        groups.push(at);
429                    }
430                    self.link(index, &groups);
431                    index
432                }
433            })
434            .collect()
435    }
436
437    /// The `li` elements of one list. In a tight list, a paragraph an
438    /// item holds with no names of its own is not an element.
439    fn items(&mut self, items: &[ListItem], tight: bool, parent: usize) -> Vec<usize> {
440        let mut indices = Vec::with_capacity(items.len());
441        for item in items {
442            let index = self.push("li", item.id, &item.attributes, Some(parent), false);
443            let mut children = Vec::new();
444            for block in &item.blocks {
445                match block {
446                    Block::Paragraph {
447                        id,
448                        inlines,
449                        attributes,
450                        ..
451                    } if tight && attributes.is_empty() => {
452                        let (inline, has_text) = self.inlines(inlines, index);
453                        children.extend(inline);
454                        self.nodes[index].has_text |= has_text;
455                        self.anonymous.push((*id, index));
456                    }
457                    _ => children.extend(self.blocks(std::slice::from_ref(block), index)),
458                }
459            }
460            self.link(index, &children);
461            indices.push(index);
462        }
463        indices
464    }
465
466    /// The rows of one group of a table, each with its cells, which
467    /// are `cell` elements.
468    fn rows(&mut self, rows: &[Row], cell: &'static str, parent: usize) -> Vec<usize> {
469        let mut indices = Vec::with_capacity(rows.len());
470        for row in rows {
471            let index = self.push("tr", row.id, &row.attributes, Some(parent), false);
472            let mut cells = Vec::with_capacity(row.cells.len());
473            for content in &row.cells {
474                let at = self.push(cell, content.id, &content.attributes, Some(index), false);
475                self.nodes[at].align = content.align;
476                let children = self.blocks(&content.blocks, at);
477                self.link(at, &children);
478                cells.push(at);
479            }
480            self.link(index, &cells);
481            indices.push(index);
482        }
483        indices
484    }
485
486    /// The element children of an inline sequence, plus whether any
487    /// text run sits directly inside it — which is what `:empty` asks.
488    fn inlines(&mut self, inlines: &[Inline], parent: usize) -> (Vec<usize>, bool) {
489        let mut children = Vec::new();
490        let mut text = false;
491        for inline in inlines {
492            let (name, id, attributes, nested): (_, _, _, Option<&[Inline]>) = match inline {
493                Inline::Text { value, .. } => {
494                    text |= !value.is_empty();
495                    continue;
496                }
497                Inline::Break { .. } => continue,
498                Inline::Code {
499                    id,
500                    value,
501                    attributes,
502                    ..
503                } => {
504                    let index = self.push("code", *id, attributes, Some(parent), !value.is_empty());
505                    children.push(index);
506                    continue;
507                }
508                Inline::Emphasis {
509                    id,
510                    children,
511                    attributes,
512                    ..
513                } => ("em", *id, attributes, Some(children)),
514                Inline::Strong {
515                    id,
516                    children,
517                    attributes,
518                    ..
519                } => ("strong", *id, attributes, Some(children)),
520                Inline::Link {
521                    id,
522                    children,
523                    attributes,
524                    ..
525                } => ("a", *id, attributes, Some(children)),
526                Inline::Strikethrough {
527                    id,
528                    children,
529                    attributes,
530                    ..
531                } => ("s", *id, attributes, Some(children)),
532                Inline::Span {
533                    id,
534                    children,
535                    attributes,
536                    ..
537                } => ("span", *id, attributes, Some(children)),
538                // A note holds blocks, so its children are blocks of
539                // its own rather than the inlines around it.
540                Inline::Note {
541                    id,
542                    blocks,
543                    attributes,
544                    ..
545                } => {
546                    let index = self.push("note", *id, attributes, Some(parent), false);
547                    let kids = self.blocks(blocks, index);
548                    self.link(index, &kids);
549                    children.push(index);
550                    continue;
551                }
552            };
553            let index = self.push(name, id, attributes, Some(parent), false);
554            if let Some(nested) = nested {
555                let (kids, has_text) = self.inlines(nested, index);
556                self.link(index, &kids);
557                self.nodes[index].has_text = has_text;
558            }
559            children.push(index);
560        }
561        (children, text)
562    }
563
564    fn push(
565        &mut self,
566        name: &'static str,
567        id: NodeId,
568        attributes: &Attributes,
569        parent: Option<usize>,
570        has_text: bool,
571    ) -> usize {
572        self.nodes.push(ElementNode {
573            name,
574            id,
575            attributes: attributes.clone(),
576            parent,
577            previous: None,
578            next: None,
579            first_child: None,
580            has_text,
581            align: None,
582        });
583        self.nodes.len() - 1
584    }
585
586    /// Chains a parent's children: first child, and each one's
587    /// siblings.
588    fn link(&mut self, parent: usize, children: &[usize]) {
589        self.nodes[parent].first_child = children.first().copied();
590        for (position, index) in children.iter().enumerate() {
591            self.nodes[*index].previous = position.checked_sub(1).map(|p| children[p]);
592            self.nodes[*index].next = children.get(position + 1).copied();
593        }
594    }
595}
596
597/// One element of the arena, as `selectors` sees it.
598#[derive(Clone, Copy)]
599pub struct ElementRef<'a> {
600    tree: &'a ElementTree,
601    /// Index into the arena, which is also document order.
602    pub index: usize,
603}
604
605impl ElementRef<'_> {
606    fn node(&self) -> &ElementNode {
607        &self.tree.nodes[self.index]
608    }
609
610    fn sibling(&self, which: fn(&ElementNode) -> Option<usize>) -> Option<Self> {
611        which(self.node()).map(|index| ElementRef {
612            tree: self.tree,
613            index,
614        })
615    }
616}
617
618impl fmt::Debug for ElementRef<'_> {
619    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
620        write!(f, "<{}>", self.node().name)
621    }
622}
623
624impl Element for ElementRef<'_> {
625    type Impl = Fleuron;
626
627    fn opaque(&self) -> OpaqueElement {
628        OpaqueElement::new(self.node())
629    }
630
631    fn parent_element(&self) -> Option<Self> {
632        self.sibling(|node| node.parent)
633    }
634
635    fn parent_node_is_shadow_root(&self) -> bool {
636        false
637    }
638
639    fn containing_shadow_host(&self) -> Option<Self> {
640        None
641    }
642
643    fn is_pseudo_element(&self) -> bool {
644        false
645    }
646
647    fn prev_sibling_element(&self) -> Option<Self> {
648        self.sibling(|node| node.previous)
649    }
650
651    fn next_sibling_element(&self) -> Option<Self> {
652        self.sibling(|node| node.next)
653    }
654
655    fn first_element_child(&self) -> Option<Self> {
656        self.sibling(|node| node.first_child)
657    }
658
659    fn is_html_element_in_html_document(&self) -> bool {
660        false
661    }
662
663    fn has_local_name(&self, name: &Atom) -> bool {
664        self.node().name == name.0
665    }
666
667    fn has_namespace(&self, ns: &Atom) -> bool {
668        ns.0.is_empty()
669    }
670
671    fn is_same_type(&self, other: &Self) -> bool {
672        self.node().name == other.node().name
673    }
674
675    fn attr_matches(
676        &self,
677        _ns: &NamespaceConstraint<&Atom>,
678        _local_name: &Atom,
679        _operation: &AttrSelectorOperation<&Atom>,
680    ) -> bool {
681        false
682    }
683
684    fn match_non_ts_pseudo_class(
685        &self,
686        pc: &PseudoClass,
687        _context: &mut MatchingContext<Fleuron>,
688    ) -> bool {
689        match *pc {}
690    }
691
692    /// Nothing in the content tree *is* a pseudo-element: a rule that
693    /// names one is matched against its originating element instead,
694    /// in `MatchingMode::ForStatelessPseudoElement`.
695    fn match_pseudo_element(
696        &self,
697        _pe: &PseudoElement,
698        _context: &mut MatchingContext<Fleuron>,
699    ) -> bool {
700        false
701    }
702
703    fn apply_selector_flags(&self, _flags: ElementSelectorFlags) {}
704
705    fn is_link(&self) -> bool {
706        self.node().name == "a"
707    }
708
709    fn is_html_slot_element(&self) -> bool {
710        false
711    }
712
713    fn has_id(&self, id: &Atom, case_sensitivity: CaseSensitivity) -> bool {
714        self.node()
715            .attributes
716            .id
717            .as_ref()
718            .is_some_and(|mine| case_sensitivity.eq(mine.as_bytes(), id.0.as_bytes()))
719    }
720
721    fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
722        self.node()
723            .attributes
724            .classes
725            .iter()
726            .any(|mine| case_sensitivity.eq(mine.as_bytes(), name.0.as_bytes()))
727    }
728
729    fn has_custom_state(&self, _name: &Atom) -> bool {
730        false
731    }
732
733    fn imported_part(&self, _name: &Atom) -> Option<Atom> {
734        None
735    }
736
737    fn is_part(&self, _name: &Atom) -> bool {
738        false
739    }
740
741    fn is_empty(&self) -> bool {
742        self.node().first_child.is_none() && !self.node().has_text
743    }
744
745    fn is_root(&self) -> bool {
746        self.node().parent.is_none()
747    }
748
749    /// The hashes a `:has()` filter tests an ancestor by: the name a
750    /// selector reaches this element under, and the names a sheet
751    /// gives it.
752    fn add_element_unique_hashes(&self, filter: &mut BloomFilter) -> bool {
753        let node = self.node();
754        let mut insert = |name: &str| filter.insert_hash(fnv(name) & BLOOM_HASH_MASK);
755        insert(node.name);
756        if let Some(id) = &node.attributes.id {
757            insert(id);
758        }
759        for class in &node.attributes.classes {
760            insert(class);
761        }
762        true
763    }
764}