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