1#![cfg_attr(docsrs, feature(doc_cfg))]
60#![forbid(unsafe_code)]
61#![deny(rustdoc::broken_intra_doc_links)]
62#![cfg_attr(not(feature = "std"), no_std)]
63
64#[cfg(any(feature = "std", test))]
65#[macro_use]
66extern crate std;
67
68#[cfg(all(not(feature = "std"), not(test)))]
69#[macro_use]
70extern crate core as std;
71
72extern crate alloc;
75
76pub mod array;
77pub mod collections;
78mod font_data;
79pub mod model;
80mod offset;
81mod offset_array;
82pub mod ps;
83mod read;
84mod table_provider;
85mod table_ref;
86pub mod tables;
87#[cfg(feature = "experimental_traverse")]
88pub mod traversal;
89
90#[cfg(any(test, feature = "codegen_test"))]
91pub mod codegen_test;
92
93pub use font_data::FontData;
94pub use offset::{Offset, ResolveNullableOffset, ResolveOffset};
95pub use offset_array::{ArrayOfNullableOffsets, ArrayOfOffsets};
96pub use read::{ComputeSize, FontRead, ReadArgs, ReadError, VarSize};
97pub use table_provider::{TableProvider, TopLevelTable};
98pub use table_ref::MinByteRange;
99
100pub extern crate font_types as types;
102
103#[doc(hidden)]
105pub(crate) mod codegen_prelude {
106 pub use crate::array::{ComputedArray, VarLenArray};
107 pub use crate::font_data::{Cursor, FontData};
108 pub use crate::offset::{Offset, ResolveNullableOffset, ResolveOffset};
109 pub use crate::offset_array::{ArrayOfNullableOffsets, ArrayOfOffsets};
110 pub use crate::read::{
111 ComputeSize, Discriminant, FontRead, Format, ReadArgs, ReadError, VarSize,
112 };
113 pub use crate::table_provider::TopLevelTable;
114 pub use crate::table_ref::MinByteRange;
115 pub use std::ops::Range;
116
117 pub use types::*;
118
119 #[cfg(feature = "experimental_traverse")]
120 pub use crate::traversal::{self, Field, FieldType, RecordResolver, SomeRecord, SomeTable};
121
122 pub(crate) mod transforms {
124 pub fn to_usize<T: TryInto<usize>>(value: T) -> usize {
125 value.try_into().unwrap_or_default()
126 }
127
128 pub fn subtract<T: TryInto<usize>, U: TryInto<usize>>(lhs: T, rhs: U) -> usize {
129 lhs.try_into()
130 .unwrap_or_default()
131 .saturating_sub(rhs.try_into().unwrap_or_default())
132 }
133
134 pub fn add<T: TryInto<usize>, U: TryInto<usize>>(lhs: T, rhs: U) -> usize {
135 lhs.try_into()
136 .unwrap_or_default()
137 .saturating_add(rhs.try_into().unwrap_or_default())
138 }
139
140 #[allow(dead_code)]
141 pub fn bitmap_len<T: TryInto<usize>>(count: T) -> usize {
142 count.try_into().unwrap_or_default().div_ceil(8)
143 }
144
145 pub fn add_multiply<T: TryInto<usize>, U: TryInto<usize>, V: TryInto<usize>>(
146 a: T,
147 b: U,
148 c: V,
149 ) -> usize {
150 a.try_into()
151 .unwrap_or_default()
152 .saturating_add(b.try_into().unwrap_or_default())
153 .saturating_mul(c.try_into().unwrap_or_default())
154 }
155
156 #[cfg(feature = "ift")]
157 pub fn multiply_add<T: TryInto<usize>, U: TryInto<usize>, V: TryInto<usize>>(
158 a: T,
159 b: U,
160 c: V,
161 ) -> usize {
162 a.try_into()
163 .unwrap_or_default()
164 .saturating_mul(b.try_into().unwrap_or_default())
165 .saturating_add(c.try_into().unwrap_or_default())
166 }
167
168 pub fn half<T: TryInto<usize>>(val: T) -> usize {
169 val.try_into().unwrap_or_default() / 2
170 }
171
172 pub fn subtract_add_two<T: TryInto<usize>, U: TryInto<usize>>(lhs: T, rhs: U) -> usize {
173 lhs.try_into()
174 .unwrap_or_default()
175 .saturating_sub(rhs.try_into().unwrap_or_default())
176 .saturating_add(2)
177 }
178 }
179
180 #[macro_export]
181 macro_rules! basic_table_impls {
182 (impl_the_methods) => {
183 pub fn resolve_offset<O: Offset, R: FontRead<'a, Args = ()>>(
185 &self,
186 offset: O,
187 ) -> Result<R, ReadError> {
188 offset.resolve(self.data)
189 }
190
191 pub fn offset_data(&self) -> FontData<'a> {
195 self.data
196 }
197
198 #[deprecated(note = "just use the base type directly")]
204 pub fn shape(&self) -> &Self {
205 &self
206 }
207 };
208 }
209
210 pub(crate) use crate::basic_table_impls;
211}
212
213include!("../generated/font.rs");
214
215#[derive(Clone)]
216pub enum FileRef<'a> {
218 Font(FontRef<'a>),
220 Collection(CollectionRef<'a>),
222}
223
224impl<'a> FileRef<'a> {
225 pub fn new(data: &'a [u8]) -> Result<Self, ReadError> {
227 Ok(if let Ok(collection) = CollectionRef::new(data) {
228 Self::Collection(collection)
229 } else {
230 Self::Font(FontRef::new(data)?)
231 })
232 }
233
234 pub fn fonts(&self) -> impl Iterator<Item = Result<FontRef<'a>, ReadError>> + 'a + Clone {
236 let (iter_one, iter_two) = match self {
237 Self::Font(font) => (Some(Ok(font.clone())), None),
238 Self::Collection(collection) => (None, Some(collection.iter())),
239 };
240 iter_two.into_iter().flatten().chain(iter_one)
241 }
242}
243
244#[derive(Clone)]
246pub struct CollectionRef<'a> {
247 data: FontData<'a>,
248 header: TTCHeader<'a>,
249}
250
251impl<'a> CollectionRef<'a> {
252 pub fn new(data: &'a [u8]) -> Result<Self, ReadError> {
254 let data = FontData::new(data);
255 let header = TTCHeader::read(data)?;
256 if header.ttc_tag() != TTC_HEADER_TAG {
257 Err(ReadError::InvalidTtc(header.ttc_tag()))
258 } else {
259 Ok(Self { data, header })
260 }
261 }
262
263 pub fn len(&self) -> u32 {
265 self.header.table_directory_offsets().len() as u32
266 }
267
268 pub fn is_empty(&self) -> bool {
270 self.len() == 0
271 }
272
273 pub fn get(&self, index: u32) -> Result<FontRef<'a>, ReadError> {
275 let offset = self
276 .header
277 .table_directory_offsets()
278 .get(index as usize)
279 .ok_or(ReadError::InvalidCollectionIndex(index))?
280 .get() as usize;
281 let table_dir_data = self.data.slice(offset..).ok_or(ReadError::OutOfBounds)?;
282 FontRef::with_table_directory(
283 self.data,
284 TableDirectory::read(table_dir_data)?,
285 Some(index),
286 )
287 }
288
289 pub fn iter(&self) -> impl Iterator<Item = Result<FontRef<'a>, ReadError>> + 'a + Clone {
291 let copy = self.clone();
292 (0..self.len()).map(move |ix| copy.get(ix))
293 }
294}
295
296impl TableDirectory<'_> {
297 fn is_sorted(&self) -> bool {
298 let mut last_tag = Tag::new(&[0u8; 4]);
299
300 for tag in self.table_records().iter().map(|rec| rec.tag()) {
301 if tag <= last_tag {
302 return false;
303 }
304
305 last_tag = tag;
306 }
307
308 true
309 }
310}
311
312#[derive(Clone)]
317pub struct FontRef<'a> {
318 data: FontData<'a>,
319 pub table_directory: TableDirectory<'a>,
320 ttc_index: u32,
322 in_ttc: bool,
327 table_directory_sorted: bool,
331}
332
333impl<'a> FontRef<'a> {
334 pub fn new(data: &'a [u8]) -> Result<Self, ReadError> {
343 let data = FontData::new(data);
344 Self::with_table_directory(data, TableDirectory::read(data)?, None)
345 }
346
347 pub fn from_index(data: &'a [u8], index: u32) -> Result<Self, ReadError> {
359 let file = FileRef::new(data)?;
360 match file {
361 FileRef::Font(font) => {
362 if index == 0 {
363 Ok(font)
364 } else {
365 Err(ReadError::InvalidCollectionIndex(index))
366 }
367 }
368 FileRef::Collection(collection) => collection.get(index),
369 }
370 }
371
372 pub fn data(&self) -> FontData<'a> {
377 self.data
378 }
379
380 pub fn ttc_index(&self) -> Option<u32> {
383 self.in_ttc.then_some(self.ttc_index)
384 }
385
386 pub fn table_directory(&self) -> &TableDirectory<'a> {
388 &self.table_directory
389 }
390
391 pub fn table_data(&self, tag: Tag) -> Option<FontData<'a>> {
393 let entry = if self.table_directory_sorted {
394 self.table_directory
395 .table_records()
396 .binary_search_by(|rec| rec.tag.get().cmp(&tag))
397 .ok()
398 } else {
399 self.table_directory
400 .table_records()
401 .iter()
402 .position(|rec| rec.tag.get().eq(&tag))
403 };
404
405 entry
406 .and_then(|idx| self.table_directory.table_records().get(idx))
407 .and_then(|record| {
408 let start = Offset32::new(record.offset()).non_null()?;
409 let len = record.length() as usize;
410 self.data.slice(start..start.checked_add(len)?)
411 })
412 }
413
414 pub fn fonts(
417 data: &'a [u8],
418 ) -> impl Iterator<Item = Result<FontRef<'a>, ReadError>> + 'a + Clone {
419 let count = match FileRef::new(data) {
420 Ok(FileRef::Font(_)) => 1,
421 Ok(FileRef::Collection(ttc)) => ttc.len(),
422 _ => 0,
423 };
424 (0..count).map(|idx| FontRef::from_index(data, idx))
425 }
426
427 fn with_table_directory(
428 data: FontData<'a>,
429 table_directory: TableDirectory<'a>,
430 ttc_index: Option<u32>,
431 ) -> Result<Self, ReadError> {
432 if [TT_SFNT_VERSION, CFF_SFNT_VERSION, TRUE_SFNT_VERSION]
433 .contains(&table_directory.sfnt_version())
434 {
435 let table_directory_sorted = table_directory.is_sorted();
436
437 Ok(FontRef {
438 data,
439 table_directory,
440 ttc_index: ttc_index.unwrap_or_default(),
441 in_ttc: ttc_index.is_some(),
442 table_directory_sorted,
443 })
444 } else {
445 Err(ReadError::InvalidSfnt(table_directory.sfnt_version()))
446 }
447 }
448}
449
450impl<'a> TableProvider<'a> for FontRef<'a> {
451 fn data_for_tag(&self, tag: Tag) -> Option<FontData<'a>> {
452 self.table_data(tag)
453 }
454}
455
456#[cfg(test)]
457mod tests {
458 use font_test_data::{be_buffer, bebuffer::BeBuffer, ttc::TTC, AHEM};
459 use types::{Tag, TT_SFNT_VERSION};
460
461 use crate::{FileRef, FontRef};
462
463 #[test]
464 fn file_ref_non_collection() {
465 assert!(matches!(FileRef::new(AHEM), Ok(FileRef::Font(_))));
466 }
467
468 #[test]
469 fn file_ref_collection() {
470 let Ok(FileRef::Collection(collection)) = FileRef::new(TTC) else {
471 panic!("Expected a collection");
472 };
473 assert_eq!(2, collection.len());
474 assert!(!collection.is_empty());
475 }
476
477 #[test]
478 fn font_ref_fonts_iter() {
479 assert_eq!(FontRef::fonts(AHEM).count(), 1);
480 assert_eq!(FontRef::fonts(TTC).count(), 2);
481 assert_eq!(FontRef::fonts(b"NOT_A_FONT").count(), 0);
482 }
483
484 #[test]
485 fn ttc_index() {
486 for (idx, font) in FontRef::fonts(TTC).map(|font| font.unwrap()).enumerate() {
487 assert_eq!(font.ttc_index(), Some(idx as u32));
488 }
489 assert!(FontRef::new(AHEM).unwrap().ttc_index().is_none());
490 }
491
492 #[test]
493 fn unsorted_table_directory() {
494 let cff2_data = font_test_data::cff2::EXAMPLE;
495 let post_data = font_test_data::post::SIMPLE;
496 let gdef_data = [
497 font_test_data::gdef::GDEF_HEADER,
498 font_test_data::gdef::GLYPHCLASSDEF_TABLE,
499 ]
500 .concat();
501 let gpos_data = font_test_data::gpos::SINGLEPOSFORMAT1;
502
503 let font_data = be_buffer! {
504 TT_SFNT_VERSION,
505 4u16, 64u16, 2u16, 0u16, (Tag::new(b"post")),
511 0u32, 76u32, (post_data.len() as u32),
514
515 (Tag::new(b"GPOS")),
516 0u32, 108u32, (gpos_data.len() as u32),
519
520 (Tag::new(b"GDEF")),
521 0u32, 128u32, (gdef_data.len() as u32),
524
525 (Tag::new(b"CFF2")),
526 0u32, 160u32, (cff2_data.len() as u32)
529 };
530
531 let mut full_font = font_data.to_vec();
532
533 full_font.extend_from_slice(post_data);
534 full_font.extend_from_slice(gpos_data);
535 full_font.extend_from_slice(&gdef_data);
536 full_font.extend_from_slice(cff2_data);
537
538 let font = FontRef::new(&full_font).unwrap();
539
540 assert!(!font.table_directory_sorted);
541
542 assert!(font.table_data(Tag::new(b"CFF2")).is_some());
543 assert!(font.table_data(Tag::new(b"GDEF")).is_some());
544 assert!(font.table_data(Tag::new(b"GPOS")).is_some());
545 assert!(font.table_data(Tag::new(b"post")).is_some());
546 }
547}