use cosmic_text::Command;
#[derive(Clone, Debug)]
pub struct QuadCurve {
pub p1: [f32; 2],
pub p2: [f32; 2],
pub p3: [f32; 2],
}
const MAX_CUBIC_DEPTH: u32 = 8;
const MAX_PIXEL_ERROR: f32 = 0.5;
pub fn commands_to_curves(commands: &[Command], font_size: f32) -> Option<Vec<QuadCurve>> {
if commands.is_empty() {
return None;
}
let inv_size = 1.0 / font_size;
let tolerance = (MAX_PIXEL_ERROR / font_size).max(0.002);
let mut curves = Vec::new();
let mut cur = [0.0f32; 2];
let mut contour_start = [0.0f32; 2];
for cmd in commands {
match *cmd {
Command::MoveTo(p) => {
cur = [p.x * inv_size, p.y * inv_size];
contour_start = cur;
}
Command::LineTo(p) => {
let p = [p.x * inv_size, p.y * inv_size];
curves.push(QuadCurve {
p1: cur,
p2: [(cur[0] + p[0]) * 0.5, (cur[1] + p[1]) * 0.5],
p3: p,
});
cur = p;
}
Command::QuadTo(c, p) => {
let c = [c.x * inv_size, c.y * inv_size];
let p = [p.x * inv_size, p.y * inv_size];
curves.push(QuadCurve {
p1: cur,
p2: c,
p3: p,
});
cur = p;
}
Command::CurveTo(c1, c2, p) => {
let c1 = [c1.x * inv_size, c1.y * inv_size];
let c2 = [c2.x * inv_size, c2.y * inv_size];
let p = [p.x * inv_size, p.y * inv_size];
cubic_to_quadratics(&cur, &c1, &c2, &p, &mut curves, tolerance, 0);
cur = p;
}
Command::Close => {
if point_dist2(&cur, &contour_start) > 1e-12 {
curves.push(QuadCurve {
p1: cur,
p2: [
(cur[0] + contour_start[0]) * 0.5,
(cur[1] + contour_start[1]) * 0.5,
],
p3: contour_start,
});
cur = contour_start;
}
}
}
}
if curves.is_empty() {
None
} else {
Some(curves)
}
}
fn cubic_to_quadratics(
p0: &[f32; 2],
p1: &[f32; 2],
p2: &[f32; 2],
p3: &[f32; 2],
out: &mut Vec<QuadCurve>,
tolerance: f32,
depth: u32,
) {
if depth < MAX_CUBIC_DEPTH && !is_flat_enough(p0, p1, p2, p3, tolerance) {
let p0p1 = midpoint(p0, p1);
let p1p2 = midpoint(p1, p2);
let p2p3 = midpoint(p2, p3);
let left_c2 = midpoint(&p0p1, &p1p2);
let right_c1 = midpoint(&p1p2, &p2p3);
let mid = midpoint(&left_c2, &right_c1);
cubic_to_quadratics(p0, &p0p1, &left_c2, &mid, out, tolerance, depth + 1);
cubic_to_quadratics(&mid, &right_c1, &p2p3, p3, out, tolerance, depth + 1);
} else {
let mid = [
(p0[0] + 3.0 * p1[0] + 3.0 * p2[0] + p3[0]) * 0.125,
(p0[1] + 3.0 * p1[1] + 3.0 * p2[1] + p3[1]) * 0.125,
];
let q1_c = midpoint(p0, p1);
out.push(QuadCurve {
p1: *p0,
p2: q1_c,
p3: mid,
});
let q2_c = midpoint(p2, p3);
out.push(QuadCurve {
p1: mid,
p2: q2_c,
p3: *p3,
});
}
}
fn midpoint(a: &[f32; 2], b: &[f32; 2]) -> [f32; 2] {
[(a[0] + b[0]) * 0.5, (a[1] + b[1]) * 0.5]
}
fn point_dist2(a: &[f32; 2], b: &[f32; 2]) -> f32 {
let dx = a[0] - b[0];
let dy = a[1] - b[1];
dx * dx + dy * dy
}
fn is_flat_enough(
p0: &[f32; 2],
p1: &[f32; 2],
p2: &[f32; 2],
p3: &[f32; 2],
tolerance: f32,
) -> bool {
let ux = p3[0] - p0[0];
let uy = p3[1] - p0[1];
let denom = ux * ux + uy * uy;
if denom < 1e-12 {
return point_dist2(p1, p0).max(point_dist2(p2, p0)) < tolerance * tolerance;
}
let inv_len = denom.sqrt().recip();
let d1 = ((p1[0] - p0[0]) * uy - (p1[1] - p0[1]) * ux).abs() * inv_len;
let d2 = ((p2[0] - p0[0]) * uy - (p2[1] - p0[1]) * ux).abs() * inv_len;
d1.max(d2) < tolerance
}