use crate::Result;
use crate::chunks::ChunkFile;
use crate::read::Bytes;
const FILE: &str = "afont.bin";
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Glyph {
pub codepoint: u32,
pub advance: f32,
pub plane: [f32; 4],
pub uv: [f32; 4],
}
impl Glyph {
pub fn is_empty(&self) -> bool {
self.plane[0] >= self.plane[2] || self.plane[1] >= self.plane[3]
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct KerningPair {
pub left: u32,
pub right: u32,
pub advance: f32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PlacedGlyph {
pub character: char,
pub index: usize,
pub origin: [f32; 2],
pub rect: [f32; 4],
pub uv: [f32; 4],
}
#[derive(Debug, Clone, PartialEq)]
pub struct Font {
pub atlas: u64,
pub ascent: f32,
pub descent: f32,
pub line_gap: f32,
pub distance_range: f32,
pub edge_value: f32,
pub em_size: f32,
pub atlas_width: u32,
pub atlas_height: u32,
pub flags: u32,
pub glyphs: Vec<Glyph>,
pub kerning: Vec<KerningPair>,
}
impl Font {
pub fn decode(bytes: &[u8]) -> Result<Self> {
let file = ChunkFile::open(bytes, "AFONT", &[1], FILE)?;
let desc = Bytes::new(file.require("DESC", FILE)?, FILE);
let glyph_count = desc.u32(0)? as usize;
if glyph_count == 0 {
return Err(desc.err("no glyphs (glyph 0 is required)"));
}
let glyphs = Bytes::new(file.require("GLYPHS", FILE)?, FILE)
.records_n(40, glyph_count)?
.map(|r| {
Ok(Glyph {
codepoint: r.u32(0)?,
advance: r.f32(4)?,
plane: r.f32s(8)?,
uv: r.f32s(24)?,
})
})
.collect::<Result<Vec<_>>>()?;
let kerning = Bytes::new(file.require("KERNING", FILE)?, FILE)
.records_n(12, desc.u32(4)? as usize)?
.map(|r| {
Ok(KerningPair {
left: r.u32(0)?,
right: r.u32(4)?,
advance: r.f32(8)?,
})
})
.collect::<Result<Vec<_>>>()?;
Ok(Self {
atlas: desc.u64(8)?,
ascent: desc.f32(16)?,
descent: desc.f32(20)?,
line_gap: desc.f32(24)?,
distance_range: desc.f32(28)?,
edge_value: desc.f32(32)?,
em_size: desc.f32(36)?,
atlas_width: desc.u32(40)?,
atlas_height: desc.u32(44)?,
flags: desc.u32(48)?,
glyphs,
kerning,
})
}
pub fn line_advance(&self) -> f32 {
self.ascent - self.descent + self.line_gap
}
pub fn glyph(&self, c: char) -> &Glyph {
self.glyphs
.binary_search_by_key(&(c as u32), |g| g.codepoint)
.map_or(&self.glyphs[0], |i| &self.glyphs[i])
}
pub fn kerning(&self, left: char, right: char) -> f32 {
self.kerning
.binary_search_by_key(&(left as u32, right as u32), |k| (k.left, k.right))
.map_or(0.0, |i| self.kerning[i].advance)
}
pub fn layout(&self, text: &str, size: f32) -> Vec<PlacedGlyph> {
let mut placed = Vec::with_capacity(text.len());
let (mut x, mut y) = (0.0f32, 0.0f32);
let mut previous = None;
for (index, c) in text.char_indices() {
match c {
'\n' => {
(x, y, previous) = (0.0, y - self.line_advance() * size, None);
continue;
}
'\r' => continue,
_ => {}
}
if let Some(p) = previous {
x += self.kerning(p, c) * size;
}
let glyph = self.glyph(c);
if !glyph.is_empty() {
let [left, bottom, right, top] = glyph.plane;
placed.push(PlacedGlyph {
character: c,
index,
origin: [x, y],
rect: [
x + left * size,
y + bottom * size,
x + right * size,
y + top * size,
],
uv: glyph.uv,
});
}
x += glyph.advance * size;
previous = Some(c);
}
placed
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chunks::tests::file;
fn glyph(codepoint: u32, advance: f32, plane: [f32; 4], uv: [f32; 4]) -> Vec<u8> {
let mut out = codepoint.to_le_bytes().to_vec();
for v in [advance].iter().chain(&plane).chain(&uv) {
out.extend(v.to_le_bytes());
}
out
}
fn sample(drop_kerning: bool) -> Vec<u8> {
let mut desc = Vec::new();
desc.extend(4u32.to_le_bytes());
desc.extend(1u32.to_le_bytes());
desc.extend(0x1234u64.to_le_bytes());
for v in [0.8f32, -0.2, 0.1, 11.95, 0.5, 48.0] {
desc.extend(v.to_le_bytes());
}
for v in [256u32, 128, 0, 0] {
desc.extend(v.to_le_bytes());
}
let box_ = [0.1, 0.0, 0.4, 0.7];
let mut glyphs = glyph(0, 0.5, box_, [0.0, 0.0, 0.1, 0.1]);
glyphs.extend(glyph(' ' as u32, 0.25, [0.0; 4], [0.0; 4]));
glyphs.extend(glyph('A' as u32, 0.5, box_, [0.1, 0.0, 0.2, 0.1]));
glyphs.extend(glyph('V' as u32, 0.5, box_, [0.2, 0.0, 0.3, 0.1]));
let mut kerning = Vec::new();
kerning.extend(('A' as u32).to_le_bytes());
kerning.extend(('V' as u32).to_le_bytes());
kerning.extend((-0.125f32).to_le_bytes());
let mut chunks: Vec<(&str, &[u8])> = vec![("DESC", &desc), ("GLYPHS", &glyphs)];
if !drop_kerning {
chunks.push(("KERNING", &kerning));
}
file("AFONT", 1, &chunks)
}
#[test]
fn decodes_metrics_glyphs_and_kerning() {
let font = Font::decode(&sample(false)).unwrap();
assert_eq!(font.atlas, 0x1234);
assert_eq!((font.ascent, font.descent, font.em_size), (0.8, -0.2, 48.0));
assert_eq!((font.atlas_width, font.atlas_height), (256, 128));
assert_eq!(font.glyphs.len(), 4);
assert_eq!(font.glyph('A').uv, [0.1, 0.0, 0.2, 0.1]);
assert_eq!(font.glyph('?').codepoint, 0);
assert!(font.glyph(' ').is_empty());
assert_eq!(font.kerning('A', 'V'), -0.125);
assert_eq!(font.kerning('V', 'A'), 0.0);
assert!((font.line_advance() - 1.1).abs() < 1e-6);
}
#[test]
fn layout_advances_and_kerns() {
let font = Font::decode(&sample(false)).unwrap();
let placed = font.layout("A V\nAV", 16.0);
let origins: Vec<[f32; 2]> = placed.iter().map(|p| p.origin).collect();
let line = -font.line_advance() * 16.0;
assert_eq!(origins, [[0.0, 0.0], [12.0, 0.0], [0.0, line], [6.0, line]]);
assert_eq!(placed[1].character, 'V');
assert_eq!(placed[1].index, 2);
assert_eq!(placed[0].rect, [1.6, 0.0, 6.4, 0.7 * 16.0]);
assert_eq!(placed[3].uv, [0.2, 0.0, 0.3, 0.1]);
}
#[test]
fn rejects_missing_kerning() {
assert!(Font::decode(&sample(true)).is_err());
}
}