render: size and color inline SVG from CSS
This commit is contained in:
1 parent
5a89ebd0a3
commit
6eafb7d6cc
3 files changed
+75
No files matched your search
@@ -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
|
||||
|
||||
@@ -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"));
|
||||
|
||||
Reference in new issue
Block a user