use core::fmt::Write as _;
use crate::ast::Color;
use crate::font::MathFont;
use crate::layout::{Item, Layout};
const MARGIN: f32 = 2.0;
pub fn to_svg(layout: &Layout, font: &MathFont) -> String {
let width = layout.width + 2.0 * MARGIN;
let height = layout.ascent + layout.descent + 2.0 * MARGIN;
let mut svg = String::new();
let _ = writeln!(
svg,
r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {} {}" width="{w}" height="{h}">"#,
fmt(width),
fmt(height),
w = fmt(width),
h = fmt(height),
);
let _ = writeln!(
svg,
r#"<g transform="translate({} {})" fill="black">"#,
fmt(MARGIN),
fmt(MARGIN + layout.ascent)
);
for item in &layout.items {
match *item {
Item::Glyph {
id,
x,
y,
size,
color,
mirrored,
} => {
let mut builder = PathBuilder::default();
if font.face().outline_glyph(id, &mut builder).is_none() {
continue; }
let s = size / font.units_per_em();
let (tx, sx) = if mirrored {
let advance = font.face().glyph_hor_advance(id).unwrap_or(0);
(x + f32::from(advance) * s, format!("-{}", fmt(s)))
} else {
(x, fmt(s))
};
let _ = writeln!(
svg,
r#"<path transform="translate({} {}) scale({sx} -{sy})"{f} d="{d}"/>"#,
fmt(tx),
fmt(y),
sx = sx,
sy = fmt(s),
f = fill(color),
d = builder.d,
);
}
Item::Rule { x, y, w, h, color } => {
let _ = writeln!(
svg,
r#"<rect x="{}" y="{}" width="{}" height="{}"{}/>"#,
fmt(x),
fmt(y),
fmt(w),
fmt(h),
fill(color)
);
}
Item::Background { x, y, w, h, color } => {
let _ = writeln!(
svg,
r#"<rect x="{}" y="{}" width="{}" height="{}"{}/>"#,
fmt(x),
fmt(y),
fmt(w),
fmt(h),
fill(Some(color))
);
}
}
}
svg.push_str("</g>\n</svg>\n");
svg
}
fn fill(color: Option<Color>) -> String {
match color {
None => String::new(),
Some(c) if c.a == 255 => format!(r##" fill="#{:02x}{:02x}{:02x}""##, c.r, c.g, c.b),
Some(c) => format!(
r##" fill="#{:02x}{:02x}{:02x}" fill-opacity="{}""##,
c.r,
c.g,
c.b,
fmt(f32::from(c.a) / 255.0)
),
}
}
fn fmt(v: f32) -> String {
let mut s = format!("{v:.3}");
while s.ends_with('0') {
s.pop();
}
if s.ends_with('.') {
s.pop();
}
if s == "-0" {
s = "0".to_string();
}
s
}
#[derive(Default)]
struct PathBuilder {
d: String,
}
impl ttf_parser::OutlineBuilder for PathBuilder {
fn move_to(&mut self, x: f32, y: f32) {
let _ = write!(self.d, "M{} {}", fmt(x), fmt(y));
}
fn line_to(&mut self, x: f32, y: f32) {
let _ = write!(self.d, "L{} {}", fmt(x), fmt(y));
}
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
let _ = write!(self.d, "Q{} {} {} {}", fmt(x1), fmt(y1), fmt(x), fmt(y));
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
let _ = write!(
self.d,
"C{} {} {} {} {} {}",
fmt(x1),
fmt(y1),
fmt(x2),
fmt(y2),
fmt(x),
fmt(y)
);
}
fn close(&mut self) {
self.d.push('Z');
}
}