float_pigment_css/sheet/
mod.rs

1//! The style sheet data structures.
2
3use alloc::{boxed::Box, rc::Rc, string::String, vec::Vec};
4use core::{cell::RefCell, num::NonZeroUsize};
5use parser::WarningKind;
6
7use hashbrown::HashMap;
8
9use super::property::*;
10use super::query::*;
11use super::*;
12use crate::group::StyleSheetResource;
13use crate::length_num::LengthNum;
14use crate::parser::Warning;
15
16mod selector;
17pub use selector::PseudoElements;
18pub(crate) use selector::{
19    Attribute, AttributeFlags, AttributeOperator, PseudoClasses, Selector, SelectorFragment,
20    SelectorRelationType, SELECTOR_WHITESPACE,
21};
22mod rule;
23pub use rule::Rule;
24mod media;
25pub use media::*;
26pub(crate) mod keyframes;
27pub use keyframes::*;
28mod font_face;
29pub use font_face::*;
30pub mod borrow;
31pub mod borrow_resource;
32pub mod str_store;
33pub use rule::PropertyMeta;
34
35#[derive(Debug, Clone)]
36pub(crate) struct CompiledStyleSheet {
37    imports: Vec<(String, Option<Rc<Media>>)>,
38    linked: bool,
39    ss: Rc<RefCell<StyleSheet>>,
40}
41
42impl CompiledStyleSheet {
43    pub(crate) fn new() -> Self {
44        Self {
45            imports: Vec::with_capacity(0),
46            linked: false,
47            ss: Rc::new(RefCell::new(StyleSheet {
48                rules: vec![],
49                index: StyleSheetIndex::NeedUpdate,
50                font_face: vec![],
51                keyframes: vec![],
52            })),
53        }
54    }
55
56    #[cfg(feature = "deserialize")]
57    pub(crate) fn new_with_config(
58        imports: Vec<(String, Option<Rc<Media>>)>,
59        rules: Vec<Rc<Rule>>,
60        font_face: Vec<Rc<FontFace>>,
61        keyframes: Vec<Rc<KeyFrames>>,
62    ) -> Self {
63        Self {
64            imports,
65            linked: false,
66            ss: Rc::new(RefCell::new(StyleSheet {
67                rules,
68                index: StyleSheetIndex::NeedUpdate,
69                font_face,
70                keyframes,
71            })),
72        }
73    }
74
75    pub(crate) fn list_deps(&self) -> Vec<String> {
76        self.imports.iter().map(|(s, _)| s.clone()).collect()
77    }
78
79    pub(crate) fn add_import(&mut self, path: String, media: Option<Rc<Media>>) {
80        self.imports.push((path, media));
81    }
82
83    pub(crate) fn add_rule(&mut self, rule: Box<Rule>) {
84        self.ss.borrow_mut().add_rule(rule);
85    }
86
87    pub(crate) fn add_font_face(&mut self, ff: FontFace) {
88        self.ss.borrow_mut().add_font_face(ff)
89    }
90
91    pub(crate) fn add_keyframes(&mut self, keyframes: KeyFrames) {
92        self.ss.borrow_mut().add_keyframes(keyframes)
93    }
94
95    pub(crate) fn add_tag_name_prefix(&mut self, prefix: &str) {
96        let mut ss = self.ss.borrow_mut();
97        for rule in ss.rules.iter_mut() {
98            let rule = Rc::make_mut(rule);
99            for frag in rule.selector.fragments.iter_mut() {
100                frag.add_tag_name_prefix(prefix)
101            }
102        }
103    }
104
105    pub(crate) fn link(
106        &mut self,
107        res: &StyleSheetResource,
108        scope: Option<NonZeroUsize>,
109    ) -> (LinkedStyleSheet, Vec<Warning>) {
110        let mut sheets = vec![];
111        let mut warnings = vec![];
112        self.link_self(res, &mut sheets, None, &mut warnings);
113        (LinkedStyleSheet { sheets, scope }, warnings)
114    }
115
116    #[allow(clippy::type_complexity)]
117    fn link_self(
118        &mut self,
119        res: &StyleSheetResource,
120        sheets: &mut Vec<(Rc<RefCell<StyleSheet>>, Option<Rc<Media>>)>,
121        parent_media: Option<Rc<Media>>,
122        warnings: &mut Vec<Warning>,
123    ) {
124        if !self.linked {
125            self.ss.borrow_mut().update_index();
126            self.linked = true;
127        }
128        for (target_path, media) in self.imports.iter() {
129            if let Some(target) = res.refs.get(target_path) {
130                if let Ok(mut target) = target.try_borrow_mut() {
131                    let m = match media.clone() {
132                        None => parent_media.clone(),
133                        Some(mut m) => {
134                            Rc::make_mut(&mut m).parent.clone_from(&parent_media);
135                            Some(m)
136                        }
137                    };
138                    target.link_self(res, sheets, m, warnings);
139                } else {
140                    warnings.push(Warning {
141                        kind: WarningKind::RecursiveImports,
142                        message: format!(
143                            "detected recursive style sheet import for {target_path:?}"
144                        )
145                        .into(),
146                        start_line: 0,
147                        start_col: 0,
148                        end_line: 0,
149                        end_col: 0,
150                    });
151                }
152            } else {
153                warnings.push(Warning {
154                    kind: WarningKind::MissingImportTarget,
155                    message: format!(r#"target style sheet {target_path:?} not found"#).into(),
156                    start_line: 0,
157                    start_col: 0,
158                    end_line: 0,
159                    end_col: 0,
160                });
161            }
162        }
163        sheets.push((self.ss.clone(), parent_media));
164    }
165
166    #[cfg(feature = "serialize")]
167    pub(crate) fn serialize_bincode(&self) -> Vec<u8> {
168        use float_pigment_consistent_bincode::Options;
169        let s = borrow::StyleSheet::from_sheet(self);
170        float_pigment_consistent_bincode::DefaultOptions::new()
171            .allow_trailing_bytes()
172            .serialize(&s)
173            .unwrap()
174    }
175
176    #[cfg(feature = "deserialize")]
177    pub(crate) fn deserialize_bincode(s: Vec<u8>) -> Result<Self, String> {
178        use float_pigment_consistent_bincode::Options;
179        let s: Result<borrow::StyleSheet, _> =
180            float_pigment_consistent_bincode::DefaultOptions::new()
181                .allow_trailing_bytes()
182                .deserialize(&s);
183        match s {
184            Ok(ss) => Ok(ss.into_sheet()),
185            Err(err) => Err(format!(
186                "Failed to deserialize bincode formatted style sheet: {err}"
187            )),
188        }
189    }
190
191    #[cfg(feature = "deserialize")]
192    pub(crate) unsafe fn deserialize_bincode_zero_copy(
193        ptr: *const [u8],
194        drop_callback: impl 'static + FnOnce(),
195    ) -> Result<Self, String> {
196        use float_pigment_consistent_bincode::Options;
197        borrow::de_static_ref_zero_copy_env(
198            ptr,
199            |s| {
200                let s: Result<borrow::StyleSheet, _> =
201                    float_pigment_consistent_bincode::DefaultOptions::new()
202                        .allow_trailing_bytes()
203                        .deserialize(s);
204                match s {
205                    Ok(ss) => Ok(ss.into_sheet()),
206                    Err(err) => Err(format!(
207                        "Failed to deserialize bincode formatted style sheet: {err}"
208                    )),
209                }
210            },
211            drop_callback,
212        )
213    }
214
215    #[cfg(all(feature = "serialize", feature = "serialize_json"))]
216    pub(crate) fn serialize_json(&self) -> String {
217        let s = borrow::StyleSheet::from_sheet(self);
218        serde_json::to_string(&s).unwrap()
219    }
220
221    #[cfg(all(feature = "serialize", feature = "deserialize_json"))]
222    pub(crate) fn deserialize_json(s: &str) -> Result<Self, String> {
223        let s: Result<borrow::StyleSheet, _> = serde_json::from_str(s);
224        match s {
225            Ok(ss) => Ok(ss.into_sheet()),
226            Err(err) => Err(format!(
227                "Failed to deserialize json formatted style sheet: {err}"
228            )),
229        }
230    }
231
232    #[cfg(all(feature = "serialize", feature = "deserialize_json"))]
233    pub(crate) unsafe fn deserialize_json_zero_copy(
234        ptr: *mut [u8],
235        drop_callback: impl 'static + FnOnce(),
236    ) -> Result<Self, String> {
237        borrow::de_static_ref_zero_copy_env(
238            ptr,
239            |s| {
240                let s: Result<borrow::StyleSheet, _> =
241                    serde_json::from_str(std::str::from_utf8_unchecked(s));
242                match s {
243                    Ok(ss) => Ok(ss.into_sheet()),
244                    Err(err) => Err(format!(
245                        "Failed to deserialize json formatted style sheet: {err}"
246                    )),
247                }
248            },
249            drop_callback,
250        )
251    }
252}
253
254/// A fully-parsed style sheet file.
255///
256/// A linked style sheet has a `scope` attached.
257/// The scope can be used in style queries, to limit the style sheets which can be matched in the queries.
258#[allow(clippy::type_complexity)]
259#[derive(Debug, Clone)]
260pub struct LinkedStyleSheet {
261    sheets: Vec<(Rc<RefCell<StyleSheet>>, Option<Rc<Media>>)>,
262    scope: Option<NonZeroUsize>,
263}
264
265impl LinkedStyleSheet {
266    /// Create an empty style sheet file with no scope limits.
267    pub fn new_empty() -> Self {
268        let mut ss = CompiledStyleSheet::new();
269        ss.link(&StyleSheetResource::new(), None).0
270    }
271
272    /// Get the scope of the style sheet file.
273    pub fn scope(&self) -> Option<NonZeroUsize> {
274        self.scope
275    }
276
277    /// Get all style sheets.
278    ///
279    /// A style sheet file can contain several `StyleSheet`.
280    /// If the file has no `@import`, it has only one `StyleSheet`.
281    /// Otherwise, other `StyleSheet` will be imported.
282    ///
283    /// All `StyleSheet`s are ordered based on the imported order.
284    pub fn sheets(&self) -> Vec<Rc<RefCell<StyleSheet>>> {
285        self.sheets.iter().map(|x| x.0.clone()).collect::<Vec<_>>()
286    }
287
288    #[doc(hidden)]
289    pub fn rules_count(&self, sheet_index: Option<usize>) -> Option<u32> {
290        if let Some(idx) = sheet_index {
291            if self.sheets.len() > (idx + 1usize) {
292                return None;
293            }
294            return Some(self.sheets.get(idx).unwrap().0.borrow().rules_count());
295        }
296        let mut count = 0;
297        self.sheets.iter().for_each(|item| {
298            count += item.0.borrow().rules_count();
299        });
300        Some(count)
301    }
302
303    /// Parse style sheet source to a style sheet directly.
304    ///
305    /// All `@import`s are ignored.
306    /// It is a convinient way if there is no `@import` in the source.
307    pub fn parse(source: &str, scope: Option<NonZeroUsize>) -> (Self, Vec<Warning>) {
308        let (mut ss, mut warnings) = parser::parse_style_sheet("", source);
309        let (ret, mut w2) = ss.link(&StyleSheetResource::new(), scope);
310        warnings.append(&mut w2);
311        (ret, warnings)
312    }
313
314    /// Get a rule by index.
315    pub fn get_rule(&self, mut rule_index: u32) -> Option<Rc<Rule>> {
316        for (sheet, _media) in self.sheets.iter() {
317            let sheet = sheet.borrow();
318            if rule_index < sheet.rules_count() {
319                return sheet.get_rule(rule_index).cloned();
320            }
321            rule_index -= sheet.rules_count();
322        }
323        None
324    }
325
326    /// Append a new rule.
327    ///
328    /// Generally it is used for debugging.
329    /// Re-query is needed when the style sheet is updated.
330    pub fn add_rule(&mut self, rule: Box<Rule>) -> u32 {
331        let mut rule_index = 0;
332        for (sheet, _media) in self.sheets.iter() {
333            rule_index += sheet.borrow().rules_count();
334        }
335        self.sheets
336            .last_mut()
337            .unwrap()
338            .0
339            .borrow_mut()
340            .add_rule(rule);
341        rule_index
342    }
343
344    /// Replace an existing rule with a new rule.
345    ///
346    /// The new rule is returned if success.
347    /// Generally it is used for debugging.
348    /// Re-query is needed when the style sheet is updated.
349    pub fn replace_rule(
350        &mut self,
351        mut rule_index: u32,
352        rule: Box<Rule>,
353    ) -> Result<Rc<Rule>, Box<Rule>> {
354        for (sheet, _media) in self.sheets.iter_mut() {
355            let mut sheet = sheet.borrow_mut();
356            if rule_index < sheet.rules_count() {
357                return sheet.replace_rule(rule_index, rule);
358            }
359            rule_index -= sheet.rules_count();
360        }
361        Err(rule)
362    }
363
364    pub(crate) fn for_each_matched_rule<L: LengthNum, T: StyleNode>(
365        &self,
366        query: &[T],
367        media_query_status: &MediaQueryStatus<L>,
368        sheet_index: u16,
369        mut f: impl FnMut(MatchedRuleRef),
370    ) {
371        // start from 1, so that computed weight of Matched rules is always non-zero
372        let mut rule_index_offset = 1;
373        for (sheet, media) in self.sheets.iter() {
374            if let Some(media) = media {
375                if !media.is_valid(media_query_status) {
376                    continue;
377                }
378            }
379            sheet.borrow_mut().for_each_matched_rule(
380                query,
381                media_query_status,
382                self.scope,
383                sheet_index,
384                rule_index_offset,
385                &mut f,
386            );
387            rule_index_offset += sheet.borrow().rules_count();
388        }
389    }
390
391    pub(crate) fn search_keyframes<L: LengthNum>(
392        &self,
393        style_scope: Option<NonZeroUsize>,
394        name: &str,
395        media_query_status: &MediaQueryStatus<L>,
396    ) -> Option<Rc<KeyFrames>> {
397        if self.scope.is_some() && self.scope != style_scope {
398            return None;
399        }
400        for (sheet, media) in self.sheets.iter() {
401            if let Some(media) = media {
402                if !media.is_valid(media_query_status) {
403                    continue;
404                }
405            }
406            // TODO consider build a hashmap index
407            for k in sheet.borrow().keyframes.iter().rev() {
408                if k.ident.as_str() == name {
409                    return Some(k.clone());
410                }
411            }
412        }
413        None
414    }
415
416    /// Get all `@font-face` definitions.
417    pub fn get_font_face(&self) -> Vec<Rc<FontFace>> {
418        let mut ret = vec![];
419        for (sheet, _) in self.sheets.iter() {
420            let sheet = sheet.borrow();
421            sheet.font_face().iter().for_each(|ff| ret.push(ff.clone()));
422        }
423        ret
424    }
425}
426
427/// A style sheet body without `@import` information.
428#[derive(Clone)]
429pub struct StyleSheet {
430    rules: Vec<Rc<Rule>>,
431    index: StyleSheetIndex,
432    font_face: Vec<Rc<FontFace>>,
433    keyframes: Vec<Rc<KeyFrames>>,
434}
435
436#[derive(Clone)]
437enum StyleSheetIndex {
438    NeedUpdate,
439    Updated {
440        class_index: HashMap<String, Vec<Rc<Rule>>>,
441        class_unindexed: Vec<Rc<Rule>>,
442    },
443}
444
445impl core::fmt::Debug for StyleSheet {
446    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
447        write!(f, "StyleSheet {{")?;
448        for rule in self.rules.iter() {
449            write!(f, " {rule:?}")?;
450        }
451        for font_face in self.font_face.iter() {
452            write!(f, " {font_face:?}")?;
453        }
454        for keyframes in self.keyframes.iter() {
455            write!(f, " {keyframes:?}")?;
456        }
457        write!(f, " }}")
458    }
459}
460
461impl core::fmt::Display for StyleSheet {
462    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
463        for rule in self.rules.iter() {
464            write!(f, " {rule}")?;
465        }
466        for font_face in self.font_face.iter() {
467            write!(f, " {font_face}")?;
468        }
469        for keyframes in self.keyframes.iter() {
470            write!(f, " {keyframes}")?;
471        }
472        Ok(())
473    }
474}
475
476impl StyleSheet {
477    #[doc(hidden)]
478    #[allow(clippy::should_implement_trait)]
479    pub fn from_str(s: &str) -> LinkedStyleSheet {
480        let (mut ss, warnings) = parser::parse_style_sheet("", s);
481        for warning in warnings {
482            warn!("{:?}", warning);
483        }
484        let ret = ss.link(&StyleSheetResource::new(), None);
485        ret.0
486    }
487
488    #[doc(hidden)]
489    pub fn from_str_with_path(path: &str, s: &str) -> LinkedStyleSheet {
490        let (mut ss, warnings) = parser::parse_style_sheet(path, s);
491        for warning in warnings {
492            warn!("{:?}", warning);
493        }
494        let ret = ss.link(&StyleSheetResource::new(), None);
495        ret.0
496    }
497
498    fn rules_count(&self) -> u32 {
499        self.rules.len().min(u32::MAX as usize) as u32
500    }
501
502    fn get_rule(&self, rule_index: u32) -> Option<&Rc<Rule>> {
503        self.rules.get(rule_index as usize)
504    }
505
506    fn add_rule(&mut self, mut rule: Box<Rule>) {
507        rule.index = self.rules.len() as u32;
508        self.rules.push(Rc::from(rule));
509        self.index = StyleSheetIndex::NeedUpdate;
510    }
511
512    fn replace_rule(
513        &mut self,
514        rule_index: u32,
515        mut rule: Box<Rule>,
516    ) -> Result<Rc<Rule>, Box<Rule>> {
517        let index = rule_index as usize;
518        if index < self.rules.len() {
519            rule.index = rule_index;
520            let mut rule = Rc::from(rule);
521            core::mem::swap(&mut self.rules[index], &mut rule);
522            self.index = StyleSheetIndex::NeedUpdate;
523            Ok(rule)
524        } else {
525            Err(rule)
526        }
527    }
528
529    fn update_index(&mut self) {
530        if let StyleSheetIndex::NeedUpdate = &self.index {
531            let mut class_index: HashMap<String, Vec<Rc<Rule>>> = HashMap::default();
532            let mut class_unindexed = vec![];
533            for rule in self.rules.iter() {
534                let index_classes = rule.selector.get_index_classes();
535                for c in index_classes {
536                    if !c.is_empty() {
537                        let c = class_index.entry(c).or_default();
538                        c.push(rule.clone());
539                    } else {
540                        class_unindexed.push(rule.clone());
541                    }
542                }
543            }
544            self.index = StyleSheetIndex::Updated {
545                class_index,
546                class_unindexed,
547            };
548        }
549    }
550
551    fn for_each_matched_rule<L: LengthNum, T: StyleNode>(
552        &mut self,
553        query: &[T],
554        media_query_status: &MediaQueryStatus<L>,
555        sheet_style_scope: Option<NonZeroUsize>,
556        sheet_index: u16,
557        rule_index_offset: u32,
558        mut f: impl FnMut(MatchedRuleRef),
559    ) {
560        self.update_index();
561        if let StyleSheetIndex::Updated {
562            class_index,
563            class_unindexed,
564        } = &self.index
565        {
566            if sheet_style_scope.is_none()
567                || query
568                    .last()
569                    .is_some_and(|x| x.contain_scope(sheet_style_scope))
570            {
571                for r in class_unindexed.iter() {
572                    if let Some(selector_weight) =
573                        r.match_query(query, media_query_status, sheet_style_scope)
574                    {
575                        let weight = RuleWeight::new(
576                            selector_weight,
577                            sheet_index,
578                            rule_index_offset + r.index,
579                        );
580                        f(MatchedRuleRef { rule: r, weight });
581                    }
582                }
583            }
584            let query_last = match query.last() {
585                Some(x) => x,
586                None => return,
587            };
588            for class in query_last.classes() {
589                if sheet_style_scope.is_none() || sheet_style_scope == class.scope() {
590                    if let Some(rules) = class_index.get(class.name()) {
591                        for r in rules {
592                            if let Some(selector_weight) =
593                                r.match_query(query, media_query_status, sheet_style_scope)
594                            {
595                                let weight = RuleWeight::new(
596                                    selector_weight,
597                                    sheet_index,
598                                    rule_index_offset + r.index,
599                                );
600                                f(MatchedRuleRef { rule: r, weight });
601                            }
602                        }
603                    }
604                }
605            }
606        }
607    }
608
609    /// Add a font-face definition to the style sheet.
610    pub fn add_font_face(&mut self, ff: FontFace) {
611        self.font_face.push(Rc::new(ff));
612    }
613
614    /// Get all font-face definitions.
615    pub fn font_face(&self) -> &[Rc<FontFace>] {
616        &self.font_face
617    }
618
619    pub(crate) fn add_keyframes(&mut self, keyframes: KeyFrames) {
620        self.keyframes.push(Rc::new(keyframes));
621    }
622}
623
624/// The weight of a rule (unique for each rule).
625///
626/// Weight of a rule is composed of multiple factors.
627///
628/// * High 16 bits is for the selector, while the detailed layout is `-MICCCCCCCCP--TT`:
629///   * `M` - the important bit;
630///   * `I` - the ID selector bit;
631///   * `C` - the sum of the class selectors and the attribute selectors (max 255);
632///   * `P` - the pseudo class bit;
633///   * `T` - the sum of the tag name selector and the pseudo element selector.
634/// * High 16th~31st bits is the style sheet index (0-based index).
635/// * High 32nd~63rd bits is the rule index in the whole linked style sheet (1-based index).
636#[derive(Debug, Clone, Copy, PartialEq, Eq)]
637pub struct RuleWeight(u64);
638
639impl RuleWeight {
640    pub(crate) fn new(selector_weight: u16, sheet_index: u16, rule_index: u32) -> Self {
641        let weight =
642            ((selector_weight as u64) << 48) + ((sheet_index as u64) << 32) + rule_index as u64;
643        Self(weight)
644    }
645
646    pub(crate) fn inline() -> Self {
647        Self(1 << 62)
648    }
649
650    /// Get the underlying weight number.
651    pub fn normal(&self) -> u64 {
652        self.0
653    }
654
655    /// Get the underlying weight number with `!important` added.
656    pub fn important(&self) -> u64 {
657        self.0 + (1 << 63)
658    }
659
660    /// Get the style sheet index.
661    pub fn sheet_index(&self) -> u16 {
662        (self.0 >> 32) as u16
663    }
664
665    /// Get the rule index.
666    pub fn rule_index(&self) -> u32 {
667        (self.0 as u32) - 1
668    }
669}