| 1 | //! A [PostScript Table]( |
| 2 | //! https://docs.microsoft.com/en-us/typography/opentype/spec/post) implementation. |
| 3 | |
| 4 | use crate::parser::{Fixed, LazyArray16, Stream}; |
| 5 | #[cfg (feature = "glyph-names" )] |
| 6 | use crate::GlyphId; |
| 7 | use crate::LineMetrics; |
| 8 | |
| 9 | const ITALIC_ANGLE_OFFSET: usize = 4; |
| 10 | const UNDERLINE_POSITION_OFFSET: usize = 8; |
| 11 | const UNDERLINE_THICKNESS_OFFSET: usize = 10; |
| 12 | const IS_FIXED_PITCH_OFFSET: usize = 12; |
| 13 | |
| 14 | // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html |
| 15 | /// A list of Macintosh glyph names. |
| 16 | #[cfg (feature = "glyph-names" )] |
| 17 | const MACINTOSH_NAMES: &[&str] = &[ |
| 18 | ".notdef" , |
| 19 | ".null" , |
| 20 | "nonmarkingreturn" , |
| 21 | "space" , |
| 22 | "exclam" , |
| 23 | "quotedbl" , |
| 24 | "numbersign" , |
| 25 | "dollar" , |
| 26 | "percent" , |
| 27 | "ampersand" , |
| 28 | "quotesingle" , |
| 29 | "parenleft" , |
| 30 | "parenright" , |
| 31 | "asterisk" , |
| 32 | "plus" , |
| 33 | "comma" , |
| 34 | "hyphen" , |
| 35 | "period" , |
| 36 | "slash" , |
| 37 | "zero" , |
| 38 | "one" , |
| 39 | "two" , |
| 40 | "three" , |
| 41 | "four" , |
| 42 | "five" , |
| 43 | "six" , |
| 44 | "seven" , |
| 45 | "eight" , |
| 46 | "nine" , |
| 47 | "colon" , |
| 48 | "semicolon" , |
| 49 | "less" , |
| 50 | "equal" , |
| 51 | "greater" , |
| 52 | "question" , |
| 53 | "at" , |
| 54 | "A" , |
| 55 | "B" , |
| 56 | "C" , |
| 57 | "D" , |
| 58 | "E" , |
| 59 | "F" , |
| 60 | "G" , |
| 61 | "H" , |
| 62 | "I" , |
| 63 | "J" , |
| 64 | "K" , |
| 65 | "L" , |
| 66 | "M" , |
| 67 | "N" , |
| 68 | "O" , |
| 69 | "P" , |
| 70 | "Q" , |
| 71 | "R" , |
| 72 | "S" , |
| 73 | "T" , |
| 74 | "U" , |
| 75 | "V" , |
| 76 | "W" , |
| 77 | "X" , |
| 78 | "Y" , |
| 79 | "Z" , |
| 80 | "bracketleft" , |
| 81 | "backslash" , |
| 82 | "bracketright" , |
| 83 | "asciicircum" , |
| 84 | "underscore" , |
| 85 | "grave" , |
| 86 | "a" , |
| 87 | "b" , |
| 88 | "c" , |
| 89 | "d" , |
| 90 | "e" , |
| 91 | "f" , |
| 92 | "g" , |
| 93 | "h" , |
| 94 | "i" , |
| 95 | "j" , |
| 96 | "k" , |
| 97 | "l" , |
| 98 | "m" , |
| 99 | "n" , |
| 100 | "o" , |
| 101 | "p" , |
| 102 | "q" , |
| 103 | "r" , |
| 104 | "s" , |
| 105 | "t" , |
| 106 | "u" , |
| 107 | "v" , |
| 108 | "w" , |
| 109 | "x" , |
| 110 | "y" , |
| 111 | "z" , |
| 112 | "braceleft" , |
| 113 | "bar" , |
| 114 | "braceright" , |
| 115 | "asciitilde" , |
| 116 | "Adieresis" , |
| 117 | "Aring" , |
| 118 | "Ccedilla" , |
| 119 | "Eacute" , |
| 120 | "Ntilde" , |
| 121 | "Odieresis" , |
| 122 | "Udieresis" , |
| 123 | "aacute" , |
| 124 | "agrave" , |
| 125 | "acircumflex" , |
| 126 | "adieresis" , |
| 127 | "atilde" , |
| 128 | "aring" , |
| 129 | "ccedilla" , |
| 130 | "eacute" , |
| 131 | "egrave" , |
| 132 | "ecircumflex" , |
| 133 | "edieresis" , |
| 134 | "iacute" , |
| 135 | "igrave" , |
| 136 | "icircumflex" , |
| 137 | "idieresis" , |
| 138 | "ntilde" , |
| 139 | "oacute" , |
| 140 | "ograve" , |
| 141 | "ocircumflex" , |
| 142 | "odieresis" , |
| 143 | "otilde" , |
| 144 | "uacute" , |
| 145 | "ugrave" , |
| 146 | "ucircumflex" , |
| 147 | "udieresis" , |
| 148 | "dagger" , |
| 149 | "degree" , |
| 150 | "cent" , |
| 151 | "sterling" , |
| 152 | "section" , |
| 153 | "bullet" , |
| 154 | "paragraph" , |
| 155 | "germandbls" , |
| 156 | "registered" , |
| 157 | "copyright" , |
| 158 | "trademark" , |
| 159 | "acute" , |
| 160 | "dieresis" , |
| 161 | "notequal" , |
| 162 | "AE" , |
| 163 | "Oslash" , |
| 164 | "infinity" , |
| 165 | "plusminus" , |
| 166 | "lessequal" , |
| 167 | "greaterequal" , |
| 168 | "yen" , |
| 169 | "mu" , |
| 170 | "partialdiff" , |
| 171 | "summation" , |
| 172 | "product" , |
| 173 | "pi" , |
| 174 | "integral" , |
| 175 | "ordfeminine" , |
| 176 | "ordmasculine" , |
| 177 | "Omega" , |
| 178 | "ae" , |
| 179 | "oslash" , |
| 180 | "questiondown" , |
| 181 | "exclamdown" , |
| 182 | "logicalnot" , |
| 183 | "radical" , |
| 184 | "florin" , |
| 185 | "approxequal" , |
| 186 | "Delta" , |
| 187 | "guillemotleft" , |
| 188 | "guillemotright" , |
| 189 | "ellipsis" , |
| 190 | "nonbreakingspace" , |
| 191 | "Agrave" , |
| 192 | "Atilde" , |
| 193 | "Otilde" , |
| 194 | "OE" , |
| 195 | "oe" , |
| 196 | "endash" , |
| 197 | "emdash" , |
| 198 | "quotedblleft" , |
| 199 | "quotedblright" , |
| 200 | "quoteleft" , |
| 201 | "quoteright" , |
| 202 | "divide" , |
| 203 | "lozenge" , |
| 204 | "ydieresis" , |
| 205 | "Ydieresis" , |
| 206 | "fraction" , |
| 207 | "currency" , |
| 208 | "guilsinglleft" , |
| 209 | "guilsinglright" , |
| 210 | "fi" , |
| 211 | "fl" , |
| 212 | "daggerdbl" , |
| 213 | "periodcentered" , |
| 214 | "quotesinglbase" , |
| 215 | "quotedblbase" , |
| 216 | "perthousand" , |
| 217 | "Acircumflex" , |
| 218 | "Ecircumflex" , |
| 219 | "Aacute" , |
| 220 | "Edieresis" , |
| 221 | "Egrave" , |
| 222 | "Iacute" , |
| 223 | "Icircumflex" , |
| 224 | "Idieresis" , |
| 225 | "Igrave" , |
| 226 | "Oacute" , |
| 227 | "Ocircumflex" , |
| 228 | "apple" , |
| 229 | "Ograve" , |
| 230 | "Uacute" , |
| 231 | "Ucircumflex" , |
| 232 | "Ugrave" , |
| 233 | "dotlessi" , |
| 234 | "circumflex" , |
| 235 | "tilde" , |
| 236 | "macron" , |
| 237 | "breve" , |
| 238 | "dotaccent" , |
| 239 | "ring" , |
| 240 | "cedilla" , |
| 241 | "hungarumlaut" , |
| 242 | "ogonek" , |
| 243 | "caron" , |
| 244 | "Lslash" , |
| 245 | "lslash" , |
| 246 | "Scaron" , |
| 247 | "scaron" , |
| 248 | "Zcaron" , |
| 249 | "zcaron" , |
| 250 | "brokenbar" , |
| 251 | "Eth" , |
| 252 | "eth" , |
| 253 | "Yacute" , |
| 254 | "yacute" , |
| 255 | "Thorn" , |
| 256 | "thorn" , |
| 257 | "minus" , |
| 258 | "multiply" , |
| 259 | "onesuperior" , |
| 260 | "twosuperior" , |
| 261 | "threesuperior" , |
| 262 | "onehalf" , |
| 263 | "onequarter" , |
| 264 | "threequarters" , |
| 265 | "franc" , |
| 266 | "Gbreve" , |
| 267 | "gbreve" , |
| 268 | "Idotaccent" , |
| 269 | "Scedilla" , |
| 270 | "scedilla" , |
| 271 | "Cacute" , |
| 272 | "cacute" , |
| 273 | "Ccaron" , |
| 274 | "ccaron" , |
| 275 | "dcroat" , |
| 276 | ]; |
| 277 | |
| 278 | /// An iterator over glyph names. |
| 279 | /// |
| 280 | /// The `post` table doesn't provide the glyph names count, |
| 281 | /// so we have to simply iterate over all of them to find it out. |
| 282 | #[derive (Clone, Copy, Default)] |
| 283 | pub struct Names<'a> { |
| 284 | data: &'a [u8], |
| 285 | offset: usize, |
| 286 | } |
| 287 | |
| 288 | impl core::fmt::Debug for Names<'_> { |
| 289 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
| 290 | write!(f, "Names {{ ... }}" ) |
| 291 | } |
| 292 | } |
| 293 | |
| 294 | impl<'a> Iterator for Names<'a> { |
| 295 | type Item = &'a str; |
| 296 | |
| 297 | fn next(&mut self) -> Option<Self::Item> { |
| 298 | // Glyph names are stored as Pascal Strings. |
| 299 | // Meaning u8 (len) + [u8] (data). |
| 300 | |
| 301 | if self.offset >= self.data.len() { |
| 302 | return None; |
| 303 | } |
| 304 | |
| 305 | let len: u8 = self.data[self.offset]; |
| 306 | self.offset += 1; |
| 307 | |
| 308 | // An empty name is an error. |
| 309 | if len == 0 { |
| 310 | return None; |
| 311 | } |
| 312 | |
| 313 | let name: &[u8] = self.data.get(self.offset..self.offset + usize::from(len))?; |
| 314 | self.offset += usize::from(len); |
| 315 | core::str::from_utf8(name).ok() |
| 316 | } |
| 317 | } |
| 318 | |
| 319 | /// A [PostScript Table](https://docs.microsoft.com/en-us/typography/opentype/spec/post). |
| 320 | #[derive (Clone, Copy, Debug)] |
| 321 | pub struct Table<'a> { |
| 322 | /// Italic angle in counter-clockwise degrees from the vertical. |
| 323 | pub italic_angle: f32, |
| 324 | /// Underline metrics. |
| 325 | pub underline_metrics: LineMetrics, |
| 326 | /// Flag that indicates that the font is monospaced. |
| 327 | pub is_monospaced: bool, |
| 328 | glyph_indexes: LazyArray16<'a, u16>, |
| 329 | names_data: &'a [u8], |
| 330 | } |
| 331 | |
| 332 | impl<'a> Table<'a> { |
| 333 | /// Parses a table from raw data. |
| 334 | pub fn parse(data: &'a [u8]) -> Option<Self> { |
| 335 | // Do not check the exact length, because some fonts include |
| 336 | // padding in table's length in table records, which is incorrect. |
| 337 | if data.len() < 32 { |
| 338 | return None; |
| 339 | } |
| 340 | |
| 341 | let version = Stream::new(data).read::<u32>()?; |
| 342 | if !(version == 0x00010000 |
| 343 | || version == 0x00020000 |
| 344 | || version == 0x00025000 |
| 345 | || version == 0x00030000 |
| 346 | || version == 0x00040000) |
| 347 | { |
| 348 | return None; |
| 349 | } |
| 350 | |
| 351 | let italic_angle = Stream::read_at::<Fixed>(data, ITALIC_ANGLE_OFFSET)?.0; |
| 352 | |
| 353 | let underline_metrics = LineMetrics { |
| 354 | position: Stream::read_at::<i16>(data, UNDERLINE_POSITION_OFFSET)?, |
| 355 | thickness: Stream::read_at::<i16>(data, UNDERLINE_THICKNESS_OFFSET)?, |
| 356 | }; |
| 357 | |
| 358 | let is_monospaced = Stream::read_at::<u32>(data, IS_FIXED_PITCH_OFFSET)? != 0; |
| 359 | |
| 360 | let mut names_data: &[u8] = &[]; |
| 361 | let mut glyph_indexes = LazyArray16::default(); |
| 362 | // Only version 2.0 of the table has data at the end. |
| 363 | if version == 0x00020000 { |
| 364 | let mut s = Stream::new_at(data, 32)?; |
| 365 | let indexes_count = s.read::<u16>()?; |
| 366 | glyph_indexes = s.read_array16::<u16>(indexes_count)?; |
| 367 | names_data = s.tail()?; |
| 368 | } |
| 369 | |
| 370 | Some(Table { |
| 371 | italic_angle, |
| 372 | underline_metrics, |
| 373 | is_monospaced, |
| 374 | names_data, |
| 375 | glyph_indexes, |
| 376 | }) |
| 377 | } |
| 378 | |
| 379 | /// Returns a glyph name by ID. |
| 380 | #[cfg (feature = "glyph-names" )] |
| 381 | pub fn glyph_name(&self, glyph_id: GlyphId) -> Option<&'a str> { |
| 382 | let mut index = self.glyph_indexes.get(glyph_id.0)?; |
| 383 | |
| 384 | // 'If the name index is between 0 and 257, treat the name index |
| 385 | // as a glyph index in the Macintosh standard order.' |
| 386 | if usize::from(index) < MACINTOSH_NAMES.len() { |
| 387 | Some(MACINTOSH_NAMES[usize::from(index)]) |
| 388 | } else { |
| 389 | // 'If the name index is between 258 and 65535, then subtract 258 and use that |
| 390 | // to index into the list of Pascal strings at the end of the table.' |
| 391 | index -= MACINTOSH_NAMES.len() as u16; |
| 392 | self.names().nth(usize::from(index)) |
| 393 | } |
| 394 | } |
| 395 | |
| 396 | /// Returns a glyph ID by a name. |
| 397 | #[cfg (feature = "glyph-names" )] |
| 398 | pub fn glyph_index_by_name(&self, name: &str) -> Option<GlyphId> { |
| 399 | let id = if let Some(index) = MACINTOSH_NAMES.iter().position(|n| *n == name) { |
| 400 | self.glyph_indexes |
| 401 | .into_iter() |
| 402 | .position(|i| usize::from(i) == index)? |
| 403 | } else { |
| 404 | let mut index = self.names().position(|n| n == name)?; |
| 405 | index += MACINTOSH_NAMES.len(); |
| 406 | self.glyph_indexes |
| 407 | .into_iter() |
| 408 | .position(|i| usize::from(i) == index)? |
| 409 | }; |
| 410 | |
| 411 | Some(GlyphId(id as u16)) |
| 412 | } |
| 413 | |
| 414 | /// Returns an iterator over glyph names. |
| 415 | /// |
| 416 | /// Default/predefined names are not included. Just the one in the font file. |
| 417 | pub fn names(&self) -> Names<'a> { |
| 418 | Names { |
| 419 | data: self.names_data, |
| 420 | offset: 0, |
| 421 | } |
| 422 | } |
| 423 | } |
| 424 | |