xref: /aosp_15_r20/external/cronet/third_party/rust/chromium_crates_io/vendor/read-fonts-0.15.6/src/lib.rs (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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