Skip to main content

rustmotion_core/css/
cascade.rs

1//! CSS cascade & inheritance.
2//!
3//! Walks the BoxTree and propagates inheritable properties from parent to
4//! child when the child has not specified them. Properties not in the
5//! inheritable list are left untouched (initial values applied at paint time).
6//!
7//! Inheritable properties (per CSS spec, restricted to our scope):
8//! - `color`
9//! - `font-family`
10//! - `font-size`
11//! - `font-weight`
12//! - `font-style`
13//! - `line-height`
14//! - `letter-spacing`
15//! - `text-align`
16//! - `white-space`
17//! - `overflow-wrap`
18//! - `visibility`
19//! - `text-decoration` (not strictly inheritable but used as such here)
20//!
21//! All other properties (background, padding, border, transform, etc.) are
22//! NOT inherited.
23
24use super::style::CssStyle;
25
26/// Inherit unset child properties from the parent. `parent` is the already
27/// resolved style (post-cascade) of the parent node.
28pub fn inherit_from(parent: &CssStyle, child: &mut CssStyle) {
29    if child.color.is_none() {
30        child.color = parent.color.clone();
31    }
32    if child.font_family.is_none() {
33        child.font_family = parent.font_family.clone();
34    }
35    if child.font_size.is_none() {
36        child.font_size = parent.font_size.clone();
37    }
38    if child.font_weight.is_none() {
39        child.font_weight = parent.font_weight.clone();
40    }
41    if child.font_style.is_none() {
42        child.font_style = parent.font_style;
43    }
44    if child.line_height.is_none() {
45        child.line_height = parent.line_height.clone();
46    }
47    if child.letter_spacing.is_none() {
48        child.letter_spacing = parent.letter_spacing.clone();
49    }
50    if child.text_align.is_none() {
51        child.text_align = parent.text_align;
52    }
53    if child.white_space.is_none() {
54        child.white_space = parent.white_space;
55    }
56    if child.overflow_wrap.is_none() {
57        child.overflow_wrap = parent.overflow_wrap;
58    }
59    if child.visibility.is_none() {
60        child.visibility = parent.visibility;
61    }
62    if child.text_decoration.is_none() {
63        child.text_decoration = parent.text_decoration.clone();
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use crate::css::style::{Color, Display, FontStyle, TextAlign};
71
72    #[test]
73    fn child_inherits_color_when_unset() {
74        let parent = CssStyle {
75            color: Some(Color::String("#ff0000".into())),
76            ..Default::default()
77        };
78        let mut child = CssStyle::default();
79        inherit_from(&parent, &mut child);
80        assert!(matches!(child.color, Some(Color::String(_))));
81    }
82
83    #[test]
84    fn child_overrides_parent_color() {
85        let parent = CssStyle {
86            color: Some(Color::String("#ff0000".into())),
87            ..Default::default()
88        };
89        let mut child = CssStyle {
90            color: Some(Color::String("#00ff00".into())),
91            ..Default::default()
92        };
93        inherit_from(&parent, &mut child);
94        match child.color {
95            Some(Color::String(s)) => assert_eq!(s, "#00ff00"),
96            _ => panic!("expected child color preserved"),
97        }
98    }
99
100    #[test]
101    fn non_inheritable_not_propagated() {
102        let parent = CssStyle {
103            display: Some(Display::Flex),
104            ..Default::default()
105        };
106        let mut child = CssStyle::default();
107        inherit_from(&parent, &mut child);
108        // display is not inherited
109        assert!(child.display.is_none());
110    }
111
112    #[test]
113    fn font_props_inherited() {
114        let parent = CssStyle {
115            font_family: Some("Arial".into()),
116            font_style: Some(FontStyle::Italic),
117            text_align: Some(TextAlign::Center),
118            ..Default::default()
119        };
120        let mut child = CssStyle::default();
121        inherit_from(&parent, &mut child);
122        assert_eq!(child.font_family.as_deref(), Some("Arial"));
123        assert_eq!(child.font_style, Some(FontStyle::Italic));
124        assert_eq!(child.text_align, Some(TextAlign::Center));
125    }
126}