use std::collections::HashMap;
use crate::css::Stylesheet;
use crate::dom::{Dom, NodeType};
use super::matching::matches_selector;
pub type ComputedStyle = HashMap<String, String>;
const INHERITED_PROPERTIES: &[&str] = &[
"color",
"font-family",
"font-size",
"font-weight",
"line-height",
"text-align",
"visibility",
];
struct MatchedDecl<'a> {
specificity: (u32, u32, u32),
order: usize,
important: bool,
property: &'a str,
value: &'a str,
}
pub fn compute_styles(dom: &Dom, stylesheet: &Stylesheet) -> HashMap<usize, ComputedStyle> {
let mut result = HashMap::new();
compute_recursive(dom, dom.root, stylesheet, None, &mut result);
result
}
fn compute_recursive(
dom: &Dom,
node_idx: usize,
stylesheet: &Stylesheet,
parent_style: Option<&ComputedStyle>,
result: &mut HashMap<usize, ComputedStyle>,
) {
if let NodeType::Element(_) = &dom.nodes[node_idx].node_type {
let own = resolve_own_declarations(dom, node_idx, stylesheet);
let mut style = ComputedStyle::new();
if let Some(parent) = parent_style {
for prop in INHERITED_PROPERTIES {
if let Some(v) = parent.get(*prop) {
style.insert(prop.to_string(), v.clone());
}
}
}
for (k, v) in own {
style.insert(k, v);
}
result.insert(node_idx, style);
}
let child_style = result.get(&node_idx).cloned();
for &child in &dom.nodes[node_idx].children.clone() {
compute_recursive(dom, child, stylesheet, child_style.as_ref(), result);
}
}
fn resolve_own_declarations(
dom: &Dom,
node_idx: usize,
stylesheet: &Stylesheet,
) -> ComputedStyle {
let mut matched: Vec<MatchedDecl> = Vec::new();
for (order, rule) in stylesheet.rules.iter().enumerate() {
let best_specificity = rule
.selectors
.iter()
.filter(|sel| matches_selector(dom, node_idx, sel))
.map(|sel| sel.specificity())
.max();
let Some(specificity) = best_specificity else {
continue;
};
for decl in &rule.declarations {
matched.push(MatchedDecl {
specificity,
order,
important: decl.important,
property: &decl.property,
value: &decl.value,
});
}
}
let mut by_property: HashMap<&str, Vec<&MatchedDecl>> = HashMap::new();
for decl in &matched {
by_property.entry(decl.property).or_default().push(decl);
}
let mut style = ComputedStyle::new();
for (property, decls) in by_property {
let winner = decls.iter().max_by(|a, b| {
(a.important, a.specificity, a.order).cmp(&(b.important, b.specificity, b.order))
});
if let Some(winner) = winner {
style.insert(property.to_string(), winner.value.to_string());
}
}
style
}