//! CSS cascade and computed style resolution. //! //! For each DOM element, resolves the final computed value of every CSS property //! by collecting matching rules, applying the cascade (specificity + source order), //! handling property inheritance, and resolving relative values. use we_css::parser::{Declaration, Stylesheet}; use we_css::values::{expand_shorthand, parse_value, Color, CssValue, LengthUnit}; use we_dom::{Document, NodeData, NodeId}; use crate::matching::collect_matching_rules; // --------------------------------------------------------------------------- // Display // --------------------------------------------------------------------------- /// CSS `display` property values (subset). #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum Display { Block, #[default] Inline, Flex, InlineFlex, None, } // --------------------------------------------------------------------------- // Position // --------------------------------------------------------------------------- /// CSS `position` property values. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum Position { #[default] Static, Relative, Absolute, Fixed, Sticky, } // --------------------------------------------------------------------------- // FontWeight // --------------------------------------------------------------------------- /// CSS `font-weight` as a numeric value (100-900). #[derive(Debug, Clone, Copy, PartialEq)] pub struct FontWeight(pub f32); impl Default for FontWeight { fn default() -> Self { FontWeight(400.0) // normal } } // --------------------------------------------------------------------------- // FontStyle // --------------------------------------------------------------------------- #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum FontStyle { #[default] Normal, Italic, Oblique, } // --------------------------------------------------------------------------- // TextAlign // --------------------------------------------------------------------------- #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum TextAlign { #[default] Left, Right, Center, Justify, } // --------------------------------------------------------------------------- // TextDecoration // --------------------------------------------------------------------------- #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum TextDecoration { #[default] None, Underline, Overline, LineThrough, } // --------------------------------------------------------------------------- // BoxSizing // --------------------------------------------------------------------------- /// CSS `box-sizing` property values. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum BoxSizing { #[default] ContentBox, BorderBox, } // --------------------------------------------------------------------------- // Float // --------------------------------------------------------------------------- /// CSS `float` property values. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum Float { #[default] None, Left, Right, } // --------------------------------------------------------------------------- // Clear // --------------------------------------------------------------------------- /// CSS `clear` property values. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum Clear { #[default] None, Left, Right, Both, } // --------------------------------------------------------------------------- // Overflow // --------------------------------------------------------------------------- #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum Overflow { #[default] Visible, Hidden, Scroll, Auto, } // --------------------------------------------------------------------------- // Visibility // --------------------------------------------------------------------------- #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum Visibility { #[default] Visible, Hidden, Collapse, } // --------------------------------------------------------------------------- // Flex enums // --------------------------------------------------------------------------- #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum FlexDirection { #[default] Row, RowReverse, Column, ColumnReverse, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum FlexWrap { #[default] Nowrap, Wrap, WrapReverse, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum JustifyContent { #[default] FlexStart, FlexEnd, Center, SpaceBetween, SpaceAround, SpaceEvenly, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum AlignItems { #[default] Stretch, FlexStart, FlexEnd, Center, Baseline, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum AlignContent { #[default] Stretch, FlexStart, FlexEnd, Center, SpaceBetween, SpaceAround, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum AlignSelf { #[default] Auto, FlexStart, FlexEnd, Center, Baseline, Stretch, } // --------------------------------------------------------------------------- // BorderStyle // --------------------------------------------------------------------------- #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum BorderStyle { #[default] None, Hidden, Dotted, Dashed, Solid, Double, Groove, Ridge, Inset, Outset, } // --------------------------------------------------------------------------- // LengthOrAuto // --------------------------------------------------------------------------- /// A computed length (resolved to px), a percentage (unresolved), or `auto`. #[derive(Debug, Clone, Copy, PartialEq, Default)] pub enum LengthOrAuto { Length(f32), /// Percentage value (0.0–100.0), resolved during layout against the containing block. Percentage(f32), #[default] Auto, } // --------------------------------------------------------------------------- // ComputedStyle // --------------------------------------------------------------------------- /// The fully resolved computed style for a single element. #[derive(Debug, Clone, PartialEq)] pub struct ComputedStyle { // Display pub display: Display, // Box model: margin pub margin_top: LengthOrAuto, pub margin_right: LengthOrAuto, pub margin_bottom: LengthOrAuto, pub margin_left: LengthOrAuto, // Box model: padding (percentages resolve against containing block width) pub padding_top: LengthOrAuto, pub padding_right: LengthOrAuto, pub padding_bottom: LengthOrAuto, pub padding_left: LengthOrAuto, // Box model: border width pub border_top_width: f32, pub border_right_width: f32, pub border_bottom_width: f32, pub border_left_width: f32, // Box model: border style pub border_top_style: BorderStyle, pub border_right_style: BorderStyle, pub border_bottom_style: BorderStyle, pub border_left_style: BorderStyle, // Box model: border color pub border_top_color: Color, pub border_right_color: Color, pub border_bottom_color: Color, pub border_left_color: Color, // Box model: dimensions pub width: LengthOrAuto, pub height: LengthOrAuto, // Box model: sizing pub box_sizing: BoxSizing, // Text / inherited pub color: Color, pub font_size: f32, pub font_weight: FontWeight, pub font_style: FontStyle, pub font_family: String, pub text_align: TextAlign, pub text_decoration: TextDecoration, pub line_height: f32, // Background pub background_color: Color, // Position pub position: Position, pub top: LengthOrAuto, pub right: LengthOrAuto, pub bottom: LengthOrAuto, pub left: LengthOrAuto, pub z_index: Option, // Float pub float: Float, pub clear: Clear, // Overflow pub overflow: Overflow, // Visibility (inherited) pub visibility: Visibility, // Flex container properties pub flex_direction: FlexDirection, pub flex_wrap: FlexWrap, pub justify_content: JustifyContent, pub align_items: AlignItems, pub align_content: AlignContent, pub row_gap: f32, pub column_gap: f32, // Flex item properties pub flex_grow: f32, pub flex_shrink: f32, pub flex_basis: LengthOrAuto, pub align_self: AlignSelf, pub order: i32, } impl Default for ComputedStyle { fn default() -> Self { ComputedStyle { display: Display::Inline, margin_top: LengthOrAuto::Length(0.0), margin_right: LengthOrAuto::Length(0.0), margin_bottom: LengthOrAuto::Length(0.0), margin_left: LengthOrAuto::Length(0.0), padding_top: LengthOrAuto::Length(0.0), padding_right: LengthOrAuto::Length(0.0), padding_bottom: LengthOrAuto::Length(0.0), padding_left: LengthOrAuto::Length(0.0), border_top_width: 0.0, border_right_width: 0.0, border_bottom_width: 0.0, border_left_width: 0.0, border_top_style: BorderStyle::None, border_right_style: BorderStyle::None, border_bottom_style: BorderStyle::None, border_left_style: BorderStyle::None, border_top_color: Color::rgb(0, 0, 0), border_right_color: Color::rgb(0, 0, 0), border_bottom_color: Color::rgb(0, 0, 0), border_left_color: Color::rgb(0, 0, 0), width: LengthOrAuto::Auto, height: LengthOrAuto::Auto, box_sizing: BoxSizing::ContentBox, color: Color::rgb(0, 0, 0), font_size: 16.0, font_weight: FontWeight(400.0), font_style: FontStyle::Normal, font_family: String::new(), text_align: TextAlign::Left, text_decoration: TextDecoration::None, line_height: 19.2, // 1.2 * 16 background_color: Color::new(0, 0, 0, 0), // transparent position: Position::Static, top: LengthOrAuto::Auto, right: LengthOrAuto::Auto, bottom: LengthOrAuto::Auto, left: LengthOrAuto::Auto, z_index: None, float: Float::None, clear: Clear::None, overflow: Overflow::Visible, visibility: Visibility::Visible, flex_direction: FlexDirection::Row, flex_wrap: FlexWrap::Nowrap, justify_content: JustifyContent::FlexStart, align_items: AlignItems::Stretch, align_content: AlignContent::Stretch, row_gap: 0.0, column_gap: 0.0, flex_grow: 0.0, flex_shrink: 1.0, flex_basis: LengthOrAuto::Auto, align_self: AlignSelf::Auto, order: 0, } } } // --------------------------------------------------------------------------- // Property classification: inherited vs non-inherited // --------------------------------------------------------------------------- fn is_inherited_property(property: &str) -> bool { matches!( property, "color" | "font-size" | "font-weight" | "font-style" | "font-family" | "text-align" | "text-decoration" | "line-height" | "visibility" ) } // --------------------------------------------------------------------------- // User-agent stylesheet // --------------------------------------------------------------------------- /// Returns the user-agent default stylesheet. pub fn ua_stylesheet() -> Stylesheet { use we_css::parser::Parser; Parser::parse(UA_CSS) } const UA_CSS: &str = r#" html, body, div, p, pre, h1, h2, h3, h4, h5, h6, ul, ol, li, blockquote, section, article, nav, header, footer, main, hr { display: block; } span, a, em, strong, b, i, u, code, small, sub, sup, br { display: inline; } head, title, script, style, link, meta { display: none; } body { margin: 8px; } h1 { font-size: 2em; margin-top: 0.67em; margin-bottom: 0.67em; font-weight: bold; } h2 { font-size: 1.5em; margin-top: 0.83em; margin-bottom: 0.83em; font-weight: bold; } h3 { font-size: 1.17em; margin-top: 1em; margin-bottom: 1em; font-weight: bold; } h4 { font-size: 1em; margin-top: 1.33em; margin-bottom: 1.33em; font-weight: bold; } h5 { font-size: 0.83em; margin-top: 1.67em; margin-bottom: 1.67em; font-weight: bold; } h6 { font-size: 0.67em; margin-top: 2.33em; margin-bottom: 2.33em; font-weight: bold; } p { margin-top: 1em; margin-bottom: 1em; } strong, b { font-weight: bold; } em, i { font-style: italic; } a { color: blue; text-decoration: underline; } u { text-decoration: underline; } "#; // --------------------------------------------------------------------------- // Resolve a CssValue to f32 px given context // --------------------------------------------------------------------------- fn resolve_length_unit(value: f64, unit: LengthUnit, em_base: f32, viewport: (f32, f32)) -> f32 { let v = value as f32; let (vw, vh) = viewport; match unit { LengthUnit::Px => v, LengthUnit::Em => v * em_base, LengthUnit::Rem => v * 16.0, // root font size is always 16px for now LengthUnit::Pt => v * (96.0 / 72.0), LengthUnit::Cm => v * (96.0 / 2.54), LengthUnit::Mm => v * (96.0 / 25.4), LengthUnit::In => v * 96.0, LengthUnit::Pc => v * 16.0, LengthUnit::Vw => v * vw / 100.0, LengthUnit::Vh => v * vh / 100.0, LengthUnit::Vmin => v * vw.min(vh) / 100.0, LengthUnit::Vmax => v * vw.max(vh) / 100.0, } } /// Resolve a CSS value to `LengthOrAuto` for layout properties (width, height, /// margin, padding, top/right/bottom/left). Percentages are preserved as /// `LengthOrAuto::Percentage` for later resolution during layout against the /// containing block. fn resolve_layout_length_or_auto( value: &CssValue, current_font_size: f32, viewport: (f32, f32), ) -> LengthOrAuto { match value { CssValue::Auto => LengthOrAuto::Auto, CssValue::Percentage(p) => LengthOrAuto::Percentage(*p as f32), CssValue::Length(n, unit) => { LengthOrAuto::Length(resolve_length_unit(*n, *unit, current_font_size, viewport)) } CssValue::Zero => LengthOrAuto::Length(0.0), CssValue::Number(n) if *n == 0.0 => LengthOrAuto::Length(0.0), _ => LengthOrAuto::Auto, } } fn resolve_color(value: &CssValue, current_color: Color) -> Option { match value { CssValue::Color(c) => Some(*c), CssValue::CurrentColor => Some(current_color), CssValue::Transparent => Some(Color::new(0, 0, 0, 0)), _ => None, } } // --------------------------------------------------------------------------- // Apply a single property value to a ComputedStyle // --------------------------------------------------------------------------- fn apply_property( style: &mut ComputedStyle, property: &str, value: &CssValue, parent: &ComputedStyle, viewport: (f32, f32), ) { // Handle inherit/initial/unset match value { CssValue::Inherit => { inherit_property(style, property, parent); return; } CssValue::Initial => { reset_property_to_initial(style, property); return; } CssValue::Unset => { if is_inherited_property(property) { inherit_property(style, property, parent); } else { reset_property_to_initial(style, property); } return; } _ => {} } let parent_fs = parent.font_size; let current_fs = style.font_size; match property { "display" => { style.display = match value { CssValue::Keyword(k) => match k.as_str() { "block" => Display::Block, "inline" => Display::Inline, "flex" => Display::Flex, "inline-flex" => Display::InlineFlex, _ => Display::Block, }, CssValue::None => Display::None, _ => style.display, }; } // Margin (percentages preserved for layout resolution) "margin-top" => { style.margin_top = resolve_layout_length_or_auto(value, current_fs, viewport); } "margin-right" => { style.margin_right = resolve_layout_length_or_auto(value, current_fs, viewport); } "margin-bottom" => { style.margin_bottom = resolve_layout_length_or_auto(value, current_fs, viewport); } "margin-left" => { style.margin_left = resolve_layout_length_or_auto(value, current_fs, viewport); } // Padding (percentages preserved for layout resolution) "padding-top" => { style.padding_top = resolve_layout_length_or_auto(value, current_fs, viewport); } "padding-right" => { style.padding_right = resolve_layout_length_or_auto(value, current_fs, viewport); } "padding-bottom" => { style.padding_bottom = resolve_layout_length_or_auto(value, current_fs, viewport); } "padding-left" => { style.padding_left = resolve_layout_length_or_auto(value, current_fs, viewport); } // Border width "border-top-width" | "border-right-width" | "border-bottom-width" | "border-left-width" => { let w = resolve_border_width(value, parent_fs, viewport); match property { "border-top-width" => style.border_top_width = w, "border-right-width" => style.border_right_width = w, "border-bottom-width" => style.border_bottom_width = w, "border-left-width" => style.border_left_width = w, _ => {} } } // Border width shorthand (single value applied to all sides) "border-width" => { let w = resolve_border_width(value, parent_fs, viewport); style.border_top_width = w; style.border_right_width = w; style.border_bottom_width = w; style.border_left_width = w; } // Border style "border-top-style" | "border-right-style" | "border-bottom-style" | "border-left-style" => { let s = parse_border_style(value); match property { "border-top-style" => style.border_top_style = s, "border-right-style" => style.border_right_style = s, "border-bottom-style" => style.border_bottom_style = s, "border-left-style" => style.border_left_style = s, _ => {} } } "border-style" => { let s = parse_border_style(value); style.border_top_style = s; style.border_right_style = s; style.border_bottom_style = s; style.border_left_style = s; } // Border color "border-top-color" | "border-right-color" | "border-bottom-color" | "border-left-color" => { if let Some(c) = resolve_color(value, style.color) { match property { "border-top-color" => style.border_top_color = c, "border-right-color" => style.border_right_color = c, "border-bottom-color" => style.border_bottom_color = c, "border-left-color" => style.border_left_color = c, _ => {} } } } "border-color" => { if let Some(c) = resolve_color(value, style.color) { style.border_top_color = c; style.border_right_color = c; style.border_bottom_color = c; style.border_left_color = c; } } // Dimensions (percentages preserved for layout resolution) "width" => { style.width = resolve_layout_length_or_auto(value, current_fs, viewport); } "height" => { style.height = resolve_layout_length_or_auto(value, current_fs, viewport); } // Box sizing "box-sizing" => { style.box_sizing = match value { CssValue::Keyword(k) => match k.as_str() { "content-box" => BoxSizing::ContentBox, "border-box" => BoxSizing::BorderBox, _ => style.box_sizing, }, _ => style.box_sizing, }; } // Color (inherited) "color" => { if let Some(c) = resolve_color(value, parent.color) { style.color = c; // Update border colors to match (currentColor default) } } // Font-size (inherited) — special: em units relative to parent "font-size" => { match value { CssValue::Length(n, unit) => { style.font_size = resolve_length_unit(*n, *unit, parent_fs, viewport); } CssValue::Percentage(p) => { style.font_size = (*p / 100.0) as f32 * parent_fs; } CssValue::Zero => { style.font_size = 0.0; } CssValue::Keyword(k) => { style.font_size = match k.as_str() { "xx-small" => 9.0, "x-small" => 10.0, "small" => 13.0, "medium" => 16.0, "large" => 18.0, "x-large" => 24.0, "xx-large" => 32.0, "smaller" => parent_fs * 0.833, "larger" => parent_fs * 1.2, _ => style.font_size, }; } _ => {} } // Update line-height when font-size changes style.line_height = style.font_size * 1.2; } // Font-weight (inherited) "font-weight" => { style.font_weight = match value { CssValue::Keyword(k) => match k.as_str() { "normal" => FontWeight(400.0), "bold" => FontWeight(700.0), "lighter" => FontWeight((parent.font_weight.0 - 100.0).max(100.0)), "bolder" => FontWeight((parent.font_weight.0 + 300.0).min(900.0)), _ => style.font_weight, }, CssValue::Number(n) => FontWeight(*n as f32), _ => style.font_weight, }; } // Font-style (inherited) "font-style" => { style.font_style = match value { CssValue::Keyword(k) => match k.as_str() { "normal" => FontStyle::Normal, "italic" => FontStyle::Italic, "oblique" => FontStyle::Oblique, _ => style.font_style, }, _ => style.font_style, }; } // Font-family (inherited) "font-family" => { if let CssValue::String(s) | CssValue::Keyword(s) = value { style.font_family = s.clone(); } } // Text-align (inherited) "text-align" => { style.text_align = match value { CssValue::Keyword(k) => match k.as_str() { "left" => TextAlign::Left, "right" => TextAlign::Right, "center" => TextAlign::Center, "justify" => TextAlign::Justify, _ => style.text_align, }, _ => style.text_align, }; } // Text-decoration (inherited) "text-decoration" => { style.text_decoration = match value { CssValue::Keyword(k) => match k.as_str() { "underline" => TextDecoration::Underline, "overline" => TextDecoration::Overline, "line-through" => TextDecoration::LineThrough, _ => style.text_decoration, }, CssValue::None => TextDecoration::None, _ => style.text_decoration, }; } // Line-height (inherited) "line-height" => match value { CssValue::Keyword(k) if k == "normal" => { style.line_height = style.font_size * 1.2; } CssValue::Number(n) => { style.line_height = *n as f32 * style.font_size; } CssValue::Length(n, unit) => { style.line_height = resolve_length_unit(*n, *unit, style.font_size, viewport); } CssValue::Percentage(p) => { style.line_height = (*p / 100.0) as f32 * style.font_size; } _ => {} }, // Background color "background-color" => { if let Some(c) = resolve_color(value, style.color) { style.background_color = c; } } // Position "position" => { style.position = match value { CssValue::Keyword(k) => match k.as_str() { "static" => Position::Static, "relative" => Position::Relative, "absolute" => Position::Absolute, "fixed" => Position::Fixed, "sticky" => Position::Sticky, _ => style.position, }, _ => style.position, }; } // Position offsets (percentages preserved for layout resolution) "top" => style.top = resolve_layout_length_or_auto(value, current_fs, viewport), "right" => style.right = resolve_layout_length_or_auto(value, current_fs, viewport), "bottom" => style.bottom = resolve_layout_length_or_auto(value, current_fs, viewport), "left" => style.left = resolve_layout_length_or_auto(value, current_fs, viewport), // z-index "z-index" => { style.z_index = match value { CssValue::Number(n) => Some(*n as i32), CssValue::Keyword(k) if k == "auto" => None, _ => style.z_index, }; } // Float "float" => { style.float = match value { CssValue::Keyword(k) => match k.as_str() { "none" => Float::None, "left" => Float::Left, "right" => Float::Right, _ => style.float, }, _ => style.float, }; } // Clear "clear" => { style.clear = match value { CssValue::Keyword(k) => match k.as_str() { "none" => Clear::None, "left" => Clear::Left, "right" => Clear::Right, "both" => Clear::Both, _ => style.clear, }, _ => style.clear, }; } // Overflow "overflow" => { style.overflow = match value { CssValue::Keyword(k) => match k.as_str() { "visible" => Overflow::Visible, "hidden" => Overflow::Hidden, "scroll" => Overflow::Scroll, _ => style.overflow, }, CssValue::Auto => Overflow::Auto, _ => style.overflow, }; } // Visibility (inherited) "visibility" => { style.visibility = match value { CssValue::Keyword(k) => match k.as_str() { "visible" => Visibility::Visible, "hidden" => Visibility::Hidden, "collapse" => Visibility::Collapse, _ => style.visibility, }, _ => style.visibility, }; } // Flex container properties "flex-direction" => { style.flex_direction = match value { CssValue::Keyword(k) => match k.as_str() { "row" => FlexDirection::Row, "row-reverse" => FlexDirection::RowReverse, "column" => FlexDirection::Column, "column-reverse" => FlexDirection::ColumnReverse, _ => style.flex_direction, }, _ => style.flex_direction, }; } "flex-wrap" => { style.flex_wrap = match value { CssValue::Keyword(k) => match k.as_str() { "nowrap" => FlexWrap::Nowrap, "wrap" => FlexWrap::Wrap, "wrap-reverse" => FlexWrap::WrapReverse, _ => style.flex_wrap, }, _ => style.flex_wrap, }; } "justify-content" => { style.justify_content = match value { CssValue::Keyword(k) => match k.as_str() { "flex-start" => JustifyContent::FlexStart, "flex-end" => JustifyContent::FlexEnd, "center" => JustifyContent::Center, "space-between" => JustifyContent::SpaceBetween, "space-around" => JustifyContent::SpaceAround, "space-evenly" => JustifyContent::SpaceEvenly, _ => style.justify_content, }, _ => style.justify_content, }; } "align-items" => { style.align_items = match value { CssValue::Keyword(k) => match k.as_str() { "stretch" => AlignItems::Stretch, "flex-start" => AlignItems::FlexStart, "flex-end" => AlignItems::FlexEnd, "center" => AlignItems::Center, "baseline" => AlignItems::Baseline, _ => style.align_items, }, _ => style.align_items, }; } "align-content" => { style.align_content = match value { CssValue::Keyword(k) => match k.as_str() { "stretch" => AlignContent::Stretch, "flex-start" => AlignContent::FlexStart, "flex-end" => AlignContent::FlexEnd, "center" => AlignContent::Center, "space-between" => AlignContent::SpaceBetween, "space-around" => AlignContent::SpaceAround, _ => style.align_content, }, _ => style.align_content, }; } "row-gap" => { if let CssValue::Length(n, unit) = value { style.row_gap = resolve_length_unit(*n, *unit, current_fs, viewport); } else if let CssValue::Zero = value { style.row_gap = 0.0; } } "column-gap" => { if let CssValue::Length(n, unit) = value { style.column_gap = resolve_length_unit(*n, *unit, current_fs, viewport); } else if let CssValue::Zero = value { style.column_gap = 0.0; } } // Flex item properties "flex-grow" => { if let CssValue::Number(n) = value { style.flex_grow = *n as f32; } else if let CssValue::Zero = value { style.flex_grow = 0.0; } } "flex-shrink" => { if let CssValue::Number(n) = value { style.flex_shrink = *n as f32; } else if let CssValue::Zero = value { style.flex_shrink = 0.0; } } "flex-basis" => { style.flex_basis = resolve_layout_length_or_auto(value, current_fs, viewport); } "align-self" => { style.align_self = match value { CssValue::Keyword(k) => match k.as_str() { "flex-start" => AlignSelf::FlexStart, "flex-end" => AlignSelf::FlexEnd, "center" => AlignSelf::Center, "baseline" => AlignSelf::Baseline, "stretch" => AlignSelf::Stretch, _ => style.align_self, }, CssValue::Auto => AlignSelf::Auto, _ => style.align_self, }; } "order" => { if let CssValue::Number(n) = value { style.order = *n as i32; } else if let CssValue::Zero = value { style.order = 0; } } _ => {} // Unknown property — ignore } } fn resolve_border_width(value: &CssValue, em_base: f32, viewport: (f32, f32)) -> f32 { match value { CssValue::Length(n, unit) => resolve_length_unit(*n, *unit, em_base, viewport), CssValue::Zero => 0.0, CssValue::Number(n) if *n == 0.0 => 0.0, CssValue::Keyword(k) => match k.as_str() { "thin" => 1.0, "medium" => 3.0, "thick" => 5.0, _ => 0.0, }, _ => 0.0, } } fn parse_border_style(value: &CssValue) -> BorderStyle { match value { CssValue::Keyword(k) => match k.as_str() { "none" => BorderStyle::None, "hidden" => BorderStyle::Hidden, "dotted" => BorderStyle::Dotted, "dashed" => BorderStyle::Dashed, "solid" => BorderStyle::Solid, "double" => BorderStyle::Double, "groove" => BorderStyle::Groove, "ridge" => BorderStyle::Ridge, "inset" => BorderStyle::Inset, "outset" => BorderStyle::Outset, _ => BorderStyle::None, }, CssValue::None => BorderStyle::None, _ => BorderStyle::None, } } fn inherit_property(style: &mut ComputedStyle, property: &str, parent: &ComputedStyle) { match property { "color" => style.color = parent.color, "font-size" => { style.font_size = parent.font_size; style.line_height = style.font_size * 1.2; } "font-weight" => style.font_weight = parent.font_weight, "font-style" => style.font_style = parent.font_style, "font-family" => style.font_family = parent.font_family.clone(), "text-align" => style.text_align = parent.text_align, "text-decoration" => style.text_decoration = parent.text_decoration, "line-height" => style.line_height = parent.line_height, "visibility" => style.visibility = parent.visibility, // Non-inherited properties: inherit from parent if explicitly requested "display" => style.display = parent.display, "margin-top" => style.margin_top = parent.margin_top, "margin-right" => style.margin_right = parent.margin_right, "margin-bottom" => style.margin_bottom = parent.margin_bottom, "margin-left" => style.margin_left = parent.margin_left, "padding-top" => style.padding_top = parent.padding_top, "padding-right" => style.padding_right = parent.padding_right, "padding-bottom" => style.padding_bottom = parent.padding_bottom, "padding-left" => style.padding_left = parent.padding_left, "width" => style.width = parent.width, "height" => style.height = parent.height, "box-sizing" => style.box_sizing = parent.box_sizing, "background-color" => style.background_color = parent.background_color, "position" => style.position = parent.position, "float" => style.float = parent.float, "clear" => style.clear = parent.clear, "overflow" => style.overflow = parent.overflow, "flex-direction" => style.flex_direction = parent.flex_direction, "flex-wrap" => style.flex_wrap = parent.flex_wrap, "justify-content" => style.justify_content = parent.justify_content, "align-items" => style.align_items = parent.align_items, "align-content" => style.align_content = parent.align_content, "row-gap" => style.row_gap = parent.row_gap, "column-gap" => style.column_gap = parent.column_gap, "flex-grow" => style.flex_grow = parent.flex_grow, "flex-shrink" => style.flex_shrink = parent.flex_shrink, "flex-basis" => style.flex_basis = parent.flex_basis, "align-self" => style.align_self = parent.align_self, "order" => style.order = parent.order, _ => {} } } fn reset_property_to_initial(style: &mut ComputedStyle, property: &str) { let initial = ComputedStyle::default(); match property { "display" => style.display = initial.display, "margin-top" => style.margin_top = initial.margin_top, "margin-right" => style.margin_right = initial.margin_right, "margin-bottom" => style.margin_bottom = initial.margin_bottom, "margin-left" => style.margin_left = initial.margin_left, "padding-top" => style.padding_top = initial.padding_top, "padding-right" => style.padding_right = initial.padding_right, "padding-bottom" => style.padding_bottom = initial.padding_bottom, "padding-left" => style.padding_left = initial.padding_left, "border-top-width" => style.border_top_width = initial.border_top_width, "border-right-width" => style.border_right_width = initial.border_right_width, "border-bottom-width" => style.border_bottom_width = initial.border_bottom_width, "border-left-width" => style.border_left_width = initial.border_left_width, "width" => style.width = initial.width, "height" => style.height = initial.height, "box-sizing" => style.box_sizing = initial.box_sizing, "color" => style.color = initial.color, "font-size" => { style.font_size = initial.font_size; style.line_height = initial.line_height; } "font-weight" => style.font_weight = initial.font_weight, "font-style" => style.font_style = initial.font_style, "font-family" => style.font_family = initial.font_family.clone(), "text-align" => style.text_align = initial.text_align, "text-decoration" => style.text_decoration = initial.text_decoration, "line-height" => style.line_height = initial.line_height, "background-color" => style.background_color = initial.background_color, "position" => style.position = initial.position, "top" => style.top = initial.top, "right" => style.right = initial.right, "bottom" => style.bottom = initial.bottom, "left" => style.left = initial.left, "float" => style.float = initial.float, "clear" => style.clear = initial.clear, "overflow" => style.overflow = initial.overflow, "visibility" => style.visibility = initial.visibility, "flex-direction" => style.flex_direction = initial.flex_direction, "flex-wrap" => style.flex_wrap = initial.flex_wrap, "justify-content" => style.justify_content = initial.justify_content, "align-items" => style.align_items = initial.align_items, "align-content" => style.align_content = initial.align_content, "row-gap" => style.row_gap = initial.row_gap, "column-gap" => style.column_gap = initial.column_gap, "flex-grow" => style.flex_grow = initial.flex_grow, "flex-shrink" => style.flex_shrink = initial.flex_shrink, "flex-basis" => style.flex_basis = initial.flex_basis, "align-self" => style.align_self = initial.align_self, "order" => style.order = initial.order, _ => {} } } // --------------------------------------------------------------------------- // Styled tree // --------------------------------------------------------------------------- /// A node in the styled tree: a DOM node paired with its computed style. #[derive(Debug)] pub struct StyledNode { pub node: NodeId, pub style: ComputedStyle, pub children: Vec, } /// Extract CSS stylesheets from `
Test
"#; let doc = we_html::parse_html(html_str); let sheets = extract_stylesheets(&doc); let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body = &styled.children[0]; let div = &body.children[0]; assert_eq!(div.style.box_sizing, BoxSizing::BorderBox); } #[test] fn box_sizing_not_inherited() { let html_str = r#"

Child

"#; let doc = we_html::parse_html(html_str); let sheets = extract_stylesheets(&doc); let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body = &styled.children[0]; let parent = &body.children[0]; let child = &parent.children[0]; assert_eq!(parent.style.box_sizing, BoxSizing::BorderBox); assert_eq!(child.style.box_sizing, BoxSizing::ContentBox); } #[test] fn z_index_parsing() { let html_str = r#"
A
B
C
"#; let doc = we_html::parse_html(html_str); let sheets = extract_stylesheets(&doc); let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body = &styled.children[0]; let a = &body.children[0]; let b = &body.children[1]; let c = &body.children[2]; assert_eq!(a.style.z_index, Some(5)); assert_eq!(b.style.z_index, Some(-3)); assert_eq!(c.style.z_index, None); } #[test] fn z_index_default_is_auto() { let html_str = r#"
test
"#; let doc = we_html::parse_html(html_str); let sheets = extract_stylesheets(&doc); let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body = &styled.children[0]; let div = &body.children[0]; assert_eq!(div.style.z_index, None); } #[test] fn position_sticky_parsing() { let html_str = r#"
Sticky
"#; let doc = we_html::parse_html(html_str); let sheets = extract_stylesheets(&doc); let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body = &styled.children[0]; let div = &body.children[0]; assert_eq!(div.style.position, Position::Sticky); assert_eq!(div.style.top, LengthOrAuto::Length(10.0)); } }