float_pigment_css/
group.rs

1use alloc::{boxed::Box, rc::Rc, string::String, vec::Vec};
2use core::{
3    cell::{Cell, RefCell},
4    num::NonZeroUsize,
5};
6
7use hashbrown::HashMap;
8
9#[cfg(feature = "wasm-entrance")]
10use wasm_bindgen::prelude::*;
11#[cfg(feature = "wasm-entrance")]
12use wasm_bindgen::JsCast;
13
14use crate::{
15    length_num::LengthNum,
16    parser::{WarningKind, DEFAULT_INPUT_CSS_EXTENSION},
17    sheet::{FontFace, KeyFrames},
18};
19
20use super::parser::Warning;
21use super::property::*;
22use super::query::*;
23#[cfg(any(feature = "serialize", feature = "deserialize"))]
24use super::sheet::borrow_resource;
25use super::sheet::{CompiledStyleSheet, LinkedStyleSheet, Rule};
26
27#[cfg(feature = "deserialize")]
28use super::sheet::borrow;
29
30pub(crate) fn drop_css_extension(path: &str) -> &str {
31    path.strip_suffix(DEFAULT_INPUT_CSS_EXTENSION)
32        .unwrap_or(path)
33}
34
35/// Resource manager to store style sheet files.
36#[cfg_attr(feature = "wasm-entrance", wasm_bindgen)]
37#[derive(Default)]
38pub struct StyleSheetResource {
39    pub(crate) refs: HashMap<String, RefCell<CompiledStyleSheet>>,
40    panic_on_warning: bool,
41}
42
43#[cfg_attr(feature = "wasm-entrance", wasm_bindgen)]
44impl StyleSheetResource {
45    /// Create a new resource manager.
46    #[cfg_attr(feature = "wasm-entrance", wasm_bindgen(constructor))]
47    pub fn new() -> Self {
48        Self::default()
49    }
50
51    /// Generate a `StyleSheetImportIndex`.
52    #[cfg_attr(
53        feature = "wasm-entrance",
54        wasm_bindgen(js_name = "generateImportIndexes")
55    )]
56    pub fn generate_import_indexes(&self) -> StyleSheetImportIndex {
57        let deps = self
58            .refs
59            .iter()
60            .map(|(k, v)| {
61                (
62                    k.clone(),
63                    (
64                        v.borrow().list_deps().into_iter().collect::<Vec<String>>(),
65                        false,
66                        Cell::new(false),
67                    ),
68                )
69            })
70            .collect::<HashMap<_, _>>();
71        StyleSheetImportIndex { deps }
72    }
73
74    /// Add a prefix to all tag names.
75    #[cfg_attr(feature = "wasm-entrance", wasm_bindgen(js_name = "addTagNamePrefix"))]
76    pub fn js_add_tag_name_prefix(&mut self, path: &str, prefix: &str) {
77        self.add_tag_name_prefix(path, prefix)
78    }
79
80    /// Get `@import` source paths (for JavaScript).
81    #[cfg(feature = "wasm-entrance")]
82    #[cfg_attr(
83        feature = "wasm-entrance",
84        wasm_bindgen(js_name = "directDependencies")
85    )]
86    pub fn js_direct_dependencies(&self, path: &str) -> Vec<JsValue> {
87        self.direct_dependencies(drop_css_extension(path))
88            .into_iter()
89            .map(|x| JsValue::from_str(&x))
90            .collect()
91    }
92
93    #[doc(hidden)]
94    #[cfg(all(feature = "serialize", feature = "serialize_json"))]
95    #[cfg_attr(feature = "wasm-entrance", wasm_bindgen(js_name = "serializeJson"))]
96    pub fn serialize_json(&self, path: &str) -> Option<String> {
97        let path = drop_css_extension(path);
98        self.refs.get(path).map(|ss| ss.borrow().serialize_json())
99    }
100
101    /// Serialize the specified style sheet to the binary format.
102    #[cfg(feature = "serialize")]
103    #[cfg_attr(feature = "wasm-entrance", wasm_bindgen(js_name = "serializeBincode"))]
104    pub fn serialize_bincode(&self, path: &str) -> Option<Vec<u8>> {
105        let path = drop_css_extension(path);
106        self.refs
107            .get(path)
108            .map(|ss| ss.borrow().serialize_bincode())
109    }
110
111    #[cfg(feature = "wasm-entrance")]
112    unsafe fn convert_warnings_to_js_unsafe(warnings: Vec<Warning>) -> JsValue {
113        let ret = js_sys::Array::new();
114        for warning in warnings {
115            let item = js_sys::Object::new();
116            js_sys::Reflect::set(
117                &item,
118                &JsValue::from("message"),
119                &JsValue::from(warning.message.as_str()),
120            )
121            .unwrap();
122            js_sys::Reflect::set(
123                &item,
124                &JsValue::from("startLine"),
125                &JsValue::from(warning.start_line),
126            )
127            .unwrap();
128            js_sys::Reflect::set(
129                &item,
130                &JsValue::from("startCol"),
131                &JsValue::from(warning.start_col),
132            )
133            .unwrap();
134            js_sys::Reflect::set(
135                &item,
136                &JsValue::from("endLine"),
137                &JsValue::from(warning.end_line),
138            )
139            .unwrap();
140            js_sys::Reflect::set(
141                &item,
142                &JsValue::from("endCol"),
143                &JsValue::from(warning.end_col),
144            )
145            .unwrap();
146
147            ret.push(&item);
148        }
149        ret.dyn_into().unwrap()
150    }
151
152    #[cfg(feature = "wasm-entrance")]
153    fn convert_warnings_to_js(warnings: Vec<Warning>) -> JsValue {
154        unsafe { Self::convert_warnings_to_js_unsafe(warnings) }
155    }
156
157    /// Add a style sheet file (for JavaScript).
158    #[cfg(feature = "wasm-entrance")]
159    #[wasm_bindgen(js_name = "addSource")]
160    pub fn js_add_source(&mut self, path: &str, source: &str) -> JsValue {
161        let ret = self.add_source(path, source);
162        Self::convert_warnings_to_js(ret)
163    }
164
165    #[doc(hidden)]
166    #[cfg(all(
167        feature = "wasm-entrance",
168        feature = "deserialize",
169        feature = "deserialize_json"
170    ))]
171    #[wasm_bindgen(js_name = "addJson")]
172    pub fn js_add_json(&mut self, path: &str, json: &str) -> JsValue {
173        let ret = self.add_json(path, json);
174        Self::convert_warnings_to_js(ret)
175    }
176
177    /// Add a style sheet file in the binary format (for JavaScript).
178    #[cfg(all(feature = "wasm-entrance", feature = "deserialize"))]
179    #[wasm_bindgen(js_name = "addBincode")]
180    pub fn js_add_bincode(&mut self, path: &str, bincode: Vec<u8>) -> JsValue {
181        let ret = self.add_bincode(path, bincode);
182        Self::convert_warnings_to_js(ret)
183    }
184}
185
186impl StyleSheetResource {
187    /// Get `@import` source paths.
188    pub fn direct_dependencies(&self, path: &str) -> Vec<String> {
189        match self.refs.get(path) {
190            None => vec![],
191            Some(v) => v.borrow().list_deps(),
192        }
193    }
194
195    fn add(&mut self, path: &str, sheet: CompiledStyleSheet) {
196        let path = drop_css_extension(path).into();
197        self.refs.insert(path, RefCell::new(sheet));
198    }
199
200    /// Add a prefix to all tag names.
201    pub fn add_tag_name_prefix(&mut self, path: &str, prefix: &str) {
202        let path = drop_css_extension(path);
203        if let Some(ss) = self.refs.get(path) {
204            ss.borrow_mut().add_tag_name_prefix(prefix);
205        }
206    }
207
208    /// Enable or disable `panic_on_warning`, i,e, panics on compilation warnings.
209    pub fn set_panic_on_warning(&mut self, panic_on_warning: bool) {
210        self.panic_on_warning = panic_on_warning;
211    }
212
213    pub(crate) fn link(
214        &self,
215        path: &str,
216        scope: Option<NonZeroUsize>,
217    ) -> (LinkedStyleSheet, Vec<Warning>) {
218        let (ss, warnings) = self
219            .refs
220            .get(path)
221            .map(|ss| ss.borrow_mut().link(self, scope))
222            .unwrap_or_else(|| {
223                let warnings = vec![Warning {
224                    kind: WarningKind::MissingImportTarget,
225                    message: format!("Target style sheet {:?} is not found.", path).into(),
226                    start_line: 0,
227                    start_col: 0,
228                    end_line: 0,
229                    end_col: 0,
230                }];
231                (LinkedStyleSheet::new_empty(), warnings)
232            });
233        if self.panic_on_warning {
234            if let Some(w) = warnings.last() {
235                panic!("{:?}", w);
236            }
237        }
238        (ss, warnings)
239    }
240
241    /// Add a style sheet.
242    pub fn add_source(&mut self, path: &str, source: &str) -> Vec<Warning> {
243        self.add_source_with_hooks(path, source, None)
244    }
245
246    /// Add a style sheet with compilation hooks.
247    pub fn add_source_with_hooks(
248        &mut self,
249        path: &str,
250        source: &str,
251        hooks: Option<Box<dyn crate::parser::hooks::Hooks>>,
252    ) -> Vec<Warning> {
253        // drop .wxss
254        let path = drop_css_extension(path);
255        let (sheet, warning) = crate::parser::parse_style_sheet_with_hooks(path, source, hooks);
256        if self.panic_on_warning {
257            if let Some(w) = warning.last() {
258                panic!("{:?}", w);
259            }
260        }
261        self.add(path, sheet);
262        warning
263    }
264
265    #[cfg(feature = "deserialize")]
266    fn deserialize_failed_warning(msg: String) -> Vec<Warning> {
267        vec![Warning {
268            kind: WarningKind::DeserializationFailed,
269            message: format!(
270                "failed to deserialize bincode formatted style sheet: {}",
271                msg
272            )
273            .into(),
274            start_line: 0,
275            start_col: 0,
276            end_line: 0,
277            end_col: 0,
278        }]
279    }
280
281    #[doc(hidden)]
282    #[cfg(all(feature = "deserialize", feature = "deserialize_json"))]
283    pub fn add_json(&mut self, path: &str, json: &str) -> Vec<Warning> {
284        let ss = CompiledStyleSheet::deserialize_json(json);
285        let ret = match ss {
286            Ok(ss) => {
287                self.add(path, ss);
288                Vec::with_capacity(0)
289            }
290            Err(err) => Self::deserialize_failed_warning(err),
291        };
292        if self.panic_on_warning {
293            if let Some(w) = ret.last() {
294                panic!("{:?}", w);
295            }
296        }
297        ret
298    }
299
300    #[doc(hidden)]
301    /// # Safety
302    ///
303    /// Deserialize CSS from json format, with zero copy support.
304    /// This format requires stable type definition.
305    /// Code changing invalidates the serialize result!
306    ///
307    /// **Safety**
308    /// * The `ptr` pointed memory must be valid before `drop_callback` triggered.
309    /// * The `ptr` pointed memory must be valid UTF-8 bytes.
310    ///
311    #[cfg(all(feature = "deserialize", feature = "deserialize_json"))]
312    pub unsafe fn add_json_zero_copy(
313        &mut self,
314        path: &str,
315        ptr: *mut [u8],
316        drop_callback: impl 'static + FnOnce(),
317    ) -> Vec<Warning> {
318        let ss = CompiledStyleSheet::deserialize_json_zero_copy(ptr, drop_callback);
319        let ret = match ss {
320            Ok(ss) => {
321                self.add(path, ss);
322                Vec::with_capacity(0)
323            }
324            Err(err) => Self::deserialize_failed_warning(err),
325        };
326        if self.panic_on_warning {
327            if let Some(w) = ret.last() {
328                panic!("{:?}", w);
329            }
330        }
331        ret
332    }
333
334    /// Add a style sheet in the binary format.
335    #[cfg(feature = "deserialize")]
336    pub fn add_bincode(&mut self, path: &str, bincode: Vec<u8>) -> Vec<Warning> {
337        let ss = CompiledStyleSheet::deserialize_bincode(bincode);
338        let ret = match ss {
339            Ok(ss) => {
340                self.add(path, ss);
341                Vec::with_capacity(0)
342            }
343            Err(err) => Self::deserialize_failed_warning(err),
344        };
345        if self.panic_on_warning {
346            if let Some(w) = ret.last() {
347                panic!("{:?}", w);
348            }
349        }
350        ret
351    }
352
353    /// Add a style sheet in bincode format, with zero copy support.
354    ///
355    /// # Safety
356    ///
357    /// The `ptr` pointed memory must be valid before `drop_callback` triggered.
358    #[cfg(feature = "deserialize")]
359    pub unsafe fn add_bincode_zero_copy(
360        &mut self,
361        path: &str,
362        ptr: *const [u8],
363        drop_callback: impl 'static + FnOnce(),
364    ) -> Vec<Warning> {
365        let ss = CompiledStyleSheet::deserialize_bincode_zero_copy(ptr, drop_callback);
366        let ret = match ss {
367            Ok(ss) => {
368                self.add(path, ss);
369                Vec::with_capacity(0)
370            }
371            Err(err) => Self::deserialize_failed_warning(err),
372        };
373        if self.panic_on_warning {
374            if let Some(w) = ret.last() {
375                panic!("{:?}", w);
376            }
377        }
378        ret
379    }
380}
381
382/// Import information of style sheet resources.
383#[cfg_attr(feature = "wasm-entrance", wasm_bindgen)]
384#[derive(Debug, Default)]
385pub struct StyleSheetImportIndex {
386    pub(crate) deps: HashMap<String, (Vec<String>, bool, Cell<bool>)>,
387}
388
389impl StyleSheetImportIndex {
390    /// Create an empty `StyleSheetImportIndex`.
391    pub fn new() -> Self {
392        Self::default()
393    }
394
395    /// List `@import` sources of the specified style sheet, and mark all style sheet as "visited".
396    ///
397    /// All returned style sheet paths will be marked "visited".
398    /// Future calls to this function will never return "visited" ones again.
399    pub fn query_and_mark_dependencies(&mut self, path: &str) -> Vec<String> {
400        let mut ret = vec![];
401        let path = drop_css_extension(path);
402        fn rec(
403            deps: &mut HashMap<String, (Vec<String>, bool, Cell<bool>)>,
404            path: &str,
405            ret: &mut Vec<String>,
406        ) {
407            let x = if let Some((x, marked, _)) = deps.get_mut(path) {
408                if *marked {
409                    return;
410                }
411                *marked = true;
412                x.clone()
413            } else {
414                return;
415            };
416            for x in x.into_iter() {
417                rec(deps, &x, ret);
418            }
419            ret.push(path.into());
420        }
421        rec(&mut self.deps, path, &mut ret);
422        ret
423    }
424
425    /// List `@import` sources of the specified style sheet.
426    ///
427    /// If `recursive` is set, it returns direct and indirect dependencies.
428    pub fn list_dependencies(&self, path: &str, recursive: bool) -> Vec<String> {
429        let mut ret = vec![];
430        let path = drop_css_extension(path);
431        fn rec(
432            deps: &HashMap<String, (Vec<String>, bool, Cell<bool>)>,
433            path: &str,
434            ret: &mut Vec<String>,
435            recursive: bool,
436        ) {
437            if let Some((x, _, rec_marked)) = deps.get(path) {
438                if rec_marked.get() {
439                    return;
440                }
441                rec_marked.set(true);
442                if recursive {
443                    for x in x.iter().map(|x| x.as_str()) {
444                        rec(deps, x, ret, recursive);
445                    }
446                }
447                ret.push(path.into());
448                rec_marked.set(false);
449            }
450        }
451        rec(&self.deps, path, &mut ret, recursive);
452        ret
453    }
454}
455
456#[cfg_attr(feature = "wasm-entrance", wasm_bindgen)]
457impl StyleSheetImportIndex {
458    /// The JavaScript version of `query_and_mark_dependencies`.
459    #[cfg(feature = "wasm-entrance")]
460    #[wasm_bindgen(js_name = "queryAndMarkDependencies")]
461    pub fn js_query_and_mark_dependencies(&mut self, path: &str) -> JsValue {
462        let deps = self.query_and_mark_dependencies(path);
463        let ret = js_sys::Array::new_with_length(deps.len() as u32);
464        for dep in deps {
465            ret.push(&JsValue::from(dep));
466        }
467        ret.dyn_into().unwrap()
468    }
469
470    #[doc(hidden)]
471    #[cfg(all(feature = "serialize", feature = "serialize_json"))]
472    #[cfg_attr(feature = "wasm-entrance", wasm_bindgen(js_name = "serializeJson"))]
473    pub fn serialize_json(&self) -> String {
474        let s = borrow_resource::StyleSheetImportIndex::from_sheet(self);
475        serde_json::to_string(&s).unwrap()
476    }
477
478    #[doc(hidden)]
479    #[cfg(all(feature = "deserialize", feature = "deserialize_json"))]
480    #[cfg_attr(feature = "wasm-entrance", wasm_bindgen(js_name = "deserializeJson"))]
481    pub fn deserialize_json(s: &str) -> Self {
482        let s: Result<borrow_resource::StyleSheetImportIndex, _> = serde_json::from_str(s);
483        match s {
484            Ok(ss) => ss.into_sheet(),
485            Err(_) => {
486                error!("Failed to deserialize json formatted style sheet import index. Use empty content instead.");
487                Self {
488                    deps: hashbrown::HashMap::default(),
489                }
490            }
491        }
492    }
493
494    /// Serialize it to the binary format.
495    #[cfg(feature = "serialize")]
496    #[cfg_attr(feature = "wasm-entrance", wasm_bindgen(js_name = "serializeBincode"))]
497    pub fn serialize_bincode(&self) -> Vec<u8> {
498        use float_pigment_consistent_bincode::Options;
499        let s = borrow_resource::StyleSheetImportIndex::from_sheet(self);
500        float_pigment_consistent_bincode::DefaultOptions::new()
501            .allow_trailing_bytes()
502            .serialize(&s)
503            .unwrap()
504    }
505
506    /// Deserialize from the binary format.
507    #[cfg(feature = "deserialize")]
508    #[cfg_attr(
509        feature = "wasm-entrance",
510        wasm_bindgen(js_name = "deserializeBincode")
511    )]
512    pub fn deserialize_bincode(s: Vec<u8>) -> Self {
513        use float_pigment_consistent_bincode::Options;
514        let s: Result<borrow_resource::StyleSheetImportIndex, _> =
515            float_pigment_consistent_bincode::DefaultOptions::new()
516                .allow_trailing_bytes()
517                .deserialize(&s);
518        match s {
519            Ok(ss) => ss.into_sheet(),
520            Err(_) => {
521                error!("Failed to deserialize bincode formatted style sheet import index. Use empty content instead.");
522                Self {
523                    deps: HashMap::default(),
524                }
525            }
526        }
527    }
528
529    /// Deserialize from the binary format and merge into `self`.
530    #[cfg(feature = "deserialize")]
531    #[cfg_attr(feature = "wasm-entrance", wasm_bindgen(js_name = "mergeBincode"))]
532    pub fn merge_bincode(&mut self, s: Vec<u8>) {
533        use float_pigment_consistent_bincode::Options;
534        let s: Result<borrow_resource::StyleSheetImportIndex, _> =
535            float_pigment_consistent_bincode::DefaultOptions::new()
536                .allow_trailing_bytes()
537                .deserialize(&s);
538        match s {
539            Ok(ss) => ss.merge_to_sheet(self),
540            Err(_) => {
541                error!("Failed to merge bincode formatted style sheet import index. Use empty content instead.");
542            }
543        }
544    }
545}
546
547impl StyleSheetImportIndex {
548    /// Deserialize from the binary format with zero copy.
549    ///
550    /// # Safety
551    ///
552    /// The `ptr` pointed memory must be valid before `drop_callback` triggered.
553    #[cfg(feature = "deserialize")]
554    pub unsafe fn deserialize_bincode_zero_copy(
555        ptr: *mut [u8],
556        drop_callback: impl 'static + FnOnce(),
557    ) -> Self {
558        use float_pigment_consistent_bincode::Options;
559        borrow::de_static_ref_zero_copy_env(
560            ptr,
561            |s| {
562                let s: Result<borrow_resource::StyleSheetImportIndex, _> =
563                    float_pigment_consistent_bincode::DefaultOptions::new()
564                        .allow_trailing_bytes()
565                        .deserialize(s);
566                match s {
567                    Ok(ss) => ss.into_sheet(),
568                    Err(_) => {
569                        error!("Failed to deserialize bincode formatted style sheet import index. Use empty content instead.");
570                        Self {
571                            deps: HashMap::default(),
572                        }
573                    }
574                }
575            },
576            drop_callback,
577        )
578    }
579
580    /// Deserialize from the binary format and merge into `self` with zero copy.
581    ///
582    /// # Safety
583    ///
584    /// The `ptr` pointed memory must be valid before `drop_callback` triggered.
585    #[cfg(feature = "deserialize")]
586    pub unsafe fn merge_bincode_zero_copy(
587        &mut self,
588        ptr: *mut [u8],
589        drop_callback: impl 'static + FnOnce(),
590    ) {
591        use float_pigment_consistent_bincode::Options;
592        borrow::de_static_ref_zero_copy_env(
593            ptr,
594            |s| {
595                let s: Result<borrow_resource::StyleSheetImportIndex, _> =
596                    float_pigment_consistent_bincode::DefaultOptions::new()
597                        .allow_trailing_bytes()
598                        .deserialize(s);
599                match s {
600                    Ok(ss) => ss.merge_to_sheet(self),
601                    Err(_) => {
602                        error!("Failed to merge bincode formatted style sheet import index. Use empty content instead.");
603                    }
604                }
605            },
606            drop_callback,
607        )
608    }
609
610    #[doc(hidden)]
611    /// # Safety
612    ///
613    /// The `ptr` pointed memory must be valid before `drop_callback` triggered.
614    #[cfg(all(feature = "deserialize", feature = "deserialize_json"))]
615    pub unsafe fn deserialize_json_zero_copy(
616        ptr: *mut [u8],
617        drop_callback: impl 'static + FnOnce(),
618    ) -> Self {
619        borrow::de_static_ref_zero_copy_env(
620            ptr,
621            |s| {
622                let s: Result<borrow_resource::StyleSheetImportIndex, _> =
623                    serde_json::from_str(std::str::from_utf8_unchecked(s));
624                match s {
625                    Ok(ss) => ss.into_sheet(),
626                    Err(_) => {
627                        error!("Failed to deserialize json formatted style sheet import index. Use empty content instead.");
628                        Self {
629                            deps: hashbrown::HashMap::default(),
630                        }
631                    }
632                }
633            },
634            drop_callback,
635        )
636    }
637}
638
639/// The style sheet index for debugging.
640pub const TEMP_SHEET_INDEX: u16 = u16::MAX;
641
642/// A group of ordered style sheets.
643#[cfg_attr(feature = "wasm-entrance", wasm_bindgen)]
644#[derive(Default, Clone)]
645pub struct StyleSheetGroup {
646    sheets: Vec<LinkedStyleSheet>,
647    temp_sheet: Option<LinkedStyleSheet>,
648}
649
650impl StyleSheetGroup {
651    /// Create an empty group.
652    pub fn new() -> Self {
653        Self::default()
654    }
655
656    /// Get the count of style sheets.
657    pub fn len(&self) -> u16 {
658        self.sheets.len() as u16
659    }
660
661    /// Return `true` if the group is empty.
662    pub fn is_empty(&self) -> bool {
663        self.sheets.is_empty()
664    }
665
666    /// Append a style sheet, returning its index.
667    pub fn append(&mut self, sheet: LinkedStyleSheet) -> u16 {
668        let ret = self.sheets.len();
669        if Self::is_invalid_index(ret) {
670            panic!("The number of stylesheets has reached the maximum limit.")
671        }
672        self.sheets.push(sheet);
673        ret as u16
674    }
675
676    /// Replace a style sheet by its index.
677    pub fn replace(&mut self, index: u16, sheet: LinkedStyleSheet) {
678        self.sheets[index as usize] = sheet;
679    }
680
681    /// Append a style sheet from the resource, returning its index.
682    pub fn append_from_resource(
683        &mut self,
684        res: &StyleSheetResource,
685        path: &str,
686        scope: Option<NonZeroUsize>,
687    ) -> u16 {
688        self.append_from_resource_with_warnings(res, path, scope).0
689    }
690
691    /// Append a style sheet from the resource, returning its index and warnings like @import not found.
692    pub fn append_from_resource_with_warnings(
693        &mut self,
694        res: &StyleSheetResource,
695        path: &str,
696        scope: Option<NonZeroUsize>,
697    ) -> (u16, Vec<Warning>) {
698        let path = drop_css_extension(path);
699        let (ss, warnings) = res.link(path, scope);
700        let ret = self.sheets.len();
701        if Self::is_invalid_index(ret) {
702            panic!("The number of stylesheets has reached the maximum limit.")
703        }
704        self.sheets.push(ss);
705        (ret as u16, warnings)
706    }
707
708    /// Replace a style sheet from the resource by its index.
709    pub fn replace_from_resource(
710        &mut self,
711        index: u16,
712        res: &StyleSheetResource,
713        path: &str,
714        scope: Option<NonZeroUsize>,
715    ) {
716        let path = drop_css_extension(path);
717        let (ss, _warnings) = res.link(path, scope);
718        self.sheets[index as usize] = ss;
719    }
720
721    /// Remove all style sheets.
722    pub fn clear(&mut self) {
723        self.sheets.truncate(0);
724    }
725
726    /// Get style sheet by index.
727    pub fn style_sheet(&self, sheet_index: u16) -> Option<&LinkedStyleSheet> {
728        if sheet_index != TEMP_SHEET_INDEX {
729            self.sheets.get(sheet_index as usize)
730        } else {
731            self.temp_sheet.as_ref()
732        }
733    }
734
735    /// Get font-face by sheet index.
736    pub fn get_font_face(&self, sheet_index: u16) -> Option<Vec<Rc<FontFace>>> {
737        self.style_sheet(sheet_index)
738            .map(|sheet| sheet.get_font_face())
739    }
740
741    /// Get a rule by index.
742    ///
743    /// If sheet index is `TEMP_SHEET_INDEX` then the temporary style sheet will be used.
744    pub fn get_rule(&self, sheet_index: u16, rule_index: u32) -> Option<Rc<Rule>> {
745        if let Some(sheet) = self.style_sheet(sheet_index) {
746            sheet.get_rule(rule_index)
747        } else {
748            None
749        }
750    }
751
752    /// Add a rule to the temporary style sheet.
753    ///
754    /// The temporary style sheet is a style sheet which has the highest priority and no scope limits.
755    /// The `rule_index` is returned.
756    /// Re-query is needed when the style sheet is updated.
757    /// Generally it is used for debugging.
758    pub fn add_rule(&mut self, rule: Box<Rule>) -> u32 {
759        if self.temp_sheet.is_none() {
760            self.temp_sheet = Some(LinkedStyleSheet::new_empty());
761        }
762        let sheet = self.temp_sheet.as_mut().unwrap();
763        sheet.add_rule(rule)
764    }
765
766    /// Replace an existing rule with the new rule.
767    ///
768    /// The existing rule is returned if success.
769    /// If sheet index is `TEMP_SHEET_INDEX` then the temporary style sheet will be used.
770    /// Re-query is needed when the style sheet is updated.
771    /// Generally it is used for debugging.
772    pub fn replace_rule(
773        &mut self,
774        sheet_index: u16,
775        rule_index: u32,
776        rule: Box<Rule>,
777    ) -> Result<Rc<Rule>, Box<Rule>> {
778        let sheet = if sheet_index != TEMP_SHEET_INDEX {
779            self.sheets.get_mut(sheet_index as usize)
780        } else {
781            self.temp_sheet.as_mut()
782        };
783        if let Some(sheet) = sheet {
784            sheet.replace_rule(rule_index, rule)
785        } else {
786            Err(rule)
787        }
788    }
789
790    /// Query a single node selector (usually for testing only).
791    ///
792    /// Note that the font size and `em` values will be converted to `px` values.
793    pub fn query_single<L: LengthNum, T: StyleNode>(
794        &self,
795        query: T,
796        media_query_status: &MediaQueryStatus<L>,
797        node_properties: &mut NodeProperties,
798    ) {
799        self.query_ancestor_path(&[query], media_query_status, node_properties, None)
800    }
801
802    /// Find rules that matches the query.
803    ///
804    /// The query is a `&[StyleQuery]` which means all selector information of the ancestors and the node itself.
805    pub fn for_each_matched_rule<L: LengthNum, T: StyleNode>(
806        &self,
807        query: &[T],
808        media_query_status: &MediaQueryStatus<L>,
809        mut f: impl FnMut(MatchedRuleRef, Option<&LinkedStyleSheet>),
810    ) {
811        for (index, sheet) in self.sheets.iter().enumerate() {
812            sheet.for_each_matched_rule(
813                query,
814                media_query_status,
815                index.min((TEMP_SHEET_INDEX - 1) as usize) as u16,
816                |r| f(r, Some(sheet)),
817            );
818        }
819        if let Some(sheet) = self.temp_sheet.as_ref() {
820            sheet.for_each_matched_rule(query, media_query_status, u16::MAX, |r| f(r, None));
821        }
822    }
823
824    /// Get a rule list that matches the query.
825    ///
826    /// The query is a `&[StyleQuery]` which means all selector information of the ancestors and the node itself.
827    pub fn query_matched_rules<L: LengthNum, T: StyleNode>(
828        &self,
829        query: &[T],
830        media_query_status: &MediaQueryStatus<L>,
831    ) -> MatchedRuleList {
832        let mut rules = vec![];
833        self.for_each_matched_rule(query, media_query_status, |matched_rule, style_sheet| {
834            let r = MatchedRule {
835                rule: matched_rule.rule.clone(),
836                weight: matched_rule.weight,
837                style_scope: style_sheet.and_then(|x| x.scope()),
838            };
839            rules.push(r);
840        });
841        MatchedRuleList { rules }
842    }
843
844    /// Query a node in tree ancestor path.
845    ///
846    /// The query is a `&[StyleQuery]` which means all selector information of the ancestors and the node itself.
847    /// Note that the font size and `em` values will be converted to `px` values.
848    pub fn query_ancestor_path<L: LengthNum, T: StyleNode>(
849        &self,
850        query: &[T],
851        media_query_status: &MediaQueryStatus<L>,
852        node_properties: &mut NodeProperties,
853        parent_node_properties: Option<&NodeProperties>,
854    ) {
855        let default_font_size = media_query_status.base_font_size.to_f32();
856        let parent_font_size = match parent_node_properties {
857            None => default_font_size,
858            Some(x) => x
859                .font_size_ref()
860                .resolve_to_f32(media_query_status, default_font_size, true)
861                .unwrap_or(default_font_size),
862        };
863        let rules = self.query_matched_rules(query, media_query_status);
864        let current_font_size = rules.get_current_font_size(
865            parent_font_size,
866            parent_node_properties,
867            &[],
868            media_query_status,
869        );
870        rules.merge_node_properties(
871            node_properties,
872            parent_node_properties,
873            current_font_size,
874            &[],
875        );
876    }
877
878    fn is_invalid_index(idx: usize) -> bool {
879        idx > (u16::MAX as usize)
880    }
881
882    /// Search for an `@keyframe`.
883    pub fn search_keyframes<L: LengthNum>(
884        &self,
885        style_scope: Option<NonZeroUsize>,
886        name: &str,
887        media_query_status: &MediaQueryStatus<L>,
888    ) -> Option<Rc<KeyFrames>> {
889        if let Some(sheet) = self.temp_sheet.as_ref() {
890            if let Some(x) = sheet.search_keyframes(style_scope, name, media_query_status) {
891                return Some(x);
892            }
893        }
894        for sheet in self.sheets.iter().rev() {
895            if let Some(x) = sheet.search_keyframes(style_scope, name, media_query_status) {
896                return Some(x);
897            }
898        }
899        None
900    }
901}