#![no_std]
mod glyph;
mod style;
pub use glyph::*;
pub use style::MiniTextStyle;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Error {
BadMagic,
BadVersion(u8),
Malformed,
KerningBounds,
AtlasTooShort,
GlyphOob,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct MiniTypeFont<'a> {
pub line_height: u16,
pub ascent: i16,
pub descent: i16,
data: &'a [u8],
charsets_count: u16,
glyph_count: u16,
kerning_count: u32,
glyph_table_off: usize,
atlas_off: usize,
kerning_off: usize,
}
const VERSION: u8 = 0;
impl<'a> MiniTypeFont<'a> {
pub fn new(data: &'a [u8]) -> Result<Self, Error> {
if data.len() < 10 {
return Err(Error::Malformed);
}
if &data[0..4] != b"MFNT" {
return Err(Error::BadMagic);
}
let version = data[4];
if data[4] != VERSION {
return Err(Error::BadVersion(version));
}
if data[5] != 0 {
return Err(Error::Malformed); }
let line_height = data[6] as u16;
let ascent = (data[7] as i8) as i16;
let descent = (data[8] as i8) as i16;
let charsets_count = data[9] as u16;
let segs_len = (charsets_count as usize).checked_mul(5).ok_or(Error::Malformed)?;
let segs_end = 10usize.checked_add(segs_len).ok_or(Error::Malformed)?;
if segs_end + 2 > data.len() {
return Err(Error::Malformed);
}
let glyph_count = le_u16_at(data, segs_end);
let glyph_table_off = segs_end + 2;
let glyph_table_len = (glyph_count as usize).checked_mul(6).ok_or(Error::Malformed)?;
let gt_end = glyph_table_off.checked_add(glyph_table_len).ok_or(Error::Malformed)?;
if gt_end > data.len() {
return Err(Error::Malformed);
}
let atlas_off = glyph_table_off + glyph_table_len;
let atlas_end = {
let mut max_end = atlas_off;
let mut i = 0usize;
while i < glyph_table_len {
let meta = GlyphMetrics::decode(&data[glyph_table_off..], i).ok_or(Error::GlyphOob)?;
let bpr = ceil_div_u16(meta.w as u16, 2) as usize; let need = (meta.h as usize).saturating_mul(bpr);
let start = atlas_off + meta.offset as usize;
let end = start.checked_add(need).ok_or(Error::AtlasTooShort)?;
if end > max_end {
max_end = end;
}
i += 6;
}
max_end
};
if atlas_end > data.len() {
return Err(Error::AtlasTooShort);
}
let (kerning_off, kerning_count) = if atlas_end < data.len() {
if atlas_end + 2 > data.len() {
return Err(Error::KerningBounds);
}
let kc = le_u16_at(data, atlas_end) as usize;
let need_pairs = kc.checked_mul(5).ok_or(Error::KerningBounds)?;
let total_need = 2usize.checked_add(need_pairs).ok_or(Error::KerningBounds)?;
if atlas_end + total_need != data.len() {
return Err(Error::KerningBounds);
}
(atlas_end + 2, kc as u32)
} else {
(data.len(), 0)
};
Ok(Self {
data,
charsets_count,
glyph_count,
line_height,
ascent,
descent,
glyph_table_off,
atlas_off,
kerning_off,
kerning_count,
})
}
#[inline]
pub const fn unchecked(data: &'a [u8]) -> Self {
let charsets_count = data[9] as u16;
let charsets_end = 10 + (charsets_count as usize) * 5;
let glyph_count = le_u16_at(data, charsets_end);
let glyph_table_off = charsets_end + 2;
let glyph_table_len = (glyph_count as usize) * 6;
let atlas_off = glyph_table_off + glyph_table_len;
Self {
data,
charsets_count,
glyph_count,
line_height: data[6] as u16,
ascent: (data[7] as i8) as i16,
descent: (data[8] as i8) as i16,
glyph_table_off,
atlas_off,
kerning_off: data.len(), kerning_count: 0,
}
}
#[inline(always)]
pub fn glyph(&self, char: char) -> Option<GlyphId> {
let cp = char as u32;
let mut offset = 10usize; let mut base: u32 = 0;
for _ in 0..self.charsets_count {
let start = le_u24_at(self.data, offset);
let len = le_u16_at(self.data, offset + 3) as u32;
offset += 5;
if cp >= start && cp < start + len {
return Some(GlyphId((base + (cp - start)) as u16));
}
base += len;
}
None
}
#[inline(always)]
pub fn metrics(&self, id: GlyphId) -> Option<GlyphMetrics> {
let idx = id.0 as usize;
if idx >= self.glyph_count as usize {
return None;
}
let off = self.glyph_table_off + idx * 6;
GlyphMetrics::decode(self.data, off)
}
#[inline(always)]
pub fn kerning(&self, left: GlyphId, right: GlyphId) -> i8 {
let kc = self.kerning_count;
if kc == 0 {
return 0;
}
let mut off = self.kerning_off;
for _ in 0..kc {
let l = le_u16_at(self.data, off);
let r = le_u16_at(self.data, off + 2);
let adj = self.data[off + 4] as i8;
if l == left.0 && r == right.0 {
return adj;
}
off += 5;
}
0
}
#[inline(always)]
pub(crate) fn glyph_pixels(&'a self, id: GlyphId) -> Option<GlyphPixels<'a>> {
let m = self.metrics(id)?;
let bpr = ceil_div_u16(m.w as u16, 2) as usize;
let start = self.atlas_off + m.offset as usize;
let len = (m.h as usize).saturating_mul(bpr);
let slab = &self.data[start..start + len];
Some(GlyphPixels::new(slab, m))
}
}
#[inline]
pub const fn ceil_div_u16(v: u16, d: u16) -> u16 {
(v + (d - 1)) / d
}
#[inline]
pub const fn le_u16_at(b: &[u8], off: usize) -> u16 {
(b[off] as u16) | ((b[off + 1] as u16) << 8)
}
#[inline]
pub const fn le_u24_at(b: &[u8], off: usize) -> u32 {
(b[off] as u32) | ((b[off + 1] as u32) << 8) | ((b[off + 2] as u32) << 16)
}