pub struct SvgWriter {
buf: String,
}
impl SvgWriter {
pub fn with_capacity(cap: usize) -> Self {
Self {
buf: String::with_capacity(cap),
}
}
pub fn new() -> Self {
Self::with_capacity(1024)
}
pub fn raw(&mut self, s: &str) -> &mut Self {
self.buf.push_str(s);
self
}
#[allow(dead_code)]
pub fn elem(
&mut self,
tag: &str,
attrs: &[(&str, &str)],
children: impl FnOnce(&mut Self),
) -> &mut Self {
self.buf.push('<');
self.buf.push_str(tag);
for (k, v) in attrs {
self.buf.push(' ');
self.buf.push_str(k);
self.buf.push_str("=\"");
self.buf.push_str(v);
self.buf.push('"');
}
self.buf.push('>');
children(self);
self.buf.push_str("</");
self.buf.push_str(tag);
self.buf.push('>');
self
}
#[allow(dead_code)]
pub fn void_elem(&mut self, tag: &str, attrs: &[(&str, &str)]) -> &mut Self {
self.buf.push('<');
self.buf.push_str(tag);
for (k, v) in attrs {
self.buf.push(' ');
self.buf.push_str(k);
self.buf.push_str("=\"");
self.buf.push_str(v);
self.buf.push('"');
}
self.buf.push_str("/>");
self
}
pub fn finish(self) -> String {
self.buf
}
}
impl Default for SvgWriter {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Write for SvgWriter {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.buf.push_str(s);
Ok(())
}
}
pub fn html_entities_to_unicode(s: &str) -> String {
static ENTITIES: &[(&str, &str)] = &[
("«", "\u{00AB}"), ("»", "\u{00BB}"), ("‹", "\u{2039}"), ("›", "\u{203A}"), (" ", "\u{00A0}"), ("—", "\u{2014}"), ("–", "\u{2013}"), ("…", "\u{2026}"), ("©", "\u{00A9}"), ("®", "\u{00AE}"), ("™", "\u{2122}"), ("°", "\u{00B0}"), ("±", "\u{00B1}"), ("×", "\u{00D7}"), ("÷", "\u{00F7}"), ("½", "\u{00BD}"), ("¼", "\u{00BC}"), ("¾", "\u{00BE}"), ("α", "\u{03B1}"), ("β", "\u{03B2}"), ("γ", "\u{03B3}"), ("δ", "\u{03B4}"), ("π", "\u{03C0}"), ("σ", "\u{03C3}"), ("μ", "\u{03BC}"), ("Ω", "\u{03A9}"), ("←", "\u{2190}"), ("→", "\u{2192}"), ("↑", "\u{2191}"), ("↓", "\u{2193}"), ("↔", "\u{2194}"), ("✓", "\u{2713}"), ("✗", "\u{2717}"), ("•", "\u{2022}"), ("′", "\u{2032}"), ("∞", "\u{221E}"), ("≠", "\u{2260}"), ("≤", "\u{2264}"), ("≥", "\u{2265}"), ("≈", "\u{2248}"), ];
let mut result = s.to_string();
for (entity, unicode) in ENTITIES {
result = result.replace(entity, unicode);
}
result
}
pub fn rounded_path(pts: &[(f64, f64)], r: f64) -> String {
let n = pts.len();
if n == 0 {
return String::new();
}
if n == 1 {
return format!("M{:.3},{:.3}", pts[0].0, pts[0].1);
}
if n == 2 {
return format!(
"M{:.3},{:.3}L{:.3},{:.3}",
pts[0].0, pts[0].1, pts[1].0, pts[1].1
);
}
let unit = |dx: f64, dy: f64| -> (f64, f64) {
let len = (dx * dx + dy * dy).sqrt();
if len < 1e-9 {
(0.0, 0.0)
} else {
(dx / len, dy / len)
}
};
let mut d = format!("M{:.3},{:.3}", pts[0].0, pts[0].1);
for i in 1..n - 1 {
let (ax, ay) = pts[i - 1];
let (bx, by) = pts[i];
let (cx, cy) = pts[i + 1];
let (ux, uy) = unit(bx - ax, by - ay);
let (vx, vy) = unit(cx - bx, cy - by);
let in_len = ((bx - ax) * (bx - ax) + (by - ay) * (by - ay)).sqrt();
let out_len = ((cx - bx) * (cx - bx) + (cy - by) * (cy - by)).sqrt();
let cr = r.min(in_len / 2.0).min(out_len / 2.0);
let p1x = bx - ux * cr;
let p1y = by - uy * cr;
let p2x = bx + vx * cr;
let p2y = by + vy * cr;
d.push_str(&format!(
"L{:.3},{:.3}C{:.3},{:.3},{:.3},{:.3},{:.3},{:.3}",
p1x,
p1y,
bx,
by, bx,
by, p2x,
p2y,
));
}
let last = pts[n - 1];
d.push_str(&format!("L{:.3},{:.3}", last.0, last.1));
d
}
pub fn smooth_bezier_path(pts: &[(f64, f64)]) -> String {
rounded_path(pts, 5.0)
}
pub fn curve_basis_path(pts: &[(f64, f64)]) -> String {
let n = pts.len();
if n == 0 {
return String::new();
}
if n == 1 {
return format!("M{:.3},{:.3}", pts[0].0, pts[0].1);
}
if n == 2 {
return format!(
"M{:.3},{:.3}L{:.3},{:.3}",
pts[0].0, pts[0].1, pts[1].0, pts[1].1
);
}
let mut e: Vec<(f64, f64)> = Vec::with_capacity(n + 2);
e.push(pts[0]);
e.extend_from_slice(pts);
e.push(pts[n - 1]);
let bs = |a: (f64, f64), b: (f64, f64), c: (f64, f64)| -> (f64, f64) {
((a.0 + 4.0 * b.0 + c.0) / 6.0, (a.1 + 4.0 * b.1 + c.1) / 6.0)
};
let bs0 = bs(e[0], e[1], e[2]);
let mut d = format!("M{:.3},{:.3}L{:.3},{:.3}", pts[0].0, pts[0].1, bs0.0, bs0.1);
for i in 0..n - 1 {
let (p0, p1, p2, p3) = (e[i], e[i + 1], e[i + 2], e[i + 3]);
let cp1 = ((2.0 * p1.0 + p2.0) / 3.0, (2.0 * p1.1 + p2.1) / 3.0);
let cp2 = ((p1.0 + 2.0 * p2.0) / 3.0, (p1.1 + 2.0 * p2.1) / 3.0);
let end = bs(p1, p2, p3);
let _ = p0; d.push_str(&format!(
"C{:.3},{:.3},{:.3},{:.3},{:.3},{:.3}",
cp1.0, cp1.1, cp2.0, cp2.1, end.0, end.1
));
}
d.push_str(&format!("L{:.3},{:.3}", pts[n - 1].0, pts[n - 1].1));
d
}
#[allow(dead_code)]
pub fn svg_attrs(pairs: &[(&str, &str)]) -> String {
pairs
.iter()
.map(|(k, v)| format!(r#" {k}="{v}""#))
.collect()
}
#[allow(dead_code)]
pub fn rect(x: f64, y: f64, w: f64, h: f64, attrs: &[(&str, &str)]) -> String {
let extra = svg_attrs(attrs);
format!(r#"<rect x="{x}" y="{y}" width="{w}" height="{h}"{extra}/>"#)
}
#[allow(dead_code)]
pub fn circle(cx: f64, cy: f64, r: f64, attrs: &[(&str, &str)]) -> String {
let extra = svg_attrs(attrs);
format!(r#"<circle cx="{cx}" cy="{cy}" r="{r}"{extra}/>"#)
}
#[allow(dead_code)]
pub fn text(x: f64, y: f64, content: &str, attrs: &[(&str, &str)]) -> String {
let extra = svg_attrs(attrs);
format!(r#"<text x="{x}" y="{y}"{extra}>{content}</text>"#)
}
#[allow(dead_code)]
pub fn line(x1: f64, y1: f64, x2: f64, y2: f64, attrs: &[(&str, &str)]) -> String {
let extra = svg_attrs(attrs);
format!(r#"<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}"{extra}/>"#)
}
#[allow(dead_code)]
pub fn g(attrs: &[(&str, &str)], children: &str) -> String {
let extra = svg_attrs(attrs);
format!(r#"<g{extra}>{children}</g>"#)
}
#[allow(dead_code)]
pub fn path(d: &str, attrs: &[(&str, &str)]) -> String {
let extra = svg_attrs(attrs);
format!(r#"<path d="{d}"{extra}/>"#)
}
#[allow(dead_code)]
pub fn defs(content: &str) -> String {
format!(r#"<defs>{content}</defs>"#)
}
#[allow(dead_code)]
pub fn marker(id: &str, attrs: &[(&str, &str)], content: &str) -> String {
let extra = svg_attrs(attrs);
format!(r#"<marker id="{id}"{extra}>{content}</marker>"#)
}
#[allow(dead_code)]
pub fn foreign_object_label(
x: f64,
y: f64,
w: f64,
h: f64,
label: &str,
div_style: &str,
) -> String {
format!(
r#"<foreignObject x="{x}" y="{y}" width="{w}" height="{h}"><div xmlns="http://www.w3.org/1999/xhtml" style="{div_style}">{label}</div></foreignObject>"#
)
}
#[allow(dead_code)]
pub fn attrs(pairs: &[(&str, &str)]) -> String {
svg_attrs(pairs)
}