1use std::collections::HashMap;
2
3use crate::{
4 css_cascade::{inheritance::is_inherited, initial::initial_value},
5 css_values::property::{CssValue, PropertyId},
6};
7
8#[derive(Debug, Clone, Default)]
9pub struct ComputedStyle {
10 properties: HashMap<PropertyId, CssValue>,
11}
12
13impl ComputedStyle {
14 pub fn new() -> Self {
15 Self::default()
16 }
17
18 pub fn set(&mut self, property: PropertyId, value: CssValue) {
19 self.properties.insert(property, value);
20 }
21
22 pub fn get(&self, property: &PropertyId) -> Option<&CssValue> {
23 self.properties.get(property)
24 }
25
26 pub fn get_or_initial(&self, property: &PropertyId) -> CssValue {
27 self.properties
28 .get(property)
29 .cloned()
30 .unwrap_or_else(|| initial_value(property))
31 }
32
33 pub fn resolve(
34 cascaded: &HashMap<PropertyId, CssValue>,
35 parent: Option<&ComputedStyle>,
36 ) -> Self {
37 let mut style = ComputedStyle::new();
38
39 let all_properties = all_property_ids();
40
41 for prop in &all_properties {
42 let value = if let Some(cascaded_value) = cascaded.get(prop) {
43 match cascaded_value {
44 CssValue::Inherit => parent
45 .and_then(|p| p.get(prop))
46 .cloned()
47 .unwrap_or_else(|| initial_value(prop)),
48 CssValue::Initial => initial_value(prop),
49 CssValue::Unset => {
50 if is_inherited(prop) {
51 parent
52 .and_then(|p| p.get(prop))
53 .cloned()
54 .unwrap_or_else(|| initial_value(prop))
55 } else {
56 initial_value(prop)
57 }
58 }
59 CssValue::Revert | CssValue::RevertLayer => {
60 if is_inherited(prop) {
61 parent
62 .and_then(|p| p.get(prop))
63 .cloned()
64 .unwrap_or_else(|| initial_value(prop))
65 } else {
66 initial_value(prop)
67 }
68 }
69 other => other.clone(),
70 }
71 } else if is_inherited(prop) {
72 parent
73 .and_then(|p| p.get(prop))
74 .cloned()
75 .unwrap_or_else(|| initial_value(prop))
76 } else {
77 initial_value(prop)
78 };
79
80 style.set(prop.clone(), value);
81 }
82
83 style
84 }
85}
86
87fn all_property_ids() -> Vec<PropertyId> {
88 vec![
89 PropertyId::Display,
90 PropertyId::Position,
91 PropertyId::Width,
92 PropertyId::Height,
93 PropertyId::MinWidth,
94 PropertyId::MinHeight,
95 PropertyId::MaxWidth,
96 PropertyId::MaxHeight,
97 PropertyId::MarginTop,
98 PropertyId::MarginRight,
99 PropertyId::MarginBottom,
100 PropertyId::MarginLeft,
101 PropertyId::PaddingTop,
102 PropertyId::PaddingRight,
103 PropertyId::PaddingBottom,
104 PropertyId::PaddingLeft,
105 PropertyId::BorderTopWidth,
106 PropertyId::BorderRightWidth,
107 PropertyId::BorderBottomWidth,
108 PropertyId::BorderLeftWidth,
109 PropertyId::BoxSizing,
110 PropertyId::OverflowX,
111 PropertyId::OverflowY,
112 PropertyId::Float,
113 PropertyId::Clear,
114 PropertyId::FlexDirection,
115 PropertyId::FlexWrap,
116 PropertyId::FlexGrow,
117 PropertyId::FlexShrink,
118 PropertyId::FlexBasis,
119 PropertyId::AlignItems,
120 PropertyId::AlignSelf,
121 PropertyId::AlignContent,
122 PropertyId::JustifyContent,
123 PropertyId::JustifyItems,
124 PropertyId::JustifySelf,
125 PropertyId::Gap,
126 PropertyId::RowGap,
127 PropertyId::ColumnGap,
128 PropertyId::FontSize,
129 PropertyId::FontFamily,
130 PropertyId::FontWeight,
131 PropertyId::FontStyle,
132 PropertyId::LineHeight,
133 PropertyId::TextAlign,
134 PropertyId::WhiteSpace,
135 PropertyId::Color,
136 PropertyId::BackgroundColor,
137 PropertyId::Visibility,
138 PropertyId::Opacity,
139 PropertyId::ZIndex,
140 PropertyId::ContentVisibility,
141 PropertyId::Transform,
142 ]
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use crate::css_values::types::{color::Color, display::Display};
149
150 #[test]
151 fn initial_values_default() {
152 let style = ComputedStyle::resolve(&HashMap::new(), None);
153 assert_eq!(
154 style.get_or_initial(&PropertyId::Display),
155 CssValue::Display(Display::Inline)
156 );
157 assert_eq!(
158 style.get_or_initial(&PropertyId::Opacity),
159 CssValue::Number(1.0)
160 );
161 }
162
163 #[test]
164 fn inheritance_works() {
165 let mut parent = ComputedStyle::new();
166 parent.set(
167 PropertyId::Color,
168 CssValue::Color(Color::Rgba {
169 r: 255,
170 g: 0,
171 b: 0,
172 a: 1.0,
173 }),
174 );
175
176 let child = ComputedStyle::resolve(&HashMap::new(), Some(&parent));
177 assert_eq!(
178 child.get(&PropertyId::Color),
179 Some(&CssValue::Color(Color::Rgba {
180 r: 255,
181 g: 0,
182 b: 0,
183 a: 1.0
184 }))
185 );
186 }
187
188 #[test]
189 fn non_inherited_uses_initial() {
190 let mut parent = ComputedStyle::new();
191 parent.set(PropertyId::Display, CssValue::Display(Display::Flex));
192
193 let child = ComputedStyle::resolve(&HashMap::new(), Some(&parent));
194 assert_eq!(
195 child.get(&PropertyId::Display),
196 Some(&CssValue::Display(Display::Inline))
197 );
198 }
199}