mod cmap;
mod glyf;
mod sfnt;
#[cfg(test)]
mod tests;
use crate::font::{require_head_hhea, require_table, FontData, FontError};
use skrifa::charmap::Charmap;
use skrifa::raw::TableProvider;
use skrifa::{MetadataProvider, Tag};
use std::collections::{BTreeMap, BTreeSet, HashMap};
pub struct FontSubset {
pub font_data: Vec<u8>,
pub char_to_gid: BTreeMap<char, u16>,
pub num_glyphs: u16,
pub widths_1000: Vec<f32>,
}
struct PatchedTables {
head: Vec<u8>,
hhea: Vec<u8>,
maxp: Vec<u8>,
}
fn patch_head_hhea_maxp(head_raw: &[u8], hhea_raw: &[u8], maxp_raw: &[u8], num_glyphs_new: u16) -> Result<PatchedTables, FontError> {
if head_raw.len() < 54 {
return Err(FontError::MalformedFont);
}
let mut head = head_raw.get(..54).ok_or(FontError::MalformedFont)?.to_vec();
head.get_mut(50..52)
.ok_or(FontError::MalformedFont)?
.copy_from_slice(&1u16.to_be_bytes());
if hhea_raw.len() < 36 {
return Err(FontError::MalformedFont);
}
let mut hhea = hhea_raw.get(..36).ok_or(FontError::MalformedFont)?.to_vec();
hhea.get_mut(34..36)
.ok_or(FontError::MalformedFont)?
.copy_from_slice(&num_glyphs_new.to_be_bytes());
if maxp_raw.len() < 6 {
return Err(FontError::MalformedFont);
}
let mut maxp = maxp_raw.to_vec();
maxp.get_mut(4..6)
.ok_or(FontError::MalformedFont)?
.copy_from_slice(&num_glyphs_new.to_be_bytes());
Ok(PatchedTables { head, hhea, maxp })
}
fn map_chars_to_glyphs(charmap: &Charmap, chars: &BTreeSet<char>) -> Result<(BTreeMap<char, u16>, BTreeSet<u16>), FontError> {
let mut char_to_orig_gid: BTreeMap<char, u16> = BTreeMap::new();
let mut used: BTreeSet<u16> = BTreeSet::new();
used.insert(0); for &ch in chars {
if let Some(gid) = charmap.map(ch) {
let gid = u16::try_from(gid.to_u32()).map_err(|_| FontError::MalformedFont)?;
char_to_orig_gid.insert(ch, gid);
used.insert(gid);
}
}
Ok((char_to_orig_gid, used))
}
type GlyphOrder = (Vec<u16>, u16, HashMap<u16, u16>);
fn build_glyph_order(used: BTreeSet<u16>) -> Result<GlyphOrder, FontError> {
let ordered: Vec<u16> = used.into_iter().collect();
let num_glyphs_new = u16::try_from(ordered.len()).map_err(|_| FontError::MalformedFont)?;
let mut orig_to_new: HashMap<u16, u16> = HashMap::with_capacity(ordered.len());
for (new_gid, &orig_gid) in ordered.iter().enumerate() {
let new_gid = u16::try_from(new_gid).map_err(|_| FontError::MalformedFont)?;
orig_to_new.insert(orig_gid, new_gid);
}
Ok((ordered, num_glyphs_new, orig_to_new))
}
fn rebuild_hmtx(ordered: &[u16], hmtx_raw: &[u8], num_hmetrics: u16, upem: f32) -> (Vec<u8>, Vec<f32>) {
let read_orig_metric = |orig_gid: u16| -> (u16, i16) {
if orig_gid < num_hmetrics {
let off = usize::from(orig_gid) * 4;
(sfnt::read_u16(hmtx_raw, off), sfnt::read_i16(hmtx_raw, off + 2))
} else {
let advance_off = (usize::from(num_hmetrics) - 1) * 4;
let advance = sfnt::read_u16(hmtx_raw, advance_off);
let lsb_off = usize::from(num_hmetrics) * 4 + (usize::from(orig_gid) - usize::from(num_hmetrics)) * 2;
let lsb = if lsb_off + 2 <= hmtx_raw.len() {
sfnt::read_i16(hmtx_raw, lsb_off)
} else {
0
};
(advance, lsb)
}
};
let mut new_hmtx = Vec::with_capacity(ordered.len() * 4);
let mut widths_1000 = Vec::with_capacity(ordered.len());
for &orig_gid in ordered {
let (advance, lsb) = read_orig_metric(orig_gid);
new_hmtx.extend_from_slice(&advance.to_be_bytes());
new_hmtx.extend_from_slice(&lsb.to_be_bytes());
widths_1000.push(advance as f32 * 1000.0 / upem);
}
(new_hmtx, widths_1000)
}
fn remap_char_to_gid(char_to_orig_gid: &BTreeMap<char, u16>, orig_to_new: &HashMap<u16, u16>) -> Result<BTreeMap<char, u16>, FontError> {
let mut char_to_gid: BTreeMap<char, u16> = BTreeMap::new();
for (&ch, &orig) in char_to_orig_gid {
let new_gid = *orig_to_new.get(&orig).ok_or(FontError::MalformedFont)?;
char_to_gid.insert(ch, new_gid);
}
Ok(char_to_gid)
}
pub fn subset_font(font: &FontData, chars: &BTreeSet<char>) -> Result<FontSubset, FontError> {
font.with_font(|font| -> Result<FontSubset, FontError> {
let glyf_raw = font.data_for_tag(Tag::new(b"glyf")).ok_or(FontError::UnsupportedFont)?.as_bytes();
let loca_raw = font.data_for_tag(Tag::new(b"loca")).ok_or(FontError::UnsupportedFont)?.as_bytes();
let hmtx_raw = font.data_for_tag(Tag::new(b"hmtx")).ok_or(FontError::UnsupportedFont)?.as_bytes();
let head_raw = font.data_for_tag(Tag::new(b"head")).ok_or(FontError::MalformedFont)?.as_bytes();
let hhea_raw = font.data_for_tag(Tag::new(b"hhea")).ok_or(FontError::MalformedFont)?.as_bytes();
let maxp_raw = font.data_for_tag(Tag::new(b"maxp")).ok_or(FontError::MalformedFont)?.as_bytes();
let (head, hhea) = require_head_hhea(font)?;
let num_glyphs_orig = require_table(font.maxp())?.num_glyphs();
let long_loca = head.index_to_loc_format() == 1;
let loca = glyf::parse_loca(loca_raw, num_glyphs_orig, long_loca)?;
let num_hmetrics = hhea.number_of_h_metrics();
if num_hmetrics == 0 {
return Err(FontError::MalformedFont);
}
let charmap = font.charmap();
let (char_to_orig_gid, used) = map_chars_to_glyphs(&charmap, chars)?;
let used = glyf::composite_glyph_closure(used, &loca, glyf_raw)?;
let (ordered, num_glyphs_new, orig_to_new) = build_glyph_order(used)?;
let (new_glyf, new_loca_bytes) = glyf::rebuild_glyf_and_loca(&ordered, &loca, glyf_raw, &orig_to_new)?;
let upem = head.units_per_em() as f32;
let (new_hmtx, widths_1000) = rebuild_hmtx(&ordered, hmtx_raw, num_hmetrics, upem);
let patched = patch_head_hhea_maxp(head_raw, hhea_raw, maxp_raw, num_glyphs_new)?;
let char_to_gid = remap_char_to_gid(&char_to_orig_gid, &orig_to_new)?;
let new_cmap = cmap::build_cmap_format4(&char_to_gid)?;
let tables: Vec<(&[u8; 4], Vec<u8>)> = vec![
(b"cmap", new_cmap),
(b"glyf", new_glyf),
(b"head", patched.head),
(b"hhea", patched.hhea),
(b"hmtx", new_hmtx),
(b"loca", new_loca_bytes),
(b"maxp", patched.maxp),
];
let font_data = sfnt::build_sfnt(&tables)?;
Ok(FontSubset {
font_data,
char_to_gid,
num_glyphs: num_glyphs_new,
widths_1000,
})
})?
}