use crate::tables::cblc::{BigGlyphMetrics, CblcEntry, SmallGlyphMetrics};
use crate::Error;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GrayBitmap {
pub width: u8,
pub height: u8,
pub bearing_x: i8,
pub bearing_y: i8,
pub advance: u8,
pub ppem: u8,
pub bit_depth: u8,
pub pixels: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct EbdtTable<'a> {
bytes: &'a [u8],
}
impl<'a> EbdtTable<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
if bytes.len() < 4 {
return Err(Error::UnexpectedEof);
}
let major = u16::from_be_bytes([bytes[0], bytes[1]]);
if major != 2 && major != 3 {
return Err(Error::BadStructure("EBDT: unknown major version"));
}
Ok(Self { bytes })
}
pub fn lookup(&self, entry: &CblcEntry) -> Result<Option<GrayBitmap>, Error> {
if entry.bit_depth == 32 {
return Ok(None);
}
if !matches!(entry.bit_depth, 1 | 2 | 4 | 8) {
return Err(Error::BadStructure("EBDT: unsupported bitDepth"));
}
let off = entry.image_data_offset as usize;
let end = off
.checked_add(entry.data_len as usize)
.ok_or(Error::BadStructure("EBDT: entry overflow"))?;
if end > self.bytes.len() {
return Err(Error::BadOffset);
}
let blob = &self.bytes[off..end];
match entry.image_format {
1 => self.decode_small(blob, entry, true),
2 => self.decode_small(blob, entry, false),
5 => self.decode_format5(blob, entry),
6 => self.decode_big(blob, entry, true),
7 => self.decode_big(blob, entry, false),
_ => Ok(None),
}
}
fn decode_small(
&self,
blob: &[u8],
entry: &CblcEntry,
byte_aligned: bool,
) -> Result<Option<GrayBitmap>, Error> {
let m = SmallGlyphMetrics::parse(blob, 0)?;
let pixels = unpack_image(
blob.get(5..).ok_or(Error::UnexpectedEof)?,
m.width,
m.height,
entry.bit_depth,
byte_aligned,
)?;
Ok(Some(GrayBitmap {
width: m.width,
height: m.height,
bearing_x: m.bearing_x,
bearing_y: m.bearing_y,
advance: m.advance,
ppem: entry.ppem_y,
bit_depth: entry.bit_depth,
pixels,
}))
}
fn decode_big(
&self,
blob: &[u8],
entry: &CblcEntry,
byte_aligned: bool,
) -> Result<Option<GrayBitmap>, Error> {
let m = BigGlyphMetrics::parse(blob, 0)?;
let pixels = unpack_image(
blob.get(8..).ok_or(Error::UnexpectedEof)?,
m.width,
m.height,
entry.bit_depth,
byte_aligned,
)?;
Ok(Some(GrayBitmap {
width: m.width,
height: m.height,
bearing_x: m.hori_bearing_x,
bearing_y: m.hori_bearing_y,
advance: m.hori_advance,
ppem: entry.ppem_y,
bit_depth: entry.bit_depth,
pixels,
}))
}
fn decode_format5(&self, blob: &[u8], entry: &CblcEntry) -> Result<Option<GrayBitmap>, Error> {
let m = entry.fixed_metrics.ok_or(Error::BadStructure(
"EBDT format 5 needs EBLC fixed metrics (IndexSubTable 2/5)",
))?;
let pixels = unpack_image(
blob,
m.width,
m.height,
entry.bit_depth,
false,
)?;
Ok(Some(GrayBitmap {
width: m.width,
height: m.height,
bearing_x: m.hori_bearing_x,
bearing_y: m.hori_bearing_y,
advance: m.hori_advance,
ppem: entry.ppem_y,
bit_depth: entry.bit_depth,
pixels,
}))
}
}
fn unpack_image(
data: &[u8],
width: u8,
height: u8,
bit_depth: u8,
byte_aligned: bool,
) -> Result<Vec<u8>, Error> {
let w = width as usize;
let h = height as usize;
if w == 0 || h == 0 {
return Ok(Vec::new());
}
let depth = bit_depth as usize;
let row_bits = w * depth;
let needed = if byte_aligned {
row_bits.div_ceil(8) * h
} else {
(row_bits * h).div_ceil(8)
};
if data.len() < needed {
return Err(Error::UnexpectedEof);
}
let mut out = Vec::with_capacity(w * h);
let max_sample = (1u32 << depth) - 1;
let mut bit_cursor = 0usize; for _row in 0..h {
if byte_aligned {
bit_cursor = bit_cursor.div_ceil(8) * 8;
}
for _col in 0..w {
let sample = read_bits(data, bit_cursor, depth);
bit_cursor += depth;
let alpha = (sample * 255 / max_sample) as u8;
out.push(alpha);
}
}
Ok(out)
}
fn read_bits(data: &[u8], bit_off: usize, count: usize) -> u32 {
let mut value = 0u32;
for i in 0..count {
let abs = bit_off + i;
let byte = data[abs / 8];
let bit = (byte >> (7 - (abs % 8))) & 1;
value = (value << 1) | bit as u32;
}
value
}
#[cfg(test)]
mod tests {
use super::*;
fn small_entry(format: u16, off: u32, len: u32, bit_depth: u8) -> CblcEntry {
CblcEntry {
image_format: format,
image_data_offset: off,
data_len: len,
ppem_x: 16,
ppem_y: 16,
bit_depth,
fixed_metrics: None,
}
}
#[test]
fn rejects_short_header() {
assert!(matches!(
EbdtTable::parse(&[0u8; 2]),
Err(Error::UnexpectedEof)
));
}
#[test]
fn rejects_unknown_major() {
let mut b = vec![0u8; 4];
b[0..2].copy_from_slice(&9u16.to_be_bytes());
assert!(matches!(EbdtTable::parse(&b), Err(Error::BadStructure(_))));
}
#[test]
fn read_bits_msb_first() {
let data = [0xA6u8];
assert_eq!(read_bits(&data, 0, 1), 1);
assert_eq!(read_bits(&data, 1, 1), 0);
assert_eq!(read_bits(&data, 0, 4), 0b1010);
assert_eq!(read_bits(&data, 4, 4), 0b0110);
assert_eq!(read_bits(&data, 0, 8), 0xA6);
}
#[test]
fn format1_monochrome_byte_aligned() {
let mut bytes = vec![0u8; 4]; bytes[0..2].copy_from_slice(&2u16.to_be_bytes());
let entry_off = bytes.len();
bytes.extend_from_slice(&[2, 3, 1, 2, 4]);
bytes.push(0xA0); bytes.push(0x60); let total = (bytes.len() - entry_off) as u32;
let t = EbdtTable::parse(&bytes).unwrap();
let e = small_entry(1, entry_off as u32, total, 1);
let g = t.lookup(&e).unwrap().unwrap();
assert_eq!((g.width, g.height), (3, 2));
assert_eq!((g.bearing_x, g.bearing_y, g.advance), (1, 2, 4));
assert_eq!(g.pixels, vec![255, 0, 255, 0, 255, 255]);
}
#[test]
fn format2_monochrome_bit_aligned() {
let mut bytes = vec![0u8; 4];
bytes[0..2].copy_from_slice(&2u16.to_be_bytes());
let entry_off = bytes.len();
bytes.extend_from_slice(&[2, 3, 0, 0, 0]); bytes.push(0xAC);
let total = (bytes.len() - entry_off) as u32;
let t = EbdtTable::parse(&bytes).unwrap();
let e = small_entry(2, entry_off as u32, total, 1);
let g = t.lookup(&e).unwrap().unwrap();
assert_eq!(g.pixels, vec![255, 0, 255, 0, 255, 255]);
}
#[test]
fn format1_vs_format2_differ_on_padding() {
let mut b1 = vec![0u8; 4];
b1[0..2].copy_from_slice(&2u16.to_be_bytes());
let off = b1.len();
b1.extend_from_slice(&[2, 5, 0, 0, 0]);
b1.extend_from_slice(&[0xF8, 0x00]);
let t1 = EbdtTable::parse(&b1).unwrap();
let g1 = t1
.lookup(&small_entry(1, off as u32, (b1.len() - off) as u32, 1))
.unwrap()
.unwrap();
assert_eq!(g1.pixels, vec![255, 255, 255, 255, 255, 0, 0, 0, 0, 0]);
let mut b2 = vec![0u8; 4];
b2[0..2].copy_from_slice(&2u16.to_be_bytes());
let off2 = b2.len();
b2.extend_from_slice(&[2, 5, 0, 0, 0]);
b2.extend_from_slice(&[0xF8, 0x00]);
let t2 = EbdtTable::parse(&b2).unwrap();
let g2 = t2
.lookup(&small_entry(2, off2 as u32, (b2.len() - off2) as u32, 1))
.unwrap()
.unwrap();
assert_eq!(g2.pixels, g1.pixels);
}
#[test]
fn format6_big_metrics_grayscale_4bit() {
let mut bytes = vec![0u8; 4];
bytes[0..2].copy_from_slice(&2u16.to_be_bytes());
let off = bytes.len();
bytes.extend_from_slice(&[1, 2, 3, 4, 5, 0, 0, 0]);
bytes.push(0xF8);
let t = EbdtTable::parse(&bytes).unwrap();
let g = t
.lookup(&small_entry(6, off as u32, (bytes.len() - off) as u32, 4))
.unwrap()
.unwrap();
assert_eq!((g.width, g.height, g.bit_depth), (2, 1, 4));
assert_eq!((g.bearing_x, g.bearing_y, g.advance), (3, 4, 5));
assert_eq!(g.pixels, vec![255, (8u32 * 255 / 15) as u8]);
}
#[test]
fn format5_uses_eblc_fixed_metrics() {
let mut bytes = vec![0u8; 4];
bytes[0..2].copy_from_slice(&2u16.to_be_bytes());
let off = bytes.len();
bytes.push(0xB0);
let mut e = small_entry(5, off as u32, 1, 1);
e.fixed_metrics = Some(BigGlyphMetrics {
height: 1,
width: 4,
hori_bearing_x: -2,
hori_bearing_y: 6,
hori_advance: 7,
vert_bearing_x: 0,
vert_bearing_y: 0,
vert_advance: 0,
});
let t = EbdtTable::parse(&bytes).unwrap();
let g = t.lookup(&e).unwrap().unwrap();
assert_eq!((g.width, g.height), (4, 1));
assert_eq!((g.bearing_x, g.bearing_y, g.advance), (-2, 6, 7));
assert_eq!(g.pixels, vec![255, 0, 255, 255]);
}
#[test]
fn format5_without_fixed_metrics_errors() {
let mut bytes = vec![0u8; 4];
bytes[0..2].copy_from_slice(&2u16.to_be_bytes());
bytes.push(0x00);
let t = EbdtTable::parse(&bytes).unwrap();
let e = small_entry(5, 4, 1, 1);
assert!(matches!(t.lookup(&e), Err(Error::BadStructure(_))));
}
#[test]
fn color_bitdepth_returns_none() {
let mut bytes = vec![0u8; 8];
bytes[0..2].copy_from_slice(&3u16.to_be_bytes());
let t = EbdtTable::parse(&bytes).unwrap();
let e = small_entry(1, 4, 1, 32); assert!(t.lookup(&e).unwrap().is_none());
}
#[test]
fn unsupported_format_returns_none() {
let mut bytes = vec![0u8; 8];
bytes[0..2].copy_from_slice(&2u16.to_be_bytes());
let t = EbdtTable::parse(&bytes).unwrap();
for fmt in [4u16, 8, 9] {
let e = small_entry(fmt, 4, 1, 1);
assert!(t.lookup(&e).unwrap().is_none(), "format {fmt}");
}
}
#[test]
fn truncated_image_data_errors() {
let mut bytes = vec![0u8; 4];
bytes[0..2].copy_from_slice(&2u16.to_be_bytes());
let off = bytes.len();
bytes.extend_from_slice(&[4, 4, 0, 0, 0]);
bytes.push(0x00);
let t = EbdtTable::parse(&bytes).unwrap();
let e = small_entry(1, off as u32, (bytes.len() - off) as u32, 1);
assert!(matches!(t.lookup(&e), Err(Error::UnexpectedEof)));
}
#[test]
fn out_of_range_offset_errors() {
let mut bytes = vec![0u8; 8];
bytes[0..2].copy_from_slice(&2u16.to_be_bytes());
let t = EbdtTable::parse(&bytes).unwrap();
let e = small_entry(1, 100, 10, 1);
assert!(matches!(t.lookup(&e), Err(Error::BadOffset)));
}
}