accessibility_tree/fonts/
mod.rs1mod cmap;
2mod parsing;
3mod tables;
4mod types;
5
6use crate::fonts::cmap::Cmap;
7use crate::fonts::parsing::*;
8use crate::fonts::tables::*;
9use crate::fonts::types::Tag;
10use std::borrow::Cow;
11use std::cmp;
12use std::sync::Arc;
13
14pub struct Em;
16
17struct FontDesignUnit;
19
20#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
21pub struct GlyphId(pub u16);
22
23#[derive(Debug)]
24pub enum FontError {
25 UnsupportedFormat,
27
28 OffsetBeyondEof,
30
31 OffsetPlusLengthBeyondEof,
34
35 MissingTable,
37
38 NoSupportedPostscriptName,
40
41 NoSupportedCmap,
43
44 NoHorizontalGlyphMetrics,
46}
47
48pub struct Font {
49 bytes: Cow<'static, [u8]>,
50 cmap: Cmap,
51 postscript_name: String,
52 glyph_count: u16,
53 font_design_units_per_em: euclid::TypedScale<f32, Em, FontDesignUnit>,
54 horizontal_metrics: Slice<LongHorizontalMetricsRecord>,
55
56 ascender: euclid::Length<i16, FontDesignUnit>,
58
59 descender: euclid::Length<i16, FontDesignUnit>,
61
62 min_x: euclid::Length<i16, FontDesignUnit>,
64 min_y: euclid::Length<i16, FontDesignUnit>,
65 max_x: euclid::Length<i16, FontDesignUnit>,
66 max_y: euclid::Length<i16, FontDesignUnit>,
67}
68
69impl Font {
76 pub fn parse<B: Into<Cow<'static, [u8]>>>(bytes: B) -> Result<Arc<Self>, FontError> {
77 Self::parse_cow(bytes.into())
78 }
79
80 fn parse_cow(bytes: Cow<'static, [u8]>) -> Result<Arc<Self>, FontError> {
81 let mut font = Self::parse_without_cow_bytes_field(&bytes)?;
82 font.bytes = bytes;
83 Ok(Arc::new(font))
84 }
85
86 #[inline]
87 fn parse_without_cow_bytes_field(bytes: &[u8]) -> Result<Self, FontError> {
88 let bytes: &[u8] = &*bytes;
89 let offset_table = Position::<OffsetSubtable>::initial();
90 let scaler_type = offset_table.scaler_type().read_from(bytes)?;
91 const TRUETYPE: u32 = 0x74727565; if scaler_type != TRUETYPE && scaler_type != 0x_0001_0000 {
93 Err(FontError::UnsupportedFormat)?
94 }
95 let table_directory = Slice::new(
96 offset_table.followed_by::<TableDirectoryEntry>(),
97 offset_table.table_count().read_from(bytes)?,
98 );
99
100 let maxp = table_directory.find_table::<MaximumProfile>(bytes)?;
101 let header = table_directory.find_table::<FontHeader>(bytes)?;
102 let glyph_count = maxp.num_glyphs().read_from(bytes)?;
103 let horizontal_header = table_directory.find_table::<HorizontalHeader>(bytes)?;
104
105 Ok(Font {
106 bytes: b""[..].into(),
107 postscript_name: read_postscript_name(&bytes, table_directory)?,
108 cmap: Cmap::parse(bytes, table_directory)?,
109 glyph_count,
110 horizontal_metrics: Slice::new(
111 table_directory.find_table::<LongHorizontalMetricsRecord>(bytes)?,
112 horizontal_header
113 .number_of_long_horizontal_metrics()
114 .read_from(bytes)?,
115 ),
116 font_design_units_per_em: header.units_per_em().read_from(bytes)?.cast(),
117 ascender: horizontal_header.ascender().read_from(bytes)?,
118 descender: horizontal_header.descender().read_from(bytes)?,
119 min_x: header.min_x().read_from(bytes)?,
120 min_y: header.min_y().read_from(bytes)?,
121 max_x: header.max_x().read_from(bytes)?,
122 max_y: header.max_y().read_from(bytes)?,
123 })
124 }
125
126 pub fn bytes(&self) -> &[u8] {
127 &self.bytes
128 }
129 pub fn postscript_name(&self) -> &str {
130 &self.postscript_name
131 }
132 pub fn glyph_count(&self) -> u16 {
133 self.glyph_count
134 }
135
136 pub fn each_code_point<F>(&self, f: F) -> Result<(), FontError>
137 where
138 F: FnMut(char, GlyphId),
139 {
140 self.cmap.each_code_point(&self.bytes, f)
141 }
142
143 pub fn glyph_id(&self, ch: char) -> Result<GlyphId, FontError> {
144 let ch = ch as u32;
145 let result = match self.cmap {
146 Cmap::Format4(ref table) => table.get(&self.bytes, ch),
147 Cmap::Format12(ref table) => table.get(&self.bytes, ch),
148 };
149 const NOTDEF_GLYPH: u16 = 0;
150 Ok(GlyphId(result?.unwrap_or(NOTDEF_GLYPH)))
151 }
152
153 pub fn glyph_width(&self, glyph_id: GlyphId) -> Result<euclid::Length<f32, Em>, FontError> {
154 let last_index = self
155 .horizontal_metrics
156 .count()
157 .checked_sub(1)
158 .ok_or(FontError::NoHorizontalGlyphMetrics)?;
159 let index = cmp::min(glyph_id.0 as u32, last_index);
160 let w = self
161 .horizontal_metrics
162 .get_unchecked(index)
163 .advance_width()
164 .read_from(&self.bytes)?;
165 Ok(self.to_ems(w))
166 }
167
168 fn to_ems<T>(&self, length: euclid::Length<T, FontDesignUnit>) -> euclid::Length<f32, Em>
169 where
170 T: num_traits::NumCast + Clone,
171 {
172 length.cast() / self.font_design_units_per_em
173 }
174
175 pub fn ascender(&self) -> euclid::Length<f32, Em> {
176 self.to_ems(self.ascender)
177 }
178 pub fn descender(&self) -> euclid::Length<f32, Em> {
179 self.to_ems(self.descender)
180 }
181 pub fn min_x(&self) -> euclid::Length<f32, Em> {
182 self.to_ems(self.min_x)
183 }
184 pub fn min_y(&self) -> euclid::Length<f32, Em> {
185 self.to_ems(self.min_y)
186 }
187 pub fn max_x(&self) -> euclid::Length<f32, Em> {
188 self.to_ems(self.max_x)
189 }
190 pub fn max_y(&self) -> euclid::Length<f32, Em> {
191 self.to_ems(self.max_y)
192 }
193}
194
195fn read_postscript_name(
196 bytes: &[u8],
197 table_directory: Slice<TableDirectoryEntry>,
198) -> Result<String, FontError> {
199 fn decode_macintosh(string_bytes: &[u8]) -> String {
201 String::from_utf8_lossy(string_bytes).into_owned()
202 }
203
204 fn decode_ucs2(string_bytes: &[u8]) -> String {
206 string_bytes
207 .chunks(2)
208 .map(|chunk| {
209 if chunk.len() < 2 || chunk[0] != 0 {
210 '\u{FFFD}'
211 } else {
212 chunk[1] as char
213 }
214 })
215 .collect::<String>()
216 }
217
218 let naming_table_header = table_directory.find_table::<NamingTableHeader>(bytes)?;
219 let name_records = Slice::new(
220 naming_table_header.followed_by::<NameRecord>(),
221 naming_table_header.count().read_from(bytes)?,
222 );
223 let string_storage_start: Position<()> =
224 naming_table_header.offset_bytes(naming_table_header.string_offset().read_from(bytes)?);
225 let string_bytes = |record: Position<NameRecord>| {
226 Slice::<u8>::new(
227 string_storage_start.offset_bytes(record.string_offset().read_from(bytes)?),
228 record.length().read_from(bytes)?,
229 )
230 .read_from(bytes)
231 };
232
233 for record in name_records {
234 const POSTSCRIPT_NAME: u16 = 6;
235 if record.name_id().read_from(bytes)? != POSTSCRIPT_NAME {
236 continue;
237 }
238
239 const MACINTOSH: u16 = 1;
240 const MICROSOFT: u16 = 3;
241 const UNICODE_BMP: u16 = 1;
242 let postscript_name = match (
243 record.platform_id().read_from(bytes)?,
244 record.encoding_id().read_from(bytes)?,
245 ) {
246 (MACINTOSH, _) => decode_macintosh(string_bytes(record)?),
247 (MICROSOFT, UNICODE_BMP) => decode_ucs2(string_bytes(record)?),
248 _ => continue,
249 };
250 return Ok(postscript_name);
251 }
252
253 Err(FontError::NoSupportedPostscriptName)
254}
255
256trait SfntTable {
257 const TAG: Tag;
258}
259
260impl Slice<TableDirectoryEntry> {
261 fn find_table<T: SfntTable>(&self, bytes: &[u8]) -> Result<Position<T>, FontError> {
262 let search = self.binary_search_by_key(&T::TAG, |entry| entry.tag().read_from(bytes))?;
263 let entry = search.ok_or(FontError::MissingTable)?;
264 let offset = entry.table_offset().read_from(bytes)?;
265 Ok(Position::<OffsetSubtable>::initial().offset_bytes(offset))
266 }
267}
268
269#[doc(hidden)]
270pub mod _reexports_for_macros {
271 pub use lazy_static::*;
272 pub use std::sync::Arc;
273}
274
275#[macro_export]
276macro_rules! include_fonts {
277 ( $( $NAME: ident: $filename: expr, )+ ) => {
278 $(
279 $crate::fonts::_reexports_for_macros::lazy_static! {
280 pub static ref $NAME:
281 $crate::fonts::_reexports_for_macros::Arc<$crate::fonts::Font> =
282 {
283 $crate::fonts::Font::parse(
284 include_bytes!($filename) as &'static [u8]
285 ).unwrap()
286 };
287 }
288 )+
289 };
290}
291
292include_fonts! {
293 BITSTREAM_VERA_SANS: "../../fonts/vera/Vera.ttf",
294}