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
72pub mod array;
73#[cfg(feature = "std")]
74pub mod collections;
75mod font_data;
76mod offset;
77mod offset_array;
78mod read;
79mod table_provider;
80mod table_ref;
81pub mod tables;
82#[cfg(feature = "experimental_traverse")]
83pub mod traversal;
84
85#[cfg(any(test, feature = "codegen_test"))]
86pub mod codegen_test;
87
88pub use font_data::FontData;
89pub use offset::{Offset, ResolveNullableOffset, ResolveOffset};
90pub use offset_array::{ArrayOfNullableOffsets, ArrayOfOffsets};
91pub use read::{ComputeSize, FontRead, FontReadWithArgs, ReadArgs, ReadError, VarSize};
92pub use table_provider::{TableProvider, TopLevelTable};
93pub use table_ref::MinByteRange;
94
95pub extern crate font_types as types;
97
98#[doc(hidden)]
100pub(crate) mod codegen_prelude {
101 pub use crate::array::{ComputedArray, VarLenArray};
102 pub use crate::font_data::{Cursor, FontData};
103 pub use crate::offset::{Offset, ResolveNullableOffset, ResolveOffset};
104 pub use crate::offset_array::{ArrayOfNullableOffsets, ArrayOfOffsets};
105 pub use crate::read::{
107 ComputeSize, FontRead, FontReadWithArgs, Format, ReadArgs, ReadError, VarSize,
108 };
109 pub use crate::table_provider::TopLevelTable;
110 pub use crate::table_ref::MinByteRange;
111 pub use std::ops::Range;
112
113 pub use types::*;
114
115 #[cfg(feature = "experimental_traverse")]
116 pub use crate::traversal::{self, Field, FieldType, RecordResolver, SomeRecord, SomeTable};
117
118 #[cfg(feature = "experimental_traverse")]
121 pub(crate) fn better_type_name<T>() -> &'static str {
122 let raw_name = std::any::type_name::<T>();
123 raw_name.rsplit("::").next().unwrap_or(raw_name)
124 }
125
126 pub(crate) mod transforms {
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 #[cfg(feature = "ift")]
146 pub fn max_value_bitmap_len<T: TryInto<usize>>(count: T) -> usize {
147 let count: usize = count.try_into().unwrap_or_default() + 1usize;
148 count.div_ceil(8)
149 }
150
151 pub fn add_multiply<T: TryInto<usize>, U: TryInto<usize>, V: TryInto<usize>>(
152 a: T,
153 b: U,
154 c: V,
155 ) -> usize {
156 a.try_into()
157 .unwrap_or_default()
158 .saturating_add(b.try_into().unwrap_or_default())
159 .saturating_mul(c.try_into().unwrap_or_default())
160 }
161
162 #[cfg(feature = "ift")]
163 pub fn multiply_add<T: TryInto<usize>, U: TryInto<usize>, V: TryInto<usize>>(
164 a: T,
165 b: U,
166 c: V,
167 ) -> usize {
168 a.try_into()
169 .unwrap_or_default()
170 .saturating_mul(b.try_into().unwrap_or_default())
171 .saturating_add(c.try_into().unwrap_or_default())
172 }
173
174 pub fn half<T: TryInto<usize>>(val: T) -> usize {
175 val.try_into().unwrap_or_default() / 2
176 }
177
178 pub fn subtract_add_two<T: TryInto<usize>, U: TryInto<usize>>(lhs: T, rhs: U) -> usize {
179 lhs.try_into()
180 .unwrap_or_default()
181 .saturating_sub(rhs.try_into().unwrap_or_default())
182 .saturating_add(2)
183 }
184 }
185
186 #[macro_export]
187 macro_rules! basic_table_impls {
188 (impl_the_methods) => {
189 pub fn resolve_offset<O: Offset, R: FontRead<'a>>(
191 &self,
192 offset: O,
193 ) -> Result<R, ReadError> {
194 offset.resolve(self.data)
195 }
196
197 pub fn offset_data(&self) -> FontData<'a> {
201 self.data
202 }
203
204 #[deprecated(note = "just use the base type directly")]
210 pub fn shape(&self) -> &Self {
211 &self
212 }
213 };
214 }
215
216 pub(crate) use crate::basic_table_impls;
217}
218
219include!("../generated/font.rs");
220
221#[derive(Clone)]
222pub enum FileRef<'a> {
224 Font(FontRef<'a>),
226 Collection(CollectionRef<'a>),
228}
229
230impl<'a> FileRef<'a> {
231 pub fn new(data: &'a [u8]) -> Result<Self, ReadError> {
233 Ok(if let Ok(collection) = CollectionRef::new(data) {
234 Self::Collection(collection)
235 } else {
236 Self::Font(FontRef::new(data)?)
237 })
238 }
239
240 pub fn fonts(&self) -> impl Iterator<Item = Result<FontRef<'a>, ReadError>> + 'a + Clone {
242 let (iter_one, iter_two) = match self {
243 Self::Font(font) => (Some(Ok(font.clone())), None),
244 Self::Collection(collection) => (None, Some(collection.iter())),
245 };
246 iter_two.into_iter().flatten().chain(iter_one)
247 }
248}
249
250#[derive(Clone)]
252pub struct CollectionRef<'a> {
253 data: FontData<'a>,
254 header: TTCHeader<'a>,
255}
256
257impl<'a> CollectionRef<'a> {
258 pub fn new(data: &'a [u8]) -> Result<Self, ReadError> {
260 let data = FontData::new(data);
261 let header = TTCHeader::read(data)?;
262 if header.ttc_tag() != TTC_HEADER_TAG {
263 Err(ReadError::InvalidTtc(header.ttc_tag()))
264 } else {
265 Ok(Self { data, header })
266 }
267 }
268
269 pub fn len(&self) -> u32 {
271 self.header.num_fonts()
272 }
273
274 pub fn is_empty(&self) -> bool {
276 self.len() == 0
277 }
278
279 pub fn get(&self, index: u32) -> Result<FontRef<'a>, ReadError> {
281 let offset = self
282 .header
283 .table_directory_offsets()
284 .get(index as usize)
285 .ok_or(ReadError::InvalidCollectionIndex(index))?
286 .get() as usize;
287 let table_dir_data = self.data.slice(offset..).ok_or(ReadError::OutOfBounds)?;
288 FontRef::with_table_directory(
289 self.data,
290 TableDirectory::read(table_dir_data)?,
291 Some(index),
292 )
293 }
294
295 pub fn iter(&self) -> impl Iterator<Item = Result<FontRef<'a>, ReadError>> + 'a + Clone {
297 let copy = self.clone();
298 (0..self.len()).map(move |ix| copy.get(ix))
299 }
300}
301
302impl TableDirectory<'_> {
303 fn is_sorted(&self) -> bool {
304 let mut last_tag = Tag::new(&[0u8; 4]);
305
306 for tag in self.table_records().iter().map(|rec| rec.tag()) {
307 if tag <= last_tag {
308 return false;
309 }
310
311 last_tag = tag;
312 }
313
314 true
315 }
316}
317
318#[derive(Clone)]
323pub struct FontRef<'a> {
324 data: FontData<'a>,
325 pub table_directory: TableDirectory<'a>,
326 ttc_index: u32,
328 in_ttc: bool,
333 table_directory_sorted: bool,
337}
338
339impl<'a> FontRef<'a> {
340 pub fn new(data: &'a [u8]) -> Result<Self, ReadError> {
349 let data = FontData::new(data);
350 Self::with_table_directory(data, TableDirectory::read(data)?, None)
351 }
352
353 pub fn from_index(data: &'a [u8], index: u32) -> Result<Self, ReadError> {
365 let file = FileRef::new(data)?;
366 match file {
367 FileRef::Font(font) => {
368 if index == 0 {
369 Ok(font)
370 } else {
371 Err(ReadError::InvalidCollectionIndex(index))
372 }
373 }
374 FileRef::Collection(collection) => collection.get(index),
375 }
376 }
377
378 pub fn data(&self) -> FontData<'a> {
383 self.data
384 }
385
386 pub fn ttc_index(&self) -> Option<u32> {
389 self.in_ttc.then_some(self.ttc_index)
390 }
391
392 pub fn table_directory(&self) -> &TableDirectory<'a> {
394 &self.table_directory
395 }
396
397 pub fn table_data(&self, tag: Tag) -> Option<FontData<'a>> {
399 let entry = if self.table_directory_sorted {
400 self.table_directory
401 .table_records()
402 .binary_search_by(|rec| rec.tag.get().cmp(&tag))
403 .ok()
404 } else {
405 self.table_directory
406 .table_records()
407 .iter()
408 .position(|rec| rec.tag.get().eq(&tag))
409 };
410
411 entry
412 .and_then(|idx| self.table_directory.table_records().get(idx))
413 .and_then(|record| {
414 let start = Offset32::new(record.offset()).non_null()?;
415 let len = record.length() as usize;
416 self.data.slice(start..start.checked_add(len)?)
417 })
418 }
419
420 pub fn fonts(
423 data: &'a [u8],
424 ) -> impl Iterator<Item = Result<FontRef<'a>, ReadError>> + 'a + Clone {
425 let count = match FileRef::new(data) {
426 Ok(FileRef::Font(_)) => 1,
427 Ok(FileRef::Collection(ttc)) => ttc.len(),
428 _ => 0,
429 };
430 (0..count).map(|idx| FontRef::from_index(data, idx))
431 }
432
433 fn with_table_directory(
434 data: FontData<'a>,
435 table_directory: TableDirectory<'a>,
436 ttc_index: Option<u32>,
437 ) -> Result<Self, ReadError> {
438 if [TT_SFNT_VERSION, CFF_SFNT_VERSION, TRUE_SFNT_VERSION]
439 .contains(&table_directory.sfnt_version())
440 {
441 let table_directory_sorted = table_directory.is_sorted();
442
443 Ok(FontRef {
444 data,
445 table_directory,
446 ttc_index: ttc_index.unwrap_or_default(),
447 in_ttc: ttc_index.is_some(),
448 table_directory_sorted,
449 })
450 } else {
451 Err(ReadError::InvalidSfnt(table_directory.sfnt_version()))
452 }
453 }
454}
455
456impl<'a> TableProvider<'a> for FontRef<'a> {
457 fn data_for_tag(&self, tag: Tag) -> Option<FontData<'a>> {
458 self.table_data(tag)
459 }
460}
461
462#[cfg(test)]
463mod tests {
464 use font_test_data::{be_buffer, bebuffer::BeBuffer, ttc::TTC, AHEM};
465 use types::{Tag, TT_SFNT_VERSION};
466
467 use crate::{FileRef, FontRef};
468
469 #[test]
470 fn file_ref_non_collection() {
471 assert!(matches!(FileRef::new(AHEM), Ok(FileRef::Font(_))));
472 }
473
474 #[test]
475 fn file_ref_collection() {
476 let Ok(FileRef::Collection(collection)) = FileRef::new(TTC) else {
477 panic!("Expected a collection");
478 };
479 assert_eq!(2, collection.len());
480 assert!(!collection.is_empty());
481 }
482
483 #[test]
484 fn font_ref_fonts_iter() {
485 assert_eq!(FontRef::fonts(AHEM).count(), 1);
486 assert_eq!(FontRef::fonts(TTC).count(), 2);
487 assert_eq!(FontRef::fonts(b"NOT_A_FONT").count(), 0);
488 }
489
490 #[test]
491 fn ttc_index() {
492 for (idx, font) in FontRef::fonts(TTC).map(|font| font.unwrap()).enumerate() {
493 assert_eq!(font.ttc_index(), Some(idx as u32));
494 }
495 assert!(FontRef::new(AHEM).unwrap().ttc_index().is_none());
496 }
497
498 #[test]
499 fn unsorted_table_directory() {
500 let cff2_data = font_test_data::cff2::EXAMPLE;
501 let post_data = font_test_data::post::SIMPLE;
502 let gdef_data = [
503 font_test_data::gdef::GDEF_HEADER,
504 font_test_data::gdef::GLYPHCLASSDEF_TABLE,
505 ]
506 .concat();
507 let gpos_data = font_test_data::gpos::SINGLEPOSFORMAT1;
508
509 let font_data = be_buffer! {
510 TT_SFNT_VERSION,
511 4u16, 64u16, 2u16, 0u16, (Tag::new(b"post")),
517 0u32, 76u32, (post_data.len() as u32),
520
521 (Tag::new(b"GPOS")),
522 0u32, 108u32, (gpos_data.len() as u32),
525
526 (Tag::new(b"GDEF")),
527 0u32, 128u32, (gdef_data.len() as u32),
530
531 (Tag::new(b"CFF2")),
532 0u32, 160u32, (cff2_data.len() as u32)
535 };
536
537 let mut full_font = font_data.to_vec();
538
539 full_font.extend_from_slice(post_data);
540 full_font.extend_from_slice(gpos_data);
541 full_font.extend_from_slice(&gdef_data);
542 full_font.extend_from_slice(cff2_data);
543
544 let font = FontRef::new(&full_font).unwrap();
545
546 assert!(!font.table_directory_sorted);
547
548 assert!(font.table_data(Tag::new(b"CFF2")).is_some());
549 assert!(font.table_data(Tag::new(b"GDEF")).is_some());
550 assert!(font.table_data(Tag::new(b"GPOS")).is_some());
551 assert!(font.table_data(Tag::new(b"post")).is_some());
552 }
553}