1 /*!
2 A high-level, safe, zero-allocation TrueType font parser.
3 
4 ## Features
5 
6 - A high-level API.
7 - Zero allocations.
8 - Zero dependencies.
9 - `no_std` compatible.
10 - Fast.
11 - Stateless.
12 - Simple and maintainable code (no magic numbers).
13 
14 ## Supported TrueType features
15 
16 - (`cmap`) Character to glyph index mapping using [glyph_index()] method.
17   <br/>All subtable formats except Mixed Coverage (8) are supported.
18 - (`cmap`) Character variation to glyph index mapping using [glyph_variation_index()] method.
19 - (`glyf`) Glyph outlining using [outline_glyph()] method.
20 - (`hmtx`) Retrieving a glyph's horizontal metrics using [glyph_hor_metrics()] method.
21 - (`vmtx`) Retrieving a glyph's vertical metrics using [glyph_ver_metrics()] method.
22 - (`kern`) Retrieving a glyphs pair kerning using [glyphs_kerning()] method.
23 - (`maxp`) Retrieving a total number of glyphs using [number_of_glyphs()] method.
24 - (`name`) Listing all name records using [names()] method.
25 - (`name`) Retrieving a font's family name using [family_name()] method.
26 - (`name`) Retrieving a font's PostScript name using [post_script_name()] method.
27 - (`post`) Retrieving a font's underline metrics name using [underline_metrics()] method.
28 - (`head`) Retrieving a font's units per EM value using [units_per_em()] method.
29 - (`hhea`) Retrieving a generic font info using: [ascender()], [descender()], [height()]
30   and [line_gap()] methods.
31 
32 [glyph_index()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.glyph_index
33 [glyph_variation_index()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.glyph_variation_index
34 [outline_glyph()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.outline_glyph
35 [glyph_hor_metrics()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.glyph_hor_metrics
36 [glyph_ver_metrics()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.glyph_ver_metrics
37 [glyphs_kerning()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.glyphs_kerning
38 [number_of_glyphs()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.number_of_glyphs
39 [names()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.names
40 [family_name()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.family_name
41 [post_script_name()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.post_script_name
42 [underline_metrics()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.underline_metrics
43 [units_per_em()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.units_per_em
44 [ascender()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.ascender
45 [descender()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.descender
46 [height()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.height
47 [line_gap()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.line_gap
48 
49 ## Supported OpenType features
50 
51 - (`CFF `) Glyph outlining using [outline_glyph()] method.
52 - (`OS/2`) Retrieving a font kind using [is_regular()], [is_italic()],
53   [is_bold()] and [is_oblique()] methods.
54 - (`OS/2`) Retrieving a font's weight using [weight()] method.
55 - (`OS/2`) Retrieving a font's width using [width()] method.
56 - (`OS/2`) Retrieving a font's X height using [x_height()] method.
57 - (`OS/2`) Retrieving a font's strikeout metrics using [strikeout_metrics()] method.
58 - (`OS/2`) Retrieving a font's subscript metrics using [subscript_metrics()] method.
59 - (`OS/2`) Retrieving a font's superscript metrics using [superscript_metrics()] method.
60 
61 [is_regular()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.is_regular
62 [is_italic()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.is_italic
63 [is_bold()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.is_bold
64 [is_oblique()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.is_oblique
65 [weight()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.weight
66 [width()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.width
67 [x_height()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.x_height
68 [strikeout_metrics()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.strikeout_metrics
69 [subscript_metrics()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.subscript_metrics
70 [superscript_metrics()]: https://docs.rs/ttf-parser/0.2.0/ttf_parser/struct.Font.html#method.superscript_metrics
71 
72 ## Methods' computational complexity
73 
74 TrueType fonts designed for fast querying, so most of the methods are very fast.
75 The main exception is glyph outlining. Glyphs can be stored using two different methods:
76 using [Glyph Data](https://docs.microsoft.com/en-us/typography/opentype/spec/glyf) format
77 and [Compact Font Format](http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/font/pdfs/5176.CFF.pdf) (pdf).
78 The first one is fairly simple which makes it faster to process.
79 The second one is basically a tiny language with a stack-based VM, which makes it way harder to process.
80 Currently, it takes 60% more time to outline all glyphs in
81 *SourceSansPro-Regular.otf* (which uses CFF) rather than in *SourceSansPro-Regular.ttf*.
82 
83 ```text
84 test outline_cff  ... bench:   1,617,138 ns/iter (+/- 1,261)
85 test outline_glyf ... bench:     995,771 ns/iter (+/- 2,801)
86 ```
87 
88 Here is some methods benchmarks:
89 
90 ```text
91 test outline_glyph_276_from_cff  ... bench:       1,203 ns/iter (+/- 4)
92 test outline_glyph_276_from_glyf ... bench:         796 ns/iter (+/- 3)
93 test outline_glyph_8_from_cff    ... bench:         497 ns/iter (+/- 3)
94 test from_data_otf               ... bench:         372 ns/iter (+/- 5)
95 test outline_glyph_8_from_glyf   ... bench:         347 ns/iter (+/- 1)
96 test family_name                 ... bench:         269 ns/iter (+/- 3)
97 test from_data_ttf               ... bench:          72 ns/iter (+/- 3)
98 test glyph_index_u41             ... bench:          24 ns/iter (+/- 0)
99 test glyph_2_hor_metrics         ... bench:           8 ns/iter (+/- 0)
100 ```
101 
102 `family_name` is expensive, because it allocates a `String` and the original data
103 is stored as UTF-16 BE.
104 
105 Some methods are too fast, so we execute them **1000 times** to get better measurements.
106 
107 ```text
108 test x_height            ... bench:         847 ns/iter (+/- 0)
109 test units_per_em        ... bench:         564 ns/iter (+/- 2)
110 test strikeout_metrics   ... bench:         564 ns/iter (+/- 0)
111 test width               ... bench:         287 ns/iter (+/- 0)
112 test ascender            ... bench:         279 ns/iter (+/- 1)
113 test subscript_metrics   ... bench:         279 ns/iter (+/- 0)
114 ```
115 
116 ## Safety
117 
118 - The library must not panic. Any panic considered as a critical bug and should be reported.
119 - The library has a single `unsafe` block for array casting.
120 */
121 
122 #![doc(html_root_url = "https://docs.rs/ttf-parser/0.3.0")]
123 
124 #![no_std]
125 #![warn(missing_docs)]
126 #![warn(missing_copy_implementations)]
127 #![warn(missing_debug_implementations)]
128 
129 #[cfg(feature = "std")]
130 extern crate std;
131 
132 use core::fmt;
133 
134 mod cff;
135 mod cmap;
136 mod glyf;
137 mod head;
138 mod hhea;
139 mod hmtx;
140 mod kern;
141 mod loca;
142 mod name;
143 mod os2;
144 mod parser;
145 mod post;
146 mod raw;
147 mod vhea;
148 mod vmtx;
149 
150 use parser::{Stream, FromData, SafeStream, TrySlice, LazyArray};
151 pub use cff::CFFError;
152 pub use name::*;
153 pub use os2::*;
154 
155 
156 /// A type-safe wrapper for glyph ID.
157 #[derive(Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Debug)]
158 pub struct GlyphId(pub u16);
159 
160 impl FromData for GlyphId {
161     #[inline]
parse(data: &[u8]) -> Self162     fn parse(data: &[u8]) -> Self {
163         let mut s = SafeStream::new(data);
164         GlyphId(s.read())
165     }
166 }
167 
168 
169 /// A font parsing error.
170 #[derive(Clone, Copy, Debug)]
171 pub enum Error {
172     /// Not a TrueType data.
173     NotATrueType,
174 
175     /// The font index is out of bounds.
176     FontIndexOutOfBounds,
177 
178     /// One of the required tables is missing.
179     TableMissing(TableName),
180 
181     /// Table has an invalid size.
182     InvalidTableSize(TableName),
183 
184     /// Font doesn't have such glyph ID.
185     NoGlyph,
186 
187     /// Glyph doesn't have an outline.
188     NoOutline,
189 
190     /// An invalid glyph class.
191     InvalidGlyphClass(u16),
192 
193     /// No horizontal metrics for this glyph.
194     NoHorizontalMetrics,
195 
196     /// No vertical metrics for this glyph.
197     NoVerticalMetrics,
198 
199     /// No kerning for this glyph.
200     NoKerning,
201 
202     /// An unsupported table version.
203     UnsupportedTableVersion(TableName, u16),
204 
205     /// A CFF table parsing error.
206     CFFError(CFFError),
207 
208     /// An attempt to slice a raw data out of bounds.
209     ///
210     /// This may be caused by a bug in the library or by a malformed font.
211     #[allow(missing_docs)]
212     SliceOutOfBounds {
213         // u32 is enough, since fonts are usually times smaller.
214         start: u32,
215         end: u32,
216         data_len: u32,
217     },
218 }
219 
220 impl core::fmt::Display for Error {
fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result221     fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
222         match *self {
223             Error::NotATrueType => {
224                 write!(f, "not a TrueType font")
225             }
226             Error::FontIndexOutOfBounds => {
227                 write!(f, "font index is out of bounds")
228             }
229             Error::TableMissing(name) => {
230                 write!(f, "font doesn't have a {:?} table", name)
231             }
232             Error::InvalidTableSize(name) => {
233                 write!(f, "table {:?} has an invalid size", name)
234             }
235             Error::SliceOutOfBounds { start, end, data_len } => {
236                 write!(f, "an attempt to slice {}..{} on 0..{}", start, end, data_len)
237             }
238             Error::NoGlyph => {
239                 write!(f, "font doesn't have such glyph ID")
240             }
241             Error::NoOutline => {
242                 write!(f, "glyph has no outline")
243             }
244             Error::InvalidGlyphClass(n) => {
245                 write!(f, "{} is not a valid glyph class", n)
246             }
247             Error::NoHorizontalMetrics => {
248                 write!(f, "glyph has no horizontal metrics")
249             }
250             Error::NoVerticalMetrics => {
251                 write!(f, "glyph has no vertical metrics")
252             }
253             Error::NoKerning => {
254                 write!(f, "glyph has no kerning")
255             }
256             Error::UnsupportedTableVersion(name, version) => {
257                 write!(f, "table {:?} with version {} is not supported", name, version)
258             }
259             Error::CFFError(e) => {
260                 write!(f, "{:?} table parsing failed cause {}", TableName::CompactFontFormat, e)
261             }
262         }
263     }
264 }
265 
266 impl From<CFFError> for Error {
267     #[inline]
from(e: CFFError) -> Self268     fn from(e: CFFError) -> Self {
269         Error::CFFError(e)
270     }
271 }
272 
273 #[cfg(feature = "std")]
274 impl std::error::Error for Error {}
275 
276 pub(crate) type Result<T> = core::result::Result<T, Error>;
277 
278 
279 /// A line metrics.
280 ///
281 /// Used for underline and strikeout.
282 #[derive(Clone, Copy, PartialEq, Debug)]
283 pub struct LineMetrics {
284     /// Line position.
285     pub position: i16,
286 
287     /// Line thickness.
288     pub thickness: i16,
289 }
290 
291 
292 /// A horizontal metrics of a glyph.
293 #[derive(Clone, Copy, PartialEq, Debug)]
294 pub struct HorizontalMetrics {
295     /// A horizontal advance.
296     pub advance: u16,
297 
298     /// Left side bearing.
299     pub left_side_bearing: i16,
300 }
301 
302 
303 /// A vertical metrics of a glyph.
304 #[derive(Clone, Copy, PartialEq, Debug)]
305 pub struct VerticalMetrics {
306     /// A vertical advance.
307     pub advance: u16,
308 
309     /// Top side bearing.
310     pub top_side_bearing: i16,
311 }
312 
313 
314 /// Rectangle.
315 #[derive(Clone, Copy, PartialEq, Debug)]
316 #[allow(missing_docs)]
317 pub struct Rect {
318     pub x_min: i16,
319     pub y_min: i16,
320     pub x_max: i16,
321     pub y_max: i16,
322 }
323 
324 impl Rect {
325     #[inline]
zero() -> Self326     pub(crate) fn zero() -> Self {
327         Rect {
328             x_min: 0,
329             y_min: 0,
330             x_max: 0,
331             y_max: 0,
332         }
333     }
334 }
335 
336 
337 /// A trait for glyph outline construction.
338 pub trait OutlineBuilder {
339     /// Appends a MoveTo segment.
340     ///
341     /// Start of a contour.
move_to(&mut self, x: f32, y: f32)342     fn move_to(&mut self, x: f32, y: f32);
343 
344     /// Appends a LineTo segment.
line_to(&mut self, x: f32, y: f32)345     fn line_to(&mut self, x: f32, y: f32);
346 
347     /// Appends a QuadTo segment.
quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32)348     fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32);
349 
350     /// Appends a CurveTo segment.
curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32)351     fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32);
352 
353     /// Appends a ClosePath segment.
354     ///
355     /// End of a contour.
close(&mut self)356     fn close(&mut self);
357 }
358 
359 
360 /// A table name.
361 #[derive(Clone, Copy, PartialEq, Debug)]
362 #[allow(missing_docs)]
363 pub enum TableName {
364     CharacterToGlyphIndexMapping,
365     CompactFontFormat,
366     GlyphData,
367     Header,
368     HorizontalHeader,
369     HorizontalMetrics,
370     IndexToLocation,
371     Kerning,
372     MaximumProfile,
373     Naming,
374     PostScript,
375     VerticalHeader,
376     VerticalMetrics,
377     WindowsMetrics,
378 }
379 
380 
381 /// A font data handle.
382 #[derive(Clone)]
383 pub struct Font<'a> {
384     head: raw::head::Table<'a>,
385     hhea: raw::hhea::Table<'a>,
386     cff_: Option<&'a [u8]>,
387     cmap: Option<&'a [u8]>,
388     glyf: Option<&'a [u8]>,
389     hmtx: Option<&'a [u8]>,
390     kern: Option<&'a [u8]>,
391     loca: Option<&'a [u8]>,
392     name: Option<&'a [u8]>,
393     os_2: Option<&'a [u8]>,
394     os_2_v0: Option<raw::os_2::TableV0<'a>>,
395     post: Option<raw::post::Table<'a>>,
396     vhea: Option<raw::vhea::Table<'a>>,
397     vmtx: Option<&'a [u8]>,
398     number_of_glyphs: GlyphId,
399     cff_metadata: cff::Metadata,
400 }
401 
402 impl<'a> Font<'a> {
403     /// Creates a `Font` object from a raw data.
404     ///
405     /// You can set `index` in case of font collections.
406     /// For simple `ttf` fonts set `index` to 0.
407     ///
408     /// This function only parses font tables, so it's relatively light.
409     ///
410     /// Required tables: `head` and `hhea`.
from_data(data: &'a [u8], index: u32) -> Result<Self>411     pub fn from_data(data: &'a [u8], index: u32) -> Result<Self> {
412         let table_data = if let Some(n) = fonts_in_collection(data) {
413             if index < n {
414                 // https://docs.microsoft.com/en-us/typography/opentype/spec/otff#ttc-header
415                 const OFFSET_32_SIZE: usize = 4;
416                 let offset = raw::TTCHeader::SIZE + OFFSET_32_SIZE * index as usize;
417                 let font_offset: u32 = Stream::read_at(data, offset)?;
418                 data.try_slice(font_offset as usize .. data.len())?
419             } else {
420                 return Err(Error::FontIndexOutOfBounds);
421             }
422         } else {
423             data
424         };
425 
426         // https://docs.microsoft.com/en-us/typography/opentype/spec/otff#organization-of-an-opentype-font
427         const OFFSET_TABLE_SIZE: usize = 12;
428         if data.len() < OFFSET_TABLE_SIZE {
429             return Err(Error::NotATrueType);
430         }
431 
432         // https://docs.microsoft.com/en-us/typography/opentype/spec/otff#organization-of-an-opentype-font
433         const SFNT_VERSION_TRUE_TYPE: u32 = 0x00010000;
434         const SFNT_VERSION_OPEN_TYPE: u32 = 0x4F54544F;
435 
436         let mut s = Stream::new(table_data);
437 
438         let sfnt_version: u32 = s.read()?;
439         if sfnt_version != SFNT_VERSION_TRUE_TYPE && sfnt_version != SFNT_VERSION_OPEN_TYPE {
440             return Err(Error::NotATrueType);
441         }
442 
443         let num_tables: u16 = s.read()?;
444         s.skip_len(6u32); // searchRange (u16) + entrySelector (u16) + rangeShift (u16)
445         let tables: LazyArray<raw::TableRecord> = s.read_array(num_tables)?;
446 
447         let mut font = Font {
448             head: raw::head::Table::new(&[0; raw::head::Table::SIZE]),
449             hhea: raw::hhea::Table::new(&[0; raw::hhea::Table::SIZE]),
450             cff_: None,
451             cmap: None,
452             glyf: None,
453             hmtx: None,
454             kern: None,
455             loca: None,
456             name: None,
457             os_2: None,
458             os_2_v0: None,
459             post: None,
460             vhea: None,
461             vmtx: None,
462             number_of_glyphs: GlyphId(0),
463             cff_metadata: cff::Metadata::default(),
464         };
465 
466         let mut has_head = false;
467         let mut has_hhea = false;
468         for table in tables {
469             let offset = table.offset() as usize;
470             let length = table.length() as usize;
471             let range = offset..(offset + length);
472 
473             // It's way faster to compare `[u8; 4]` with `&[u8]`
474             // rather than `&[u8]` with `&[u8]`.
475             match &table.table_tag() {
476                 b"head" => {
477                     if length != raw::head::Table::SIZE {
478                         return Err(Error::InvalidTableSize(TableName::Header));
479                     }
480 
481                     font.head = raw::head::Table::new(data.try_slice(range)?);
482                     has_head = true;
483                 }
484                 b"hhea" => {
485                     if length != raw::hhea::Table::SIZE {
486                         return Err(Error::InvalidTableSize(TableName::HorizontalHeader));
487                     }
488 
489                     font.hhea = raw::hhea::Table::new(data.try_slice(range)?);
490                     has_hhea = true;
491                 }
492                 b"maxp" => {
493                     if length < raw::maxp::Table::SIZE {
494                         return Err(Error::InvalidTableSize(TableName::MaximumProfile));
495                     }
496 
497                     let data = &data[offset..(offset + raw::maxp::Table::SIZE)];
498                     let table = raw::maxp::Table::new(data);
499                     font.number_of_glyphs = GlyphId(table.num_glyphs());
500                 }
501                 b"OS/2" => {
502                     if length < raw::os_2::TableV0::SIZE {
503                         return Err(Error::InvalidTableSize(TableName::WindowsMetrics));
504                     }
505 
506                     if let Some(data) = data.get(range) {
507                         font.os_2 = Some(data);
508 
509                         let data = &data[0..raw::os_2::TableV0::SIZE];
510                         font.os_2_v0 = Some(raw::os_2::TableV0::new(data));
511                     }
512                 }
513                 b"post" => {
514                     if length < raw::post::Table::SIZE {
515                         return Err(Error::InvalidTableSize(TableName::PostScript));
516                     }
517 
518                     let data = data.try_slice(offset..(offset + raw::post::Table::SIZE))?;
519                     font.post = Some(raw::post::Table::new(data));
520                 }
521                 b"vhea" => {
522                     if length != raw::vhea::Table::SIZE {
523                         return Err(Error::InvalidTableSize(TableName::VerticalHeader));
524                     }
525 
526                     font.vhea = data.get(range).map(raw::vhea::Table::new);
527                 }
528                 b"CFF " => {
529                     if let Some(data) = data.get(range) {
530                         if let Ok(metadata) = cff::parse_metadata(data) {
531                             font.cff_ = Some(data);
532                             font.cff_metadata = metadata;
533                         }
534                     }
535                 }
536                 b"cmap" => font.cmap = data.get(range),
537                 b"glyf" => font.glyf = data.get(range),
538                 b"hmtx" => font.hmtx = data.get(range),
539                 b"kern" => font.kern = data.get(range),
540                 b"loca" => font.loca = data.get(range),
541                 b"name" => font.name = data.get(range),
542                 b"vmtx" => font.vmtx = data.get(range),
543                 _ => {}
544             }
545         }
546 
547         // Check for mandatory tables.
548         if !has_head {
549             return Err(Error::TableMissing(TableName::Header));
550         }
551 
552         if !has_hhea {
553             return Err(Error::TableMissing(TableName::HorizontalHeader));
554         }
555 
556         Ok(font)
557     }
558 
559     /// Checks that font has a specified table.
560     #[inline]
has_table(&self, name: TableName) -> bool561     pub fn has_table(&self, name: TableName) -> bool {
562         match name {
563             TableName::Header                       => true,
564             TableName::HorizontalHeader             => true,
565             TableName::MaximumProfile               => true,
566             TableName::CharacterToGlyphIndexMapping => self.cmap.is_some(),
567             TableName::CompactFontFormat            => self.cff_.is_some(),
568             TableName::GlyphData                    => self.glyf.is_some(),
569             TableName::HorizontalMetrics            => self.hmtx.is_some(),
570             TableName::IndexToLocation              => self.loca.is_some(),
571             TableName::Kerning                      => self.kern.is_some(),
572             TableName::Naming                       => self.name.is_some(),
573             TableName::PostScript                   => self.post.is_some(),
574             TableName::VerticalHeader               => self.vhea.is_some(),
575             TableName::VerticalMetrics              => self.vmtx.is_some(),
576             TableName::WindowsMetrics               => self.os_2.is_some(),
577         }
578     }
579 
580     /// Returns a total number of glyphs in the font.
581     ///
582     /// The value was already parsed, so this function doesn't involve any parsing.
583     #[inline]
number_of_glyphs(&self) -> u16584     pub fn number_of_glyphs(&self) -> u16 {
585         self.number_of_glyphs.0
586     }
587 
588     #[inline]
check_glyph_id(&self, glyph_id: GlyphId) -> Result<()>589     pub(crate) fn check_glyph_id(&self, glyph_id: GlyphId) -> Result<()> {
590         if glyph_id < self.number_of_glyphs {
591             Ok(())
592         } else {
593             Err(Error::NoGlyph)
594         }
595     }
596 
597     /// Outlines a glyph. Returns a tight glyph bounding box.
598     ///
599     /// This method support both `glyf` and `CFF` tables.
600     ///
601     /// # Example
602     ///
603     /// ```
604     /// use std::fmt::Write;
605     /// use ttf_parser;
606     ///
607     /// struct Builder(String);
608     ///
609     /// impl ttf_parser::OutlineBuilder for Builder {
610     ///     fn move_to(&mut self, x: f32, y: f32) {
611     ///         write!(&mut self.0, "M {} {} ", x, y).unwrap();
612     ///     }
613     ///
614     ///     fn line_to(&mut self, x: f32, y: f32) {
615     ///         write!(&mut self.0, "L {} {} ", x, y).unwrap();
616     ///     }
617     ///
618     ///     fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
619     ///         write!(&mut self.0, "Q {} {} {} {} ", x1, y1, x, y).unwrap();
620     ///     }
621     ///
622     ///     fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
623     ///         write!(&mut self.0, "C {} {} {} {} {} {} ", x1, y1, x2, y2, x, y).unwrap();
624     ///     }
625     ///
626     ///     fn close(&mut self) {
627     ///         write!(&mut self.0, "Z ").unwrap();
628     ///     }
629     /// }
630     ///
631     /// let data = std::fs::read("tests/fonts/glyphs.ttf").unwrap();
632     /// let font = ttf_parser::Font::from_data(&data, 0).unwrap();
633     /// let mut builder = Builder(String::new());
634     /// let glyph = font.outline_glyph(ttf_parser::GlyphId(0), &mut builder).unwrap();
635     /// assert_eq!(builder.0, "M 50 0 L 50 750 L 450 750 L 450 0 L 50 0 Z ");
636     /// ```
637     #[inline]
outline_glyph( &self, glyph_id: GlyphId, builder: &mut impl OutlineBuilder, ) -> Result<Rect>638     pub fn outline_glyph(
639         &self,
640         glyph_id: GlyphId,
641         builder: &mut impl OutlineBuilder,
642     ) -> Result<Rect> {
643         if self.glyf.is_some() {
644             self.glyf_glyph_outline(glyph_id, builder)
645         } else if self.cff_.is_some() {
646             self.cff_glyph_outline(glyph_id, builder)
647         } else {
648             Err(Error::NoGlyph)
649         }
650     }
651 }
652 
653 impl fmt::Debug for Font<'_> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result654     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
655         write!(f, "Font()")
656     }
657 }
658 
659 /// Returns a number of fonts stored in a TrueType font collection.
660 ///
661 /// Returns `None` if a provided data is not a TrueType font collection.
662 #[inline]
fonts_in_collection(data: &[u8]) -> Option<u32>663 pub fn fonts_in_collection(data: &[u8]) -> Option<u32> {
664     let table = raw::TTCHeader::new(data.get(0..raw::TTCHeader::SIZE)?);
665 
666     if &table.ttc_tag() != b"ttcf" {
667         return None;
668     }
669 
670     Some(table.num_fonts())
671 }
672