use std::fmt::Write;
pub(crate) fn in_range(mut v: usize, start: usize, end: usize) -> usize {
if v < start {
v = start;
}
if v >= end {
if end > 0 {
v = end - 1;
} else {
v = 0;
}
}
v
}
pub(crate) fn escape_html(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for ch in s.chars() {
match ch {
'&' => out.push_str("&"),
'<' => out.push_str("<"),
'>' => out.push_str(">"),
'"' => out.push_str("""),
'\'' => out.push_str("'"),
'/' => out.push_str("/"),
c => out.push(c),
}
}
out
}
pub(crate) fn timing_for_svg(delays_ms: &[usize]) -> (f64, String, Vec<String>) {
let total_ms: usize = delays_ms.iter().sum();
let total_s = (total_ms as f64) / 1000.0;
let mut cum: Vec<usize> = Vec::with_capacity(delays_ms.len() + 1);
let mut acc = 0usize;
cum.push(acc);
for &d in delays_ms {
acc += d;
cum.push(acc);
}
let key_times_parts: Vec<String> = cum
.iter()
.map(|&ms| {
let frac = (ms as f64) / (total_ms as f64);
let mut s = format!("{:.6}", frac);
while s.contains('.') && (s.ends_with('0') || s.ends_with('.')) {
if s.ends_with('0') {
s.pop();
} else if s.ends_with('.') {
s.pop();
break;
}
}
if s.is_empty() {
s = "0".into();
}
s
})
.collect();
let key_times = key_times_parts.join(";");
let n = delays_ms.len();
let mut values_vec: Vec<String> = Vec::with_capacity(n);
for k in 0..n {
let mut parts = Vec::with_capacity(n + 1);
for i in 0..=n {
parts.push(if i == k { "1" } else { "0" }.to_string());
}
let joined = parts.join(";");
values_vec.push(joined);
}
(total_s, key_times, values_vec)
}
pub(crate) fn json_quote(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
for ch in s.chars() {
match ch {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\u{0000}'..='\u{001F}' | '\u{007F}'..='\u{009F}' => {
write!(out, "\\u{:04x}", ch as u32).unwrap();
}
_ => out.push(ch),
}
}
out.push('"');
out
}
#[cfg(test)]
mod tests {
use super::escape_html;
#[test]
fn basic() {
assert_eq!(escape_html("<&>\"'"), "<&>"'");
}
#[test]
fn unicode_kept() {
assert_eq!(escape_html("😀 & <"), "😀 & <");
}
#[test]
fn already_escaped_becomes_double_escaped() {
assert_eq!(escape_html("&"), "&amp;");
}
#[test]
fn script_injection_becomes_safe() {
assert_eq!(escape_html("</script>"), "</script>");
}
}