use clap::{Arg, App};
use rusttype::{point, Font, Scale, OutlineBuilder};
const BEZIER_RESOLUTION: usize = 4;
#[derive(Default)]
struct Builder {
points: Vec<(f64, f64)>,
contours: Vec<Vec<usize>>,
x: f32, y: f32,
dx: f32, dy: f32,
}
impl Builder {
fn set_offset(&mut self, dx: f32, dy: f32) {
self.dx = dx;
self.dy = dy;
}
fn set_position(&mut self, x: f32, y: f32) {
self.x = x;
self.y = -y;
self.points.push(((x + self.dx) as f64, (self.dy - y) as f64));
}
}
impl OutlineBuilder for Builder {
fn move_to(&mut self, x: f32, y: f32) {
self.contours.push(vec![self.points.len()]);
self.set_position(x, y);
}
fn line_to(&mut self, x: f32, y: f32) {
self.contours.last_mut().unwrap().push(self.points.len());
self.set_position(x, y);
}
fn close(&mut self) {
let c = self.contours.last_mut().unwrap();
*c.last_mut().unwrap() = c[0];
self.points.pop().unwrap();
}
fn quad_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32) {
let x0 = self.x;
let y0 = -self.y;
for i in 1..=BEZIER_RESOLUTION {
let t = i as f32 / (BEZIER_RESOLUTION as f32);
let f = |a, b, c| (1.0 - t).powf(2.0) * a +
2.0 * (1.0 - t) * t * b + t.powf(2.0) * c;
self.line_to(f(x0, x1, x2), f(y0, y1, y2));
}
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) {
let x0 = self.x;
let y0 = -self.y;
for i in 1..=BEZIER_RESOLUTION {
let t = i as f32 / (BEZIER_RESOLUTION as f32);
let f = |a, b, c, d|
(1.0 - t).powf(3.0) * a +
3.0 * (1.0 - t).powf(2.0) * t * b +
3.0 * (1.0 - t) * t.powf(2.0) * c +
t.powf(3.0) * d;
self.line_to(f(x0, x1, x2, x3), f(y0, y1, y2, y3));
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let matches = App::new("font")
.author("Matt Keeter <matt.j.keeter@gmail.com>")
.about("Triangulates a few characters from a font")
.arg(Arg::with_name("font")
.short("f")
.long("font")
.help("path to the font TTF")
.takes_value(true))
.arg(Arg::with_name("output")
.short("o")
.long("out")
.help("svg file to target")
.takes_value(true))
.arg(Arg::with_name("check")
.short("c")
.long("check")
.help("check invariants after each step (slow)"))
.arg(Arg::with_name("text")
.short("t")
.long("text")
.help("text to triangulate")
.takes_value(true))
.get_matches();
let font_path = matches.value_of("font")
.unwrap_or("/Library/Fonts/Georgia.ttf");
let font = {
let data = std::fs::read(&font_path)?;
Font::try_from_vec(data).unwrap()
};
let text = matches.value_of("text").unwrap_or("hello, world");
let mut builder = Builder::default();
let scale = Scale::uniform(10.0);
for g in font.layout(text, scale, point(0.0, 0.0)) {
let pos = g.position();
builder.set_offset(pos.x, pos.y);
g.unpositioned().build_outline(&mut builder);
}
let now = std::time::Instant::now();
let mut t = cdt::Triangulation::new_from_contours(
&builder.points,
&builder.contours)?;
while !t.done() {
t.step()?;
if matches.is_present("check") {
t.check();
}
}
let result = t.triangles().collect::<Vec<_>>();
let elapsed = now.elapsed();
eprintln!(
" Triangulated '{}' in {}.{}s.\n Generated {} triangles.",
text,
elapsed.as_secs(),
elapsed.subsec_millis(),
result.len(),
);
if let Some(out) = matches.value_of("output") {
eprintln!(" Saving {}", out);
t.save_debug_svg(out).expect("Could not save SVG");
}
Ok(())
}