///|
fn xml_text(text : String) -> Result[String, RqaError] {
if text.length() > 512 {
return Err(Limit("SVG title length"))
}
let out = StringBuilder()
for ch in text.iter() {
if ch.to_int() < 32 && ch != '\n' && ch != '\r' && ch != '\t' {
return Err(Invalid("SVG XML control character"))
}
match ch {
'&' => out.write_string("&")
'<' => out.write_string("<")
'>' => out.write_string(">")
'"' => out.write_string(""")
'\'' => out.write_string("'")
_ => out.write_char(ch)
}
}
Ok(out.to_string())
}
///|
/// A static SVG document with no active content, external resources or injected markup.
/// Columns are x, rows are y increasing downward. Gray means excluded, white eligible,
/// dark blue recurrent. The budget caps output primitives independently of analysis.
pub fn Recurrence::to_svg(
self : Recurrence,
cell_size : Int32,
title : String,
max_cells : Int32,
) -> Result[String, RqaError] {
if cell_size < 1 || cell_size > 32 {
return Err(Invalid("SVG cell size 1..32"))
}
if max_cells < 1 || max_cells > 65536 || self.height > max_cells / self.width {
return Err(Limit("SVG cell budget <=65536"))
}
let escaped = match xml_text(title) {
Err(e) => return Err(e)
Ok(s) => s
}
let width = self.width * cell_size
let height = self.height * cell_size
let out = StringBuilder()
out.write_string(
"\n")
Ok(out.to_string())
}