// SPDX-License-Identifier: Apache-2.0
///|
fn safe_svg_paint(value : String) -> Bool {
if [
"none", "transparent", "black", "white", "red", "green", "blue", "gray", "grey",
"currentColor",
].contains(value) {
return true
}
if !value.has_prefix("#") || ![4, 5, 7, 9].contains(value.length()) {
return false
}
value[1:]
.iter()
.all(c => {
(c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')
})
}
///|
fn svg_attribute(value : String) -> String {
let parts : Array[String] = []
for c in value {
parts.push(
match c {
'&' => "&"
'<' => "<"
'>' => ">"
'"' => """
'\'' => "'"
_ => c.to_string()
},
)
}
parts.join("")
}
///|
/// Bounded approximate SVG preview. Validates drawing options, escapes string
/// attributes and deterministically samples at most max_faces (up to 20000).
/// A desc element identifies downsampling. It is not a complete mesh rendering.
pub fn render_svg_checked(
mesh : Mesh,
options : PreviewOptions,
max_faces~ : Int32 = 20000,
max_output_bytes~ : Int32 = 8000000,
) -> Result[String, String] {
if options.width < 1 ||
options.height < 1 ||
options.width > 16384 ||
options.height > 16384 ||
not_finite(options.yaw) ||
not_finite(options.pitch) ||
not_finite(options.stroke_width) ||
options.stroke_width < 0.0 ||
options.stroke_width > 256.0 ||
!safe_svg_paint(options.stroke) ||
options.stroke.iter().any(c => c < ' ') {
return Err("invalid SVG preview options")
}
if max_faces < 1 ||
max_faces > 20000 ||
max_output_bytes < 1 ||
max_output_bytes > 16000000 {
return Err("invalid SVG preview limits")
}
if mesh.is_empty() {
return Ok("")
}
let count = mesh.triangle_count()
let selected : Array[Triangle] = []
let budget = count.min(max_faces)
for i in 0.. 0.0 { scale } else { 1.0 }
let to_local = fn(v : Vec3) {
let d = v.sub(origin)
Vec3::new(d.x / scale, d.y / scale, d.z / scale)
}
let local_mesh = Mesh::new(
selected.map(t => {
Triangle::from_vertices(to_local(t.a), to_local(t.b), to_local(t.c))
}),
)
let mut svg = render_svg_inner(local_mesh, options)
if count > budget {
svg = svg.replace(
old=" max_output_bytes {
return Err("resource_limit: max_preview_output_bytes")
}
Ok(svg)
}
///|
/// Compatibility preview API; unavailable previews return a safe constant
/// comment. Use render_svg_checked() to obtain the refusal reason.
pub fn render_svg(mesh : Mesh, options : PreviewOptions) -> String {
match render_svg_checked(mesh, options) {
Ok(svg) => svg
Err(_) =>
""
}
}