use crate::parser::{read_u16, read_u32};
use crate::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ColorLayer {
pub layer_glyph_id: u16,
pub palette_index: u16,
}
#[derive(Debug, Clone)]
pub struct ColrTable<'a> {
bytes: &'a [u8],
num_base_records: u16,
base_records_offset: u32,
num_layer_records: u16,
layer_records_offset: u32,
}
impl<'a> ColrTable<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
if bytes.len() < 14 {
return Err(Error::UnexpectedEof);
}
let _version = read_u16(bytes, 0)?;
let num_base_records = read_u16(bytes, 2)?;
let base_records_offset = read_u32(bytes, 4)?;
let layer_records_offset = read_u32(bytes, 8)?;
let num_layer_records = read_u16(bytes, 12)?;
if num_base_records > 0 {
let end = (base_records_offset as u64)
.checked_add(num_base_records as u64 * 6)
.ok_or(Error::BadOffset)?;
if end > bytes.len() as u64 {
return Err(Error::BadOffset);
}
}
if num_layer_records > 0 {
let end = (layer_records_offset as u64)
.checked_add(num_layer_records as u64 * 4)
.ok_or(Error::BadOffset)?;
if end > bytes.len() as u64 {
return Err(Error::BadOffset);
}
}
Ok(Self {
bytes,
num_base_records,
base_records_offset,
num_layer_records,
layer_records_offset,
})
}
fn find_base_record(&self, glyph_id: u16) -> Option<(u16, u16)> {
let base = self.base_records_offset as usize;
let mut lo = 0i32;
let mut hi = self.num_base_records as i32 - 1;
while lo <= hi {
let mid = ((lo + hi) >> 1) as usize;
let off = base + mid * 6;
let gid = read_u16(self.bytes, off).ok()?;
match gid.cmp(&glyph_id) {
std::cmp::Ordering::Less => lo = mid as i32 + 1,
std::cmp::Ordering::Greater => hi = mid as i32 - 1,
std::cmp::Ordering::Equal => {
let first = read_u16(self.bytes, off + 2).ok()?;
let count = read_u16(self.bytes, off + 4).ok()?;
return Some((first, count));
}
}
}
None
}
pub fn layers(&self, glyph_id: u16) -> Vec<ColorLayer> {
let (first, count) = match self.find_base_record(glyph_id) {
Some(p) => p,
None => return Vec::new(),
};
let mut out = Vec::with_capacity(count as usize);
let layer_base = self.layer_records_offset as usize;
for i in 0..count {
let idx = first as usize + i as usize;
if idx >= self.num_layer_records as usize {
break;
}
let off = layer_base + idx * 4;
let layer_glyph_id = match read_u16(self.bytes, off) {
Ok(v) => v,
Err(_) => break,
};
let palette_index = match read_u16(self.bytes, off + 2) {
Ok(v) => v,
Err(_) => break,
};
out.push(ColorLayer {
layer_glyph_id,
palette_index,
});
}
out
}
pub fn num_base_records(&self) -> u16 {
self.num_base_records
}
}
#[cfg(test)]
mod tests {
use super::*;
fn synth_colr_one_base_three_layers() -> Vec<u8> {
let mut bytes = vec![0u8; 32];
bytes[0..2].copy_from_slice(&0u16.to_be_bytes());
bytes[2..4].copy_from_slice(&1u16.to_be_bytes());
bytes[4..8].copy_from_slice(&14u32.to_be_bytes());
bytes[8..12].copy_from_slice(&20u32.to_be_bytes());
bytes[12..14].copy_from_slice(&3u16.to_be_bytes());
bytes[14..16].copy_from_slice(&65u16.to_be_bytes());
bytes[16..18].copy_from_slice(&0u16.to_be_bytes());
bytes[18..20].copy_from_slice(&3u16.to_be_bytes());
bytes[20..22].copy_from_slice(&100u16.to_be_bytes());
bytes[22..24].copy_from_slice(&2u16.to_be_bytes());
bytes[24..26].copy_from_slice(&101u16.to_be_bytes());
bytes[26..28].copy_from_slice(&5u16.to_be_bytes());
bytes[28..30].copy_from_slice(&102u16.to_be_bytes());
bytes[30..32].copy_from_slice(&0xFFFFu16.to_be_bytes());
bytes
}
#[test]
fn parses_v0_header() {
let bytes = synth_colr_one_base_three_layers();
let colr = ColrTable::parse(&bytes).expect("parse");
assert_eq!(colr.num_base_records(), 1);
}
#[test]
fn layers_for_known_base_glyph() {
let bytes = synth_colr_one_base_three_layers();
let colr = ColrTable::parse(&bytes).expect("parse");
let layers = colr.layers(65);
assert_eq!(
layers,
vec![
ColorLayer {
layer_glyph_id: 100,
palette_index: 2
},
ColorLayer {
layer_glyph_id: 101,
palette_index: 5
},
ColorLayer {
layer_glyph_id: 102,
palette_index: 0xFFFF
},
]
);
}
#[test]
fn layers_for_non_base_glyph_is_empty() {
let bytes = synth_colr_one_base_three_layers();
let colr = ColrTable::parse(&bytes).expect("parse");
assert!(colr.layers(0).is_empty());
assert!(colr.layers(64).is_empty());
assert!(colr.layers(66).is_empty());
assert!(colr.layers(0xFFFF).is_empty());
}
#[test]
fn rejects_truncated_header() {
assert!(matches!(
ColrTable::parse(&[0u8; 10]),
Err(Error::UnexpectedEof)
));
}
#[test]
fn rejects_array_past_end() {
let mut bytes = vec![0u8; 14];
bytes[2..4].copy_from_slice(&1u16.to_be_bytes());
bytes[4..8].copy_from_slice(&14u32.to_be_bytes());
assert!(matches!(ColrTable::parse(&bytes), Err(Error::BadOffset)));
}
#[test]
fn binary_search_three_records() {
let mut bytes = vec![0u8; 14 + 18 + 12];
bytes[0..2].copy_from_slice(&0u16.to_be_bytes());
bytes[2..4].copy_from_slice(&3u16.to_be_bytes());
bytes[4..8].copy_from_slice(&14u32.to_be_bytes());
bytes[8..12].copy_from_slice(&32u32.to_be_bytes());
bytes[12..14].copy_from_slice(&3u16.to_be_bytes());
let recs: [(u16, u16, u16); 3] = [(10, 0, 1), (50, 1, 1), (200, 2, 1)];
for (i, (g, first, count)) in recs.iter().enumerate() {
let off = 14 + i * 6;
bytes[off..off + 2].copy_from_slice(&g.to_be_bytes());
bytes[off + 2..off + 4].copy_from_slice(&first.to_be_bytes());
bytes[off + 4..off + 6].copy_from_slice(&count.to_be_bytes());
}
for i in 0..3 {
let off = 32 + i * 4;
bytes[off..off + 2].copy_from_slice(&(1000 + i as u16).to_be_bytes());
bytes[off + 2..off + 4].copy_from_slice(&(i as u16).to_be_bytes());
}
let colr = ColrTable::parse(&bytes).expect("parse");
for (gid, _first, _count) in &recs {
let layers = colr.layers(*gid);
assert_eq!(layers.len(), 1, "gid {gid}");
}
assert!(colr.layers(0).is_empty());
assert!(colr.layers(11).is_empty());
assert!(colr.layers(199).is_empty());
assert!(colr.layers(201).is_empty());
}
}