use super::sfnt::{read_i16, read_u16, read_u32};
use crate::font::FontError;
use std::collections::{BTreeSet, HashMap, VecDeque};
pub(super) fn parse_loca(loca: &[u8], num_glyphs: u16, long_format: bool) -> Result<Vec<u32>, FontError> {
let count = usize::from(num_glyphs) + 1;
if long_format {
if loca.len() < count * 4 {
return Err(FontError::MalformedFont);
}
Ok((0..count).map(|i| read_u32(loca, i * 4)).collect())
} else {
if loca.len() < count * 2 {
return Err(FontError::MalformedFont);
}
Ok((0..count).map(|i| u32::from(read_u16(loca, i * 2)) * 2).collect())
}
}
pub(super) struct Component {
pub(super) gid_offset: usize,
pub(super) gid: u16,
}
pub(super) fn glyph_components(glyph: &[u8]) -> Vec<Component> {
let mut out = Vec::new();
if glyph.len() < 10 {
return out;
}
if read_i16(glyph, 0) >= 0 {
return out; }
let mut pos = 10usize;
loop {
if pos + 4 > glyph.len() {
break;
}
let flags = read_u16(glyph, pos);
let gid_offset = pos + 2;
let gid = read_u16(glyph, gid_offset);
out.push(Component { gid_offset, gid });
pos = gid_offset + 2;
pos += if flags & 0x0001 != 0 { 4 } else { 2 };
if flags & 0x0008 != 0 {
pos += 2;
} else if flags & 0x0040 != 0 {
pos += 4;
} else if flags & 0x0080 != 0 {
pos += 8;
}
if flags & 0x0020 == 0 {
break; }
}
out
}
pub(super) fn glyph_range(loca: &[u32], gid: u16) -> Result<(usize, usize), FontError> {
let start = usize::try_from(*loca.get(usize::from(gid)).ok_or(FontError::MalformedFont)?).map_err(|_| FontError::MalformedFont)?;
let end = usize::try_from(*loca.get(usize::from(gid) + 1).ok_or(FontError::MalformedFont)?).map_err(|_| FontError::MalformedFont)?;
Ok((start, end))
}
pub(super) fn composite_glyph_closure(mut used: BTreeSet<u16>, loca: &[u32], glyf_raw: &[u8]) -> Result<BTreeSet<u16>, FontError> {
let gid_in_range = |gid: u16| usize::from(gid) + 1 < loca.len();
let mut queue: VecDeque<u16> = used.iter().copied().collect();
while let Some(gid) = queue.pop_front() {
if !gid_in_range(gid) {
continue; }
let (start, end) = glyph_range(loca, gid)?;
let glyph_slice = glyf_raw.get(start..end).ok_or(FontError::MalformedFont)?;
for comp in glyph_components(glyph_slice) {
if gid_in_range(comp.gid) && used.insert(comp.gid) {
queue.push_back(comp.gid);
}
}
}
Ok(used)
}
pub(super) fn rebuild_glyf_and_loca(
ordered: &[u16],
loca: &[u32],
glyf_raw: &[u8],
orig_to_new: &HashMap<u16, u16>,
) -> Result<(Vec<u8>, Vec<u8>), FontError> {
let mut new_glyf = Vec::new();
let mut new_loca: Vec<u32> = Vec::with_capacity(ordered.len() + 1);
for &orig_gid in ordered {
new_loca.push(u32::try_from(new_glyf.len()).map_err(|_| FontError::MalformedFont)?);
let (start, end) = glyph_range(loca, orig_gid)?;
let mut glyph_bytes = glyf_raw.get(start..end).ok_or(FontError::MalformedFont)?.to_vec();
for comp in glyph_components(&glyph_bytes) {
let new_gid = orig_to_new.get(&comp.gid).copied().unwrap_or(0);
glyph_bytes
.get_mut(comp.gid_offset..comp.gid_offset + 2)
.ok_or(FontError::MalformedFont)?
.copy_from_slice(&new_gid.to_be_bytes());
}
new_glyf.extend_from_slice(&glyph_bytes);
}
new_loca.push(u32::try_from(new_glyf.len()).map_err(|_| FontError::MalformedFont)?);
let mut new_loca_bytes = Vec::with_capacity(new_loca.len() * 4);
for off in &new_loca {
new_loca_bytes.extend_from_slice(&off.to_be_bytes());
}
Ok((new_glyf, new_loca_bytes))
}