From b13a1e5e710f0d173a5b6175320e4a70ae2e9bf0 Mon Sep 17 00:00:00 2001 From: SGavrl <277843151+SGavrl@users.noreply.github.com> Date: Thu, 23 Jul 2026 12:30:44 +0000 Subject: [PATCH] render: resolve relative box edge lengths --- crates/obscura-render/src/dom.rs | 35 ++++++++++++++--- crates/obscura-render/src/lib.rs | 7 ++++ crates/obscura-render/src/style.rs | 62 +++++++++++++++++++++++++++--- 3 files changed, 93 insertions(+), 11 deletions(-) diff --git a/crates/obscura-render/src/dom.rs b/crates/obscura-render/src/dom.rs index 550cad7..6419e98 100644 --- a/crates/obscura-render/src/dom.rs +++ b/crates/obscura-render/src/dom.rs @@ -448,13 +448,38 @@ pub fn layout_dom_with_images( } inh.box_sizing = style.box_sizing; - // Percentage padding/margin resolve against the containing - // block WIDTH on every side (per CSS: `padding-top:56.25%` in a - // 1000px block is 562.5px, not a fraction of the height). The - // f32 Edges cannot carry a percentage, so bake the resolved px - // back into `padding`/`margin`, which then feed taffy. + // Resolve font/viewport-relative box edges now that their + // reference sizes are known. Percentage padding/margin then + // resolve against the containing block WIDTH on every side + // (per CSS: `padding-top:56.25%` in a 1000px block is 562.5px, + // not a fraction of the height). Bake the resolved px back into + // `padding`/`margin`, which then feed taffy. let cb_w = inh.cb_width; for i in 0..4 { + if let Some(relative) = style.padding_relative[i] { + if let crate::Dimension::Px(px) = + relative.resolve(em_px, root_fs, vw, vh) + { + match i { + 0 => style.padding.top = px.max(0.0), + 1 => style.padding.right = px.max(0.0), + 2 => style.padding.bottom = px.max(0.0), + _ => style.padding.left = px.max(0.0), + } + } + } + if let Some(relative) = style.margin_relative[i] { + if let crate::Dimension::Px(px) = + relative.resolve(em_px, root_fs, vw, vh) + { + match i { + 0 => style.margin.top = px, + 1 => style.margin.right = px, + 2 => style.margin.bottom = px, + _ => style.margin.left = px, + } + } + } if let Some(frac) = style.padding_percent[i] { let px = (frac * cb_w).max(0.0); match i { diff --git a/crates/obscura-render/src/lib.rs b/crates/obscura-render/src/lib.rs index a8abe97..0f48e02 100644 --- a/crates/obscura-render/src/lib.rs +++ b/crates/obscura-render/src/lib.rs @@ -197,6 +197,10 @@ pub struct LayoutStyle { /// percentage, so this is resolved to px during `dom::layout_dom`'s top-down /// pass once the containing-block width is known. pub margin_percent: [Option; 4], + /// Font- and viewport-relative margin lengths (top, right, bottom, left). + /// These retain their unit until the top-down pass knows the element font + /// size, root font size, and viewport dimensions. + pub margin_relative: [Option; 4], pub padding: Edges, /// Percentage padding per side (top, right, bottom, left) as a 0..1 /// fraction, `None` when the side is a fixed length. All four sides resolve @@ -206,6 +210,9 @@ pub struct LayoutStyle { /// resolved to px in `dom::layout_dom`'s top-down pass and written back into /// `padding`, which then feeds taffy as a fixed length. pub padding_percent: [Option; 4], + /// Font- and viewport-relative padding lengths (top, right, bottom, left), + /// resolved alongside `margin_relative` during the top-down pass. + pub padding_relative: [Option; 4], pub border: Edges, /// `border-radius` (uniform; the first value of the shorthand). Rounds the /// background fill and border. In px after resolution. diff --git a/crates/obscura-render/src/style.rs b/crates/obscura-render/src/style.rs index aefe5bc..b34a25e 100644 --- a/crates/obscura-render/src/style.rs +++ b/crates/obscura-render/src/style.rs @@ -1672,12 +1672,31 @@ fn set_margin_side(style: &mut LayoutStyle, idx: usize, value: &str) { let is_auto = v.eq_ignore_ascii_case("auto"); if let Some(frac) = percent_fraction(v) { style.margin_percent[idx] = Some(frac); + style.margin_relative[idx] = None; set_margin_px(&mut style.margin, idx, 0.0); style.margin_auto[idx] = false; return; } - let px = if is_auto { 0.0 } else { px(value).unwrap_or(0.0) }; - set_margin_px(&mut style.margin, idx, px); + let dimension = dimension_value(v); + match dimension { + crate::Dimension::Px(px) => { + set_margin_px(&mut style.margin, idx, px); + style.margin_relative[idx] = None; + } + crate::Dimension::Em(_) + | crate::Dimension::Rem(_) + | crate::Dimension::Vw(_) + | crate::Dimension::Vh(_) + | crate::Dimension::Vmin(_) + | crate::Dimension::Vmax(_) => { + set_margin_px(&mut style.margin, idx, 0.0); + style.margin_relative[idx] = Some(dimension); + } + _ => { + set_margin_px(&mut style.margin, idx, 0.0); + style.margin_relative[idx] = None; + } + } style.margin_auto[idx] = is_auto; style.margin_percent[idx] = None; } @@ -1696,14 +1715,31 @@ fn set_margin_px(margin: &mut Edges, idx: usize, px: f32) { /// recorded in `padding_percent` and resolved against the containing-block /// width during the top-down pass; a length is stored directly. fn set_padding_side(style: &mut LayoutStyle, idx: usize, value: &str) { - if let Some(frac) = percent_fraction(value.trim()) { + let value = value.trim(); + if let Some(frac) = percent_fraction(value) { style.padding_percent[idx] = Some(frac); + style.padding_relative[idx] = None; set_padding_px(&mut style.padding, idx, 0.0); return; } - if let Some(px) = px(value) { - set_padding_px(&mut style.padding, idx, px); - style.padding_percent[idx] = None; + let dimension = dimension_value(value); + match dimension { + crate::Dimension::Px(px) => { + set_padding_px(&mut style.padding, idx, px); + style.padding_relative[idx] = None; + style.padding_percent[idx] = None; + } + crate::Dimension::Em(_) + | crate::Dimension::Rem(_) + | crate::Dimension::Vw(_) + | crate::Dimension::Vh(_) + | crate::Dimension::Vmin(_) + | crate::Dimension::Vmax(_) => { + set_padding_px(&mut style.padding, idx, 0.0); + style.padding_relative[idx] = Some(dimension); + style.padding_percent[idx] = None; + } + _ => {} } } @@ -2202,6 +2238,20 @@ mod tests { assert!(!s.margin_auto[3]); } + #[test] + fn relative_box_edges_remain_unresolved() { + let s = compute_style( + "div", + Some("font-size:20px;margin:15vh auto 2em 10vw;padding:1rem 2vmin"), + ); + assert_eq!(s.margin_relative[0], Some(crate::Dimension::Vh(15.0))); + assert!(s.margin_auto[1]); + assert_eq!(s.margin_relative[2], Some(crate::Dimension::Em(2.0))); + assert_eq!(s.margin_relative[3], Some(crate::Dimension::Vw(10.0))); + assert_eq!(s.padding_relative[0], Some(crate::Dimension::Rem(1.0))); + assert_eq!(s.padding_relative[1], Some(crate::Dimension::Vmin(2.0))); + } + #[test] fn ua_defaults_and_ignore_unknown() { let s = compute_style("span", Some("color: red; ; bogus: ; display: none"));