use kurbo::{Affine, BezPath, Point};
use ttf_parser::{Face, GlyphId, OutlineBuilder};
#[derive(Clone, Debug)]
pub struct Glyph {
pub codepoint: Option<char>,
pub advance: f64,
pub path: BezPath,
}
impl Glyph {
pub fn new(face: &Face, codepoint: Option<char>, gid: GlyphId) -> Option<Self> {
let advance = f64::from(face.glyph_hor_advance(gid)?);
let mut path = KurboPathWrapper(BezPath::new());
face.outline_glyph(gid, &mut path);
let path = path.0;
Some(Self {
codepoint,
advance,
path,
})
}
pub fn notdef(cap_height: f64, slope: f64) -> Self {
let mut path = BezPath::new();
path.move_to((0., 0.)); path.line_to((0., 1000.)); path.line_to((650., 1000.)); path.line_to((650., 0.)); path.line_to((0., 0.)); path.close_path();
path.move_to((80., 150.)); path.line_to((270., 500.)); path.line_to((80., 850.)); path.line_to((80., 150.)); path.close_path();
path.move_to((125., 920.)); path.line_to((325., 560.)); path.line_to((525., 920.)); path.line_to((125., 920.)); path.close_path();
path.move_to((570., 850.)); path.line_to((380., 500.)); path.line_to((570., 150.)); path.line_to((570., 850.)); path.close_path();
path.move_to((525., 80.)); path.line_to((325., 440.)); path.line_to((125., 80.)); path.line_to((525., 80.)); path.close_path();
let skew_x = slope.to_radians().tan();
let scale = cap_height / 1e3;
let affine = Affine::skew(skew_x, 0.).pre_scale(scale);
path.apply_affine(affine);
let advance = scale * (650. + (1000. * skew_x).abs());
Self {
codepoint: None,
advance,
path,
}
}
}
struct KurboPathWrapper(pub BezPath);
impl From<BezPath> for KurboPathWrapper {
fn from(value: BezPath) -> Self {
Self(value)
}
}
impl From<KurboPathWrapper> for BezPath {
fn from(value: KurboPathWrapper) -> Self {
value.0
}
}
impl OutlineBuilder for KurboPathWrapper {
fn move_to(&mut self, x: f32, y: f32) {
self.0.move_to(Point::new(x.into(), (-y).into()));
}
fn line_to(&mut self, x: f32, y: f32) {
self.0.line_to(Point::new(x.into(), (-y).into()));
}
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
self.0.quad_to(
Point::new(x1.into(), (-y1).into()),
Point::new(x.into(), (-y).into()),
);
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
self.0.curve_to(
Point::new(x1.into(), (-y1).into()),
Point::new(x2.into(), (-y2).into()),
Point::new(x.into(), (-y).into()),
);
}
fn close(&mut self) {
self.0.close_path();
}
}
#[cfg(test)]
mod tests {
use assert_approx_eq::assert_approx_eq;
use assert_matches::assert_matches;
use itertools::Itertools;
use kurbo::{PathEl, Shape, Size};
use super::*;
use crate::utils::KurboAbs;
#[test]
fn test_new() {
let demo = std::fs::read("tests/fonts/demo.ttf").unwrap();
let demo = Face::parse(&demo, 0).unwrap();
let a = Glyph::new(&demo, Some('A'), GlyphId(1)).unwrap();
assert_approx_eq!(a.advance, 540.);
assert_eq!(a.path.into_iter().collect_vec().len(), 15);
let null = std::fs::read("tests/fonts/null.ttf").unwrap();
let null = Face::parse(&null, 0).unwrap();
let a = Glyph::new(&null, Some('A'), GlyphId(1));
assert!(a.is_none());
let notdef = Glyph::new(&null, None, GlyphId(0));
assert!(notdef.is_none()); }
#[test]
fn test_notdef() {
let notdef = Glyph::notdef(500., 0.);
assert_approx_eq!(notdef.path.bounding_box().origin(), Point::new(0., 0.));
assert_approx_eq!(notdef.path.bounding_box().size(), Size::new(325., 500.));
assert_approx_eq!(notdef.advance, 325.);
let notdef = Glyph::notdef(500., 45.);
assert_approx_eq!(notdef.path.bounding_box().size(), Size::new(825., 500.));
}
#[test]
fn test_outline_builder() {
let mut path: KurboPathWrapper = BezPath::new().into();
path.move_to(0., 0.);
path.line_to(1., 1.);
path.quad_to(2., 1., 2., 0.);
path.curve_to(2., -0.5, 1.5, -1., 1., -1.);
path.close();
let bez_path: BezPath = path.into();
let els = bez_path.into_iter().collect_vec();
assert_eq!(els.len(), 5);
assert_matches!(els[0], PathEl::MoveTo(..));
assert_matches!(els[1], PathEl::LineTo(..));
assert_matches!(els[2], PathEl::QuadTo(..));
assert_matches!(els[3], PathEl::CurveTo(..));
assert_matches!(els[4], PathEl::ClosePath);
}
}