//! the [post (PostScript)](https://docs.microsoft.com/en-us/typography/opentype/spec/post#header) table include!("../../generated/generated_post.rs"); #[allow(clippy::needless_lifetimes)] // 'a is used with experimental_traverse feature below impl<'a> Post<'a> { /// The number of glyph names covered by this table 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<&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, } } //FIXME: how do we want to traverse this? I want to stop needing to // add special cases for things... #[cfg(feature = "experimental_traverse")] fn traverse_string_data(&self) -> FieldType<'a> { FieldType::I8(-42) // meaningless value } } /// A string in the post table. /// /// This is basically just a newtype that knows how to parse from a Pascal-style /// string. #[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<'a> FontRead<'a> for PString<'a> { fn read(data: FontData<'a>) -> Result { 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 { //FIXME not really sure how we want to handle this? Err(ReadError::MalformedData("Must be valid ascii")) } } } impl VarSize for PString<'_> { type Size = u8; } /// The 258 glyph names defined for Macintosh TrueType fonts #[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)]) //underline pos/thickness .push(0u32) // isFixedPitch .extend([7u32, 8, 9, 10]); // min/max mem x if include_num_glyphs { buf.push(0u16) } else { buf } } #[test] fn parse_versioned_fields_v1() { // v1, even if it has the extra field will not read it: 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(); // v2 will fail to read if data is missing assert!(postv2.num_glyphs().is_none()); // but read if data is present let buf = make_basic_post(Version16Dot16::VERSION_2_0, true); let postv2 = Post::read(buf.data().into()).unwrap(); // v2 will fail to read if data is missing assert_eq!(postv2.num_glyphs(), Some(0)); } #[test] fn parse_versioned_fields_v3() { // v3 will again not read since this field is not compatible 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(); // Just don't panic 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(); // Just don't panic assert_eq!(post.glyph_name(GlyphId16::new(0)), None); } }