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 {path:?} is not found.").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!("failed to deserialize bincode formatted style sheet: {msg}").into(),
270            start_line: 0,
271            start_col: 0,
272            end_line: 0,
273            end_col: 0,
274        }]
275    }
276
277    #[doc(hidden)]
278    #[cfg(all(feature = "deserialize", feature = "deserialize_json"))]
279    pub fn add_json(&mut self, path: &str, json: &str) -> Vec<Warning> {
280        let ss = CompiledStyleSheet::deserialize_json(json);
281        let ret = match ss {
282            Ok(ss) => {
283                self.add(path, ss);
284                Vec::with_capacity(0)
285            }
286            Err(err) => Self::deserialize_failed_warning(err),
287        };
288        if self.panic_on_warning {
289            if let Some(w) = ret.last() {
290                panic!("{w:?}");
291            }
292        }
293        ret
294    }
295
296    #[doc(hidden)]
297    /// # Safety
298    ///
299    /// Deserialize CSS from json format, with zero copy support.
300    /// This format requires stable type definition.
301    /// Code changing invalidates the serialize result!
302    ///
303    /// **Safety**
304    /// * The `ptr` pointed memory must be valid before `drop_callback` triggered.
305    /// * The `ptr` pointed memory must be valid UTF-8 bytes.
306    ///
307    #[cfg(all(feature = "deserialize", feature = "deserialize_json"))]
308    pub unsafe fn add_json_zero_copy(
309        &mut self,
310        path: &str,
311        ptr: *mut [u8],
312        drop_callback: impl 'static + FnOnce(),
313    ) -> Vec<Warning> {
314        let ss = CompiledStyleSheet::deserialize_json_zero_copy(ptr, drop_callback);
315        let ret = match ss {
316            Ok(ss) => {
317                self.add(path, ss);
318                Vec::with_capacity(0)
319            }
320            Err(err) => Self::deserialize_failed_warning(err),
321        };
322        if self.panic_on_warning {
323            if let Some(w) = ret.last() {
324                panic!("{w:?}");
325            }
326        }
327        ret
328    }
329
330    /// Add a style sheet in the binary format.
331    #[cfg(feature = "deserialize")]
332    pub fn add_bincode(&mut self, path: &str, bincode: Vec<u8>) -> Vec<Warning> {
333        let ss = CompiledStyleSheet::deserialize_bincode(bincode);
334        let ret = match ss {
335            Ok(ss) => {
336                self.add(path, ss);
337                Vec::with_capacity(0)
338            }
339            Err(err) => Self::deserialize_failed_warning(err),
340        };
341        if self.panic_on_warning {
342            if let Some(w) = ret.last() {
343                panic!("{w:?}");
344            }
345        }
346        ret
347    }
348
349    /// Add a style sheet in bincode format, with zero copy support.
350    ///
351    /// # Safety
352    ///
353    /// The `ptr` pointed memory must be valid before `drop_callback` triggered.
354    #[cfg(feature = "deserialize")]
355    pub unsafe fn add_bincode_zero_copy(
356        &mut self,
357        path: &str,
358        ptr: *const [u8],
359        drop_callback: impl 'static + FnOnce(),
360    ) -> Vec<Warning> {
361        let ss = CompiledStyleSheet::deserialize_bincode_zero_copy(ptr, drop_callback);
362        let ret = match ss {
363            Ok(ss) => {
364                self.add(path, ss);
365                Vec::with_capacity(0)
366            }
367            Err(err) => Self::deserialize_failed_warning(err),
368        };
369        if self.panic_on_warning {
370            if let Some(w) = ret.last() {
371                panic!("{w:?}");
372            }
373        }
374        ret
375    }
376}
377
378/// Import information of style sheet resources.
379#[cfg_attr(feature = "wasm-entrance", wasm_bindgen)]
380#[derive(Debug, Default)]
381pub struct StyleSheetImportIndex {
382    pub(crate) deps: HashMap<String, (Vec<String>, bool, Cell<bool>)>,
383}
384
385impl StyleSheetImportIndex {
386    /// Create an empty `StyleSheetImportIndex`.
387    pub fn new() -> Self {
388        Self::default()
389    }
390
391    /// List `@import` sources of the specified style sheet, and mark all style sheet as "visited".
392    ///
393    /// All returned style sheet paths will be marked "visited".
394    /// Future calls to this function will never return "visited" ones again.
395    pub fn query_and_mark_dependencies(&mut self, path: &str) -> Vec<String> {
396        let mut ret = vec![];
397        let path = drop_css_extension(path);
398        fn rec(
399            deps: &mut HashMap<String, (Vec<String>, bool, Cell<bool>)>,
400            path: &str,
401            ret: &mut Vec<String>,
402        ) {
403            let x = if let Some((x, marked, _)) = deps.get_mut(path) {
404                if *marked {
405                    return;
406                }
407                *marked = true;
408                x.clone()
409            } else {
410                return;
411            };
412            for x in x.into_iter() {
413                rec(deps, &x, ret);
414            }
415            ret.push(path.into());
416        }
417        rec(&mut self.deps, path, &mut ret);
418        ret
419    }
420
421    /// List `@import` sources of the specified style sheet.
422    ///
423    /// If `recursive` is set, it returns direct and indirect dependencies.
424    pub fn list_dependencies(&self, path: &str, recursive: bool) -> Vec<String> {
425        let mut ret = vec![];
426        let path = drop_css_extension(path);
427        fn rec(
428            deps: &HashMap<String, (Vec<String>, bool, Cell<bool>)>,
429            path: &str,
430            ret: &mut Vec<String>,
431            recursive: bool,
432        ) {
433            if let Some((x, _, rec_marked)) = deps.get(path) {
434                if rec_marked.get() {
435                    return;
436                }
437                rec_marked.set(true);
438                if recursive {
439                    for x in x.iter().map(|x| x.as_str()) {
440                        rec(deps, x, ret, recursive);
441                    }
442                }
443                ret.push(path.into());
444                rec_marked.set(false);
445            }
446        }
447        rec(&self.deps, path, &mut ret, recursive);
448        ret
449    }
450}
451
452#[cfg_attr(feature = "wasm-entrance", wasm_bindgen)]
453impl StyleSheetImportIndex {
454    /// The JavaScript version of `query_and_mark_dependencies`.
455    #[cfg(feature = "wasm-entrance")]
456    #[wasm_bindgen(js_name = "queryAndMarkDependencies")]
457    pub fn js_query_and_mark_dependencies(&mut self, path: &str) -> JsValue {
458        let deps = self.query_and_mark_dependencies(path);
459        let ret = js_sys::Array::new_with_length(deps.len() as u32);
460        for dep in deps {
461            ret.push(&JsValue::from(dep));
462        }
463        ret.dyn_into().unwrap()
464    }
465
466    #[doc(hidden)]
467    #[cfg(all(feature = "serialize", feature = "serialize_json"))]
468    #[cfg_attr(feature = "wasm-entrance", wasm_bindgen(js_name = "serializeJson"))]
469    pub fn serialize_json(&self) -> String {
470        let s = borrow_resource::StyleSheetImportIndex::from_sheet(self);
471        serde_json::to_string(&s).unwrap()
472    }
473
474    #[doc(hidden)]
475    #[cfg(all(feature = "deserialize", feature = "deserialize_json"))]
476    #[cfg_attr(feature = "wasm-entrance", wasm_bindgen(js_name = "deserializeJson"))]
477    pub fn deserialize_json(s: &str) -> Self {
478        let s: Result<borrow_resource::StyleSheetImportIndex, _> = serde_json::from_str(s);
479        match s {
480            Ok(ss) => ss.into_sheet(),
481            Err(_) => {
482                error!("Failed to deserialize json formatted style sheet import index. Use empty content instead.");
483                Self {
484                    deps: hashbrown::HashMap::default(),
485                }
486            }
487        }
488    }
489
490    /// Serialize it to the binary format.
491    #[cfg(feature = "serialize")]
492    #[cfg_attr(feature = "wasm-entrance", wasm_bindgen(js_name = "serializeBincode"))]
493    pub fn serialize_bincode(&self) -> Vec<u8> {
494        use float_pigment_consistent_bincode::Options;
495        let s = borrow_resource::StyleSheetImportIndex::from_sheet(self);
496        float_pigment_consistent_bincode::DefaultOptions::new()
497            .allow_trailing_bytes()
498            .serialize(&s)
499            .unwrap()
500    }
501
502    /// Deserialize from the binary format.
503    #[cfg(feature = "deserialize")]
504    #[cfg_attr(
505        feature = "wasm-entrance",
506        wasm_bindgen(js_name = "deserializeBincode")
507    )]
508    pub fn deserialize_bincode(s: Vec<u8>) -> Self {
509        use float_pigment_consistent_bincode::Options;
510        let s: Result<borrow_resource::StyleSheetImportIndex, _> =
511            float_pigment_consistent_bincode::DefaultOptions::new()
512                .allow_trailing_bytes()
513                .deserialize(&s);
514        match s {
515            Ok(ss) => ss.into_sheet(),
516            Err(_) => {
517                error!("Failed to deserialize bincode formatted style sheet import index. Use empty content instead.");
518                Self {
519                    deps: HashMap::default(),
520                }
521            }
522        }
523    }
524
525    /// Deserialize from the binary format and merge into `self`.
526    #[cfg(feature = "deserialize")]
527    #[cfg_attr(feature = "wasm-entrance", wasm_bindgen(js_name = "mergeBincode"))]
528    pub fn merge_bincode(&mut self, s: Vec<u8>) {
529        use float_pigment_consistent_bincode::Options;
530        let s: Result<borrow_resource::StyleSheetImportIndex, _> =
531            float_pigment_consistent_bincode::DefaultOptions::new()
532                .allow_trailing_bytes()
533                .deserialize(&s);
534        match s {
535            Ok(ss) => ss.merge_to_sheet(self),
536            Err(_) => {
537                error!("Failed to merge bincode formatted style sheet import index. Use empty content instead.");
538            }
539        }
540    }
541}
542
543impl StyleSheetImportIndex {
544    /// Deserialize from the binary format with zero copy.
545    ///
546    /// # Safety
547    ///
548    /// The `ptr` pointed memory must be valid before `drop_callback` triggered.
549    #[cfg(feature = "deserialize")]
550    pub unsafe fn deserialize_bincode_zero_copy(
551        ptr: *mut [u8],
552        drop_callback: impl 'static + FnOnce(),
553    ) -> Self {
554        use float_pigment_consistent_bincode::Options;
555        borrow::de_static_ref_zero_copy_env(
556            ptr,
557            |s| {
558                let s: Result<borrow_resource::StyleSheetImportIndex, _> =
559                    float_pigment_consistent_bincode::DefaultOptions::new()
560                        .allow_trailing_bytes()
561                        .deserialize(s);
562                match s {
563                    Ok(ss) => ss.into_sheet(),
564                    Err(_) => {
565                        error!("Failed to deserialize bincode formatted style sheet import index. Use empty content instead.");
566                        Self {
567                            deps: HashMap::default(),
568                        }
569                    }
570                }
571            },
572            drop_callback,
573        )
574    }
575
576    /// Deserialize from the binary format and merge into `self` with zero copy.
577    ///
578    /// # Safety
579    ///
580    /// The `ptr` pointed memory must be valid before `drop_callback` triggered.
581    #[cfg(feature = "deserialize")]
582    pub unsafe fn merge_bincode_zero_copy(
583        &mut self,
584        ptr: *mut [u8],
585        drop_callback: impl 'static + FnOnce(),
586    ) {
587        use float_pigment_consistent_bincode::Options;
588        borrow::de_static_ref_zero_copy_env(
589            ptr,
590            |s| {
591                let s: Result<borrow_resource::StyleSheetImportIndex, _> =
592                    float_pigment_consistent_bincode::DefaultOptions::new()
593                        .allow_trailing_bytes()
594                        .deserialize(s);
595                match s {
596                    Ok(ss) => ss.merge_to_sheet(self),
597                    Err(_) => {
598                        error!("Failed to merge bincode formatted style sheet import index. Use empty content instead.");
599                    }
600                }
601            },
602            drop_callback,
603        )
604    }
605
606    #[doc(hidden)]
607    /// # Safety
608    ///
609    /// The `ptr` pointed memory must be valid before `drop_callback` triggered.
610    #[cfg(all(feature = "deserialize", feature = "deserialize_json"))]
611    pub unsafe fn deserialize_json_zero_copy(
612        ptr: *mut [u8],
613        drop_callback: impl 'static + FnOnce(),
614    ) -> Self {
615        borrow::de_static_ref_zero_copy_env(
616            ptr,
617            |s| {
618                let s: Result<borrow_resource::StyleSheetImportIndex, _> =
619                    serde_json::from_str(std::str::from_utf8_unchecked(s));
620                match s {
621                    Ok(ss) => ss.into_sheet(),
622                    Err(_) => {
623                        error!("Failed to deserialize json formatted style sheet import index. Use empty content instead.");
624                        Self {
625                            deps: hashbrown::HashMap::default(),
626                        }
627                    }
628                }
629            },
630            drop_callback,
631        )
632    }
633}
634
635/// The style sheet index for debugging.
636pub const TEMP_SHEET_INDEX: u16 = u16::MAX;
637
638/// A group of ordered style sheets.
639#[cfg_attr(feature = "wasm-entrance", wasm_bindgen)]
640#[derive(Default, Clone)]
641pub struct StyleSheetGroup {
642    sheets: Vec<LinkedStyleSheet>,
643    temp_sheet: Option<LinkedStyleSheet>,
644}
645
646impl StyleSheetGroup {
647    /// Create an empty group.
648    pub fn new() -> Self {
649        Self::default()
650    }
651
652    /// Get the count of style sheets.
653    pub fn len(&self) -> u16 {
654        self.sheets.len() as u16
655    }
656
657    /// Return `true` if the group is empty.
658    pub fn is_empty(&self) -> bool {
659        self.sheets.is_empty()
660    }
661
662    /// Append a style sheet, returning its index.
663    pub fn append(&mut self, sheet: LinkedStyleSheet) -> u16 {
664        let ret = self.sheets.len();
665        if Self::is_invalid_index(ret) {
666            panic!("The number of stylesheets has reached the maximum limit.")
667        }
668        self.sheets.push(sheet);
669        ret as u16
670    }
671
672    /// Replace a style sheet by its index.
673    pub fn replace(&mut self, index: u16, sheet: LinkedStyleSheet) {
674        self.sheets[index as usize] = sheet;
675    }
676
677    /// Append a style sheet from the resource, returning its index.
678    pub fn append_from_resource(
679        &mut self,
680        res: &StyleSheetResource,
681        path: &str,
682        scope: Option<NonZeroUsize>,
683    ) -> u16 {
684        self.append_from_resource_with_warnings(res, path, scope).0
685    }
686
687    /// Append a style sheet from the resource, returning its index and warnings like @import not found.
688    pub fn append_from_resource_with_warnings(
689        &mut self,
690        res: &StyleSheetResource,
691        path: &str,
692        scope: Option<NonZeroUsize>,
693    ) -> (u16, Vec<Warning>) {
694        let path = drop_css_extension(path);
695        let (ss, warnings) = res.link(path, scope);
696        let ret = self.sheets.len();
697        if Self::is_invalid_index(ret) {
698            panic!("The number of stylesheets has reached the maximum limit.")
699        }
700        self.sheets.push(ss);
701        (ret as u16, warnings)
702    }
703
704    /// Replace a style sheet from the resource by its index.
705    pub fn replace_from_resource(
706        &mut self,
707        index: u16,
708        res: &StyleSheetResource,
709        path: &str,
710        scope: Option<NonZeroUsize>,
711    ) {
712        let path = drop_css_extension(path);
713        let (ss, _warnings) = res.link(path, scope);
714        self.sheets[index as usize] = ss;
715    }
716
717    /// Remove all style sheets.
718    pub fn clear(&mut self) {
719        self.sheets.truncate(0);
720    }
721
722    /// Get style sheet by index.
723    pub fn style_sheet(&self, sheet_index: u16) -> Option<&LinkedStyleSheet> {
724        if sheet_index != TEMP_SHEET_INDEX {
725            self.sheets.get(sheet_index as usize)
726        } else {
727            self.temp_sheet.as_ref()
728        }
729    }
730
731    /// Get font-face by sheet index.
732    pub fn get_font_face(&self, sheet_index: u16) -> Option<Vec<Rc<FontFace>>> {
733        self.style_sheet(sheet_index)
734            .map(|sheet| sheet.get_font_face())
735    }
736
737    /// Get a rule by index.
738    ///
739    /// If sheet index is `TEMP_SHEET_INDEX` then the temporary style sheet will be used.
740    pub fn get_rule(&self, sheet_index: u16, rule_index: u32) -> Option<Rc<Rule>> {
741        if let Some(sheet) = self.style_sheet(sheet_index) {
742            sheet.get_rule(rule_index)
743        } else {
744            None
745        }
746    }
747
748    /// Add a rule to the temporary style sheet.
749    ///
750    /// The temporary style sheet is a style sheet which has the highest priority and no scope limits.
751    /// The `rule_index` is returned.
752    /// Re-query is needed when the style sheet is updated.
753    /// Generally it is used for debugging.
754    pub fn add_rule(&mut self, rule: Box<Rule>) -> u32 {
755        if self.temp_sheet.is_none() {
756            self.temp_sheet = Some(LinkedStyleSheet::new_empty());
757        }
758        let sheet = self.temp_sheet.as_mut().unwrap();
759        sheet.add_rule(rule)
760    }
761
762    /// Replace an existing rule with the new rule.
763    ///
764    /// The existing rule is returned if success.
765    /// If sheet index is `TEMP_SHEET_INDEX` then the temporary style sheet will be used.
766    /// Re-query is needed when the style sheet is updated.
767    /// Generally it is used for debugging.
768    pub fn replace_rule(
769        &mut self,
770        sheet_index: u16,
771        rule_index: u32,
772        rule: Box<Rule>,
773    ) -> Result<Rc<Rule>, Box<Rule>> {
774        let sheet = if sheet_index != TEMP_SHEET_INDEX {
775            self.sheets.get_mut(sheet_index as usize)
776        } else {
777            self.temp_sheet.as_mut()
778        };
779        if let Some(sheet) = sheet {
780            sheet.replace_rule(rule_index, rule)
781        } else {
782            Err(rule)
783        }
784    }
785
786    /// Query a single node selector (usually for testing only).
787    ///
788    /// Note that the font size and `em` values will be converted to `px` values.
789    pub fn query_single<L: LengthNum, T: StyleNode>(
790        &self,
791        query: T,
792        media_query_status: &MediaQueryStatus<L>,
793        node_properties: &mut NodeProperties,
794    ) {
795        self.query_ancestor_path(&[query], media_query_status, node_properties, None)
796    }
797
798    /// Find rules that matches the query.
799    ///
800    /// The query is a `&[StyleQuery]` which means all selector information of the ancestors and the node itself.
801    pub fn for_each_matched_rule<L: LengthNum, T: StyleNode>(
802        &self,
803        query: &[T],
804        media_query_status: &MediaQueryStatus<L>,
805        mut f: impl FnMut(MatchedRuleRef, Option<&LinkedStyleSheet>),
806    ) {
807        for (index, sheet) in self.sheets.iter().enumerate() {
808            sheet.for_each_matched_rule(
809                query,
810                media_query_status,
811                index.min((TEMP_SHEET_INDEX - 1) as usize) as u16,
812                |r| f(r, Some(sheet)),
813            );
814        }
815        if let Some(sheet) = self.temp_sheet.as_ref() {
816            sheet.for_each_matched_rule(query, media_query_status, u16::MAX, |r| f(r, None));
817        }
818    }
819
820    /// Get a rule list that matches the query.
821    ///
822    /// The query is a `&[StyleQuery]` which means all selector information of the ancestors and the node itself.
823    pub fn query_matched_rules<L: LengthNum, T: StyleNode>(
824        &self,
825        query: &[T],
826        media_query_status: &MediaQueryStatus<L>,
827    ) -> MatchedRuleList {
828        let mut rules = vec![];
829        self.for_each_matched_rule(query, media_query_status, |matched_rule, style_sheet| {
830            let r = MatchedRule {
831                rule: matched_rule.rule.clone(),
832                weight: matched_rule.weight,
833                style_scope: style_sheet.and_then(|x| x.scope()),
834            };
835            rules.push(r);
836        });
837        MatchedRuleList { rules }
838    }
839
840    /// Query a node in tree ancestor path.
841    ///
842    /// The query is a `&[StyleQuery]` which means all selector information of the ancestors and the node itself.
843    /// Note that the font size and `em` values will be converted to `px` values.
844    pub fn query_ancestor_path<L: LengthNum, T: StyleNode>(
845        &self,
846        query: &[T],
847        media_query_status: &MediaQueryStatus<L>,
848        node_properties: &mut NodeProperties,
849        parent_node_properties: Option<&NodeProperties>,
850    ) {
851        let default_font_size = media_query_status.base_font_size.to_f32();
852        let parent_font_size = match parent_node_properties {
853            None => default_font_size,
854            Some(x) => x
855                .font_size_ref()
856                .resolve_to_f32(media_query_status, default_font_size, true)
857                .unwrap_or(default_font_size),
858        };
859        let rules = self.query_matched_rules(query, media_query_status);
860        let current_font_size = rules.get_current_font_size(
861            parent_font_size,
862            parent_node_properties,
863            &[],
864            media_query_status,
865        );
866        rules.merge_node_properties(
867            node_properties,
868            parent_node_properties,
869            current_font_size,
870            &[],
871        );
872    }
873
874    fn is_invalid_index(idx: usize) -> bool {
875        idx > (u16::MAX as usize)
876    }
877
878    /// Search for an `@keyframe`.
879    pub fn search_keyframes<L: LengthNum>(
880        &self,
881        style_scope: Option<NonZeroUsize>,
882        name: &str,
883        media_query_status: &MediaQueryStatus<L>,
884    ) -> Option<Rc<KeyFrames>> {
885        if let Some(sheet) = self.temp_sheet.as_ref() {
886            if let Some(x) = sheet.search_keyframes(style_scope, name, media_query_status) {
887                return Some(x);
888            }
889        }
890        for sheet in self.sheets.iter().rev() {
891            if let Some(x) = sheet.search_keyframes(style_scope, name, media_query_status) {
892                return Some(x);
893            }
894        }
895        None
896    }
897}