accessibility_tree/fonts/
mod.rs

1mod 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
14/// The EM square unit
15pub struct Em;
16
17/// The unit of FWord and UFWord
18struct FontDesignUnit;
19
20#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
21pub struct GlyphId(pub u16);
22
23#[derive(Debug)]
24pub enum FontError {
25    /// Victor only supports TrueType fonts at the moment.
26    UnsupportedFormat,
27
28    /// The font file contains an offset to beyond the end of the file.
29    OffsetBeyondEof,
30
31    /// The font file contains an offset that puts the end of the pointed object
32    /// beyond the end of the file.
33    OffsetPlusLengthBeyondEof,
34
35    /// One of the required TrueType tables is missing in this font.
36    MissingTable,
37
38    /// This font doesn’t have a “PostScript name” string in a supported encoding.
39    NoSupportedPostscriptName,
40
41    /// This font doesn’t have a character map in a supported format.
42    NoSupportedCmap,
43
44    /// This font doesn’t have any horizontal metrics for glyphs.
45    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    /// Distance from baseline of highest ascender
57    ascender: euclid::Length<i16, FontDesignUnit>,
58
59    /// Distance from baseline of lowest descender
60    descender: euclid::Length<i16, FontDesignUnit>,
61
62    /// The bounding box of the union of all glyphs
63    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
69// #[cfg(target_pointer_width = "64")]
70// fn _assert_size_of() {
71//     let _ = std::mem::transmute::<Cmap, [u8; 24]>;
72//     let _ = std::mem::transmute::<Font, [u8; 112]>;
73// }
74
75impl 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; // "true" in big-endian
92        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    /// Macintosh encodings seem to be ASCII-compatible, and a PostScript name is within ASCII
200    fn decode_macintosh(string_bytes: &[u8]) -> String {
201        String::from_utf8_lossy(string_bytes).into_owned()
202    }
203
204    /// Latin-1 range only
205    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}