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, 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 #[cfg(feature = "experimental_traverse")]
126 pub(crate) fn better_type_name<T>() -> &'static str {
127 let raw_name = std::any::type_name::<T>();
128 raw_name.rsplit("::").next().unwrap_or(raw_name)
129 }
130
131 pub(crate) mod transforms {
133 pub fn subtract<T: TryInto<usize>, U: TryInto<usize>>(lhs: T, rhs: U) -> usize {
134 lhs.try_into()
135 .unwrap_or_default()
136 .saturating_sub(rhs.try_into().unwrap_or_default())
137 }
138
139 pub fn add<T: TryInto<usize>, U: TryInto<usize>>(lhs: T, rhs: U) -> usize {
140 lhs.try_into()
141 .unwrap_or_default()
142 .saturating_add(rhs.try_into().unwrap_or_default())
143 }
144
145 #[allow(dead_code)]
146 pub fn bitmap_len<T: TryInto<usize>>(count: T) -> usize {
147 count.try_into().unwrap_or_default().div_ceil(8)
148 }
149
150 #[cfg(feature = "ift")]
151 pub fn max_value_bitmap_len<T: TryInto<usize>>(count: T) -> usize {
152 let count: usize = count.try_into().unwrap_or_default() + 1usize;
153 count.div_ceil(8)
154 }
155
156 pub fn add_multiply<T: TryInto<usize>, U: TryInto<usize>, V: TryInto<usize>>(
157 a: T,
158 b: U,
159 c: V,
160 ) -> usize {
161 a.try_into()
162 .unwrap_or_default()
163 .saturating_add(b.try_into().unwrap_or_default())
164 .saturating_mul(c.try_into().unwrap_or_default())
165 }
166
167 #[cfg(feature = "ift")]
168 pub fn multiply_add<T: TryInto<usize>, U: TryInto<usize>, V: TryInto<usize>>(
169 a: T,
170 b: U,
171 c: V,
172 ) -> usize {
173 a.try_into()
174 .unwrap_or_default()
175 .saturating_mul(b.try_into().unwrap_or_default())
176 .saturating_add(c.try_into().unwrap_or_default())
177 }
178
179 pub fn half<T: TryInto<usize>>(val: T) -> usize {
180 val.try_into().unwrap_or_default() / 2
181 }
182
183 pub fn subtract_add_two<T: TryInto<usize>, U: TryInto<usize>>(lhs: T, rhs: U) -> usize {
184 lhs.try_into()
185 .unwrap_or_default()
186 .saturating_sub(rhs.try_into().unwrap_or_default())
187 .saturating_add(2)
188 }
189 }
190
191 #[macro_export]
192 macro_rules! basic_table_impls {
193 (impl_the_methods) => {
194 pub fn resolve_offset<O: Offset, R: FontRead<'a>>(
196 &self,
197 offset: O,
198 ) -> Result<R, ReadError> {
199 offset.resolve(self.data)
200 }
201
202 pub fn offset_data(&self) -> FontData<'a> {
206 self.data
207 }
208
209 #[deprecated(note = "just use the base type directly")]
215 pub fn shape(&self) -> &Self {
216 &self
217 }
218 };
219 }
220
221 pub(crate) use crate::basic_table_impls;
222}
223
224include!("../generated/font.rs");
225
226#[derive(Clone)]
227pub enum FileRef<'a> {
229 Font(FontRef<'a>),
231 Collection(CollectionRef<'a>),
233}
234
235impl<'a> FileRef<'a> {
236 pub fn new(data: &'a [u8]) -> Result<Self, ReadError> {
238 Ok(if let Ok(collection) = CollectionRef::new(data) {
239 Self::Collection(collection)
240 } else {
241 Self::Font(FontRef::new(data)?)
242 })
243 }
244
245 pub fn fonts(&self) -> impl Iterator<Item = Result<FontRef<'a>, ReadError>> + 'a + Clone {
247 let (iter_one, iter_two) = match self {
248 Self::Font(font) => (Some(Ok(font.clone())), None),
249 Self::Collection(collection) => (None, Some(collection.iter())),
250 };
251 iter_two.into_iter().flatten().chain(iter_one)
252 }
253}
254
255#[derive(Clone)]
257pub struct CollectionRef<'a> {
258 data: FontData<'a>,
259 header: TTCHeader<'a>,
260}
261
262impl<'a> CollectionRef<'a> {
263 pub fn new(data: &'a [u8]) -> Result<Self, ReadError> {
265 let data = FontData::new(data);
266 let header = TTCHeader::read(data)?;
267 if header.ttc_tag() != TTC_HEADER_TAG {
268 Err(ReadError::InvalidTtc(header.ttc_tag()))
269 } else {
270 Ok(Self { data, header })
271 }
272 }
273
274 pub fn len(&self) -> u32 {
276 self.header.table_directory_offsets().len() as u32
277 }
278
279 pub fn is_empty(&self) -> bool {
281 self.len() == 0
282 }
283
284 pub fn get(&self, index: u32) -> Result<FontRef<'a>, ReadError> {
286 let offset = self
287 .header
288 .table_directory_offsets()
289 .get(index as usize)
290 .ok_or(ReadError::InvalidCollectionIndex(index))?
291 .get() as usize;
292 let table_dir_data = self.data.slice(offset..).ok_or(ReadError::OutOfBounds)?;
293 FontRef::with_table_directory(
294 self.data,
295 TableDirectory::read(table_dir_data)?,
296 Some(index),
297 )
298 }
299
300 pub fn iter(&self) -> impl Iterator<Item = Result<FontRef<'a>, ReadError>> + 'a + Clone {
302 let copy = self.clone();
303 (0..self.len()).map(move |ix| copy.get(ix))
304 }
305}
306
307impl TableDirectory<'_> {
308 fn is_sorted(&self) -> bool {
309 let mut last_tag = Tag::new(&[0u8; 4]);
310
311 for tag in self.table_records().iter().map(|rec| rec.tag()) {
312 if tag <= last_tag {
313 return false;
314 }
315
316 last_tag = tag;
317 }
318
319 true
320 }
321}
322
323#[derive(Clone)]
328pub struct FontRef<'a> {
329 data: FontData<'a>,
330 pub table_directory: TableDirectory<'a>,
331 ttc_index: u32,
333 in_ttc: bool,
338 table_directory_sorted: bool,
342}
343
344impl<'a> FontRef<'a> {
345 pub fn new(data: &'a [u8]) -> Result<Self, ReadError> {
354 let data = FontData::new(data);
355 Self::with_table_directory(data, TableDirectory::read(data)?, None)
356 }
357
358 pub fn from_index(data: &'a [u8], index: u32) -> Result<Self, ReadError> {
370 let file = FileRef::new(data)?;
371 match file {
372 FileRef::Font(font) => {
373 if index == 0 {
374 Ok(font)
375 } else {
376 Err(ReadError::InvalidCollectionIndex(index))
377 }
378 }
379 FileRef::Collection(collection) => collection.get(index),
380 }
381 }
382
383 pub fn data(&self) -> FontData<'a> {
388 self.data
389 }
390
391 pub fn ttc_index(&self) -> Option<u32> {
394 self.in_ttc.then_some(self.ttc_index)
395 }
396
397 pub fn table_directory(&self) -> &TableDirectory<'a> {
399 &self.table_directory
400 }
401
402 pub fn table_data(&self, tag: Tag) -> Option<FontData<'a>> {
404 let entry = if self.table_directory_sorted {
405 self.table_directory
406 .table_records()
407 .binary_search_by(|rec| rec.tag.get().cmp(&tag))
408 .ok()
409 } else {
410 self.table_directory
411 .table_records()
412 .iter()
413 .position(|rec| rec.tag.get().eq(&tag))
414 };
415
416 entry
417 .and_then(|idx| self.table_directory.table_records().get(idx))
418 .and_then(|record| {
419 let start = Offset32::new(record.offset()).non_null()?;
420 let len = record.length() as usize;
421 self.data.slice(start..start.checked_add(len)?)
422 })
423 }
424
425 pub fn fonts(
428 data: &'a [u8],
429 ) -> impl Iterator<Item = Result<FontRef<'a>, ReadError>> + 'a + Clone {
430 let count = match FileRef::new(data) {
431 Ok(FileRef::Font(_)) => 1,
432 Ok(FileRef::Collection(ttc)) => ttc.len(),
433 _ => 0,
434 };
435 (0..count).map(|idx| FontRef::from_index(data, idx))
436 }
437
438 fn with_table_directory(
439 data: FontData<'a>,
440 table_directory: TableDirectory<'a>,
441 ttc_index: Option<u32>,
442 ) -> Result<Self, ReadError> {
443 if [TT_SFNT_VERSION, CFF_SFNT_VERSION, TRUE_SFNT_VERSION]
444 .contains(&table_directory.sfnt_version())
445 {
446 let table_directory_sorted = table_directory.is_sorted();
447
448 Ok(FontRef {
449 data,
450 table_directory,
451 ttc_index: ttc_index.unwrap_or_default(),
452 in_ttc: ttc_index.is_some(),
453 table_directory_sorted,
454 })
455 } else {
456 Err(ReadError::InvalidSfnt(table_directory.sfnt_version()))
457 }
458 }
459}
460
461impl<'a> TableProvider<'a> for FontRef<'a> {
462 fn data_for_tag(&self, tag: Tag) -> Option<FontData<'a>> {
463 self.table_data(tag)
464 }
465}
466
467#[cfg(test)]
468mod tests {
469 use font_test_data::{be_buffer, bebuffer::BeBuffer, ttc::TTC, AHEM};
470 use types::{Tag, TT_SFNT_VERSION};
471
472 use crate::{FileRef, FontRef};
473
474 #[test]
475 fn file_ref_non_collection() {
476 assert!(matches!(FileRef::new(AHEM), Ok(FileRef::Font(_))));
477 }
478
479 #[test]
480 fn file_ref_collection() {
481 let Ok(FileRef::Collection(collection)) = FileRef::new(TTC) else {
482 panic!("Expected a collection");
483 };
484 assert_eq!(2, collection.len());
485 assert!(!collection.is_empty());
486 }
487
488 #[test]
489 fn font_ref_fonts_iter() {
490 assert_eq!(FontRef::fonts(AHEM).count(), 1);
491 assert_eq!(FontRef::fonts(TTC).count(), 2);
492 assert_eq!(FontRef::fonts(b"NOT_A_FONT").count(), 0);
493 }
494
495 #[test]
496 fn ttc_index() {
497 for (idx, font) in FontRef::fonts(TTC).map(|font| font.unwrap()).enumerate() {
498 assert_eq!(font.ttc_index(), Some(idx as u32));
499 }
500 assert!(FontRef::new(AHEM).unwrap().ttc_index().is_none());
501 }
502
503 #[test]
504 fn unsorted_table_directory() {
505 let cff2_data = font_test_data::cff2::EXAMPLE;
506 let post_data = font_test_data::post::SIMPLE;
507 let gdef_data = [
508 font_test_data::gdef::GDEF_HEADER,
509 font_test_data::gdef::GLYPHCLASSDEF_TABLE,
510 ]
511 .concat();
512 let gpos_data = font_test_data::gpos::SINGLEPOSFORMAT1;
513
514 let font_data = be_buffer! {
515 TT_SFNT_VERSION,
516 4u16, 64u16, 2u16, 0u16, (Tag::new(b"post")),
522 0u32, 76u32, (post_data.len() as u32),
525
526 (Tag::new(b"GPOS")),
527 0u32, 108u32, (gpos_data.len() as u32),
530
531 (Tag::new(b"GDEF")),
532 0u32, 128u32, (gdef_data.len() as u32),
535
536 (Tag::new(b"CFF2")),
537 0u32, 160u32, (cff2_data.len() as u32)
540 };
541
542 let mut full_font = font_data.to_vec();
543
544 full_font.extend_from_slice(post_data);
545 full_font.extend_from_slice(gpos_data);
546 full_font.extend_from_slice(&gdef_data);
547 full_font.extend_from_slice(cff2_data);
548
549 let font = FontRef::new(&full_font).unwrap();
550
551 assert!(!font.table_directory_sorted);
552
553 assert!(font.table_data(Tag::new(b"CFF2")).is_some());
554 assert!(font.table_data(Tag::new(b"GDEF")).is_some());
555 assert!(font.table_data(Tag::new(b"GPOS")).is_some());
556 assert!(font.table_data(Tag::new(b"post")).is_some());
557 }
558}