use crate::font::PathCommand;
use rand::Rng;
struct GlyphTransform {
angle: f64,
dx: f64,
dy: f64,
scale: f64,
cx: f64,
cy: f64,
}
pub fn apply_jitter(
glyph_commands: &[Vec<PathCommand>],
intensity: f64,
units_per_em: f64,
) -> Vec<Vec<PathCommand>> {
let mut rng = rand::thread_rng();
glyph_commands
.iter()
.map(|commands| {
let (cx, cy) = compute_center(commands);
let transform = GlyphTransform {
angle: rng.gen_range(-5.0..5.0_f64).to_radians() * intensity,
dx: rng.gen_range(-1.0..1.0) * units_per_em * 0.03 * intensity,
dy: rng.gen_range(-1.0..1.0) * units_per_em * 0.03 * intensity,
scale: 1.0 + rng.gen_range(-0.05..0.05) * intensity,
cx,
cy,
};
apply_transform(commands, &transform)
})
.collect()
}
fn compute_center(commands: &[PathCommand]) -> (f64, f64) {
let mut min_x = f64::MAX;
let mut min_y = f64::MAX;
let mut max_x = f64::MIN;
let mut max_y = f64::MIN;
for cmd in commands {
let points: Vec<(f64, f64)> = match cmd {
PathCommand::MoveTo(x, y) | PathCommand::LineTo(x, y) => {
vec![(*x as f64, *y as f64)]
}
PathCommand::QuadTo(cx, cy, x, y) => {
vec![(*cx as f64, *cy as f64), (*x as f64, *y as f64)]
}
PathCommand::CurveTo(cx0, cy0, cx1, cy1, x, y) => {
vec![
(*cx0 as f64, *cy0 as f64),
(*cx1 as f64, *cy1 as f64),
(*x as f64, *y as f64),
]
}
PathCommand::Close => vec![],
};
for (x, y) in points {
min_x = min_x.min(x);
min_y = min_y.min(y);
max_x = max_x.max(x);
max_y = max_y.max(y);
}
}
if min_x == f64::MAX {
(0.0, 0.0)
} else {
((min_x + max_x) / 2.0, (min_y + max_y) / 2.0)
}
}
fn apply_transform(commands: &[PathCommand], t: &GlyphTransform) -> Vec<PathCommand> {
commands
.iter()
.map(|cmd| match cmd {
PathCommand::MoveTo(x, y) => {
let (nx, ny) = transform_point(*x as f64, *y as f64, t);
PathCommand::MoveTo(nx as f32, ny as f32)
}
PathCommand::LineTo(x, y) => {
let (nx, ny) = transform_point(*x as f64, *y as f64, t);
PathCommand::LineTo(nx as f32, ny as f32)
}
PathCommand::QuadTo(cx, cy, x, y) => {
let (ncx, ncy) = transform_point(*cx as f64, *cy as f64, t);
let (nx, ny) = transform_point(*x as f64, *y as f64, t);
PathCommand::QuadTo(ncx as f32, ncy as f32, nx as f32, ny as f32)
}
PathCommand::CurveTo(cx0, cy0, cx1, cy1, x, y) => {
let (ncx0, ncy0) = transform_point(*cx0 as f64, *cy0 as f64, t);
let (ncx1, ncy1) = transform_point(*cx1 as f64, *cy1 as f64, t);
let (nx, ny) = transform_point(*x as f64, *y as f64, t);
PathCommand::CurveTo(
ncx0 as f32,
ncy0 as f32,
ncx1 as f32,
ncy1 as f32,
nx as f32,
ny as f32,
)
}
PathCommand::Close => PathCommand::Close,
})
.collect()
}
fn transform_point(x: f64, y: f64, t: &GlyphTransform) -> (f64, f64) {
let dx = x - t.cx;
let dy = y - t.cy;
let dx = dx * t.scale;
let dy = dy * t.scale;
let cos = t.angle.cos();
let sin = t.angle.sin();
let rx = dx * cos - dy * sin;
let ry = dx * sin + dy * cos;
(rx + t.cx + t.dx, ry + t.cy + t.dy)
}