use super::payload;
use super::sdf::{EdtScratch, SdfScratch, cell_coverage_to_sdf};
use crate::gfx::font::GlyphMetrics;
use alloc::format;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
const SDF_SPREAD: f32 = 4.0;
const OVERSAMPLE: u32 = 8;
const SUPERSAMPLE: u32 = 2;
pub const BUILTIN_FONT_FILE: &str = "Questrial-Regular.ttf";
pub const BUILTIN_FONT_BYTES: &[u8] = include_bytes!("fonts/Questrial-Regular.ttf");
pub fn compile(ttf_bytes: &[u8], size_px: u32, source: &str) -> Result<Vec<u8>, String> {
let logical_size_px = size_px as f32;
let raster_size_px = logical_size_px * SUPERSAMPLE as f32;
let settings = fontdue::FontSettings {
scale: raster_size_px * OVERSAMPLE as f32,
..Default::default()
};
let font = fontdue::Font::from_bytes(ttf_bytes, settings)
.map_err(|e| format!("Font: failed to parse '{}': {}", source, e))?;
let chars: Vec<char> = (32u8..=126u8).map(|b| b as char).collect();
let rast_size = raster_size_px * OVERSAMPLE as f32;
let mut bitmaps: Vec<(char, Vec<u8>, fontdue::Metrics)> = Vec::new();
for &ch in &chars {
let (metrics, bitmap) = font.rasterize(ch, rast_size);
bitmaps.push((ch, bitmap, metrics));
}
const PAD_LO: u16 = 4;
let pad_hi = PAD_LO * OVERSAMPLE as u16;
let oversample_u16 = OVERSAMPLE as u16;
let glyph_dims_hi: Vec<(u16, u16)> = bitmaps
.iter()
.map(|(_, _, m)| {
(
round_up_to(m.width as u16, oversample_u16),
round_up_to(m.height as u16, oversample_u16),
)
})
.collect();
let max_glyph_w_hi = glyph_dims_hi.iter().map(|(w, _)| *w).max().unwrap_or(0) + pad_hi * 2;
let max_glyph_h_hi = glyph_dims_hi.iter().map(|(_, h)| *h).max().unwrap_or(0) + pad_hi * 2;
let glyph_count = bitmaps.len() as u16;
let ideal_w_hi = (max_glyph_w_hi as u32 + pad_hi as u32) * glyph_count as u32;
let atlas_w_hi = u32::next_power_of_two(ideal_w_hi.max(64)).min(2048 * OVERSAMPLE) as u16;
let glyphs_per_row = (atlas_w_hi / (max_glyph_w_hi + pad_hi)).max(1);
let rows = glyph_count.div_ceil(glyphs_per_row);
let atlas_h_hi = u32::next_power_of_two(
(max_glyph_h_hi as u32 + pad_hi as u32) * rows as u32 + pad_hi as u32,
) as u16;
let atlas_w_hi = atlas_w_hi as u32;
let atlas_h_hi = atlas_h_hi as u32;
debug_assert_eq!(atlas_w_hi % OVERSAMPLE, 0);
debug_assert_eq!(atlas_h_hi % OVERSAMPLE, 0);
let cell_w_hi = max_glyph_w_hi as u32; let cell_h_hi = max_glyph_h_hi as u32;
let cell_w_lo = cell_w_hi / OVERSAMPLE;
let cell_h_lo = cell_h_hi / OVERSAMPLE;
let cell_n = (cell_w_hi * cell_h_hi) as usize;
let block_count = OVERSAMPLE * OVERSAMPLE;
let oversample_f = OVERSAMPLE as f32;
let sdf_spread = SDF_SPREAD * oversample_f;
let atlas_w = atlas_w_hi / OVERSAMPLE;
let atlas_h = atlas_h_hi / OVERSAMPLE;
let mut atlas = vec![0u8; (atlas_w * atlas_h * 4) as usize];
let mut cell_hi = vec![0u8; cell_n * 4];
let max_cell_dim = cell_w_hi.max(cell_h_hi) as usize;
let mut sdf_scratch = SdfScratch {
inside_dist2: vec![0.0f32; cell_n],
outside_dist2: vec![0.0f32; cell_n],
edt: EdtScratch {
v: vec![0usize; max_cell_dim],
z: vec![0.0f32; max_cell_dim + 1],
row_tmp: vec![0.0f32; cell_w_hi as usize],
col_src: vec![0.0f32; cell_h_hi as usize],
col_dst: vec![0.0f32; cell_h_hi as usize],
},
};
let mut metrics_out: Vec<GlyphMetrics> = Vec::new();
for (i, (ch, bitmap, metrics)) in bitmaps.iter().enumerate() {
let col = (i as u16) % glyphs_per_row;
let row = (i as u16) / glyphs_per_row;
let ax_hi = pad_hi + col * (max_glyph_w_hi + pad_hi);
let ay_hi = pad_hi + row * (max_glyph_h_hi + pad_hi);
let gw_raw = metrics.width as u16;
let gh_raw = metrics.height as u16;
cell_hi.fill(0);
for py in 0..gh_raw {
for px in 0..gw_raw {
let src = (py as usize) * (gw_raw as usize) + px as usize;
let dst = ((pad_hi as u32 + py as u32) * cell_w_hi + pad_hi as u32 + px as u32)
as usize
* 4;
cell_hi[dst] = bitmap[src];
}
}
cell_coverage_to_sdf(
&mut cell_hi,
cell_w_hi as usize,
cell_h_hi as usize,
sdf_spread,
&mut sdf_scratch,
);
let cell_ax_lo = col as u32 * (cell_w_lo + PAD_LO as u32);
let cell_ay_lo = row as u32 * (cell_h_lo + PAD_LO as u32);
for ly in 0..cell_h_lo {
for lx in 0..cell_w_lo {
let mut sum = [0u32; 1];
for dy in 0..OVERSAMPLE {
for dx in 0..OVERSAMPLE {
let hx = lx * OVERSAMPLE + dx;
let hy = ly * OVERSAMPLE + dy;
let hi_idx = ((hy * cell_w_hi + hx) * 4) as usize;
sum[0] += cell_hi[hi_idx] as u32;
}
}
let ax = cell_ax_lo + lx;
let ay = cell_ay_lo + ly;
let lo_idx = ((ay * atlas_w + ax) * 4) as usize;
let v = (sum[0] / block_count) as u8;
atlas[lo_idx] = v;
atlas[lo_idx + 1] = v;
atlas[lo_idx + 2] = v;
atlas[lo_idx + 3] = v;
}
}
let gw_hi = glyph_dims_hi[i].0;
let gh_hi = glyph_dims_hi[i].1;
let ss_f = SUPERSAMPLE as f32;
metrics_out.push(GlyphMetrics {
char_code: *ch as u32,
atlas_x: ax_hi / oversample_u16,
atlas_y: ay_hi / oversample_u16,
atlas_w: gw_hi / oversample_u16,
atlas_h: gh_hi / oversample_u16,
advance_px: metrics.advance_width / oversample_f / ss_f,
bearing_x: metrics.xmin as f32 / oversample_f / ss_f,
bearing_y: (metrics.ymin as f32 + gh_raw as f32) / oversample_f / ss_f,
});
}
Ok(payload::serialise(
atlas_w,
atlas_h,
SUPERSAMPLE,
size_px,
&atlas,
&metrics_out,
))
}
fn round_up_to(n: u16, mult: u16) -> u16 {
n.div_ceil(mult) * mult
}
#[cfg(test)]
mod tests {
use super::*;
use crate::build::font::deserialise;
#[test]
fn builtin_face_compiles_to_a_decodable_atlas() {
let bytes = compile(BUILTIN_FONT_BYTES, 32, "<built-in>").expect("builtin face compiles");
let (aw, ah, supersample, size_px, rgba, metrics) =
deserialise(&bytes).expect("payload decodes");
assert_eq!(size_px, 32);
assert_eq!(supersample, SUPERSAMPLE);
assert!(aw > 0 && ah > 0);
assert_eq!(rgba.len(), (aw * ah * 4) as usize);
assert_eq!(metrics.len(), (32u8..=126u8).count());
assert!(metrics.iter().any(|m| m.char_code == 'A' as u32));
}
#[test]
fn unparseable_face_reports_its_source() {
let err = compile(b"not a font", 32, "bogus.ttf").expect_err("garbage is rejected");
assert!(err.contains("bogus.ttf"), "error names the source: {err}");
}
#[test]
fn round_up_to_snaps_to_the_next_multiple() {
assert_eq!(round_up_to(0, 8), 0);
assert_eq!(round_up_to(1, 8), 8);
assert_eq!(round_up_to(8, 8), 8);
assert_eq!(round_up_to(9, 8), 16);
}
}