From bd4845b52ce47a6f1d5c198226d67b5a5cb29470 Mon Sep 17 00:00:00 2001 From: SGavrl <277843151+SGavrl@users.noreply.github.com> Date: Thu, 2 Jul 2026 20:26:55 +0000 Subject: [PATCH] fix(render): correct center tag alignment and percentage dimensions --- crates/obscura-render/src/lib.rs | 51 +++++++++++++++++++----------- crates/obscura-render/src/style.rs | 39 +++++++++++++++++------ 2 files changed, 63 insertions(+), 27 deletions(-) diff --git a/crates/obscura-render/src/lib.rs b/crates/obscura-render/src/lib.rs index 6268ed0..ad9458a 100644 --- a/crates/obscura-render/src/lib.rs +++ b/crates/obscura-render/src/lib.rs @@ -54,13 +54,20 @@ pub enum Display { None, } +#[derive(Debug, Clone, Copy, Default, PartialEq)] +pub enum Dimension { + #[default] + Auto, + Px(f32), + Percent(f32), +} + /// The subset of CSS that influences box layout. Expanded in later phases. #[derive(Debug, Clone, Default)] pub struct LayoutStyle { pub display: Display, - /// Content-box size. None means auto. - pub width: Option, - pub height: Option, + pub width: Dimension, + pub height: Dimension, pub margin: Edges, pub padding: Edges, pub border: Edges, @@ -70,6 +77,8 @@ pub struct LayoutStyle { pub color: Option<[u8; 4]>, pub border_color: Option<[u8; 4]>, pub font_size: Option, + pub align_items: Option, + pub flex_direction: Option, } /// A node in the input layout tree. `text` is carried for the paint phase; it @@ -148,7 +157,9 @@ pub(crate) fn to_taffy_style(style: &LayoutStyle) -> Style { Display::Inline => taffy::style::Display::Flex, Display::None => taffy::style::Display::None, }; - if style.display == Display::Inline { + if let Some(fd) = style.flex_direction { + s.flex_direction = fd; + } else if style.display == Display::Inline { s.flex_direction = taffy::FlexDirection::Row; } s.flex_wrap = taffy::FlexWrap::Wrap; @@ -156,16 +167,20 @@ pub(crate) fn to_taffy_style(style: &LayoutStyle) -> Style { width: dimension(style.width), height: dimension(style.height), }; + if let Some(ai) = style.align_items { + s.align_items = Some(ai); + } s.margin = rect_auto(style.margin); s.padding = rect_lp(style.padding); s.border = rect_lp(style.border); s } -fn dimension(v: Option) -> taffy::style::Dimension { +fn dimension(v: Dimension) -> taffy::style::Dimension { match v { - Some(px) => taffy::style::Dimension::Length(px), - None => taffy::style::Dimension::Auto, + Dimension::Px(px) => taffy::style::Dimension::Length(px), + Dimension::Percent(p) => taffy::style::Dimension::Percent(p), + Dimension::Auto => taffy::style::Dimension::Auto, } } @@ -191,8 +206,8 @@ fn rect_auto(e: Edges) -> taffy::Rect { mod tests { use super::*; - fn styled(display: Display, w: Option, h: Option) -> LayoutStyle { - LayoutStyle { display, width: w, height: h, ..Default::default() } + fn make_box(display: Display, w: f32, h: f32) -> LayoutStyle { + LayoutStyle { display, width: Dimension::Px(w), height: Dimension::Px(h), ..Default::default() } } #[test] @@ -200,11 +215,11 @@ mod tests { // A 1000px-wide viewport, two fixed-size block children: they should // stack top-to-bottom at the expected y offsets. let root = LayoutNode { - style: styled(Display::Block, Some(1000.0), None), + style: make_box(Display::Block, 1000.0, 800.0), text: None, children: vec![ - LayoutNode::leaf(styled(Display::Block, Some(1000.0), Some(50.0))), - LayoutNode::leaf(styled(Display::Block, Some(1000.0), Some(30.0))), + LayoutNode::leaf(make_box(Display::Block, 1000.0, 50.0)), + LayoutNode::leaf(make_box(Display::Block, 1000.0, 30.0)), ], }; let out = layout(&root, (1000.0, 800.0)); @@ -223,13 +238,13 @@ mod tests { } #[test] -fn flex_row_lays_out_horizontally() { + fn flex_row_lays_out_horizontally() { let root = LayoutNode { - style: LayoutStyle { display: Display::Flex, width: Some(600.0), height: Some(100.0), ..Default::default() }, + style: LayoutStyle { display: Display::Flex, width: Dimension::Px(600.0), height: Dimension::Px(100.0), ..Default::default() }, text: None, children: vec![ - LayoutNode::leaf(styled(Display::Block, Some(200.0), Some(100.0))), - LayoutNode::leaf(styled(Display::Block, Some(200.0), Some(100.0))), + LayoutNode::leaf(make_box(Display::Block, 200.0, 100.0)), + LayoutNode::leaf(make_box(Display::Block, 200.0, 100.0)), ], }; let out = layout(&root, (600.0, 400.0)); @@ -249,8 +264,8 @@ fn flex_row_lays_out_horizontally() { let root = LayoutNode { style: LayoutStyle { display: Display::Block, - width: Some(100.0), - height: Some(100.0), + width: Dimension::Px(100.0), + height: Dimension::Px(100.0), padding: Edges { top: 10.0, right: 10.0, bottom: 10.0, left: 10.0 }, ..Default::default() }, diff --git a/crates/obscura-render/src/style.rs b/crates/obscura-render/src/style.rs index 2fca229..8924e4f 100644 --- a/crates/obscura-render/src/style.rs +++ b/crates/obscura-render/src/style.rs @@ -23,11 +23,20 @@ pub fn compute_style(tag: &str, inline_css: Option<&str>) -> LayoutStyle { /// Built-in UA defaults. Inline elements currently map to block layout; real /// inline/text layout arrives with the text/paint phase. pub fn ua_style(tag: &str) -> LayoutStyle { - let display = match tag { - "span" | "a" | "b" | "i" | "strong" | "em" => Display::Inline, + let mut style = LayoutStyle::default(); + style.display = match tag { + "span" | "a" | "b" | "i" | "strong" | "em" | "font" => Display::Inline, + "tr" => Display::Flex, _ => Display::Block, }; - LayoutStyle { display, ..Default::default() } + if tag == "center" { + style.display = Display::Flex; + style.flex_direction = Some(taffy::FlexDirection::Column); + style.align_items = Some(taffy::AlignItems::Center); + } else if tag == "body" { + style.margin = Edges { top: 8.0, right: 8.0, bottom: 8.0, left: 8.0 }; + } + style } pub fn apply_inline(style: &mut LayoutStyle, css: &str) { @@ -58,8 +67,8 @@ fn apply_value(style: &mut LayoutStyle, name: &str, value: &str) { "none" => style.display = Display::None, _ => {} }, - "width" => style.width = px(value), - "height" => style.height = px(value), + "width" => style.width = dimension_value(value), + "height" => style.height = dimension_value(value), "margin" => { if let Some(e) = edges(value) { style.margin = e; } } "margin-top" => set_edge(&mut style.margin, Side::Top, px(value)), "margin-right" => set_edge(&mut style.margin, Side::Right, px(value)), @@ -150,6 +159,18 @@ fn px(value: &str) -> Option { token(value).and_then(px_value) } +fn dimension_value(tok: &str) -> crate::Dimension { + let n = tok.trim(); + if n.ends_with("%") { + if let Ok(v) = n[..n.len()-1].parse::() { + return crate::Dimension::Percent(v / 100.0); + } + } else if let Some(px) = px(tok) { + return crate::Dimension::Px(px); + } + crate::Dimension::Auto +} + /// Parse every length token in a value as px (for box shorthands). fn edges(value: &str) -> Option { let dims: Vec = value.split_whitespace().filter_map(px_value).collect(); @@ -205,8 +226,8 @@ mod tests { fn parses_display_and_size() { let s = compute_style("div", Some("display: flex; width: 200px; height: 50px")); assert_eq!(s.display, Display::Flex); - assert_eq!(s.width, Some(200.0)); - assert_eq!(s.height, Some(50.0)); + assert_eq!(s.width, crate::Dimension::Px(200.0)); + assert_eq!(s.height, crate::Dimension::Px(50.0)); } #[test] @@ -231,7 +252,7 @@ mod tests { #[test] fn important_and_auto() { let s = compute_style("div", Some("width: 100px !important; height: auto")); - assert_eq!(s.width, Some(100.0)); - assert_eq!(s.height, None); + assert_eq!(s.width, crate::Dimension::Px(100.0)); + assert_eq!(s.height, crate::Dimension::Auto); } }