use crate::parser::{read_u16, read_u32};
use crate::Error;
#[allow(dead_code)]
pub const CLASS_BASE: u16 = 1;
#[allow(dead_code)]
pub const CLASS_LIGATURE: u16 = 2;
pub const CLASS_MARK: u16 = 3;
#[allow(dead_code)]
pub const CLASS_COMPONENT: u16 = 4;
#[derive(Debug, Clone)]
pub struct GdefTable<'a> {
bytes: &'a [u8],
glyph_class_def_off: Option<u32>,
}
impl<'a> GdefTable<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
if bytes.len() < 12 {
return Err(Error::UnexpectedEof);
}
let major = read_u16(bytes, 0)?;
let minor = read_u16(bytes, 2)?;
if major != 1 {
return Err(Error::BadStructure("GDEF: unsupported version"));
}
let _ = minor; let glyph_class_def_off = read_u16(bytes, 4)? as u32;
let glyph_class_def_off = if glyph_class_def_off == 0 {
None
} else {
Some(glyph_class_def_off)
};
Ok(Self {
bytes,
glyph_class_def_off,
})
}
pub fn glyph_class(&self, glyph_id: u16) -> u16 {
let off = match self.glyph_class_def_off {
Some(o) => o as usize,
None => return 0,
};
let sub = match self.bytes.get(off..) {
Some(s) => s,
None => return 0,
};
class_def_lookup(sub, glyph_id).unwrap_or(0)
}
pub fn is_mark(&self, glyph_id: u16) -> bool {
self.glyph_class(glyph_id) == CLASS_MARK
}
}
pub(crate) fn class_def_lookup(bytes: &[u8], glyph_id: u16) -> Option<u16> {
if bytes.len() < 2 {
return None;
}
let format = read_u16(bytes, 0).ok()?;
match format {
1 => {
if bytes.len() < 6 {
return None;
}
let start = read_u16(bytes, 2).ok()?;
let count = read_u16(bytes, 4).ok()?;
if glyph_id < start {
return None;
}
let idx = glyph_id - start;
if idx >= count {
return None;
}
let val = read_u16(bytes, 6 + idx as usize * 2).ok()?;
if val == 0 {
None
} else {
Some(val)
}
}
2 => {
if bytes.len() < 4 {
return None;
}
let n = read_u16(bytes, 2).ok()? as usize;
let header = 4usize;
let mut lo = 0usize;
let mut hi = n;
while lo < hi {
let mid = (lo + hi) / 2;
let off = header + mid * 6;
let s = read_u16(bytes, off).ok()?;
let e = read_u16(bytes, off + 2).ok()?;
if glyph_id < s {
hi = mid;
} else if glyph_id > e {
lo = mid + 1;
} else {
let v = read_u16(bytes, off + 4).ok()?;
return if v == 0 { None } else { Some(v) };
}
}
None
}
_ => None,
}
}
pub(crate) fn coverage_lookup(bytes: &[u8], glyph_id: u16) -> Option<u16> {
if bytes.len() < 4 {
return None;
}
let format = read_u16(bytes, 0).ok()?;
match format {
1 => {
let count = read_u16(bytes, 2).ok()? as usize;
let mut lo = 0usize;
let mut hi = count;
while lo < hi {
let mid = (lo + hi) / 2;
let g = read_u16(bytes, 4 + mid * 2).ok()?;
if g == glyph_id {
return Some(mid as u16);
}
if g < glyph_id {
lo = mid + 1;
} else {
hi = mid;
}
}
None
}
2 => {
let n = read_u16(bytes, 2).ok()? as usize;
let header = 4usize;
let mut lo = 0usize;
let mut hi = n;
while lo < hi {
let mid = (lo + hi) / 2;
let off = header + mid * 6;
let s = read_u16(bytes, off).ok()?;
let e = read_u16(bytes, off + 2).ok()?;
let start_idx = read_u16(bytes, off + 4).ok()?;
if glyph_id < s {
hi = mid;
} else if glyph_id > e {
lo = mid + 1;
} else {
return Some(start_idx + (glyph_id - s));
}
}
None
}
_ => None,
}
}
pub(crate) fn popcount_u16(v: u16) -> usize {
v.count_ones() as usize
}
pub(crate) fn lookup_table_slice(
table_bytes: &[u8],
lookup_list_off: u32,
lookup_index: u16,
) -> Option<&[u8]> {
let lookup_list = table_bytes.get(lookup_list_off as usize..)?;
if lookup_list.len() < 2 {
return None;
}
let count = read_u16(lookup_list, 0).ok()?;
if lookup_index >= count {
return None;
}
let off = read_u16(lookup_list, 2 + lookup_index as usize * 2).ok()? as usize;
let lookup_off_abs = lookup_list_off as usize + off;
table_bytes.get(lookup_off_abs..)
}
#[allow(dead_code)]
pub(crate) fn offset16(table_bytes: &[u8], abs_off: usize) -> Result<u32, Error> {
Ok(read_u16(table_bytes, abs_off)? as u32)
}
#[allow(dead_code)]
pub(crate) fn offset32(table_bytes: &[u8], abs_off: usize) -> Result<u32, Error> {
read_u32(table_bytes, abs_off)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn class_def_format1_lookup() {
let mut b = vec![0u8; 12];
b[0..2].copy_from_slice(&1u16.to_be_bytes());
b[2..4].copy_from_slice(&10u16.to_be_bytes());
b[4..6].copy_from_slice(&3u16.to_be_bytes());
b[6..8].copy_from_slice(&1u16.to_be_bytes());
b[8..10].copy_from_slice(&3u16.to_be_bytes());
b[10..12].copy_from_slice(&2u16.to_be_bytes());
assert_eq!(class_def_lookup(&b, 10), Some(1));
assert_eq!(class_def_lookup(&b, 11), Some(3));
assert_eq!(class_def_lookup(&b, 12), Some(2));
assert_eq!(class_def_lookup(&b, 13), None);
assert_eq!(class_def_lookup(&b, 9), None);
}
#[test]
fn coverage_format1() {
let mut b = vec![0u8; 4 + 6];
b[0..2].copy_from_slice(&1u16.to_be_bytes());
b[2..4].copy_from_slice(&3u16.to_be_bytes());
b[4..6].copy_from_slice(&5u16.to_be_bytes());
b[6..8].copy_from_slice(&10u16.to_be_bytes());
b[8..10].copy_from_slice(&15u16.to_be_bytes());
assert_eq!(coverage_lookup(&b, 5), Some(0));
assert_eq!(coverage_lookup(&b, 10), Some(1));
assert_eq!(coverage_lookup(&b, 15), Some(2));
assert_eq!(coverage_lookup(&b, 11), None);
}
#[test]
fn coverage_format2_indexes_correctly() {
let mut b = vec![0u8; 4 + 12];
b[0..2].copy_from_slice(&2u16.to_be_bytes());
b[2..4].copy_from_slice(&2u16.to_be_bytes());
b[4..6].copy_from_slice(&10u16.to_be_bytes());
b[6..8].copy_from_slice(&12u16.to_be_bytes());
b[8..10].copy_from_slice(&0u16.to_be_bytes());
b[10..12].copy_from_slice(&50u16.to_be_bytes());
b[12..14].copy_from_slice(&51u16.to_be_bytes());
b[14..16].copy_from_slice(&3u16.to_be_bytes());
assert_eq!(coverage_lookup(&b, 10), Some(0));
assert_eq!(coverage_lookup(&b, 12), Some(2));
assert_eq!(coverage_lookup(&b, 50), Some(3));
assert_eq!(coverage_lookup(&b, 51), Some(4));
assert_eq!(coverage_lookup(&b, 13), None);
}
#[test]
fn gdef_class_marks_correctly() {
let class_def_off: u16 = 12;
let mut t = vec![0u8; class_def_off as usize];
t[0..2].copy_from_slice(&1u16.to_be_bytes()); t[2..4].copy_from_slice(&0u16.to_be_bytes()); t[4..6].copy_from_slice(&class_def_off.to_be_bytes()); let mut cd = vec![0u8; 8];
cd[0..2].copy_from_slice(&1u16.to_be_bytes());
cd[2..4].copy_from_slice(&99u16.to_be_bytes());
cd[4..6].copy_from_slice(&1u16.to_be_bytes());
cd[6..8].copy_from_slice(&3u16.to_be_bytes());
t.extend_from_slice(&cd);
let g = GdefTable::parse(&t).unwrap();
assert!(g.is_mark(99));
assert!(!g.is_mark(100));
}
}