diff --git a/crates/obscura-render/src/dom.rs b/crates/obscura-render/src/dom.rs index c3f7a1a..6d76133 100644 --- a/crates/obscura-render/src/dom.rs +++ b/crates/obscura-render/src/dom.rs @@ -146,6 +146,7 @@ pub(crate) fn resolve_translate(d: crate::Dimension, basis: f32) -> f32 { crate::Dimension::Px(px) => px, crate::Dimension::Percent(p) => p * basis, crate::Dimension::Em(v) | crate::Dimension::Rem(v) => v * 16.0, + crate::Dimension::Ex(v) => v * 16.0 * 0.528_320_3, crate::Dimension::Vw(v) | crate::Dimension::Vh(v) | crate::Dimension::Vmin(v) @@ -2860,10 +2861,76 @@ fn build_children_with_float_zone( } } if float_count >= 2 { - let run_children: Vec = dom_children[float_idx..run_end] + let mut run_children: Vec = dom_children[float_idx..run_end] .iter() + // Formatting whitespace between floats does not generate an + // in-flow flex item or consume horizontal space. + .filter(|&&cid| styles.get(&cid).and_then(|s| s.float) == float_side) .flat_map(|&cid| build_any(tree, cid, taffy_tree, id_map, words, engine, ifc_items, styles)) .collect(); + // A common navigation-bar shape is a run of left floats followed by + // one right float. The right float still scans the current float band: + // it does not start a new row merely because multiple left floats + // precede it. Keep the run in one wrapping row and use an auto inline + // margin to reserve all remaining space before the opposing float. + // + // This stays deliberately narrower than a general float manager. In + // particular, multiple right floats have reverse source-order + // placement semantics and need their own representation. + let trailing_right = (float_side == Some(crate::Float::Left) + && dom_children + .get(run_end) + .and_then(|cid| styles.get(cid)) + .and_then(|style| style.float) + == Some(crate::Float::Right)) + .then(|| dom_children[run_end]); + if let Some(right_dom) = trailing_right { + let right = build( + tree, + right_dom, + taffy_tree, + id_map, + words, + engine, + ifc_items, + styles, + ); + if let Some(right) = right { + if let Ok(current) = taffy_tree.style(right) { + let mut pushed_right = current.clone(); + pushed_right.margin.left = + taffy::style::LengthPercentageAuto::auto(); + let _ = taffy_tree.set_style(right, pushed_right); + } + run_children.push(right); + let row_style = taffy::Style { + display: taffy::style::Display::Flex, + flex_direction: taffy::FlexDirection::Row, + flex_wrap: taffy::FlexWrap::Wrap, + align_items: Some(taffy::AlignItems::FLEX_START), + size: taffy::Size { + width: taffy::Dimension::percent(1.0), + height: taffy::Dimension::auto(), + }, + ..Default::default() + }; + if let Ok(row) = taffy_tree.new_with_children(row_style, &run_children) { + result.push(row); + } + result.extend(build_children_with_float_zone( + tree, + parent_id, + &dom_children[run_end + 1..], + taffy_tree, + id_map, + words, + engine, + ifc_items, + styles, + )); + return result; + } + } let row_style = taffy::Style { display: taffy::style::Display::Flex, flex_direction: taffy::FlexDirection::Row, diff --git a/crates/obscura-render/src/lib.rs b/crates/obscura-render/src/lib.rs index 273acd4..fc3a0d9 100644 --- a/crates/obscura-render/src/lib.rs +++ b/crates/obscura-render/src/lib.rs @@ -133,9 +133,11 @@ pub enum Dimension { /// Font-relative and viewport-relative units, kept unresolved at parse /// time (the element font-size and viewport are not known then) and /// resolved to `Px` during `dom::layout_dom`'s top-down pass via - /// [`Dimension::resolve`]. Resolving em/rem/vw against a hardcoded 16px at + /// [`Dimension::resolve`]. Resolving font/viewport units against a + /// hardcoded 16px at /// parse time (the old behavior) silently corrupted every relative length. Em(f32), + Ex(f32), Rem(f32), Vw(f32), Vh(f32), @@ -151,6 +153,10 @@ impl Dimension { pub fn resolve(self, em_px: f32, rem_px: f32, vw: f32, vh: f32) -> Dimension { match self { Dimension::Em(v) => Dimension::Px(v * em_px), + // Liberation Sans is the deterministic generic sans face used by + // the renderer. This is its x-height as a fraction of the em, + // matching Chromium's generic sans face on the capture host. + Dimension::Ex(v) => Dimension::Px(v * em_px * 0.528_320_3), Dimension::Rem(v) => Dimension::Px(v * rem_px), Dimension::Vw(v) => Dimension::Px(v * vw), Dimension::Vh(v) => Dimension::Px(v * vh), @@ -813,6 +819,7 @@ fn dimension(v: Dimension) -> taffy::style::Dimension { // through unresolved, fall back to its raw magnitude (em/rem ~16px) // rather than panicking. Dimension::Em(v) | Dimension::Rem(v) => taffy::style::Dimension::length(v * 16.0), + Dimension::Ex(v) => taffy::style::Dimension::length(v * 16.0 * 0.528_320_3), Dimension::Vw(v) | Dimension::Vh(v) | Dimension::Vmin(v) | Dimension::Vmax(v) => taffy::style::Dimension::length(v), } } diff --git a/crates/obscura-render/src/style.rs b/crates/obscura-render/src/style.rs index e49f8a4..0be5e64 100644 --- a/crates/obscura-render/src/style.rs +++ b/crates/obscura-render/src/style.rs @@ -91,15 +91,15 @@ pub fn ua_style(tag: &str) -> LayoutStyle { style.font_size = Some(16.0); style.font_weight = Some("bold".to_string()); style.margin = Edges { top: 21.0, bottom: 21.0, left: 0.0, right: 0.0 }; - } else if tag == "p" || tag == "ul" || tag == "ol" { + } else if matches!(tag, "p" | "ul" | "ol" | "menu" | "dir") { style.margin = Edges { top: 16.0, bottom: 16.0, left: 0.0, right: 0.0 }; - if tag == "ul" { + if matches!(tag, "ul" | "menu" | "dir") { style.list_style = Some(crate::ListStyle::Disc); + style.padding.left = 40.0; } else if tag == "ol" { style.list_style = Some(crate::ListStyle::Decimal); + style.padding.left = 40.0; } - } else if tag == "li" { - style.margin = Edges { top: 0.0, bottom: 0.0, left: 40.0, right: 0.0 }; } else if tag == "b" || tag == "strong" { style.font_weight = Some("bold".to_string()); } else if tag == "i" || tag == "em" || tag == "cite" || tag == "var" || tag == "dfn" || tag == "address" { @@ -1757,6 +1757,9 @@ fn contextual_atom(value: &str, context: &LengthContext) -> Option { if let Some(value) = lower.strip_suffix("em").and_then(parse) { return Some(value * context.em_px); } + if let Some(value) = lower.strip_suffix("ex").and_then(parse) { + return Some(value * context.em_px * 0.528_320_3); + } if let Some(value) = lower.strip_suffix("vmin").and_then(parse) { return Some(value * context.vw.min(context.vh)); } @@ -2153,7 +2156,7 @@ fn is_font_size_token(value: &str) -> bool { { return true; } - ["px", "pt", "em", "rem", "vw", "vh", "vmin", "vmax", "%"] + ["px", "pt", "em", "ex", "rem", "vw", "vh", "vmin", "vmax", "%"] .iter() .any(|unit| lower.strip_suffix(unit).and_then(|number| number.parse::().ok()).is_some()) } @@ -2177,6 +2180,7 @@ fn dimension_value(tok: &str) -> crate::Dimension { // Order matters: check `rem` before `em`, `vmin`/`vmax` before `vw`/`vh`. if let Some(v) = lower.strip_suffix("rem").and_then(parse) { return Dimension::Rem(v); } if let Some(v) = lower.strip_suffix("em").and_then(parse) { return Dimension::Em(v); } + if let Some(v) = lower.strip_suffix("ex").and_then(parse) { return Dimension::Ex(v); } if let Some(v) = lower.strip_suffix("vmin").and_then(parse) { return Dimension::Vmin(v); } if let Some(v) = lower.strip_suffix("vmax").and_then(parse) { return Dimension::Vmax(v); } if let Some(v) = lower.strip_suffix("vw").and_then(parse) { return Dimension::Vw(v); } @@ -2231,6 +2235,7 @@ fn set_margin_side(style: &mut LayoutStyle, idx: usize, value: &str) { style.margin_relative[idx] = None; } crate::Dimension::Em(_) + | crate::Dimension::Ex(_) | crate::Dimension::Rem(_) | crate::Dimension::Vw(_) | crate::Dimension::Vh(_) @@ -2285,6 +2290,7 @@ fn set_padding_side(style: &mut LayoutStyle, idx: usize, value: &str) { style.padding_percent[idx] = None; } crate::Dimension::Em(_) + | crate::Dimension::Ex(_) | crate::Dimension::Rem(_) | crate::Dimension::Vw(_) | crate::Dimension::Vh(_) @@ -2366,6 +2372,9 @@ fn px_value(tok: &str) -> Option { } else if n.ends_with("em") || n.ends_with("rem") { n = n.trim_end_matches(|c: char| c.is_ascii_alphabetic()); scale = 16.0; // 1em = 16px + } else if n.ends_with("ex") { + n = n.trim_end_matches(|c: char| c.is_ascii_alphabetic()); + scale = 16.0 * 0.528_320_3; } else if n.ends_with('%') { n = &n[..n.len() - 1]; scale = 16.0 / 100.0;