///|
/// SVG display normalization and attribute sizing helpers.
///|
/// Check if tag is an SVG element
fn is_svg_element(tag : String) -> Bool {
match tag {
"svg"
| "g"
| "rect"
| "circle"
| "ellipse"
| "line"
| "polyline"
| "polygon"
| "path"
| "text"
| "tspan"
| "defs"
| "use"
| "symbol"
| "clipPath"
| "mask"
| "pattern"
| "linearGradient"
| "radialGradient"
| "stop"
| "image"
| "foreignObject"
| "switch"
| "a" => true
// Note: "a" in SVG context is an SVG element, but we can't distinguish here
// It will be handled by context in the future
_ => false
}
}
///|
fn selector_parent_is_svg(selector_elem : @css.Element) -> Bool {
match selector_elem.parent {
Some(parent) => is_svg_element(parent.tag_name.to_lower())
None => false
}
}
///|
fn normalize_svg_display_contents(
tag : String,
style : @style.Style,
parent_is_svg : Bool,
) -> @style.Style {
if style.display != @types.Contents {
return style
}
if tag == "svg" && !parent_is_svg {
// Root-level SVG display:contents should not leak SVG descendants into
// HTML flow sizing.
return { ..style, display: @types.Display::None }
}
if tag == "text" && parent_is_svg {
// SVG with display:contents does not create a CSS flow box.
return { ..style, display: @types.Display::None }
}
style
}
///|
/// Apply SVG attributes (width, height, x, y, line coords, circle/ellipse) to style
fn apply_svg_attributes_to_style(
style : @style.Style,
attrs : Map[String, String],
tag : String,
) -> @style.Style {
let mut result = style
fn parse_svg_dimension(value : String) -> @types.Dimension? {
let trimmed = value.trim().to_owned()
if trimmed.is_empty() {
return None
}
if trimmed.has_suffix("%") {
let percent_str = remove_suffix(trimmed, 1)
let percent = @string.parse_double(percent_str) catch { _ => return None }
return Some(@types.Percent(percent / 100.0))
}
let length = parse_svg_length(trimmed)
// Keep zero as a valid explicit length (e.g. width="0").
if length == 0.0 {
let raw = @string.parse_double(trimmed) catch { _ => return None }
return Some(@types.Length(raw))
}
Some(@types.Length(length))
}
let attr_width_for_ratio = match attrs.get("width") {
Some(w) => parse_svg_length(w)
None => 0.0
}
let attr_height_for_ratio = match attrs.get("height") {
Some(h) => parse_svg_length(h)
None => 0.0
}
if attr_width_for_ratio > 0.0 &&
attr_height_for_ratio > 0.0 &&
result.aspect_ratio is None {
result = {
..result,
aspect_ratio: Some(attr_width_for_ratio / attr_height_for_ratio),
}
}
let viewbox_size = match attrs.get("viewBox") {
Some(vb) => parse_viewbox(vb)
None => None
}
if result.aspect_ratio is None {
match viewbox_size {
Some((vb_w, vb_h)) if vb_w > 0.0 && vb_h > 0.0 =>
result = { ..result, aspect_ratio: Some(vb_w / vb_h) }
_ => ()
}
}
// Root