1 //! Reading OpenType tables 2 //! 3 //! This crate provides memory safe zero-allocation parsing of font files. 4 //! It is unopinionated, and attempts to provide raw access to the underlying 5 //! font data as it is described in the [OpenType specification][spec]. 6 //! 7 //! This crate is intended for use by other parts of a font stack, such as a 8 //! shaping engine or a glyph rasterizer. 9 //! 10 //! In addition to raw data access, this crate may also provide reference 11 //! implementations of algorithms for interpreting that data, where such an 12 //! implementation is required for the data to be useful. For instance, we 13 //! provide functions for [mapping codepoints to glyph identifiers][cmap-impl] 14 //! using the `cmap` table, or for [decoding entries in the `name` table][NameString]. 15 //! 16 //! For higher level/more ergonomic access to font data, you may want to look 17 //! into using [`skrifa`] instead. 18 //! 19 //! ## Structure & codegen 20 //! 21 //! The root [`tables`] module contains a submodule for each supported 22 //! [table][table-directory], and that submodule contains items for each table, 23 //! record, flagset or enum described in the relevant portion of the spec. 24 //! 25 //! The majority of the code in the tables module is auto-generated. For more 26 //! information on our use of codegen, see the [codegen tour]. 27 //! 28 //! # Related projects 29 //! 30 //! - [`write-fonts`] is a companion crate for creating/modifying font files 31 //! - [`skrifa`] provides access to glyph outlines and metadata (in the same vein 32 //! as [freetype]) 33 //! 34 //! # Example 35 //! 36 //! ```no_run 37 //! # let path_to_my_font_file = std::path::Path::new(""); 38 //! use read_fonts::{FontRef, TableProvider}; 39 //! let font_bytes = std::fs::read(path_to_my_font_file).unwrap(); 40 //! // Single fonts only. for font collections (.ttc) use FontRef::from_index 41 //! let font = FontRef::new(&font_bytes).expect("failed to read font data"); 42 //! let head = font.head().expect("missing 'head' table"); 43 //! let maxp = font.maxp().expect("missing 'maxp' table"); 44 //! 45 //! println!("font version {} containing {} glyphs", head.font_revision(), maxp.num_glyphs()); 46 //! ``` 47 //! 48 //! 49 //! [spec]: https://learn.microsoft.com/en-us/typography/opentype/spec/ 50 //! [codegen-tour]: https://github.com/googlefonts/fontations/blob/main/docs/codegen-tour.md 51 //! [cmap-impl]: tables::cmap::Cmap::map_codepoint 52 //! [`write-fonts`]: https://docs.rs/write-fonts/ 53 //! [`skrifa`]: https://docs.rs/skrifa/ 54 //! [freetype]: http://freetype.org 55 //! [codegen tour]: https://github.com/googlefonts/fontations/blob/main/docs/codegen-tour.md 56 //! [NameString]: tables::name::NameString 57 //! [table-directory]: https://learn.microsoft.com/en-us/typography/opentype/spec/otff#table-directory 58 59 #![deny(rustdoc::broken_intra_doc_links)] 60 #![cfg_attr(not(feature = "std"), no_std)] 61 62 #[cfg(any(feature = "std", test))] 63 #[macro_use] 64 extern crate std; 65 66 #[cfg(all(not(feature = "std"), not(test)))] 67 #[macro_use] 68 extern crate core as std; 69 70 pub mod array; 71 mod font_data; 72 mod offset; 73 mod offset_array; 74 mod read; 75 mod table_provider; 76 mod table_ref; 77 pub mod tables; 78 #[cfg(feature = "traversal")] 79 pub mod traversal; 80 81 #[cfg(any(test, feature = "codegen_test"))] 82 pub mod codegen_test; 83 84 #[cfg(test)] 85 #[path = "tests/test_helpers.rs"] 86 mod test_helpers; 87 88 #[cfg(any(test, feature = "scaler_test"))] 89 pub mod scaler_test; 90 91 pub use font_data::FontData; 92 pub use offset::{Offset, ResolveNullableOffset, ResolveOffset}; 93 pub use offset_array::{ArrayOfNullableOffsets, ArrayOfOffsets}; 94 pub use read::{ComputeSize, FontRead, FontReadWithArgs, FromBytes, ReadArgs, ReadError, VarSize}; 95 pub use table_provider::{TableProvider, TopLevelTable}; 96 pub use table_ref::TableRef; 97 98 /// Public re-export of the font-types crate. 99 pub extern crate font_types as types; 100 101 /// All the types that may be referenced in auto-generated code. 102 #[doc(hidden)] 103 pub(crate) mod codegen_prelude { 104 pub use crate::array::{ComputedArray, VarLenArray}; 105 pub use crate::font_data::{Cursor, FontData}; 106 pub use crate::offset::{Offset, ResolveNullableOffset, ResolveOffset}; 107 pub use crate::offset_array::{ArrayOfNullableOffsets, ArrayOfOffsets}; 108 pub(crate) use crate::read::sealed; 109 pub use crate::read::{ 110 ComputeSize, FontRead, FontReadWithArgs, Format, FromBytes, ReadArgs, ReadError, VarSize, 111 }; 112 pub use crate::table_provider::TopLevelTable; 113 pub use crate::table_ref::TableRef; 114 pub use std::ops::Range; 115 116 pub use types::*; 117 118 #[cfg(feature = "traversal")] 119 pub use crate::traversal::{self, Field, FieldType, RecordResolver, SomeRecord, SomeTable}; 120 121 // used in generated traversal code to get type names of offset fields, which 122 // may include generics 123 #[cfg(feature = "traversal")] better_type_name<T>() -> &'static str124 pub(crate) fn better_type_name<T>() -> &'static str { 125 let raw_name = std::any::type_name::<T>(); 126 let last = raw_name.rsplit("::").next().unwrap_or(raw_name); 127 // this happens if we end up getting a type name like TableRef<'a, module::SomeMarker> 128 last.trim_end_matches("Marker>") 129 } 130 131 /// named transforms used in 'count', e.g 132 pub(crate) mod transforms { subtract<T: TryInto<usize>, U: TryInto<usize>>(lhs: T, rhs: U) -> usize133 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 add<T: TryInto<usize>, U: TryInto<usize>>(lhs: T, rhs: U) -> usize139 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 add_multiply<T: TryInto<usize>, U: TryInto<usize>, V: TryInto<usize>>( a: T, b: U, c: V, ) -> usize145 pub fn add_multiply<T: TryInto<usize>, U: TryInto<usize>, V: TryInto<usize>>( 146 a: T, 147 b: U, 148 c: V, 149 ) -> usize { 150 a.try_into() 151 .unwrap_or_default() 152 .saturating_add(b.try_into().unwrap_or_default()) 153 .saturating_mul(c.try_into().unwrap_or_default()) 154 } 155 half<T: TryInto<usize>>(val: T) -> usize156 pub fn half<T: TryInto<usize>>(val: T) -> usize { 157 val.try_into().unwrap_or_default() / 2 158 } 159 } 160 } 161 162 include!("../generated/font.rs"); 163 164 #[derive(Clone)] 165 /// Reference to the content of a font or font collection file. 166 pub enum FileRef<'a> { 167 /// A single font. 168 Font(FontRef<'a>), 169 /// A collection of fonts. 170 Collection(CollectionRef<'a>), 171 } 172 173 impl<'a> FileRef<'a> { 174 /// Creates a new reference to a file representing a font or font collection. new(data: &'a [u8]) -> Result<Self, ReadError>175 pub fn new(data: &'a [u8]) -> Result<Self, ReadError> { 176 Ok(if let Ok(collection) = CollectionRef::new(data) { 177 Self::Collection(collection) 178 } else { 179 Self::Font(FontRef::new(data)?) 180 }) 181 } 182 183 /// Returns an iterator over the fonts contained in the file. fonts(&self) -> impl Iterator<Item = Result<FontRef<'a>, ReadError>> + 'a + Clone184 pub fn fonts(&self) -> impl Iterator<Item = Result<FontRef<'a>, ReadError>> + 'a + Clone { 185 let (iter_one, iter_two) = match self { 186 Self::Font(font) => (Some(Ok(font.clone())), None), 187 Self::Collection(collection) => (None, Some(collection.iter())), 188 }; 189 iter_two.into_iter().flatten().chain(iter_one) 190 } 191 } 192 193 /// Reference to the content of a font collection file. 194 #[derive(Clone)] 195 pub struct CollectionRef<'a> { 196 data: FontData<'a>, 197 header: TTCHeader<'a>, 198 } 199 200 impl<'a> CollectionRef<'a> { 201 /// Creates a new reference to a font collection. new(data: &'a [u8]) -> Result<Self, ReadError>202 pub fn new(data: &'a [u8]) -> Result<Self, ReadError> { 203 let data = FontData::new(data); 204 let header = TTCHeader::read(data)?; 205 if header.ttc_tag() != TTC_HEADER_TAG { 206 Err(ReadError::InvalidTtc(header.ttc_tag())) 207 } else { 208 Ok(Self { data, header }) 209 } 210 } 211 212 /// Returns the number of fonts in the collection. len(&self) -> u32213 pub fn len(&self) -> u32 { 214 self.header.num_fonts() 215 } 216 217 /// Returns true if the collection is empty. is_empty(&self) -> bool218 pub fn is_empty(&self) -> bool { 219 self.len() == 0 220 } 221 222 /// Returns the font in the collection at the specified index. get(&self, index: u32) -> Result<FontRef<'a>, ReadError>223 pub fn get(&self, index: u32) -> Result<FontRef<'a>, ReadError> { 224 let offset = self 225 .header 226 .table_directory_offsets() 227 .get(index as usize) 228 .ok_or(ReadError::InvalidCollectionIndex(index))? 229 .get() as usize; 230 let table_dir_data = self.data.slice(offset..).ok_or(ReadError::OutOfBounds)?; 231 FontRef::with_table_directory(self.data, TableDirectory::read(table_dir_data)?) 232 } 233 234 /// Returns an iterator over the fonts in the collection. iter(&self) -> impl Iterator<Item = Result<FontRef<'a>, ReadError>> + 'a + Clone235 pub fn iter(&self) -> impl Iterator<Item = Result<FontRef<'a>, ReadError>> + 'a + Clone { 236 let copy = self.clone(); 237 (0..self.len()).map(move |ix| copy.get(ix)) 238 } 239 } 240 241 /// Reference to an in-memory font. 242 /// 243 /// This is a simple implementation of the [`TableProvider`] trait backed 244 /// by a borrowed slice containing font data. 245 #[derive(Clone)] 246 pub struct FontRef<'a> { 247 data: FontData<'a>, 248 pub table_directory: TableDirectory<'a>, 249 } 250 251 impl<'a> FontRef<'a> { 252 /// Creates a new reference to an in-memory font backed by the given data. 253 /// 254 /// The data must be a single font (not a font collection) and must begin with a 255 /// [table directory] to be considered valid. 256 /// 257 /// To load a font from a font collection, use [`FontRef::from_index`] instead. 258 /// 259 /// [table directory]: https://github.com/googlefonts/fontations/pull/549 new(data: &'a [u8]) -> Result<Self, ReadError>260 pub fn new(data: &'a [u8]) -> Result<Self, ReadError> { 261 let data = FontData::new(data); 262 Self::with_table_directory(data, TableDirectory::read(data)?) 263 } 264 265 /// Creates a new reference to an in-memory font at the specified index 266 /// backed by the given data. 267 /// 268 /// The data slice must begin with either a 269 /// [table directory](https://learn.microsoft.com/en-us/typography/opentype/spec/otff#table-directory) 270 /// or a [ttc header](https://learn.microsoft.com/en-us/typography/opentype/spec/otff#ttc-header) 271 /// to be considered valid. 272 /// 273 /// In other words, this accepts either font collection (ttc) or single 274 /// font (ttf/otf) files. If a single font file is provided, the index 275 /// parameter must be 0. from_index(data: &'a [u8], index: u32) -> Result<Self, ReadError>276 pub fn from_index(data: &'a [u8], index: u32) -> Result<Self, ReadError> { 277 let file = FileRef::new(data)?; 278 match file { 279 FileRef::Font(font) => { 280 if index == 0 { 281 Ok(font) 282 } else { 283 Err(ReadError::InvalidCollectionIndex(index)) 284 } 285 } 286 FileRef::Collection(collection) => collection.get(index), 287 } 288 } 289 290 /// Returns the data for the table with the specified tag, if present. table_data(&self, tag: Tag) -> Option<FontData<'a>>291 pub fn table_data(&self, tag: Tag) -> Option<FontData<'a>> { 292 self.table_directory 293 .table_records() 294 .binary_search_by(|rec| rec.tag.get().cmp(&tag)) 295 .ok() 296 .and_then(|idx| self.table_directory.table_records().get(idx)) 297 .and_then(|record| { 298 let start = Offset32::new(record.offset()).non_null()?; 299 let len = record.length() as usize; 300 self.data.slice(start..start + len) 301 }) 302 } 303 with_table_directory( data: FontData<'a>, table_directory: TableDirectory<'a>, ) -> Result<Self, ReadError>304 fn with_table_directory( 305 data: FontData<'a>, 306 table_directory: TableDirectory<'a>, 307 ) -> Result<Self, ReadError> { 308 if [TT_SFNT_VERSION, CFF_SFTN_VERSION].contains(&table_directory.sfnt_version()) { 309 Ok(FontRef { 310 data, 311 table_directory, 312 }) 313 } else { 314 Err(ReadError::InvalidSfnt(table_directory.sfnt_version())) 315 } 316 } 317 } 318 319 impl<'a> TableProvider<'a> for FontRef<'a> { data_for_tag(&self, tag: Tag) -> Option<FontData<'a>>320 fn data_for_tag(&self, tag: Tag) -> Option<FontData<'a>> { 321 self.table_data(tag) 322 } 323 } 324