use fdsm::{
bezier::scanline::FillRule,
correct_error::{ErrorCorrectionConfig, correct_error_mtsdf},
generate::generate_mtsdf,
render::correct_sign_mtsdf,
shape::Shape,
transform::Transform,
};
use fdsm_ttf_parser::load_shape_from_face;
use nalgebra::{Affine2, Matrix3};
use ttf_parser::{Face, GlyphId};
#[derive(Clone, Debug, PartialEq)]
pub struct MsdfGlyph {
pub rgba: Vec<u8>,
pub width: u32,
pub height: u32,
pub bearing_x: f32,
pub bearing_y: f32,
pub advance: f32,
pub spread: f32,
}
pub fn build_glyph_msdf(
face: &Face<'_>,
glyph_id: u16,
base_em: u32,
spread: f64,
) -> Option<MsdfGlyph> {
let gid = GlyphId(glyph_id);
let bbox = face.glyph_bounding_box(gid)?;
let mut shape = load_shape_from_face(face, gid)?;
let upem = face.units_per_em() as f64;
let scale = base_em as f64 / upem;
let bb_w = (bbox.x_max - bbox.x_min) as f64 * scale;
let bb_h = (bbox.y_max - bbox.y_min) as f64 * scale;
let width = (bb_w + 2.0 * spread).ceil() as u32;
let height = (bb_h + 2.0 * spread).ceil() as u32;
if width == 0 || height == 0 {
return None;
}
let tx = spread - bbox.x_min as f64 * scale;
let ty = height as f64 - spread + bbox.y_min as f64 * scale;
let m = Matrix3::new(scale, 0.0, tx, 0.0, -scale, ty, 0.0, 0.0, 1.0);
let transform = Affine2::from_matrix_unchecked(m);
shape.transform(&transform);
let colored = Shape::edge_coloring_simple(shape, 0.03, 0);
let prepared = colored.prepare();
let mut buf_f = image::Rgba32FImage::new(width, height);
generate_mtsdf(&prepared, spread, &mut buf_f);
correct_error_mtsdf(
&mut buf_f,
&colored,
&prepared,
spread,
&ErrorCorrectionConfig::default(),
);
correct_sign_mtsdf(&mut buf_f, &prepared, FillRule::Nonzero);
let buf = rgba32f_to_rgba8(&buf_f);
let advance = face.glyph_hor_advance(gid).unwrap_or(0) as f32 * scale as f32;
let bearing_x = bbox.x_min as f32 * scale as f32 - spread as f32;
let bearing_y = -(bbox.y_max as f32 * scale as f32) - spread as f32;
Some(MsdfGlyph {
rgba: buf.into_raw(),
width,
height,
bearing_x,
bearing_y,
advance,
spread: spread as f32,
})
}
fn rgba32f_to_rgba8(src: &image::Rgba32FImage) -> image::RgbaImage {
let mut dst = image::RgbaImage::new(src.width(), src.height());
for (x, y, p) in src.enumerate_pixels() {
let r = (p[0].clamp(0.0, 1.0) * 255.0).round() as u8;
let g = (p[1].clamp(0.0, 1.0) * 255.0).round() as u8;
let b = (p[2].clamp(0.0, 1.0) * 255.0).round() as u8;
let a = (p[3].clamp(0.0, 1.0) * 255.0).round() as u8;
dst.put_pixel(x, y, image::Rgba([r, g, b, a]));
}
dst
}
pub fn glyph_advance(face: &Face<'_>, glyph_id: u16, base_em: u32) -> f32 {
let gid = GlyphId(glyph_id);
let upem = face.units_per_em() as f64;
let scale = base_em as f64 / upem;
face.glyph_hor_advance(gid).unwrap_or(0) as f32 * scale as f32
}
#[cfg(test)]
mod tests {
use super::*;
fn test_face() -> ttf_parser::Face<'static> {
ttf_parser::Face::parse(aetna_fonts::INTER_VARIABLE, 0).unwrap()
}
#[test]
fn produces_msdf_for_letter_a() {
let face = test_face();
let glyph_id = face.glyph_index('A').unwrap().0;
let glyph = build_glyph_msdf(&face, glyph_id, 32, 4.0).expect("MSDF for A");
assert_eq!(glyph.spread, 4.0);
assert_eq!(glyph.rgba.len() as u32, glyph.width * glyph.height * 4);
assert!(glyph.height >= 24 && glyph.height <= 36, "{}", glyph.height);
assert!(glyph.width >= 16 && glyph.width <= 32, "{}", glyph.width);
assert!(glyph.bearing_x.abs() < 6.0, "{}", glyph.bearing_x);
assert!(glyph.bearing_y < 0.0, "{}", glyph.bearing_y);
assert!(
glyph.advance > 10.0 && glyph.advance < 30.0,
"{}",
glyph.advance
);
}
#[test]
fn whitespace_returns_none_but_keeps_advance() {
let face = test_face();
let glyph_id = face.glyph_index(' ').unwrap().0;
assert!(build_glyph_msdf(&face, glyph_id, 32, 4.0).is_none());
let advance = glyph_advance(&face, glyph_id, 32);
assert!(advance > 0.0);
}
#[test]
fn bitmap_has_inside_pixels() {
let face = test_face();
let glyph_id = face.glyph_index('O').unwrap().0;
let glyph = build_glyph_msdf(&face, glyph_id, 32, 4.0).unwrap();
let mut found_inside = false;
for px in glyph.rgba.chunks_exact(4) {
let mut v = [px[0], px[1], px[2]];
v.sort_unstable();
if v[1] > 200 {
found_inside = true;
break;
}
}
assert!(found_inside, "expected interior pixels in 'O'");
}
#[test]
fn bitmap_has_outside_pixels() {
let face = test_face();
let glyph_id = face.glyph_index('A').unwrap().0;
let glyph = build_glyph_msdf(&face, glyph_id, 32, 4.0).unwrap();
let stride = glyph.width as usize * 4;
let corner = &glyph.rgba[0..3];
let mut v = [corner[0], corner[1], corner[2]];
v.sort_unstable();
assert!(
v[1] < 60,
"top-left corner median should be far outside, got {v:?}"
);
let last_row = (glyph.height as usize - 1) * stride;
let bottom_right = &glyph.rgba[last_row + stride - 4..last_row + stride - 1];
let mut v = [bottom_right[0], bottom_right[1], bottom_right[2]];
v.sort_unstable();
assert!(
v[1] < 60,
"bottom-right corner median should be far outside, got {v:?}"
);
}
#[test]
fn distinct_glyphs_have_distinct_bitmaps() {
let face = test_face();
let a = build_glyph_msdf(&face, face.glyph_index('A').unwrap().0, 32, 4.0).unwrap();
let b = build_glyph_msdf(&face, face.glyph_index('B').unwrap().0, 32, 4.0).unwrap();
assert!(a.rgba != b.rgba || a.width != b.width || a.height != b.height);
}
}