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