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, FontReadWithArgs, 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::{
112 ComputeSize, Discriminant, FontRead, FontReadWithArgs, Format, ReadArgs, ReadError, VarSize,
113 };
114 pub use crate::table_provider::TopLevelTable;
115 pub use crate::table_ref::MinByteRange;
116 pub use std::ops::Range;
117
118 pub use types::*;
119
120 #[cfg(feature = "experimental_traverse")]
121 pub use crate::traversal::{self, Field, FieldType, RecordResolver, SomeRecord, SomeTable};
122
123 pub(crate) mod transforms {
125 pub fn subtract<T: TryInto<usize>, U: TryInto<usize>>(lhs: T, rhs: U) -> usize {
126 lhs.try_into()
127 .unwrap_or_default()
128 .saturating_sub(rhs.try_into().unwrap_or_default())
129 }
130
131 pub fn add<T: TryInto<usize>, U: TryInto<usize>>(lhs: T, rhs: U) -> usize {
132 lhs.try_into()
133 .unwrap_or_default()
134 .saturating_add(rhs.try_into().unwrap_or_default())
135 }
136
137 #[allow(dead_code)]
138 pub fn bitmap_len<T: TryInto<usize>>(count: T) -> usize {
139 count.try_into().unwrap_or_default().div_ceil(8)
140 }
141
142 #[cfg(feature = "ift")]
143 pub fn max_value_bitmap_len<T: TryInto<usize>>(count: T) -> usize {
144 let count: usize = count.try_into().unwrap_or_default() + 1usize;
145 count.div_ceil(8)
146 }
147
148 pub fn add_multiply<T: TryInto<usize>, U: TryInto<usize>, V: TryInto<usize>>(
149 a: T,
150 b: U,
151 c: V,
152 ) -> usize {
153 a.try_into()
154 .unwrap_or_default()
155 .saturating_add(b.try_into().unwrap_or_default())
156 .saturating_mul(c.try_into().unwrap_or_default())
157 }
158
159 #[cfg(feature = "ift")]
160 pub fn multiply_add<T: TryInto<usize>, U: TryInto<usize>, V: TryInto<usize>>(
161 a: T,
162 b: U,
163 c: V,
164 ) -> usize {
165 a.try_into()
166 .unwrap_or_default()
167 .saturating_mul(b.try_into().unwrap_or_default())
168 .saturating_add(c.try_into().unwrap_or_default())
169 }
170
171 pub fn half<T: TryInto<usize>>(val: T) -> usize {
172 val.try_into().unwrap_or_default() / 2
173 }
174
175 pub fn subtract_add_two<T: TryInto<usize>, U: TryInto<usize>>(lhs: T, rhs: U) -> usize {
176 lhs.try_into()
177 .unwrap_or_default()
178 .saturating_sub(rhs.try_into().unwrap_or_default())
179 .saturating_add(2)
180 }
181 }
182
183 #[macro_export]
184 macro_rules! basic_table_impls {
185 (impl_the_methods) => {
186 pub fn resolve_offset<O: Offset, R: FontRead<'a>>(
188 &self,
189 offset: O,
190 ) -> Result<R, ReadError> {
191 offset.resolve(self.data)
192 }
193
194 pub fn offset_data(&self) -> FontData<'a> {
198 self.data
199 }
200
201 #[deprecated(note = "just use the base type directly")]
207 pub fn shape(&self) -> &Self {
208 &self
209 }
210 };
211 }
212
213 pub(crate) use crate::basic_table_impls;
214}
215
216include!("../generated/font.rs");
217
218#[derive(Clone)]
219pub enum FileRef<'a> {
221 Font(FontRef<'a>),
223 Collection(CollectionRef<'a>),
225}
226
227impl<'a> FileRef<'a> {
228 pub fn new(data: &'a [u8]) -> Result<Self, ReadError> {
230 Ok(if let Ok(collection) = CollectionRef::new(data) {
231 Self::Collection(collection)
232 } else {
233 Self::Font(FontRef::new(data)?)
234 })
235 }
236
237 pub fn fonts(&self) -> impl Iterator<Item = Result<FontRef<'a>, ReadError>> + 'a + Clone {
239 let (iter_one, iter_two) = match self {
240 Self::Font(font) => (Some(Ok(font.clone())), None),
241 Self::Collection(collection) => (None, Some(collection.iter())),
242 };
243 iter_two.into_iter().flatten().chain(iter_one)
244 }
245}
246
247#[derive(Clone)]
249pub struct CollectionRef<'a> {
250 data: FontData<'a>,
251 header: TTCHeader<'a>,
252}
253
254impl<'a> CollectionRef<'a> {
255 pub fn new(data: &'a [u8]) -> Result<Self, ReadError> {
257 let data = FontData::new(data);
258 let header = TTCHeader::read(data)?;
259 if header.ttc_tag() != TTC_HEADER_TAG {
260 Err(ReadError::InvalidTtc(header.ttc_tag()))
261 } else {
262 Ok(Self { data, header })
263 }
264 }
265
266 pub fn len(&self) -> u32 {
268 self.header.table_directory_offsets().len() as u32
269 }
270
271 pub fn is_empty(&self) -> bool {
273 self.len() == 0
274 }
275
276 pub fn get(&self, index: u32) -> Result<FontRef<'a>, ReadError> {
278 let offset = self
279 .header
280 .table_directory_offsets()
281 .get(index as usize)
282 .ok_or(ReadError::InvalidCollectionIndex(index))?
283 .get() as usize;
284 let table_dir_data = self.data.slice(offset..).ok_or(ReadError::OutOfBounds)?;
285 FontRef::with_table_directory(
286 self.data,
287 TableDirectory::read(table_dir_data)?,
288 Some(index),
289 )
290 }
291
292 pub fn iter(&self) -> impl Iterator<Item = Result<FontRef<'a>, ReadError>> + 'a + Clone {
294 let copy = self.clone();
295 (0..self.len()).map(move |ix| copy.get(ix))
296 }
297}
298
299impl TableDirectory<'_> {
300 fn is_sorted(&self) -> bool {
301 let mut last_tag = Tag::new(&[0u8; 4]);
302
303 for tag in self.table_records().iter().map(|rec| rec.tag()) {
304 if tag <= last_tag {
305 return false;
306 }
307
308 last_tag = tag;
309 }
310
311 true
312 }
313}
314
315#[derive(Clone)]
320pub struct FontRef<'a> {
321 data: FontData<'a>,
322 pub table_directory: TableDirectory<'a>,
323 ttc_index: u32,
325 in_ttc: bool,
330 table_directory_sorted: bool,
334}
335
336impl<'a> FontRef<'a> {
337 pub fn new(data: &'a [u8]) -> Result<Self, ReadError> {
346 let data = FontData::new(data);
347 Self::with_table_directory(data, TableDirectory::read(data)?, None)
348 }
349
350 pub fn from_index(data: &'a [u8], index: u32) -> Result<Self, ReadError> {
362 let file = FileRef::new(data)?;
363 match file {
364 FileRef::Font(font) => {
365 if index == 0 {
366 Ok(font)
367 } else {
368 Err(ReadError::InvalidCollectionIndex(index))
369 }
370 }
371 FileRef::Collection(collection) => collection.get(index),
372 }
373 }
374
375 pub fn data(&self) -> FontData<'a> {
380 self.data
381 }
382
383 pub fn ttc_index(&self) -> Option<u32> {
386 self.in_ttc.then_some(self.ttc_index)
387 }
388
389 pub fn table_directory(&self) -> &TableDirectory<'a> {
391 &self.table_directory
392 }
393
394 pub fn table_data(&self, tag: Tag) -> Option<FontData<'a>> {
396 let entry = if self.table_directory_sorted {
397 self.table_directory
398 .table_records()
399 .binary_search_by(|rec| rec.tag.get().cmp(&tag))
400 .ok()
401 } else {
402 self.table_directory
403 .table_records()
404 .iter()
405 .position(|rec| rec.tag.get().eq(&tag))
406 };
407
408 entry
409 .and_then(|idx| self.table_directory.table_records().get(idx))
410 .and_then(|record| {
411 let start = Offset32::new(record.offset()).non_null()?;
412 let len = record.length() as usize;
413 self.data.slice(start..start.checked_add(len)?)
414 })
415 }
416
417 pub fn fonts(
420 data: &'a [u8],
421 ) -> impl Iterator<Item = Result<FontRef<'a>, ReadError>> + 'a + Clone {
422 let count = match FileRef::new(data) {
423 Ok(FileRef::Font(_)) => 1,
424 Ok(FileRef::Collection(ttc)) => ttc.len(),
425 _ => 0,
426 };
427 (0..count).map(|idx| FontRef::from_index(data, idx))
428 }
429
430 fn with_table_directory(
431 data: FontData<'a>,
432 table_directory: TableDirectory<'a>,
433 ttc_index: Option<u32>,
434 ) -> Result<Self, ReadError> {
435 if [TT_SFNT_VERSION, CFF_SFNT_VERSION, TRUE_SFNT_VERSION]
436 .contains(&table_directory.sfnt_version())
437 {
438 let table_directory_sorted = table_directory.is_sorted();
439
440 Ok(FontRef {
441 data,
442 table_directory,
443 ttc_index: ttc_index.unwrap_or_default(),
444 in_ttc: ttc_index.is_some(),
445 table_directory_sorted,
446 })
447 } else {
448 Err(ReadError::InvalidSfnt(table_directory.sfnt_version()))
449 }
450 }
451}
452
453impl<'a> TableProvider<'a> for FontRef<'a> {
454 fn data_for_tag(&self, tag: Tag) -> Option<FontData<'a>> {
455 self.table_data(tag)
456 }
457}
458
459#[cfg(test)]
460mod tests {
461 use font_test_data::{be_buffer, bebuffer::BeBuffer, ttc::TTC, AHEM};
462 use types::{Tag, TT_SFNT_VERSION};
463
464 use crate::{FileRef, FontRef};
465
466 #[test]
467 fn file_ref_non_collection() {
468 assert!(matches!(FileRef::new(AHEM), Ok(FileRef::Font(_))));
469 }
470
471 #[test]
472 fn file_ref_collection() {
473 let Ok(FileRef::Collection(collection)) = FileRef::new(TTC) else {
474 panic!("Expected a collection");
475 };
476 assert_eq!(2, collection.len());
477 assert!(!collection.is_empty());
478 }
479
480 #[test]
481 fn font_ref_fonts_iter() {
482 assert_eq!(FontRef::fonts(AHEM).count(), 1);
483 assert_eq!(FontRef::fonts(TTC).count(), 2);
484 assert_eq!(FontRef::fonts(b"NOT_A_FONT").count(), 0);
485 }
486
487 #[test]
488 fn ttc_index() {
489 for (idx, font) in FontRef::fonts(TTC).map(|font| font.unwrap()).enumerate() {
490 assert_eq!(font.ttc_index(), Some(idx as u32));
491 }
492 assert!(FontRef::new(AHEM).unwrap().ttc_index().is_none());
493 }
494
495 #[test]
496 fn unsorted_table_directory() {
497 let cff2_data = font_test_data::cff2::EXAMPLE;
498 let post_data = font_test_data::post::SIMPLE;
499 let gdef_data = [
500 font_test_data::gdef::GDEF_HEADER,
501 font_test_data::gdef::GLYPHCLASSDEF_TABLE,
502 ]
503 .concat();
504 let gpos_data = font_test_data::gpos::SINGLEPOSFORMAT1;
505
506 let font_data = be_buffer! {
507 TT_SFNT_VERSION,
508 4u16, 64u16, 2u16, 0u16, (Tag::new(b"post")),
514 0u32, 76u32, (post_data.len() as u32),
517
518 (Tag::new(b"GPOS")),
519 0u32, 108u32, (gpos_data.len() as u32),
522
523 (Tag::new(b"GDEF")),
524 0u32, 128u32, (gdef_data.len() as u32),
527
528 (Tag::new(b"CFF2")),
529 0u32, 160u32, (cff2_data.len() as u32)
532 };
533
534 let mut full_font = font_data.to_vec();
535
536 full_font.extend_from_slice(post_data);
537 full_font.extend_from_slice(gpos_data);
538 full_font.extend_from_slice(&gdef_data);
539 full_font.extend_from_slice(cff2_data);
540
541 let font = FontRef::new(&full_font).unwrap();
542
543 assert!(!font.table_directory_sorted);
544
545 assert!(font.table_data(Tag::new(b"CFF2")).is_some());
546 assert!(font.table_data(Tag::new(b"GDEF")).is_some());
547 assert!(font.table_data(Tag::new(b"GPOS")).is_some());
548 assert!(font.table_data(Tag::new(b"post")).is_some());
549 }
550}