use crate::gfx::font::GlyphMetrics;
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use crate::decode::{ByteReader, checked_product};
const GLYPH_STRIDE: usize = 4 + 2 + 2 + 2 + 2 + 4 + 4 + 4;
pub(crate) type DecodedFont = (u32, u32, u32, u32, Vec<u8>, Vec<GlyphMetrics>);
pub fn deserialise(bytes: &[u8]) -> Result<DecodedFont, String> {
let mut r = ByteReader::new(bytes, "font payload");
let atlas_w = r.u32()?;
let atlas_h = r.u32()?;
let supersample = r.u32()?;
let size_px = r.u32()?;
let pixel_bytes = checked_product("font atlas", &[atlas_w as usize, atlas_h as usize, 4])?;
let rgba = r.take(pixel_bytes)?.to_vec();
let glyph_count = r.u32()? as usize;
let metrics_bytes = checked_product("font metrics", &[glyph_count, GLYPH_STRIDE])?;
if r.remaining() < metrics_bytes {
return Err(format!(
"font payload truncated: need {} metric bytes, have {}",
metrics_bytes,
r.remaining()
));
}
let mut metrics = Vec::with_capacity(glyph_count);
for _ in 0..glyph_count {
metrics.push(GlyphMetrics {
char_code: r.u32()?,
atlas_x: r.u16()?,
atlas_y: r.u16()?,
atlas_w: r.u16()?,
atlas_h: r.u16()?,
advance_px: r.f32()?,
bearing_x: r.f32()?,
bearing_y: r.f32()?,
});
}
Ok((atlas_w, atlas_h, supersample, size_px, rgba, metrics))
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
fn payload(atlas_w: u32, atlas_h: u32, glyphs: &[GlyphMetrics]) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(&atlas_w.to_le_bytes());
out.extend_from_slice(&atlas_h.to_le_bytes());
out.extend_from_slice(&2u32.to_le_bytes());
out.extend_from_slice(&32u32.to_le_bytes());
out.extend(core::iter::repeat_n(
0xABu8,
(atlas_w as usize) * (atlas_h as usize) * 4,
));
out.extend_from_slice(&(glyphs.len() as u32).to_le_bytes());
for m in glyphs {
out.extend_from_slice(&m.char_code.to_le_bytes());
out.extend_from_slice(&m.atlas_x.to_le_bytes());
out.extend_from_slice(&m.atlas_y.to_le_bytes());
out.extend_from_slice(&m.atlas_w.to_le_bytes());
out.extend_from_slice(&m.atlas_h.to_le_bytes());
out.extend_from_slice(&m.advance_px.to_le_bytes());
out.extend_from_slice(&m.bearing_x.to_le_bytes());
out.extend_from_slice(&m.bearing_y.to_le_bytes());
}
out
}
fn glyph(char_code: u32) -> GlyphMetrics {
GlyphMetrics {
char_code,
atlas_x: 1,
atlas_y: 2,
atlas_w: 3,
atlas_h: 4,
advance_px: 5.5,
bearing_x: 6.5,
bearing_y: 7.5,
}
}
#[test]
fn decodes_a_well_formed_payload() {
let glyphs = [glyph(b'A' as u32), glyph(b'B' as u32)];
let bytes = payload(4, 2, &glyphs);
let (w, h, ss, size_px, rgba, metrics) = deserialise(&bytes).unwrap();
assert_eq!((w, h, ss, size_px), (4, 2, 2, 32));
assert_eq!(rgba.len(), 4 * 2 * 4);
assert!(rgba.iter().all(|b| *b == 0xAB));
assert_eq!(metrics.len(), 2);
assert_eq!(metrics[0].char_code, b'A' as u32);
assert_eq!(metrics[1].char_code, b'B' as u32);
assert_eq!(metrics[1].atlas_x, 1);
assert_eq!(metrics[1].advance_px, 5.5);
assert_eq!(metrics[1].bearing_y, 7.5);
}
#[test]
fn decodes_a_payload_with_no_glyphs() {
let (_, _, _, _, _, metrics) = deserialise(&payload(1, 1, &[])).unwrap();
assert!(metrics.is_empty());
}
#[test]
fn rejects_a_payload_shorter_than_the_header() {
for len in 0..16 {
let bytes = vec![0u8; len];
assert!(deserialise(&bytes).is_err(), "len {} decoded", len);
}
}
#[test]
fn rejects_a_payload_truncated_mid_atlas() {
let mut bytes = payload(8, 8, &[glyph(b'x' as u32)]);
bytes.truncate(16 + 40);
assert!(deserialise(&bytes).is_err());
}
#[test]
fn rejects_a_payload_truncated_before_the_glyph_count() {
let full = payload(2, 2, &[]);
let bytes = &full[..full.len() - 2];
assert!(deserialise(bytes).is_err());
}
#[test]
fn rejects_a_payload_truncated_mid_metrics() {
let mut bytes = payload(2, 2, &[glyph(b'q' as u32), glyph(b'r' as u32)]);
bytes.truncate(bytes.len() - 5);
let err = deserialise(&bytes).unwrap_err();
assert!(err.contains("truncated"), "{}", err);
}
#[test]
fn rejects_atlas_dimensions_that_overflow_the_footprint() {
let mut bytes = u32::MAX.to_le_bytes().to_vec();
bytes.extend_from_slice(&u32::MAX.to_le_bytes());
bytes.extend_from_slice(&1u32.to_le_bytes());
bytes.extend_from_slice(&1u32.to_le_bytes());
let err = deserialise(&bytes).unwrap_err();
assert!(err.contains("overflow"), "{}", err);
}
#[test]
fn rejects_an_atlas_larger_than_the_buffer() {
let mut bytes = 4096u32.to_le_bytes().to_vec();
bytes.extend_from_slice(&4096u32.to_le_bytes());
bytes.extend_from_slice(&1u32.to_le_bytes());
bytes.extend_from_slice(&1u32.to_le_bytes());
bytes.extend_from_slice(&[0u8; 64]);
let err = deserialise(&bytes).unwrap_err();
assert!(err.contains("unexpected end"), "{}", err);
}
#[test]
fn rejects_an_absurd_glyph_count() {
let mut bytes = payload(1, 1, &[]);
let count_at = bytes.len() - 4;
bytes[count_at..].copy_from_slice(&u32::MAX.to_le_bytes());
let err = deserialise(&bytes).unwrap_err();
assert!(err.contains("truncated"), "{}", err);
}
}