///|
fn parse_use(
attrs : Array[(String, String)],
ctx : SVGParseContext,
) -> SVGNode? {
let href = get_href_attr(attrs)
let href = match href {
Some(v) => v
None => return None
}
let style_node = SVGNode::new(Group)
let element_style = resolve_element_computed_style(
style_node,
"use",
attrs,
ctx,
ctx.current_style(),
false,
)
let declarations = cascaded_style_declarations(
ctx.cascaded_style("use", attrs),
)
fn geometry_value(name : String) -> String? {
resolved_element_noncomputed_value(
attrs,
declarations,
element_style.custom_properties,
name,
)
}
let viewport = ctx.current_length_viewport().unwrap_or((300.0, 150.0))
let length_context = LengthContext::new(
viewport.0,
viewport.1,
font_size=element_style.css_style.font_size,
root_font_size=element_style.root_font_size,
css_viewport_width=ctx.css_viewport.0,
css_viewport_height=ctx.css_viewport.1,
)
let x = match geometry_value("x") {
Some(v) => parse_length_with_context(v, Horizontal, length_context)
None => 0.0
}
let y = match geometry_value("y") {
Some(v) => parse_length_with_context(v, Vertical, length_context)
None => 0.0
}
let width = match geometry_value("width") {
Some(v) => Some(parse_length_with_context(v, Horizontal, length_context))
None => None
}
let height = match geometry_value("height") {
Some(v) => Some(parse_length_with_context(v, Vertical, length_context))
None => None
}
let transform = match geometry_value("transform") {
Some(v) => parse_transform(v)
None => Transform::identity()
}
let use_elem = { ..UseElement::new(href, x, y), width, height, transform }
let viewport_fallback = ctx.current_viewport()
let (base_href, frag) = split_href_fragment(use_elem.href)
let allow_data_url_use = false
match decode_data_svg(base_href) {
Some(svg_data) if allow_data_url_use =>
match parse_svg_document(svg_data) {
Some(doc) => {
let node = match frag {
Some(id) =>
match find_node_by_id(doc.root, id) {
Some(found) => found.clone()
None => return None
}
None => doc.root.clone()
}
let (vb, force_non_uniform) = match node.view_box {
Some(vb) => (Some(vb), false)
None =>
match (node.viewport_width, node.viewport_height) {
(Some(w), Some(h)) =>
(Some({ min_x: 0.0, min_y: 0.0, width: w, height: h }), true)
_ =>
match viewport_fallback {
Some((vw, vh)) =>
(
Some({ min_x: 0.0, min_y: 0.0, width: vw, height: vh }),
true,
)
None => (None, false)
}
}
}
if force_non_uniform {
node.preserve_aspect_ratio = { align: None, meet_or_slice: Meet }
}
let vw = match use_elem.width {
Some(w) => Some(w)
None =>
match node.viewport_width {
Some(w) => Some(w)
None =>
match viewport_fallback {
Some((vw, _)) => Some(vw)
None => None
}
}
}
let vh = match use_elem.height {
Some(h) => Some(h)
None =>
match node.viewport_height {
Some(h) => Some(h)
None =>
match viewport_fallback {
Some((_, vh)) => Some(vh)
None => None
}
}
}
node.view_box = vb
node.viewport_width = vw
node.viewport_height = vh
let translate = Transform::translate(use_elem.x, use_elem.y)
let base = use_elem.transform.multiply(translate)
node.transform = base.multiply(node.transform)
node.id = get_attr(attrs, "id").unwrap_or("")
apply_presentation_attributes(node, attrs)
let base_style = inherited_style_from_node(
node,
ctx.current_style().css_style,
ctx.current_style().custom_properties,
ctx.current_style().stop_color,
ctx.current_style().stop_opacity,
ctx.current_style().root_font_size,
)
let _ = resolve_element_computed_style(
node, "use", attrs, ctx, base_style, false,
)
if element_style_property_is_declared(
"use", attrs, ctx, "paint-order",
) {
apply_paint_order_recursive(node, node.paint_order)
}
Some(node)
}
None => None
}
_ =>
match use_elem.instantiate(ctx.symbols) {
Some(node) => {
if viewport_fallback is Some((vw, vh)) {
if node.viewport_width is None {
node.viewport_width = Some(vw)
}
if node.viewport_height is None {
node.viewport_height = Some(vh)
}
if node.view_box is None &&
node.viewport_width is Some(_) &&
node.viewport_height is Some(_) {
node.view_box = Some({
min_x: 0.0,
min_y: 0.0,
width: node.viewport_width.unwrap(),
height: node.viewport_height.unwrap(),
})
node.preserve_aspect_ratio = { align: None, meet_or_slice: Meet }
}
}
node.id = get_attr(attrs, "id").unwrap_or("")
apply_presentation_attributes(node, attrs)
let base_style = inherited_style_from_node(
node,
ctx.current_style().css_style,
ctx.current_style().custom_properties,
ctx.current_style().stop_color,
ctx.current_style().stop_opacity,
ctx.current_style().root_font_size,
)
let _ = resolve_element_computed_style(
node, "use", attrs, ctx, base_style, false,
)
if element_style_property_is_declared(
"use", attrs, ctx, "paint-order",
) {
apply_paint_order_recursive(node, node.paint_order)
}
Some(node)
}
None =>
match use_elem.instantiate_definition(ctx.defs, viewport_fallback) {
Some(node) => {
node.id = get_attr(attrs, "id").unwrap_or("")
apply_presentation_attributes(node, attrs)
let base_style = inherited_style_from_node(
node,
ctx.current_style().css_style,
ctx.current_style().custom_properties,
ctx.current_style().stop_color,
ctx.current_style().stop_opacity,
ctx.current_style().root_font_size,
)
let _ = resolve_element_computed_style(
node, "use", attrs, ctx, base_style, false,
)
if element_style_property_is_declared(
"use", attrs, ctx, "paint-order",
) {
apply_paint_order_recursive(node, node.paint_order)
}
Some(node)
}
None => None
}
}
}
}
///|
fn decode_html_entities(value : String) -> String {
let mut i = 0
let len = value.length()
let buf = StringBuilder::new()
while i < len {
let c = Int::unsafe_to_char(value[i].to_int())
if c == '&' {
if i + 3 < len &&
value[i + 1] == 'l' &&
value[i + 2] == 't' &&
value[i + 3] == ';' {
buf.write_char('<')
i = i + 4
continue
}
if i + 3 < len &&
value[i + 1] == 'g' &&
value[i + 2] == 't' &&
value[i + 3] == ';' {
buf.write_char('>')
i = i + 4
continue
}
if i + 4 < len &&
value[i + 1] == 'a' &&
value[i + 2] == 'm' &&
value[i + 3] == 'p' &&
value[i + 4] == ';' {
buf.write_char('&')
i = i + 5
continue
}
if i + 5 < len &&
value[i + 1] == 'q' &&
value[i + 2] == 'u' &&
value[i + 3] == 'o' &&
value[i + 4] == 't' &&
value[i + 5] == ';' {
buf.write_char('"')
i = i + 6
continue
}
if i + 5 < len &&
value[i + 1] == 'a' &&
value[i + 2] == 'p' &&
value[i + 3] == 'o' &&
value[i + 4] == 's' &&
value[i + 5] == ';' {
buf.write_char('\'')
i = i + 6
continue
}
}
buf.write_char(c)
i = i + 1
}
buf.to_string()
}
///|
fn hex_value_local(c : Char) -> Int? {
if c >= '0' && c <= '9' {
Some(c.to_int() - '0'.to_int())
} else if c >= 'a' && c <= 'f' {
Some(10 + (c.to_int() - 'a'.to_int()))
} else if c >= 'A' && c <= 'F' {
Some(10 + (c.to_int() - 'A'.to_int()))
} else {
None
}
}
///|
fn decode_percent_local(value : String) -> String {
let len = value.length()
let buf = StringBuilder::new()
let mut i = 0
while i < len {
let c = Int::unsafe_to_char(value[i].to_int())
if c == '%' && i + 2 < len {
let c1 = Int::unsafe_to_char(value[i + 1].to_int())
let c2 = Int::unsafe_to_char(value[i + 2].to_int())
match (hex_value_local(c1), hex_value_local(c2)) {
(Some(h1), Some(h2)) => {
let v = h1 * 16 + h2
buf.write_char(Int::unsafe_to_char(v))
i = i + 3
continue
}
_ => ()
}
}
buf.write_char(c)
i = i + 1
}
buf.to_string()
}
///|
fn base64_value_local(c : Char) -> Int? {
if c >= 'A' && c <= 'Z' {
Some(c.to_int() - 'A'.to_int())
} else if c >= 'a' && c <= 'z' {
Some(26 + (c.to_int() - 'a'.to_int()))
} else if c >= '0' && c <= '9' {
Some(52 + (c.to_int() - '0'.to_int()))
} else if c == '+' || c == '-' {
Some(62)
} else if c == '/' || c == '_' {
Some(63)
} else {
None
}
}
///|
fn is_base64_ws_local(c : Char) -> Bool {
c == ' ' || c == '\n' || c == '\r' || c == '\t'
}
///|
fn decode_base64_local(value : String) -> String? {
let len = value.length()
let buf = StringBuilder::new()
let mut q0 = 0
let mut q1 = 0
let mut q2 = 0
let mut q3 = 0
let mut qlen = 0
let mut pad = 0
let mut finished = false
let mut i = 0
while i < len {
let c = Int::unsafe_to_char(value[i].to_int())
if is_base64_ws_local(c) {
i = i + 1
continue
}
if finished {
return None
}
let v = if c == '=' {
pad = pad + 1
-1
} else {
match base64_value_local(c) {
Some(v) => v
None => return None
}
}
if qlen == 0 {
q0 = v
} else if qlen == 1 {
q1 = v
} else if qlen == 2 {
q2 = v
} else {
q3 = v
}
qlen = qlen + 1
if qlen == 4 {
if q0 < 0 || q1 < 0 {
return None
}
if pad == 0 {
if q2 < 0 || q3 < 0 {
return None
}
let b0 = (q0 << 2) | (q1 >> 4)
let b1 = ((q1 & 15) << 4) | (q2 >> 2)
let b2 = ((q2 & 3) << 6) | q3
buf.write_char(Int::unsafe_to_char(b0))
buf.write_char(Int::unsafe_to_char(b1))
buf.write_char(Int::unsafe_to_char(b2))
} else if pad == 1 {
if q2 < 0 || q3 >= 0 {
return None
}
let b0 = (q0 << 2) | (q1 >> 4)
let b1 = ((q1 & 15) << 4) | (q2 >> 2)
buf.write_char(Int::unsafe_to_char(b0))
buf.write_char(Int::unsafe_to_char(b1))
finished = true
} else if pad == 2 {
if q2 >= 0 || q3 >= 0 {
return None
}
let b0 = (q0 << 2) | (q1 >> 4)
buf.write_char(Int::unsafe_to_char(b0))
finished = true
} else {
return None
}
qlen = 0
pad = 0
}
i = i + 1
}
if qlen != 0 {
return None
}
Some(buf.to_string())
}
///|
fn decode_data_svg(href : String) -> String? {
let v = trim_string(href)
if !string_starts_with(v, "data:image/svg+xml") {
return None
}
let comma = find_first(v, ',')
if comma < 0 {
return None
}
let meta = build_substring(v, 0, comma)
let data = build_substring(v, comma + 1, v.length())
let meta_lower = meta.to_lower()
let mut has_base64 = false
let mut start = 0
for i = 0; i <= meta_lower.length(); i = i + 1 {
let is_sep = i == meta_lower.length() ||
Int::unsafe_to_char(meta_lower[i].to_int()) == ';'
if is_sep {
let token = trim_string(build_substring(meta_lower, start, i))
if token == "base64" {
has_base64 = true
}
start = i + 1
}
}
if has_base64 {
let cleaned = decode_percent_local(data)
return decode_base64_local(cleaned)
}
let decoded = decode_html_entities(data)
decoded |> decode_percent_local |> Some
}
///|
fn split_href_fragment(href : String) -> (String, String?) {
let idx = find_first(href, '#')
if idx < 0 {
(href, None)
} else {
let base = build_substring(href, 0, idx)
let frag = build_substring(href, idx + 1, href.length())
(base, frag |> decode_percent_local |> Some)
}
}
///|
fn find_node_by_id(node : SVGNode, id : String) -> SVGNode? {
if node.id == id {
return Some(node)
}
for child in node.children {
if find_node_by_id(child, id) is Some(found) {
return Some(found)
}
}
None
}
///|
fn parse_image(
attrs : Array[(String, String)],
ctx : SVGParseContext,
) -> SVGNode? {
let href = get_href_attr(attrs)
let href = match href {
Some(v) => v
None => return None
}
let x = match get_attr(attrs, "x") {
Some(v) => parse_length(v)
None => 0.0
}
let y = match get_attr(attrs, "y") {
Some(v) => parse_length(v)
None => 0.0
}
let mut width = match get_attr(attrs, "width") {
Some(v) => parse_length(v)
None => 0.0
}
let mut height = match get_attr(attrs, "height") {
Some(v) => parse_length(v)
None => 0.0
}
if height <= 0.0 && width > 0.0 {
height = width
}
if width <= 0.0 && height > 0.0 {
width = height
}
let node = SVGNode::new(Image(x~, y~, width~, height~, href~))
apply_attributes(node, attrs)
let _ = resolve_element_computed_style(
node,
"image",
attrs,
ctx,
ctx.current_style(),
true,
)
match node.shape {
Image(..) as img =>
node.shape = Image(
x=img.x,
y=img.y,
width=img.width,
height=img.height,
href~,
)
_ => ()
}
Some(node)
}