///|
fn qualify_resource_id(base_uri : String, id : String) -> String {
if id == "" || id.find("#") is Some(_) {
id
} else {
resource_strip_fragment(base_uri) + "#" + id
}
}
///|
fn qualify_paint_reference(paint : Paint, base_uri : String) -> Paint {
match paint {
PaintServerRef(id, fallback) =>
PaintServerRef(qualify_resource_id(base_uri, id), fallback)
_ => paint
}
}
///|
fn qualify_node_resource_references(node : SVGNode, base_uri : String) -> Unit {
node.fill = qualify_paint_reference(node.fill, base_uri)
node.stroke = {
..node.stroke,
paint: qualify_paint_reference(node.stroke.paint, base_uri),
}
node.marker_start = node.marker_start.map(fn(id) {
qualify_resource_id(base_uri, id)
})
node.marker_mid = node.marker_mid.map(fn(id) {
qualify_resource_id(base_uri, id)
})
node.marker_end = node.marker_end.map(fn(id) {
qualify_resource_id(base_uri, id)
})
node.filter_graph_id = node.filter_graph_id.map(fn(id) {
qualify_resource_id(base_uri, id)
})
node.mask_id = node.mask_id.map(fn(id) { qualify_resource_id(base_uri, id) })
node.clip_path_id = node.clip_path_id.map(fn(id) {
qualify_resource_id(base_uri, id)
})
for child in node.children {
qualify_node_resource_references(child, base_uri)
}
}
///|
fn qualify_document_resource_references(
document : SVGDocument,
base_uri : String,
) -> Unit {
qualify_node_resource_references(document.root, base_uri)
document.definitions.elements.each(fn(_id, node) {
qualify_node_resource_references(node, base_uri)
})
document.symbols.symbols.each(fn(_id, symbol) {
qualify_node_resource_references(symbol.content, base_uri)
})
document.clips.clips.each(fn(_id, clip) {
for node in clip.content {
qualify_node_resource_references(node, base_uri)
}
})
document.masks.masks.each(fn(_id, mask) {
for node in mask.content {
qualify_node_resource_references(node, base_uri)
}
})
document.patterns.patterns.each(fn(_id, pattern) {
for node in pattern.content {
qualify_node_resource_references(node, base_uri)
}
})
document.markers.markers.each(fn(_id, marker) {
qualify_node_resource_references(marker.content, base_uri)
})
}
///|
fn merge_external_document(
ctx : SVGParseContext,
document : SVGDocument,
base_uri : String,
) -> Unit {
let prefix = resource_strip_fragment(base_uri) + "#"
ctx.defs.elements[resource_strip_fragment(base_uri)] = document.root
if document.root.id != "" {
ctx.defs.elements[prefix + document.root.id] = document.root
}
document.definitions.elements.each(fn(id, node) {
ctx.defs.elements[prefix + id] = node
})
document.symbols.symbols.each(fn(id, symbol) {
ctx.symbols.symbols[prefix + id] = { ..symbol, id: prefix + id }
})
document.clips.clips.each(fn(id, clip) {
ctx.clips.clips[prefix + id] = { ..clip, id: prefix + id }
})
document.masks.masks.each(fn(id, mask) {
ctx.masks.masks[prefix + id] = { ..mask, id: prefix + id }
})
document.filter_graphs.graphs.each(fn(id, graph) {
ctx.filter_graphs.graphs[prefix + id] = { ..graph, id: prefix + id }
})
document.patterns.patterns.each(fn(id, pattern) {
ctx.patterns.patterns[prefix + id] = { ..pattern, id: prefix + id }
})
document.patterns.references.each(fn(id, parent) {
ctx.patterns.references[prefix + id] = qualify_resource_id(base_uri, parent)
})
document.patterns.specified.each(fn(id, specified) {
ctx.patterns.specified[prefix + id] = specified
})
document.gradients.gradients.each(fn(id, gradient) {
ctx.gradients.gradients[prefix + id] = gradient
})
document.gradients.references.each(fn(id, parent) {
ctx.gradients.references[prefix + id] = qualify_resource_id(
base_uri, parent,
)
})
document.gradients.specified.each(fn(id, specified) {
ctx.gradients.specified[prefix + id] = specified
})
document.markers.markers.each(fn(id, marker) {
ctx.markers.markers[prefix + id] = { ..marker, id: prefix + id }
})
}
///|
fn canonicalize_svg_element_references(
element : SVGXmlElement,
base_uri : String,
) -> SVGXmlElement {
let attrs = element.attributes.map(fn(attr) {
let value = if attr.0 == "href" || attr.0 == xlink_href_attribute_name {
if element.name == "use" && attr.1.find("#") is None {
resolve_resource_uri(base_uri, attr.1)
} else {
canonicalize_fragment_reference(base_uri, attr.1)
}
} else {
canonicalize_url_references(attr.1, base_uri)
}
(attr.0, value)
})
{ name: element.name, attributes: attrs }
}
///|
fn canonicalize_svg_reference_events(
events : Array[SVGXmlEvent],
base_uri : String,
) -> Array[SVGXmlEvent] {
events.map(fn(event) {
match event {
XmlStart(element) =>
XmlStart(canonicalize_svg_element_references(element, base_uri))
XmlEmpty(element) =>
XmlEmpty(canonicalize_svg_element_references(element, base_uri))
_ => event
}
})
}
///|
fn collect_url_references(value : String) -> Array[String] {
let result : Array[String] = []
let lower = value.to_lower()
let mut pos = 0
while pos < value.length() {
let found = match lower[pos:].find("url(") {
Some(index) => pos + index
None => break
}
let content_start = found + 4
let close = match value[content_start:].find(")") {
Some(index) => content_start + index
None => break
}
result.push(strip_resource_quotes(value[content_start:close].to_owned()))
pos = close + 1
}
result
}
///|
fn push_external_document_uri(
uris : Array[String],
reference : String,
allow_whole_document : Bool,
) -> Unit {
if reference == "" || reference.has_prefix("#") {
return
}
let uri = match reference.find("#") {
Some(index) => reference[:index].to_owned()
None if allow_whole_document => reference
None => return
}
if uri == "" {
return
}
for existing in uris {
if existing == uri {
return
}
}
uris.push(uri)
}
///|
fn collect_external_document_uris(
events : Array[SVGXmlEvent],
stylesheets : Array[@css_cascade.Stylesheet],
) -> Array[String] {
let uris : Array[String] = []
for event in events {
let element = match event {
XmlStart(element) | XmlEmpty(element) => Some(element)
_ => None
}
match element {
None => ()
Some(element) =>
for attr in element.attributes {
if attr.0 == "href" || attr.0 == xlink_href_attribute_name {
let whole_document = element.name == "use"
if whole_document ||
element.name == "linearGradient" ||
element.name == "radialGradient" ||
element.name == "pattern" {
push_external_document_uri(uris, attr.1, whole_document)
}
}
for reference in collect_url_references(attr.1) {
push_external_document_uri(uris, reference, false)
}
}
}
}
for stylesheet in stylesheets {
for rule in stylesheet.rules {
for declaration in rule.declarations {
match declaration.value {
Value(value) =>
for reference in collect_url_references(value) {
push_external_document_uri(uris, reference, false)
}
_ => ()
}
}
}
for keyframes in stylesheet.keyframes {
for block in keyframes.blocks {
for declaration in block.declarations {
match declaration.value {
Value(value) =>
for reference in collect_url_references(value) {
push_external_document_uri(uris, reference, false)
}
_ => ()
}
}
}
}
}
uris
}
///|
fn TextResourceLoadState::load_svg_document(
self : TextResourceLoadState,
uri : String,
viewport_width : Double,
viewport_height : Double,
) -> SVGDocument? {
match self.documents.get(uri) {
Some(document) => return Some(document)
None => ()
}
let active_key = "svg:" + uri
if self.active.contains(active_key) {
self.record(ResourceCycle, Document, uri)
return None
}
if self.depth >= self.options.max_external_depth {
self.record(ResourceLimitExceeded, Document, uri)
return None
}
self.active[active_key] = true
self.depth = self.depth + 1
let result = match self.resolve_text(uri, SvgDocument, Document) {
Some(text) => {
let parsed = parse_svg_document_with_state(
text,
viewport_width,
viewport_height,
self.options,
self,
uri,
)
if parsed is None {
self.record(ParseFailed, Document, uri)
}
parsed
}
None => None
}
self.active.remove(active_key)
self.depth = self.depth - 1
match result {
Some(document) => {
qualify_document_resource_references(document, uri)
self.documents[uri] = document
Some(document)
}
None => None
}
}