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, Copy, PartialEq, Eq)]
pub struct EbdtComponent {
pub glyph_id: u16,
pub x_offset: i8,
pub y_offset: i8,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompositeBitmap {
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 components: Vec<EbdtComponent>,
}
#[derive(Debug, Clone)]
#[doc(hidden)]
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,
}))
}
pub fn lookup_composite(&self, entry: &CblcEntry) -> Result<Option<CompositeBitmap>, Error> {
let off = entry.image_data_offset as usize;
let end = off
.checked_add(entry.data_len as usize)
.ok_or(Error::BadStructure("EBDT: composite entry overflow"))?;
if end > self.bytes.len() {
return Err(Error::BadOffset);
}
let blob = &self.bytes[off..end];
match entry.image_format {
8 => self.decode_composite(blob, entry, false),
9 => self.decode_composite(blob, entry, true),
_ => Ok(None),
}
}
fn decode_composite(
&self,
blob: &[u8],
entry: &CblcEntry,
big_metrics: bool,
) -> Result<Option<CompositeBitmap>, Error> {
let (width, height, bearing_x, bearing_y, advance, n_off) = if big_metrics {
let m = BigGlyphMetrics::parse(blob, 0)?;
(
m.width,
m.height,
m.hori_bearing_x,
m.hori_bearing_y,
m.hori_advance,
8usize,
)
} else {
let m = SmallGlyphMetrics::parse(blob, 0)?;
(
m.width,
m.height,
m.bearing_x,
m.bearing_y,
m.advance,
6usize,
)
};
let num_components = blob
.get(n_off..n_off + 2)
.map(|b| u16::from_be_bytes([b[0], b[1]]))
.ok_or(Error::UnexpectedEof)? as usize;
let arr_off = n_off + 2;
let needed = arr_off
.checked_add(num_components.checked_mul(4).ok_or(Error::BadStructure(
"EBDT composite: component count overflow",
))?)
.ok_or(Error::BadStructure("EBDT composite: array overflow"))?;
if blob.len() < needed {
return Err(Error::UnexpectedEof);
}
let mut components = Vec::with_capacity(num_components);
for i in 0..num_components {
let base = arr_off + i * 4;
components.push(EbdtComponent {
glyph_id: u16::from_be_bytes([blob[base], blob[base + 1]]),
x_offset: blob[base + 2] as i8,
y_offset: blob[base + 3] as i8,
});
}
Ok(Some(CompositeBitmap {
width,
height,
bearing_x,
bearing_y,
advance,
ppem: entry.ppem_y,
bit_depth: entry.bit_depth,
components,
}))
}
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)));
}
#[test]
fn composite_format8_descriptor() {
let mut bytes = vec![0u8; 4];
bytes[0..2].copy_from_slice(&2u16.to_be_bytes());
let off = bytes.len();
bytes.extend_from_slice(&[10, 8, 1, 9, 8]);
bytes.push(0x00); bytes.extend_from_slice(&2u16.to_be_bytes()); bytes.extend_from_slice(&5u16.to_be_bytes());
bytes.push(0);
bytes.push(0);
bytes.extend_from_slice(&6u16.to_be_bytes());
bytes.push(3);
bytes.push(0xFF);
let total = (bytes.len() - off) as u32;
let t = EbdtTable::parse(&bytes).unwrap();
let e = small_entry(8, off as u32, total, 1);
assert!(t.lookup(&e).unwrap().is_none());
let c = t.lookup_composite(&e).unwrap().unwrap();
assert_eq!((c.width, c.height), (8, 10));
assert_eq!((c.bearing_x, c.bearing_y, c.advance), (1, 9, 8));
assert_eq!(c.components.len(), 2);
assert_eq!(
c.components[0],
EbdtComponent {
glyph_id: 5,
x_offset: 0,
y_offset: 0
}
);
assert_eq!(
c.components[1],
EbdtComponent {
glyph_id: 6,
x_offset: 3,
y_offset: -1
}
);
}
#[test]
fn composite_format9_big_metrics() {
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, 4, 4, 0, 0, 4]);
bytes.extend_from_slice(&1u16.to_be_bytes()); bytes.extend_from_slice(&7u16.to_be_bytes());
bytes.push(2);
bytes.push(2);
let total = (bytes.len() - off) as u32;
let t = EbdtTable::parse(&bytes).unwrap();
let e = small_entry(9, off as u32, total, 4);
let c = t.lookup_composite(&e).unwrap().unwrap();
assert_eq!((c.width, c.height), (4, 4));
assert_eq!(c.bit_depth, 4);
assert_eq!(c.components.len(), 1);
assert_eq!(
c.components[0],
EbdtComponent {
glyph_id: 7,
x_offset: 2,
y_offset: 2
}
);
}
#[test]
fn composite_truncated_array_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(&[10, 8, 1, 9, 8]); bytes.push(0x00); bytes.extend_from_slice(&3u16.to_be_bytes()); bytes.extend_from_slice(&[0, 5, 0, 0]);
let total = (bytes.len() - off) as u32;
let t = EbdtTable::parse(&bytes).unwrap();
let e = small_entry(8, off as u32, total, 1);
assert!(matches!(t.lookup_composite(&e), Err(Error::UnexpectedEof)));
}
#[test]
fn lookup_composite_none_for_pixel_format() {
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, 4, 1, 1);
assert!(t.lookup_composite(&e).unwrap().is_none());
}
}