use std::fmt::Write;
use crate::boxer::{Box as MBox, BoxKind, Child, Point};
use crate::font;
const PAD: f32 = 1.0;
pub fn emit(root: &MBox, fill: crate::Color) -> Vec<u8> {
let w = root.width.max(0.0) + 2.0 * PAD;
let h = (root.height + root.depth).max(0.0) + 2.0 * PAD;
let mut out = String::new();
let _ = write!(
&mut out,
r#"<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="{h}" viewBox="0 0 {w} {h}">"#,
w = w,
h = h
);
let wrap = fill != crate::Color::BLACK;
if wrap {
let _ = write!(&mut out, r#"<g fill="{}">"#, fill.hex());
}
walk(&mut out, root, Point { x: PAD, y: PAD });
if wrap {
out.push_str("</g>");
}
out.push_str("</svg>");
out.into_bytes()
}
fn walk(out: &mut String, b: &MBox, origin: Point) {
match &b.kind {
BoxKind::Glyph {
glyph_id,
font_size,
} => {
let s = font_size / font::units_per_em();
let path_d = font::outline_path(*glyph_id);
if path_d.is_empty() {
return;
}
let baseline_y = origin.y + b.height;
let _ = write!(
out,
r#"<path transform="matrix({s} 0 0 {neg_s} {ox} {oy})" d="{d}"/>"#,
s = s,
neg_s = -s,
ox = origin.x,
oy = baseline_y,
d = path_d
);
}
BoxKind::Rule { thickness } => {
let _ = write!(
out,
r#"<rect x="{x}" y="{y}" width="{w}" height="{h}"/>"#,
x = origin.x,
y = origin.y,
w = b.width,
h = thickness
);
}
BoxKind::HBox(children) | BoxKind::VBox(children) => {
for Child { offset, child } in children {
walk(
out,
child,
Point {
x: origin.x + offset.x,
y: origin.y + offset.y,
},
);
}
}
BoxKind::Empty => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::Style;
use crate::{boxer, parse, Color};
#[test]
fn emits_well_formed_svg_for_atom() {
let ir = parse::to_ir("x", 16.0, Style::Text).unwrap();
let b = boxer::layout(&ir, Style::Text);
let bytes = emit(&b, Color::BLACK);
let s = std::str::from_utf8(&bytes).unwrap();
assert!(s.starts_with("<svg"));
assert!(s.ends_with("</svg>"));
assert!(s.contains("<path"), "atom should emit at least one path");
}
#[test]
fn emits_rect_for_frac_rule() {
let ir = parse::to_ir(r"\frac{1}{2}", 16.0, Style::Text).unwrap();
let b = boxer::layout(&ir, Style::Text);
let bytes = emit(&b, Color::BLACK);
let s = std::str::from_utf8(&bytes).unwrap();
assert!(s.contains("<rect"), "frac should emit a rule rect");
}
}