1#![cfg_attr(docsrs, feature(doc_auto_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, TableRef};
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, TableRef};
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 let last = raw_name.rsplit("::").next().unwrap_or(raw_name);
124 last.trim_end_matches("Marker>")
126 }
127
128 pub(crate) mod transforms {
130 pub fn subtract<T: TryInto<usize>, U: TryInto<usize>>(lhs: T, rhs: U) -> usize {
131 lhs.try_into()
132 .unwrap_or_default()
133 .saturating_sub(rhs.try_into().unwrap_or_default())
134 }
135
136 pub fn add<T: TryInto<usize>, U: TryInto<usize>>(lhs: T, rhs: U) -> usize {
137 lhs.try_into()
138 .unwrap_or_default()
139 .saturating_add(rhs.try_into().unwrap_or_default())
140 }
141
142 #[allow(dead_code)]
143 pub fn bitmap_len<T: TryInto<usize>>(count: T) -> usize {
144 count.try_into().unwrap_or_default().div_ceil(8)
145 }
146
147 #[cfg(feature = "ift")]
148 pub fn max_value_bitmap_len<T: TryInto<usize>>(count: T) -> usize {
149 let count: usize = count.try_into().unwrap_or_default() + 1usize;
150 count.div_ceil(8)
151 }
152
153 pub fn add_multiply<T: TryInto<usize>, U: TryInto<usize>, V: TryInto<usize>>(
154 a: T,
155 b: U,
156 c: V,
157 ) -> usize {
158 a.try_into()
159 .unwrap_or_default()
160 .saturating_add(b.try_into().unwrap_or_default())
161 .saturating_mul(c.try_into().unwrap_or_default())
162 }
163
164 #[cfg(feature = "ift")]
165 pub fn multiply_add<T: TryInto<usize>, U: TryInto<usize>, V: TryInto<usize>>(
166 a: T,
167 b: U,
168 c: V,
169 ) -> usize {
170 a.try_into()
171 .unwrap_or_default()
172 .saturating_mul(b.try_into().unwrap_or_default())
173 .saturating_add(c.try_into().unwrap_or_default())
174 }
175
176 pub fn half<T: TryInto<usize>>(val: T) -> usize {
177 val.try_into().unwrap_or_default() / 2
178 }
179
180 pub fn subtract_add_two<T: TryInto<usize>, U: TryInto<usize>>(lhs: T, rhs: U) -> usize {
181 lhs.try_into()
182 .unwrap_or_default()
183 .saturating_sub(rhs.try_into().unwrap_or_default())
184 .saturating_add(2)
185 }
186 }
187}
188
189include!("../generated/font.rs");
190
191#[derive(Clone)]
192pub enum FileRef<'a> {
194 Font(FontRef<'a>),
196 Collection(CollectionRef<'a>),
198}
199
200impl<'a> FileRef<'a> {
201 pub fn new(data: &'a [u8]) -> Result<Self, ReadError> {
203 Ok(if let Ok(collection) = CollectionRef::new(data) {
204 Self::Collection(collection)
205 } else {
206 Self::Font(FontRef::new(data)?)
207 })
208 }
209
210 pub fn fonts(&self) -> impl Iterator<Item = Result<FontRef<'a>, ReadError>> + 'a + Clone {
212 let (iter_one, iter_two) = match self {
213 Self::Font(font) => (Some(Ok(font.clone())), None),
214 Self::Collection(collection) => (None, Some(collection.iter())),
215 };
216 iter_two.into_iter().flatten().chain(iter_one)
217 }
218}
219
220#[derive(Clone)]
222pub struct CollectionRef<'a> {
223 data: FontData<'a>,
224 header: TTCHeader<'a>,
225}
226
227impl<'a> CollectionRef<'a> {
228 pub fn new(data: &'a [u8]) -> Result<Self, ReadError> {
230 let data = FontData::new(data);
231 let header = TTCHeader::read(data)?;
232 if header.ttc_tag() != TTC_HEADER_TAG {
233 Err(ReadError::InvalidTtc(header.ttc_tag()))
234 } else {
235 Ok(Self { data, header })
236 }
237 }
238
239 pub fn len(&self) -> u32 {
241 self.header.num_fonts()
242 }
243
244 pub fn is_empty(&self) -> bool {
246 self.len() == 0
247 }
248
249 pub fn get(&self, index: u32) -> Result<FontRef<'a>, ReadError> {
251 let offset = self
252 .header
253 .table_directory_offsets()
254 .get(index as usize)
255 .ok_or(ReadError::InvalidCollectionIndex(index))?
256 .get() as usize;
257 let table_dir_data = self.data.slice(offset..).ok_or(ReadError::OutOfBounds)?;
258 FontRef::with_table_directory(
259 self.data,
260 TableDirectory::read(table_dir_data)?,
261 Some(index),
262 )
263 }
264
265 pub fn iter(&self) -> impl Iterator<Item = Result<FontRef<'a>, ReadError>> + 'a + Clone {
267 let copy = self.clone();
268 (0..self.len()).map(move |ix| copy.get(ix))
269 }
270}
271
272impl TableDirectory<'_> {
273 fn is_sorted(&self) -> bool {
274 let mut last_tag = Tag::new(&[0u8; 4]);
275
276 for tag in self.table_records().iter().map(|rec| rec.tag()) {
277 if tag <= last_tag {
278 return false;
279 }
280
281 last_tag = tag;
282 }
283
284 true
285 }
286}
287
288#[derive(Clone)]
293pub struct FontRef<'a> {
294 data: FontData<'a>,
295 pub table_directory: TableDirectory<'a>,
296 ttc_index: u32,
298 in_ttc: bool,
303 table_directory_sorted: bool,
307}
308
309impl<'a> FontRef<'a> {
310 pub fn new(data: &'a [u8]) -> Result<Self, ReadError> {
319 let data = FontData::new(data);
320 Self::with_table_directory(data, TableDirectory::read(data)?, None)
321 }
322
323 pub fn from_index(data: &'a [u8], index: u32) -> Result<Self, ReadError> {
335 let file = FileRef::new(data)?;
336 match file {
337 FileRef::Font(font) => {
338 if index == 0 {
339 Ok(font)
340 } else {
341 Err(ReadError::InvalidCollectionIndex(index))
342 }
343 }
344 FileRef::Collection(collection) => collection.get(index),
345 }
346 }
347
348 pub fn data(&self) -> FontData<'a> {
353 self.data
354 }
355
356 pub fn ttc_index(&self) -> Option<u32> {
359 self.in_ttc.then_some(self.ttc_index)
360 }
361
362 pub fn table_directory(&self) -> &TableDirectory<'a> {
364 &self.table_directory
365 }
366
367 pub fn table_data(&self, tag: Tag) -> Option<FontData<'a>> {
369 let entry = if self.table_directory_sorted {
370 self.table_directory
371 .table_records()
372 .binary_search_by(|rec| rec.tag.get().cmp(&tag))
373 .ok()
374 } else {
375 self.table_directory
376 .table_records()
377 .iter()
378 .position(|rec| rec.tag.get().eq(&tag))
379 };
380
381 entry
382 .and_then(|idx| self.table_directory.table_records().get(idx))
383 .and_then(|record| {
384 let start = Offset32::new(record.offset()).non_null()?;
385 let len = record.length() as usize;
386 self.data.slice(start..start.checked_add(len)?)
387 })
388 }
389
390 pub fn fonts(
393 data: &'a [u8],
394 ) -> impl Iterator<Item = Result<FontRef<'a>, ReadError>> + 'a + Clone {
395 let count = match FileRef::new(data) {
396 Ok(FileRef::Font(_)) => 1,
397 Ok(FileRef::Collection(ttc)) => ttc.len(),
398 _ => 0,
399 };
400 (0..count).map(|idx| FontRef::from_index(data, idx))
401 }
402
403 fn with_table_directory(
404 data: FontData<'a>,
405 table_directory: TableDirectory<'a>,
406 ttc_index: Option<u32>,
407 ) -> Result<Self, ReadError> {
408 if [TT_SFNT_VERSION, CFF_SFNT_VERSION, TRUE_SFNT_VERSION]
409 .contains(&table_directory.sfnt_version())
410 {
411 let table_directory_sorted = table_directory.is_sorted();
412
413 Ok(FontRef {
414 data,
415 table_directory,
416 ttc_index: ttc_index.unwrap_or_default(),
417 in_ttc: ttc_index.is_some(),
418 table_directory_sorted,
419 })
420 } else {
421 Err(ReadError::InvalidSfnt(table_directory.sfnt_version()))
422 }
423 }
424}
425
426impl<'a> TableProvider<'a> for FontRef<'a> {
427 fn data_for_tag(&self, tag: Tag) -> Option<FontData<'a>> {
428 self.table_data(tag)
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use font_test_data::{be_buffer, bebuffer::BeBuffer, ttc::TTC, AHEM};
435 use types::{Tag, TT_SFNT_VERSION};
436
437 use crate::{FileRef, FontRef};
438
439 #[test]
440 fn file_ref_non_collection() {
441 assert!(matches!(FileRef::new(AHEM), Ok(FileRef::Font(_))));
442 }
443
444 #[test]
445 fn file_ref_collection() {
446 let Ok(FileRef::Collection(collection)) = FileRef::new(TTC) else {
447 panic!("Expected a collection");
448 };
449 assert_eq!(2, collection.len());
450 assert!(!collection.is_empty());
451 }
452
453 #[test]
454 fn font_ref_fonts_iter() {
455 assert_eq!(FontRef::fonts(AHEM).count(), 1);
456 assert_eq!(FontRef::fonts(TTC).count(), 2);
457 assert_eq!(FontRef::fonts(b"NOT_A_FONT").count(), 0);
458 }
459
460 #[test]
461 fn ttc_index() {
462 for (idx, font) in FontRef::fonts(TTC).map(|font| font.unwrap()).enumerate() {
463 assert_eq!(font.ttc_index(), Some(idx as u32));
464 }
465 assert!(FontRef::new(AHEM).unwrap().ttc_index().is_none());
466 }
467
468 #[test]
469 fn unsorted_table_directory() {
470 let cff2_data = font_test_data::cff2::EXAMPLE;
471 let post_data = font_test_data::post::SIMPLE;
472 let gdef_data = [
473 font_test_data::gdef::GDEF_HEADER,
474 font_test_data::gdef::GLYPHCLASSDEF_TABLE,
475 ]
476 .concat();
477 let gpos_data = font_test_data::gpos::SINGLEPOSFORMAT1;
478
479 let font_data = be_buffer! {
480 TT_SFNT_VERSION,
481 4u16, 64u16, 2u16, 0u16, (Tag::new(b"post")),
487 0u32, 76u32, (post_data.len() as u32),
490
491 (Tag::new(b"GPOS")),
492 0u32, 108u32, (gpos_data.len() as u32),
495
496 (Tag::new(b"GDEF")),
497 0u32, 128u32, (gdef_data.len() as u32),
500
501 (Tag::new(b"CFF2")),
502 0u32, 160u32, (cff2_data.len() as u32)
505 };
506
507 let mut full_font = font_data.to_vec();
508
509 full_font.extend_from_slice(post_data);
510 full_font.extend_from_slice(gpos_data);
511 full_font.extend_from_slice(&gdef_data);
512 full_font.extend_from_slice(cff2_data);
513
514 let font = FontRef::new(&full_font).unwrap();
515
516 assert!(!font.table_directory_sorted);
517
518 assert!(font.table_data(Tag::new(b"CFF2")).is_some());
519 assert!(font.table_data(Tag::new(b"GDEF")).is_some());
520 assert!(font.table_data(Tag::new(b"GPOS")).is_some());
521 assert!(font.table_data(Tag::new(b"post")).is_some());
522 }
523}