float_pigment_css/sheet/
rule.rs

1use alloc::borrow::Cow;
2use core::fmt;
3
4use super::*;
5use crate::property::Property;
6
7/// A CSS property with some metadata.
8#[derive(Clone, Debug, PartialEq)]
9pub enum PropertyMeta {
10    /// A single normal property, e.g. `font-size: 16px`.
11    Normal {
12        /// The property body.
13        property: Property,
14    },
15    /// A single property with `!important`, e.g. `font-size: 16px !important`.
16    Important {
17        /// The property body.
18        property: Property,
19    },
20    /// A group of properties.
21    ///
22    /// It is designed for debugging only.
23    /// In production environment, properties are well-normalized -
24    /// shorthand properties (e.g. `font` `background`) are splitted in advance.
25    /// However, we may add new shorthand properties in debugger -
26    /// we can keep the shorthand properties as-is with `DebugGroup`s.
27    DebugGroup {
28        /// The original name-value string pair.
29        original_name_value: Box<(String, String)>,
30        /// The parsed property list.
31        properties: Box<[Property]>,
32        /// `!important` or not.
33        important: bool,
34        /// Disabled or not.
35        disabled: bool,
36    },
37}
38
39impl PropertyMeta {
40    /// Generate a new property.
41    ///
42    /// Note that the property is in *debug* mode so that:
43    /// * it cannot be serialized even if it has been inserted to a rule;
44    /// * it has a little performance penalty.
45    pub fn new_debug_properties(source: &str) -> Vec<Self> {
46        parser::parse_inline_style(source, parser::StyleParsingDebugMode::Debug).0
47    }
48
49    /// Clone the property and set the disable state of the new property.
50    ///
51    /// Note that the new property is in *debug* mode so that:
52    /// * it cannot be serialized even if it has been inserted to a rule;
53    /// * it has a little performance penalty.
54    pub fn to_debug_state(&self, disabled: bool) -> Self {
55        match self {
56            Self::Normal { property } => Self::DebugGroup {
57                original_name_value: Box::new((
58                    self.get_property_name().into(),
59                    self.get_property_value_string(),
60                )),
61                properties: Box::new([property.clone()]),
62                important: false,
63                disabled,
64            },
65            Self::Important { property } => Self::DebugGroup {
66                original_name_value: Box::new((
67                    self.get_property_name().into(),
68                    self.get_property_value_string(),
69                )),
70                properties: Box::new([property.clone()]),
71                important: false,
72                disabled,
73            },
74            Self::DebugGroup {
75                original_name_value,
76                properties,
77                important,
78                ..
79            } => Self::DebugGroup {
80                original_name_value: original_name_value.clone(),
81                properties: properties.clone(),
82                important: *important,
83                disabled,
84            },
85        }
86    }
87
88    /// The property is `!important` or not.
89    pub fn is_important(&self) -> bool {
90        match self {
91            Self::Normal { .. } => false,
92            Self::Important { .. } => true,
93            Self::DebugGroup { important, .. } => *important,
94        }
95    }
96
97    /// Get the property name.
98    pub fn get_property_name(&self) -> Cow<'static, str> {
99        match self {
100            Self::Normal { property } => property.get_property_name().into(),
101            Self::Important { property } => property.get_property_name().into(),
102            Self::DebugGroup {
103                original_name_value,
104                ..
105            } => original_name_value.0.clone().into(),
106        }
107    }
108
109    /// Get the property value as a string.
110    ///
111    /// Note that it may (and may not) be normalized.
112    pub fn get_property_value_string(&self) -> String {
113        match self {
114            Self::Normal { property } => property.get_property_value_string(),
115            Self::Important { property } => {
116                let mut v = property.get_property_value_string();
117                v.push_str(" !important");
118                v
119            }
120            Self::DebugGroup {
121                original_name_value,
122                ..
123            } => original_name_value.1.clone(),
124        }
125    }
126
127    /// The property is disabled (only possible in debug state) or not.
128    pub fn is_disabled(&self) -> bool {
129        match self {
130            Self::Normal { .. } => false,
131            Self::Important { .. } => false,
132            Self::DebugGroup { disabled, .. } => *disabled,
133        }
134    }
135
136    /// The property is invalid (only possible in debug state) or not.
137    pub fn is_invalid(&self) -> bool {
138        match self {
139            Self::Normal { .. } => false,
140            Self::Important { .. } => false,
141            Self::DebugGroup { properties, .. } => properties.is_empty(),
142        }
143    }
144
145    /// The property is deprecated or not.
146    pub fn is_deprecated(&self) -> bool {
147        match self {
148            Self::Normal { property, .. } | Self::Important { property, .. } => {
149                property.is_deprecated()
150            }
151            Self::DebugGroup { .. } => false,
152        }
153    }
154
155    /// Merge the property into a `NodeProperties`.
156    pub fn merge_to_node_properties(
157        &self,
158        node_properties: &mut NodeProperties,
159        parent_node_properties: Option<&NodeProperties>,
160        current_font_size: f32,
161    ) {
162        match self {
163            PropertyMeta::Normal { property: p } => {
164                node_properties.merge_property(p, parent_node_properties, current_font_size)
165            }
166            PropertyMeta::Important { property: p } => {
167                node_properties.merge_property(p, parent_node_properties, current_font_size)
168            }
169            PropertyMeta::DebugGroup {
170                properties,
171                disabled,
172                ..
173            } => {
174                if !disabled {
175                    for p in &**properties {
176                        node_properties.merge_property(p, parent_node_properties, current_font_size)
177                    }
178                }
179            }
180        }
181    }
182
183    /// Get an iterate of the properties.
184    pub fn iter(&self) -> PropertyMetaIter {
185        PropertyMetaIter { pm: self, cur: 0 }
186    }
187
188    #[cfg(test)]
189    #[doc(hidden)]
190    pub fn property(&self) -> Option<Property> {
191        match self {
192            Self::Normal { property } | Self::Important { property } => Some(property.clone()),
193            Self::DebugGroup { .. } => None,
194        }
195    }
196}
197
198impl fmt::Display for PropertyMeta {
199    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
200        if self.is_disabled() {
201            write!(
202                f,
203                "/* {}: {}; */",
204                self.get_property_name(),
205                self.get_property_value_string(),
206            )
207        } else {
208            write!(
209                f,
210                "{}: {};",
211                self.get_property_name(),
212                self.get_property_value_string(),
213            )
214        }
215    }
216}
217
218/// The iterator for `PropertyMeta` .
219pub struct PropertyMetaIter<'a> {
220    pm: &'a PropertyMeta,
221    cur: usize,
222}
223
224impl<'a> Iterator for PropertyMetaIter<'a> {
225    type Item = &'a Property;
226
227    fn next(&mut self) -> Option<Self::Item> {
228        match self.pm {
229            PropertyMeta::Normal { property } | PropertyMeta::Important { property } => {
230                if self.cur == 0 {
231                    self.cur = 1;
232                    Some(property)
233                } else {
234                    None
235                }
236            }
237            PropertyMeta::DebugGroup { properties, .. } => {
238                if self.cur < properties.len() {
239                    let ret = &properties[self.cur];
240                    self.cur += 1;
241                    Some(ret)
242                } else {
243                    None
244                }
245            }
246        }
247    }
248}
249
250/// A CSS rule, e.g. `.my-class { ... }`.
251#[derive(Clone, Debug)]
252pub struct Rule {
253    pub(crate) selector: Selector,
254    pub(crate) properties: Vec<PropertyMeta>,
255    pub(crate) media: Option<Rc<Media>>,
256    pub(super) index: u32,
257    pub(crate) has_font_size: bool,
258}
259
260impl fmt::Display for Rule {
261    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262        let media_queries = self.get_media_query_string_list();
263        for media in media_queries.iter() {
264            write!(f, "@media {} {{ ", media)?;
265        }
266        let selector = self.get_selector_string();
267        write!(f, "{} {{ ", selector)?;
268        for prop in self.properties() {
269            write!(
270                f,
271                "{}: {}; ",
272                prop.get_property_name(),
273                prop.get_property_value_string()
274            )?;
275        }
276        write!(f, "}}")?;
277        for _ in media_queries.iter() {
278            write!(f, " }}")?;
279        }
280        Ok(())
281    }
282}
283
284impl Rule {
285    /// Create an empty rule.
286    pub fn new_empty() -> Box<Self> {
287        Box::new(Self {
288            selector: Selector::star_selector(),
289            properties: Vec::with_capacity(0),
290            media: None,
291            index: 0,
292            has_font_size: false,
293        })
294    }
295
296    pub(crate) fn new(
297        selector: Selector,
298        properties: Vec<PropertyMeta>,
299        media: Option<Rc<Media>>,
300    ) -> Box<Self> {
301        Self::new_with_index(selector, properties, media, 0)
302    }
303
304    pub(crate) fn new_with_index(
305        selector: Selector,
306        properties: Vec<PropertyMeta>,
307        media: Option<Rc<Media>>,
308        index: u32,
309    ) -> Box<Self> {
310        let mut has_font_size = false;
311        for p in properties.iter() {
312            match p {
313                PropertyMeta::Normal { property } | PropertyMeta::Important { property } => {
314                    match property {
315                        Property::FontSize(..) => {
316                            has_font_size = true;
317                        }
318                        _ => {}
319                    }
320                }
321                PropertyMeta::DebugGroup { properties, .. } => {
322                    for property in properties.iter() {
323                        match property {
324                            Property::FontSize(..) => {
325                                has_font_size = true;
326                            }
327                            _ => {}
328                        }
329                    }
330                }
331            }
332        }
333        Box::new(Self {
334            selector,
335            properties,
336            media,
337            index,
338            has_font_size,
339        })
340    }
341
342    /// Construct a new rule with media query strings and a selector string
343    ///
344    /// The media query strings should not contain the leading `@media` segment (matches the output of `get_media_query_string_list` ).
345    pub fn from_parts_str<'a>(
346        media_query_str_list: impl IntoIterator<Item = &'a str>,
347        selector_str: &str,
348    ) -> Result<Box<Self>, Warning> {
349        let mut media = None;
350        for (index, media_str) in media_query_str_list.into_iter().enumerate() {
351            let cur_media = parser::parse_media_expression_only(media_str).map_err(|mut w| {
352                w.message = format!("{} (in media index {})", w.message.as_str(), index).into();
353                w
354            })?;
355            media = Some(Rc::new(cur_media));
356        }
357        let selector = parser::parse_selector_only(selector_str)?;
358        Ok(Self::new(selector, vec![], media))
359    }
360
361    /// Modify the rule with a different selector (and construct a new one as the result)
362    pub fn modify_selector(&self, selector_str: &str) -> Result<Box<Self>, Warning> {
363        let media = self.media.clone();
364        let selector = parser::parse_selector_only(selector_str)?;
365        let properties = self.properties.clone();
366        Ok(Self::new(selector, properties, media))
367    }
368
369    /// Modify the rule by adding a new property (and construct a new one as the result)
370    pub fn add_properties(&self, p: impl IntoIterator<Item = PropertyMeta>) -> Box<Self> {
371        let media = self.media.clone();
372        let selector = self.selector.clone();
373        let mut properties = self.properties.clone();
374        for p in p {
375            properties.push(p);
376        }
377        Self::new(selector, properties, media)
378    }
379
380    /// Enable or disable the rule (and construct a new one as the result if success)
381    pub fn set_property_disabled(&self, index: usize, disabled: bool) -> Option<Box<Self>> {
382        let media = self.media.clone();
383        let selector = self.selector.clone();
384        let mut properties = self.properties.clone();
385        if index < properties.len() {
386            properties[index] = properties[index].to_debug_state(disabled);
387            Some(Self::new(selector, properties, media))
388        } else {
389            None
390        }
391    }
392
393    /// Modify the rule by removing a property (and construct a new one as the result if success)
394    pub fn remove_property(&self, index: usize) -> Option<Box<Self>> {
395        let media = self.media.clone();
396        let selector = self.selector.clone();
397        let mut properties = self.properties.clone();
398        if index < properties.len() {
399            properties.remove(index);
400            Some(Self::new(selector, properties, media))
401        } else {
402            None
403        }
404    }
405
406    /// Modify the rule by replacing properties (and construct a new one as the result if success)
407    pub fn replace_properties(
408        &self,
409        range: impl core::ops::RangeBounds<usize>,
410        p: impl IntoIterator<Item = PropertyMeta>,
411    ) -> Option<Box<Self>> {
412        use core::ops::Bound;
413        let media = self.media.clone();
414        let selector = self.selector.clone();
415        let mut properties = self.properties.clone();
416        let no_overflow = match range.end_bound() {
417            Bound::Unbounded => true,
418            Bound::Included(stp) => *stp < properties.len(),
419            Bound::Excluded(stp) => *stp <= properties.len(),
420        };
421        let no_reversed = match range.start_bound() {
422            Bound::Unbounded => true,
423            Bound::Included(st) => match range.end_bound() {
424                Bound::Unbounded => true,
425                Bound::Included(stp) => *st <= *stp,
426                Bound::Excluded(stp) => *st < *stp,
427            },
428            Bound::Excluded(st) => match range.end_bound() {
429                Bound::Unbounded => true,
430                Bound::Included(stp) => *st < *stp,
431                Bound::Excluded(stp) => st + 1 < *stp,
432            },
433        };
434        if no_overflow && no_reversed {
435            properties.splice(range, p);
436            Some(Self::new(selector, properties, media))
437        } else {
438            None
439        }
440    }
441
442    /// Get the `@media` list.
443    pub fn get_media_query_string_list(&self) -> Vec<String> {
444        let mut list = vec![];
445        if let Some(x) = &self.media {
446            x.to_media_query_string_list(&mut list);
447        }
448        list
449    }
450
451    /// Get the selector as a string.
452    pub fn get_selector_string(&self) -> String {
453        format!("{}", self.selector)
454    }
455
456    /// Get an iterator of the properties.
457    pub fn properties(&self) -> impl Iterator<Item = &PropertyMeta> {
458        self.properties.iter()
459    }
460
461    pub(crate) fn match_query<L: LengthNum, T: StyleNode>(
462        &self,
463        query: &[T],
464        media_query_status: &MediaQueryStatus<L>,
465        sheet_style_scope: Option<NonZeroUsize>,
466    ) -> Option<u16> {
467        match &self.media {
468            Some(media) => {
469                if !media.is_valid(media_query_status) {
470                    return None;
471                }
472            }
473            None => {}
474        }
475        self.selector.match_query(query, sheet_style_scope)
476    }
477}