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