render: size and color inline SVG from CSS

This commit is contained in:
SGavrl committed 2026-07-26 12:18:07 +00:00
1 parent 5a89ebd0a3
commit 6eafb7d6cc
3 files changed
+75

No files matched your search

+23
View File
@@ -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<f32> = view_box
.split(|c: char| c.is_ascii_whitespace() || c == ',')
.filter(|part| !part.is_empty())
.filter_map(|part| part.parse::<f32>().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
+37
View File
@@ -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);
}
// `<use href="url#id">` pointing at an EXTERNAL sprite file resolves
// to nothing in resvg (the symbol lives in another document). Fetch
// the sprite, splice the referenced `<symbol>` into a local `<defs>`,
@@ -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("<svg") else { return };
let Some(end) = markup[start..].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 + "<svg".len(), &attribute);
}
fn serialize_svg_node(tree: &DomTree, nid: obscura_dom::tree::NodeId, is_root: bool, buf: &mut String) {
let node = match tree.get_node(nid) {
Some(n) => n,
@@ -2046,6 +2070,19 @@ mod tests {
assert!(found_red, "expected inline svg <rect> to paint red");
}
#[test]
fn paints_inline_svg_current_color_from_computed_style() {
let tree = parse_html(
r##"<html><body><svg style="color:#0784aa" width="40" height="40" viewBox="0 0 40 40"><circle cx="20" cy="20" r="18" fill="currentColor"/></svg></body></html>"##,
);
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(
@@ -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#"<html><head><style>html,body{margin:0}#logo{width:112px}</style></head>
<body><svg id="logo" viewBox="-10.5 -9.45 21 18.9"></svg></body></html>"#,
);
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"));