include!("../../generated/generated_post.rs");
impl<'a> Post<'a> {
pub fn num_names(&self) -> usize {
match self.version() {
Version16Dot16::VERSION_1_0 => DEFAULT_GLYPH_NAMES.len(),
Version16Dot16::VERSION_2_0 => self.num_glyphs().unwrap_or_default() as usize,
_ => 0,
}
}
pub fn glyph_name(&self, glyph_id: GlyphId16) -> Option<&'a str> {
let glyph_id = glyph_id.to_u16() as usize;
match self.version() {
Version16Dot16::VERSION_1_0 => DEFAULT_GLYPH_NAMES.get(glyph_id).copied(),
Version16Dot16::VERSION_2_0 => {
let idx = self.glyph_name_index()?.get(glyph_id)?.get() as usize;
if idx < DEFAULT_GLYPH_NAMES.len() {
return DEFAULT_GLYPH_NAMES.get(idx).copied();
}
let idx = idx - DEFAULT_GLYPH_NAMES.len();
self.string_data()?.get(idx)?.ok().map(|s| s.0)
}
_ => None,
}
}
pub fn glyph_names(&self) -> GlyphNames<'a> {
let num_names = self.num_names() as u32;
let kind = match self.version() {
Version16Dot16::VERSION_1_0 => GlyphNameIterKind::V1(self.clone(), 0),
Version16Dot16::VERSION_2_0 => GlyphNameIterKind::V2 {
post: self.clone(),
idx: 0,
checkpoint_stride: (num_names as usize).div_ceil(NUM_CHECKPOINTS + 1).max(1),
checkpoints: [UNSET_CHECKPOINT; NUM_CHECKPOINTS],
last_actual_idx: None,
last_offset: 0,
strings_end: usize::MAX,
},
_ => GlyphNameIterKind::None,
};
GlyphNames { num_names, kind }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PString<'a>(&'a str);
impl<'a> PString<'a> {
pub fn as_str(&self) -> &'a str {
self.0
}
}
impl std::ops::Deref for PString<'_> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.0
}
}
impl PartialEq<&str> for PString<'_> {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
impl ReadArgs for PString<'_> {
type Args = ();
}
impl<'a> FontRead<'a> for PString<'a> {
fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
let len: u8 = data.read_at(0)?;
let pstring = data
.as_bytes()
.get(1..len as usize + 1)
.ok_or(ReadError::OutOfBounds)?;
if pstring.is_ascii() {
Ok(PString(std::str::from_utf8(pstring).unwrap()))
} else {
Err(ReadError::MalformedData("Must be valid ascii"))
}
}
}
impl VarSize for PString<'_> {
type Size = u8;
}
const NUM_CHECKPOINTS: usize = 16;
const UNSET_CHECKPOINT: u32 = u32::MAX;
fn checkpoint_slot(index: usize, stride: usize) -> Option<usize> {
(index % stride == 0)
.then(|| index / stride)
.filter(|slot| (1..=NUM_CHECKPOINTS).contains(slot))
.map(|slot| slot - 1)
}
#[derive(Clone)]
pub struct GlyphNames<'a> {
num_names: u32,
kind: GlyphNameIterKind<'a>,
}
#[derive(Clone)]
enum GlyphNameIterKind<'a> {
None,
V1(Post<'a>, u32),
V2 {
post: Post<'a>,
idx: u32,
checkpoint_stride: usize,
checkpoints: [u32; NUM_CHECKPOINTS],
last_actual_idx: Option<usize>,
last_offset: usize,
strings_end: usize,
},
}
impl<'a> Iterator for GlyphNames<'a> {
type Item = (GlyphId, &'a str);
fn next(&mut self) -> Option<Self::Item> {
loop {
let (gid, name) = self.next_entry()?;
if let Some(name) = name {
return Some((gid.into(), name));
}
}
}
}
impl<'a> GlyphNames<'a> {
fn next_entry(&mut self) -> Option<(GlyphId16, Option<&'a str>)> {
match &mut self.kind {
GlyphNameIterKind::None => None,
GlyphNameIterKind::V1(post, idx) => {
if *idx >= self.num_names {
return None;
}
let gid = GlyphId16::new(*idx as u16);
*idx += 1;
Some((gid, post.glyph_name(gid)))
}
GlyphNameIterKind::V2 {
post,
idx,
checkpoint_stride,
checkpoints,
last_actual_idx,
last_offset,
strings_end,
} => {
if *idx >= self.num_names {
return None;
}
let stride = *checkpoint_stride;
let gid = GlyphId16::new(*idx as u16);
let index = *idx as usize;
*idx += 1;
let mut name = || -> Option<&'a str> {
let mut actual_idx = post.glyph_name_index()?.get(index)?.get() as usize;
Some(if actual_idx < DEFAULT_GLYPH_NAMES.len() {
DEFAULT_GLYPH_NAMES.get(actual_idx).copied()?
} else {
actual_idx -= DEFAULT_GLYPH_NAMES.len();
if actual_idx >= *strings_end {
return None;
}
let string_data =
post.offset_data().slice(post.string_data_byte_range())?;
let target_slot = (actual_idx / stride).min(NUM_CHECKPOINTS);
let (mut scan_idx, mut offset) = {
let mut slot = target_slot;
while slot > 0 && checkpoints[slot - 1] == UNSET_CHECKPOINT {
slot -= 1;
}
if slot == 0 {
(0, 0)
} else {
(slot * stride, checkpoints[slot - 1] as usize)
}
};
if let Some(last_idx) = *last_actual_idx {
if last_idx <= actual_idx && last_idx > scan_idx {
scan_idx = last_idx;
offset = *last_offset;
}
}
while scan_idx < actual_idx {
let Some(next) = PString::read_len_at(string_data, offset)
.and_then(|len| offset.checked_add(len))
else {
*strings_end = scan_idx;
return None;
};
offset = next;
scan_idx += 1;
if let Some(slot) = checkpoint_slot(scan_idx, stride) {
checkpoints[slot] = u32::try_from(offset).ok()?;
}
}
if let Some(slot) = checkpoint_slot(actual_idx, stride) {
checkpoints[slot] = u32::try_from(offset).ok()?;
}
*last_actual_idx = Some(actual_idx);
*last_offset = offset;
PString::read(string_data.split_off(offset)?).ok()?.0
})
};
Some((gid, name()))
}
}
}
}
#[rustfmt::skip]
pub static DEFAULT_GLYPH_NAMES: [&str; 258] = [
".notdef", ".null", "nonmarkingreturn", "space", "exclam", "quotedbl", "numbersign", "dollar",
"percent", "ampersand", "quotesingle", "parenleft", "parenright", "asterisk", "plus", "comma",
"hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven",
"eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B",
"C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U",
"V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum",
"underscore", "grave", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n",
"o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright",
"asciitilde", "Adieresis", "Aring", "Ccedilla", "Eacute", "Ntilde", "Odieresis", "Udieresis",
"aacute", "agrave", "acircumflex", "adieresis", "atilde", "aring", "ccedilla", "eacute",
"egrave", "ecircumflex", "edieresis", "iacute", "igrave", "icircumflex", "idieresis", "ntilde",
"oacute", "ograve", "ocircumflex", "odieresis", "otilde", "uacute", "ugrave", "ucircumflex",
"udieresis", "dagger", "degree", "cent", "sterling", "section", "bullet", "paragraph",
"germandbls", "registered", "copyright", "trademark", "acute", "dieresis", "notequal", "AE",
"Oslash", "infinity", "plusminus", "lessequal", "greaterequal", "yen", "mu", "partialdiff",
"summation", "product", "pi", "integral", "ordfeminine", "ordmasculine", "Omega", "ae",
"oslash", "questiondown", "exclamdown", "logicalnot", "radical", "florin", "approxequal",
"Delta", "guillemotleft", "guillemotright", "ellipsis", "nonbreakingspace", "Agrave", "Atilde",
"Otilde", "OE", "oe", "endash", "emdash", "quotedblleft", "quotedblright", "quoteleft",
"quoteright", "divide", "lozenge", "ydieresis", "Ydieresis", "fraction", "currency",
"guilsinglleft", "guilsinglright", "fi", "fl", "daggerdbl", "periodcentered", "quotesinglbase",
"quotedblbase", "perthousand", "Acircumflex", "Ecircumflex", "Aacute", "Edieresis", "Egrave",
"Iacute", "Icircumflex", "Idieresis", "Igrave", "Oacute", "Ocircumflex", "apple", "Ograve",
"Uacute", "Ucircumflex", "Ugrave", "dotlessi", "circumflex", "tilde", "macron", "breve",
"dotaccent", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "Lslash", "lslash",
"Scaron", "scaron", "Zcaron", "zcaron", "brokenbar", "Eth", "eth", "Yacute", "yacute", "Thorn",
"thorn", "minus", "multiply", "onesuperior", "twosuperior", "threesuperior", "onehalf",
"onequarter", "threequarters", "franc", "Gbreve", "gbreve", "Idotaccent", "Scedilla",
"scedilla", "Cacute", "cacute", "Ccaron", "ccaron", "dcroat",
];
#[cfg(test)]
mod tests {
use super::*;
use font_test_data::{bebuffer::BeBuffer, post as test_data};
#[test]
fn test_post() {
let table = Post::read(test_data::SIMPLE.into()).unwrap();
assert_eq!(table.version(), Version16Dot16::VERSION_2_0);
assert_eq!(table.underline_position(), FWord::new(-75));
assert_eq!(table.glyph_name(GlyphId16::new(1)), Some(".notdef"));
assert_eq!(table.glyph_name(GlyphId16::new(2)), Some("space"));
assert_eq!(table.glyph_name(GlyphId16::new(7)), Some("hello"));
assert_eq!(table.glyph_name(GlyphId16::new(8)), Some("hi"));
assert_eq!(table.glyph_name(GlyphId16::new(9)), Some("hola"));
}
fn make_basic_post(version: Version16Dot16, include_num_glyphs: bool) -> BeBuffer {
let buf = BeBuffer::new()
.push(version)
.push(Fixed::from_i32(5))
.extend([FWord::new(6), FWord::new(7)]) .push(0u32) .extend([7u32, 8, 9, 10]); if include_num_glyphs {
buf.push(0u16)
} else {
buf
}
}
#[test]
fn parse_versioned_fields_v1() {
let buf = make_basic_post(Version16Dot16::VERSION_1_0, true);
let postv1 = Post::read(buf.data().into()).unwrap();
assert!(postv1.num_glyphs().is_none());
}
#[test]
fn parse_versioned_fields_v2() {
let buf = make_basic_post(Version16Dot16::VERSION_2_0, false);
let postv2 = Post::read(buf.data().into()).unwrap();
assert!(postv2.num_glyphs().is_none());
let buf = make_basic_post(Version16Dot16::VERSION_2_0, true);
let postv2 = Post::read(buf.data().into()).unwrap();
assert_eq!(postv2.num_glyphs(), Some(0));
}
#[test]
fn parse_versioned_fields_v3() {
let buf = make_basic_post(Version16Dot16::VERSION_3_0, true);
let postv3 = Post::read(buf.data().into()).unwrap();
assert!(postv3.num_glyphs().is_none());
}
#[test]
fn num_names_defaults_to_zero_without_num_glyphs() {
let buf = make_basic_post(Version16Dot16::VERSION_2_0, false);
let post = Post::read(buf.data().into()).unwrap();
assert_eq!(post.num_names(), 0);
}
#[test]
fn glyph_name_missing_string_data_returns_none() {
let buf = BeBuffer::new()
.push(Version16Dot16::VERSION_2_0)
.push(Fixed::from_i32(5))
.extend([FWord::new(6), FWord::new(7)])
.push(0u32)
.extend([7u32, 8, 9, 10])
.push(1u16)
.push(258u16);
let post = Post::read(buf.data().into()).unwrap();
assert_eq!(post.glyph_name(GlyphId16::new(0)), None);
}
#[test]
fn glyph_names_matches_naive_on_varied_synthetic_v2_data() {
let num_glyphs = 2_000u16;
let orders = [
test_data::GlyphNameOrder::Monotonic,
test_data::GlyphNameOrder::MostlyMonotonicWithBackrefs,
test_data::GlyphNameOrder::AllPointToLast,
];
for order in orders {
let (bytes, expected_custom_names) =
test_data::v2_with_varied_glyph_names(num_glyphs, 63, order);
let post = Post::read(bytes.as_slice().into()).unwrap();
let from_naive: Vec<_> = (0..num_glyphs)
.map(|gid| post.glyph_name(GlyphId16::new(gid)).unwrap())
.collect();
let from_iter: Vec<_> = post.glyph_names().map(|(_, name)| name).collect();
assert_eq!(from_iter, from_naive);
for (name, expected) in from_iter.iter().zip(expected_custom_names.iter()) {
assert_eq!(*name, expected.as_str());
}
}
}
#[test]
fn glyph_names_skip_a_name_that_cannot_be_read() {
let mut buf = BeBuffer::new()
.push(Version16Dot16::VERSION_2_0)
.push(Fixed::from_i32(0))
.extend([FWord::new(0), FWord::new(0)])
.push(0u32)
.extend([0u32, 0, 0, 0])
.push(3u16);
for index in [258u16, 258 + 9, 3] {
buf = buf.push(index);
}
buf = buf.push(2u8).extend(*b"ok");
let post = Post::read(buf.data().into()).unwrap();
assert_eq!(post.glyph_name(GlyphId16::new(1)), None);
let names: Vec<_> = post.glyph_names().collect();
assert_eq!(names, [(GlyphId::new(0), "ok"), (GlyphId::new(2), "space")]);
}
#[test]
fn glyph_names_past_the_string_data_stay_linear() {
let num_glyphs = u16::MAX;
let mut buf = BeBuffer::new()
.push(Version16Dot16::VERSION_2_0)
.push(Fixed::from_i32(0))
.extend([FWord::new(0), FWord::new(0)])
.push(0u32)
.extend([0u32, 0, 0, 0])
.push(num_glyphs);
for gid in 0..num_glyphs {
let index = if gid < 2 { 258 + gid } else { 258 + 60_000 };
buf = buf.push(index);
}
for _ in 0..3854 {
buf = buf.push(2u8).extend(*b"ab");
}
let post = Post::read(buf.data().into()).unwrap();
let start = std::time::Instant::now();
let names: Vec<_> = post.glyph_names().collect();
let elapsed = start.elapsed();
assert_eq!(names, [(GlyphId::new(0), "ab"), (GlyphId::new(1), "ab")]);
assert!(elapsed.as_millis() < 2000, "{elapsed:?}");
}
#[test]
fn glyph_names_matches_naive_past_the_last_checkpoint() {
let indices = [258u16 + 50, 258 + 30];
let mut buf = BeBuffer::new()
.push(Version16Dot16::VERSION_2_0)
.push(Fixed::from_i32(0))
.extend([FWord::new(0), FWord::new(0)])
.push(0u32)
.extend([0u32, 0, 0, 0])
.push(indices.len() as u16);
for index in indices {
buf = buf.push(index);
}
for i in 0..70u8 {
buf = buf.push(2u8).extend([b'a' + i / 26, b'a' + i % 26]);
}
let post = Post::read(buf.data().into()).unwrap();
let from_naive: Vec<_> = (0..indices.len() as u16)
.map(|gid| post.glyph_name(GlyphId16::new(gid)).unwrap())
.collect();
assert_eq!(from_naive, ["by", "be"]);
let from_iter: Vec<_> = post.glyph_names().map(|(_, name)| name).collect();
assert_eq!(from_iter, from_naive);
}
}