use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
use percent_encoding::{utf8_percent_encode, CONTROLS};
use pinot::{
math::MathVariants,
otl::{Feature, SubtableKind},
types::{FWord, Tag, UfWord},
FontDataRef, TableProvider,
};
use std::{collections::HashMap, num::Wrapping, path::Path};
use tectonic_errors::prelude::*;
use crate::FixedPoint;
pub type GlyphId = u16;
pub type Usv = u32;
const SSTY: Tag = Tag(0x73_73_74_79);
#[derive(Debug)]
pub struct FontFileData {
buffer: Vec<u8>,
gmap: HashMap<GlyphId, MapEntry>,
space_glyph: GlyphId,
units_per_em: UfWord,
hmetrics: Vec<HorizontalMetrics>,
ascender: FWord,
descender: FWord,
baseline_factor: f32,
variant_map_counts: HashMap<char, usize>,
variant_map_allocations: HashMap<GlyphId, GlyphVariantMapping>,
no_new_variants: bool,
fontdata_cmap_trec_idx: usize,
fontdata_head_offset: u32,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum MapEntry {
Direct(char),
SubSuperScript(char, bool),
MathGrowingVariant(char, bool, u16),
}
impl MapEntry {
fn get_char(&self) -> char {
match *self {
MapEntry::Direct(c) => c,
MapEntry::SubSuperScript(c, _) => c,
MapEntry::MathGrowingVariant(c, _, _) => c,
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct GlyphVariantMapping {
pub usv: char,
pub variant_map_index: usize,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct GlyphMetrics {
pub advance: FixedPoint,
pub lsb: FixedPoint,
pub ascent: FixedPoint,
pub descent: FixedPoint,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
struct HorizontalMetrics {
advance: UfWord,
lsb: FWord,
}
impl FontFileData {
pub fn from_opentype(buffer: Vec<u8>, face_index: u32) -> Result<Self> {
let font_data = a_ok_or!(
FontDataRef::new(&buffer);
["unable to parse buffer as OpenType font"]
);
let font = a_ok_or!(
font_data.get(face_index);
["unable to load face #{} in the OpenType font", face_index]
);
let head = a_ok_or!(
font.head();
["unable to parse OpenType font: missing/invalid HEAD table"]
);
let units_per_em = head.units_per_em();
let cmap = a_ok_or!(
font.cmap();
["unable to parse OpenType font: missing/invalid CMAP table"]
);
let mut gmap = HashMap::new();
let mut space_glyph = 0;
for usv in valid_usvs() {
let c = char::from_u32(usv).unwrap();
let gidx = match cmap.map(usv) {
Some(g) if g != 0 => g,
_ => {
continue;
}
};
if c == ' ' {
space_glyph = gidx;
}
gmap.insert(gidx, MapEntry::Direct(c));
}
let dglyphs: Vec<_> = gmap.keys().copied().collect();
if let Some(gsub) = font.gsub() {
for feat in gsub.features() {
if feat.record.tag == SSTY {
load_ssty_mappings(&mut gmap, &feat, &dglyphs[..])?;
}
}
}
if let Some(math) = font.math() {
if let Some(variants) = math.variants() {
load_math_variants(&mut gmap, &variants, &dglyphs[..])?;
}
}
let hhea = a_ok_or!(
font.hhea();
["unable to parse OpenType font: missing/invalid HMTX table"]
);
let ascender = hhea.ascender();
let descender = hhea.descender();
let baseline_factor = ascender as f32 / (ascender - descender) as f32;
let hmtx = a_ok_or!(
font.hmtx();
["unable to parse OpenType font: missing/invalid HMTX table"]
);
let mut hmetrics = Vec::new();
for hm in hmtx.hmetrics() {
hmetrics.push(HorizontalMetrics {
advance: hm.advance_width,
lsb: hm.lsb,
});
}
let advance = hmetrics[hmetrics.len() - 1].advance;
for lsb in hmtx.lsbs() {
hmetrics.push(HorizontalMetrics { advance, lsb });
}
let mut fontdata_cmap_trec_idx = 0;
let mut fontdata_head_offset = 0;
for (idx, trec) in font.records().iter().enumerate() {
if trec.tag == pinot::head::HEAD {
fontdata_head_offset = trec.offset;
} else if trec.tag == pinot::cmap::CMAP {
fontdata_cmap_trec_idx = idx;
}
}
Ok(FontFileData {
buffer,
gmap,
space_glyph,
units_per_em,
hmetrics,
ascender,
descender,
baseline_factor,
variant_map_counts: HashMap::new(),
variant_map_allocations: HashMap::new(),
no_new_variants: false,
fontdata_head_offset,
fontdata_cmap_trec_idx,
})
}
pub fn lookup_mapping(&self, glyph: GlyphId) -> Option<MapEntry> {
self.gmap.get(&glyph).copied()
}
pub fn baseline_factor(&self) -> f32 {
self.baseline_factor
}
pub fn lookup_metrics(&self, glyph: GlyphId, tex_size: FixedPoint) -> Option<GlyphMetrics> {
let fword_to_tex = |f: FWord| -> FixedPoint {
(f as f64 * tex_size as f64 / self.units_per_em as f64) as FixedPoint
};
let ufword_to_tex = |f: UfWord| -> FixedPoint {
(f as f64 * tex_size as f64 / self.units_per_em as f64) as FixedPoint
};
self.hmetrics.get(glyph as usize).map(|hm| GlyphMetrics {
advance: ufword_to_tex(hm.advance),
lsb: fword_to_tex(hm.lsb),
ascent: fword_to_tex(self.ascender),
descent: fword_to_tex(self.descender),
})
}
pub fn space_width(&self, tex_size: FixedPoint) -> Option<FixedPoint> {
if self.space_glyph == 0 {
None
} else {
self.hmetrics.get(self.space_glyph as usize).map(|hm| {
(hm.advance as f64 * tex_size as f64 / self.units_per_em as f64) as FixedPoint
})
}
}
pub fn request_variant(
&mut self,
glyph: GlyphId,
suggested: char,
) -> Option<GlyphVariantMapping> {
let map_entry = self.variant_map_allocations.entry(glyph);
if self.no_new_variants {
if let std::collections::hash_map::Entry::Vacant(_) = map_entry {
return None;
}
}
let new_index = self
.variant_map_counts
.get(&suggested)
.copied()
.unwrap_or(0);
let map = map_entry.or_insert(GlyphVariantMapping {
usv: suggested,
variant_map_index: new_index,
});
if map.usv == suggested && map.variant_map_index == new_index {
self.variant_map_counts.insert(suggested, new_index + 1);
}
Some(*map)
}
pub fn emit(
self,
out_base: Option<&Path>,
rel_path: &str,
) -> Result<Vec<(Option<usize>, String)>> {
let mut out_path = out_base.map(|p| p.to_owned());
if let Some(out_path) = out_path.as_mut() {
out_path.push(rel_path);
let display_path = out_path.clone();
atry!(
std::fs::write(out_path, &self.buffer);
["cannot write output file `{}`", display_path.display()]
);
}
let rel_url = utf8_percent_encode(rel_path, CONTROLS).to_string();
let mut rv = vec![(None, format!(r#"url("{rel_url}") format("opentype")"#))];
let mut buffer = self.buffer;
let orig_len = buffer.len();
for cur_map_index in 0.. {
let mut mappings = Vec::new();
for (glyph, altmap) in &self.variant_map_allocations {
if altmap.variant_map_index == cur_map_index {
mappings.push((altmap.usv, *glyph));
}
}
if mappings.is_empty() {
break;
}
let varname = format!("vg{cur_map_index}{rel_path}");
if let Some(out_path) = out_path.as_mut() {
buffer.truncate(orig_len);
mappings.sort_unstable();
append_simple_cmap(&mut buffer, &mappings[..]);
let cmap_size = buffer.len() - orig_len;
let cs = opentype_checksum(&buffer[orig_len..]);
let ofs = 12 + self.fontdata_cmap_trec_idx * 16;
BigEndian::write_u32(&mut buffer[ofs + 4..ofs + 8], cs); BigEndian::write_u32(&mut buffer[ofs + 8..ofs + 12], orig_len as u32); BigEndian::write_u32(&mut buffer[ofs + 12..ofs + 16], cmap_size as u32);
let cs = opentype_checksum(&buffer[..]);
let chkadj = Wrapping(0xB1B0AFBA) - Wrapping(cs);
let ofs = self.fontdata_head_offset as usize + 8;
BigEndian::write_u32(&mut buffer[ofs..ofs + 4], chkadj.0);
out_path.pop();
out_path.push(&varname);
let display_path = out_path.clone();
atry!(
std::fs::write(out_path, &buffer);
["cannot write output file `{}`", display_path.display()]
);
}
let rel_url = utf8_percent_encode(&varname, CONTROLS).to_string();
rv.push((
Some(cur_map_index),
format!(r#"url("{rel_url}") format("opentype")"#),
));
}
Ok(rv)
}
pub fn into_vglyphs(mut self) -> HashMap<String, crate::assets::syntax::GlyphVariantMapping> {
let mut vglyphs = HashMap::default();
for (glyph, altmap) in self.variant_map_allocations.drain() {
vglyphs.insert(glyph.to_string(), altmap.into());
}
vglyphs
}
pub(crate) fn match_to_precomputed(&mut self, ffad: &crate::assets::syntax::FontFileAssetData) {
self.variant_map_counts.clear();
self.variant_map_allocations.clear();
for (gid, mapping) in &ffad.vglyphs {
let gid: GlyphId = gid.parse().unwrap();
self.variant_map_allocations.insert(gid, (*mapping).into());
let c = self.variant_map_counts.entry(mapping.usv).or_default();
*c = std::cmp::max(mapping.index + 1, *c);
}
self.no_new_variants = true;
}
}
fn load_ssty_mappings(
map: &mut HashMap<GlyphId, MapEntry>,
feat: &Feature,
dglyphs: &[GlyphId],
) -> Result<()> {
for look in feat.lookups() {
for st in look.subtables() {
for glyph in dglyphs {
let c = map.get(glyph).unwrap().get_char();
if let Some(cov) = st.covered(*glyph) {
if let SubtableKind::AlternateSubst1(t) = st.kind() {
if let Some(sl) = t.get(cov) {
if let Some(g) = sl.get(0) {
map.insert(g, MapEntry::SubSuperScript(c, false));
}
if let Some(g) = sl.get(1) {
map.insert(g, MapEntry::SubSuperScript(c, true));
}
}
}
}
}
}
}
Ok(())
}
fn load_math_variants(
map: &mut HashMap<GlyphId, MapEntry>,
variants: &MathVariants,
dglyphs: &[GlyphId],
) -> Result<()> {
let maybe_vcov = variants.vert_glyph_coverage();
let maybe_hcov = variants.horiz_glyph_coverage();
for glyph in dglyphs {
let c = map.get(glyph).unwrap().get_char();
if let Some(vvars) = maybe_vcov
.and_then(|c| c.get(*glyph))
.and_then(|i| variants.vert_glyph_construction(i))
.and_then(|c| c.variants())
{
for (idx, vinfo) in vvars.iter().enumerate() {
map.insert(
vinfo.variant_glyph,
MapEntry::MathGrowingVariant(c, true, idx as u16),
);
}
}
if let Some(hvars) = maybe_hcov
.and_then(|c| c.get(*glyph))
.and_then(|i| variants.horiz_glyph_construction(i))
.and_then(|c| c.variants())
{
for (idx, vinfo) in hvars.iter().enumerate() {
map.insert(
vinfo.variant_glyph,
MapEntry::MathGrowingVariant(c, false, idx as u16),
);
}
}
}
Ok(())
}
fn valid_usvs() -> impl Iterator<Item = Usv> {
(0..0xD800).chain(0xE000..0x11_0000)
}
fn opentype_checksum(data: &[u8]) -> u32 {
let mut iter = data.chunks_exact(4);
let cs: Wrapping<u32> = iter
.by_ref()
.map(|c| Wrapping(BigEndian::read_u32(c)))
.sum();
let rem = iter.remainder();
let mut padded = [0u8; 4];
padded[..rem.len()].copy_from_slice(rem);
(cs + Wrapping(BigEndian::read_u32(&padded[..]))).0
}
fn append_simple_cmap(buf: &mut Vec<u8>, map: &[(char, GlyphId)]) {
buf.write_u16::<BigEndian>(0).unwrap(); buf.write_u16::<BigEndian>(1).unwrap();
buf.write_u16::<BigEndian>(0).unwrap(); buf.write_u16::<BigEndian>(4).unwrap(); buf.write_u32::<BigEndian>(12).unwrap();
buf.write_u16::<BigEndian>(12).unwrap(); buf.write_u16::<BigEndian>(0).unwrap();
let subtable_len = 16 + 12 * map.len() as u32;
buf.write_u32::<BigEndian>(subtable_len).unwrap(); buf.write_u32::<BigEndian>(0).unwrap(); buf.write_u32::<BigEndian>(map.len() as u32).unwrap();
for (usv, gid) in map {
buf.write_u32::<BigEndian>(*usv as u32).unwrap(); buf.write_u32::<BigEndian>(*usv as u32).unwrap(); buf.write_u32::<BigEndian>(*gid as u32).unwrap(); }
}
impl From<crate::assets::syntax::GlyphVariantMapping> for GlyphVariantMapping {
fn from(m: crate::assets::syntax::GlyphVariantMapping) -> Self {
GlyphVariantMapping {
usv: m.usv,
variant_map_index: m.index,
}
}
}