use crate::parser::read_u32;
use crate::tables::cblc::{BigGlyphMetrics, CblcEntry, SmallGlyphMetrics};
use crate::Error;
#[derive(Debug, Clone, Copy)]
pub struct ColorBitmap<'a> {
pub width: u8,
pub height: u8,
pub bearing_x: i8,
pub bearing_y: i8,
pub advance: u8,
pub ppem: u8,
pub png_bytes: &'a [u8],
}
#[derive(Debug, Clone)]
pub struct CbdtTable<'a> {
bytes: &'a [u8],
}
impl<'a> CbdtTable<'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("CBDT: unknown major version"));
}
Ok(Self { bytes })
}
pub fn lookup(&self, entry: &CblcEntry) -> Result<Option<ColorBitmap<'a>>, Error> {
let off = entry.image_data_offset as usize;
let end = off
.checked_add(entry.data_len as usize)
.ok_or(Error::BadStructure("CBDT: entry overflow"))?;
if end > self.bytes.len() {
return Err(Error::BadOffset);
}
let blob = &self.bytes[off..end];
match entry.image_format {
17 => {
let metrics = SmallGlyphMetrics::parse(blob, 0)?;
if blob.len() < 5 + 4 {
return Err(Error::UnexpectedEof);
}
let data_len = read_u32(blob, 5)? as usize;
let png = blob.get(9..9 + data_len).ok_or(Error::BadOffset)?;
Ok(Some(ColorBitmap {
width: metrics.width,
height: metrics.height,
bearing_x: metrics.bearing_x,
bearing_y: metrics.bearing_y,
advance: metrics.advance,
ppem: entry.ppem_y,
png_bytes: png,
}))
}
18 => {
let metrics = BigGlyphMetrics::parse(blob, 0)?;
if blob.len() < 8 + 4 {
return Err(Error::UnexpectedEof);
}
let data_len = read_u32(blob, 8)? as usize;
let png = blob.get(12..12 + data_len).ok_or(Error::BadOffset)?;
Ok(Some(ColorBitmap {
width: metrics.width,
height: metrics.height,
bearing_x: metrics.hori_bearing_x,
bearing_y: metrics.hori_bearing_y,
advance: metrics.hori_advance,
ppem: entry.ppem_y,
png_bytes: png,
}))
}
19 => {
let metrics = entry.fixed_metrics.ok_or(Error::BadStructure(
"CBDT format 19 needs CBLC fixed metrics",
))?;
if blob.len() < 4 {
return Err(Error::UnexpectedEof);
}
let data_len = read_u32(blob, 0)? as usize;
let png = blob.get(4..4 + data_len).ok_or(Error::BadOffset)?;
Ok(Some(ColorBitmap {
width: metrics.width,
height: metrics.height,
bearing_x: metrics.hori_bearing_x,
bearing_y: metrics.hori_bearing_y,
advance: metrics.hori_advance,
ppem: entry.ppem_y,
png_bytes: png,
}))
}
_ => Ok(None),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_format17_entry() {
let mut bytes = vec![0u8; 64];
bytes[0..2].copy_from_slice(&3u16.to_be_bytes());
bytes[16] = 10; bytes[17] = 12; bytes[18] = (-1i8) as u8; bytes[19] = 8; bytes[20] = 14; bytes[21..25].copy_from_slice(&5u32.to_be_bytes()); bytes[25..30].copy_from_slice(&[0x89, b'P', b'N', b'G', 0x0D]);
let cbdt = CbdtTable::parse(&bytes).expect("parse");
let entry = CblcEntry {
image_format: 17,
image_data_offset: 16,
data_len: 30 - 16,
ppem_x: 96,
ppem_y: 96,
bit_depth: 32,
fixed_metrics: None,
};
let cb = cbdt.lookup(&entry).expect("lookup ok").expect("entry");
assert_eq!(cb.width, 12);
assert_eq!(cb.height, 10);
assert_eq!(cb.bearing_x, -1);
assert_eq!(cb.bearing_y, 8);
assert_eq!(cb.advance, 14);
assert_eq!(cb.png_bytes.len(), 5);
assert_eq!(cb.png_bytes[0], 0x89);
}
#[test]
fn parses_format18_entry() {
let mut bytes = vec![0u8; 64];
bytes[0..2].copy_from_slice(&3u16.to_be_bytes());
bytes[12] = 20; bytes[13] = 24; bytes[14] = 2; bytes[15] = 18; bytes[16] = 26; bytes[17..20].copy_from_slice(&[0; 3]); bytes[20..24].copy_from_slice(&3u32.to_be_bytes()); bytes[24..27].copy_from_slice(&[0xA1, 0xB2, 0xC3]);
let cbdt = CbdtTable::parse(&bytes).expect("parse");
let entry = CblcEntry {
image_format: 18,
image_data_offset: 12,
data_len: 27 - 12,
ppem_x: 109,
ppem_y: 109,
bit_depth: 32,
fixed_metrics: None,
};
let cb = cbdt.lookup(&entry).expect("lookup ok").expect("entry");
assert_eq!(cb.width, 24);
assert_eq!(cb.height, 20);
assert_eq!(cb.bearing_x, 2);
assert_eq!(cb.bearing_y, 18);
assert_eq!(cb.advance, 26);
assert_eq!(cb.ppem, 109);
assert_eq!(cb.png_bytes, &[0xA1, 0xB2, 0xC3]);
}
#[test]
fn returns_none_for_unsupported_format() {
let mut bytes = vec![0u8; 16];
bytes[0..2].copy_from_slice(&3u16.to_be_bytes());
let cbdt = CbdtTable::parse(&bytes).expect("parse");
let entry = CblcEntry {
image_format: 1, image_data_offset: 4,
data_len: 4,
ppem_x: 32,
ppem_y: 32,
bit_depth: 1,
fixed_metrics: None,
};
assert!(cbdt.lookup(&entry).expect("lookup ok").is_none());
}
#[test]
fn parses_format19_with_cblc_metrics() {
let mut bytes = vec![0u8; 32];
bytes[0..2].copy_from_slice(&3u16.to_be_bytes());
bytes[8..12].copy_from_slice(&4u32.to_be_bytes());
bytes[12..16].copy_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
let cbdt = CbdtTable::parse(&bytes).expect("parse");
let entry = CblcEntry {
image_format: 19,
image_data_offset: 8,
data_len: 16 - 8,
ppem_x: 64,
ppem_y: 64,
bit_depth: 32,
fixed_metrics: Some(BigGlyphMetrics {
height: 7,
width: 9,
hori_bearing_x: 3,
hori_bearing_y: 11,
hori_advance: 13,
vert_bearing_x: 0,
vert_bearing_y: 0,
vert_advance: 0,
}),
};
let cb = cbdt.lookup(&entry).expect("lookup ok").expect("entry");
assert_eq!(cb.width, 9);
assert_eq!(cb.height, 7);
assert_eq!(cb.bearing_x, 3);
assert_eq!(cb.bearing_y, 11);
assert_eq!(cb.advance, 13);
assert_eq!(cb.png_bytes, &[0xDE, 0xAD, 0xBE, 0xEF]);
}
}