diff --git a/crates/obscura-render/src/dom.rs b/crates/obscura-render/src/dom.rs index 4d4c672..457944d 100644 --- a/crates/obscura-render/src/dom.rs +++ b/crates/obscura-render/src/dom.rs @@ -216,6 +216,29 @@ fn apply_presentational_hints(node: &obscura_dom::tree::Node, style: &mut crate: } } } + // An inline SVG with one CSS axis and a viewBox derives the other axis + // from the viewBox's intrinsic aspect ratio. Framework logos commonly set + // only a responsive utility width (`w-24 lg:w-28`) and rely on this rule; + // without it the SVG border box had zero height and paint skipped it. + if style.aspect_ratio.is_none() + && node + .as_element() + .map_or(false, |element| element.local.as_ref() == "svg") + { + if let Some(view_box) = node + .get_attribute("viewBox") + .or_else(|| node.get_attribute("viewbox")) + { + let values: Vec = view_box + .split(|c: char| c.is_ascii_whitespace() || c == ',') + .filter(|part| !part.is_empty()) + .filter_map(|part| part.parse::().ok()) + .collect(); + if values.len() == 4 && values[2] > 0.0 && values[3] > 0.0 { + style.aspect_ratio = Some(values[2] / values[3]); + } + } + } } /// Compute the UA + author style for every element in preorder, maintaining diff --git a/crates/obscura-render/src/paint.rs b/crates/obscura-render/src/paint.rs index 8a82713..c403c1e 100644 --- a/crates/obscura-render/src/paint.rs +++ b/crates/obscura-render/src/paint.rs @@ -320,6 +320,14 @@ pub fn paint_dom(tree: &DomTree, viewport: (f32, f32), base_url: Option<&str>) - // overflow-visible region. if name.local.as_ref() == "svg" { let mut markup = serialize_svg(tree, nid); + // resvg parses the serialized subtree as a standalone SVG + // document, outside the page's author stylesheet. Preserve the + // host element's computed `color` so paths using `currentColor` + // (the standard framework-logo/icon pattern) do not fall back to + // black. + if let Some(color) = style.color { + inject_svg_current_color(&mut markup, color); + } // `` pointing at an EXTERNAL sprite file resolves // to nothing in resvg (the symbol lives in another document). Fetch // the sprite, splice the referenced `` into a local ``, @@ -1362,6 +1370,22 @@ fn serialize_svg(tree: &DomTree, root: obscura_dom::tree::NodeId) -> String { buf } +fn inject_svg_current_color(markup: &mut String, color: [u8; 4]) { + let Some(start) = markup.find("').map(|offset| start + offset) else { return }; + let root = &markup[start..end]; + // An explicit presentation attribute already survives serialization and + // is the correct local currentColor source. + if root.contains(" color=") { + return; + } + let attribute = format!( + " color=\"#{:02x}{:02x}{:02x}\"", + color[0], color[1], color[2] + ); + markup.insert_str(start + " n, @@ -2046,6 +2070,19 @@ mod tests { assert!(found_red, "expected inline svg to paint red"); } + #[test] + fn paints_inline_svg_current_color_from_computed_style() { + let tree = parse_html( + r##""##, + ); + let pixmap = paint_dom(&tree, (80.0, 80.0), None).expect("pixmap"); + let mut found = false; + for pixel in pixmap.pixels() { + found |= pixel.blue() > 120 && pixel.green() > 80 && pixel.red() < 40; + } + assert!(found, "computed color should resolve currentColor in inline svg"); + } + #[test] fn paints_inline_svg_with_framework_colon_attribute() { let tree = obscura_dom::parse_html( diff --git a/crates/obscura-render/tests/layout_test.rs b/crates/obscura-render/tests/layout_test.rs index 70999e4..649e68c 100644 --- a/crates/obscura-render/tests/layout_test.rs +++ b/crates/obscura-render/tests/layout_test.rs @@ -690,6 +690,21 @@ fn text_alignment_does_not_shrink_block_children_or_wrap_inline_block_rows() { ); } +#[test] +fn inline_svg_derives_auto_height_from_view_box() { + let tree = parse_html( + r#" + "#, + ); + let layout = layout_dom(&tree, (900.0, 1000.0)); + let logo = layout.rects[&tree.get_element_by_id("logo").unwrap()]; + assert!((logo.width - 112.0).abs() < 0.01, "svg width: {logo:?}"); + assert!( + (logo.height - 101.0).abs() < 0.01, + "viewBox should supply the intrinsic auto-height (device-pixel rounded): {logo:?}" + ); +} + #[test] fn inline_block_flex_items_keep_block_inner_flow() { let tree = parse_html(include_str!("../../../render-repros/inline-block-flex-items.html"));