ttf_parser/tables/
cblc.rs

1// https://docs.microsoft.com/en-us/typography/opentype/spec/cblc
2
3use crate::GlyphId;
4use crate::parser::{Stream, FromData, Offset, Offset16, Offset32, NumFrom};
5
6#[derive(Clone, Copy, PartialEq, Debug)]
7pub enum BitmapFormat {
8    Format17,
9    Format18,
10    Format19,
11}
12
13#[derive(Clone, Copy, Default, Debug)]
14pub struct Metrics {
15    pub x: i8,
16    pub y: i8,
17    pub width: u8,
18    pub height: u8,
19}
20
21#[derive(Clone, Copy, Debug)]
22pub struct Location {
23    pub format: BitmapFormat,
24    pub offset: usize,
25    pub metrics: Metrics,
26    pub ppem: u16,
27}
28
29pub fn find_location(
30    data: &[u8],
31    glyph_id: GlyphId,
32    pixels_per_em: u16,
33) -> Option<Location> {
34    let mut s = Stream::new(data);
35
36    // The CBLC table version is a bit tricky, so we are ignoring it for now.
37    // The CBLC table is based on EBLC table, which was based on the `bloc` table.
38    // And before the CBLC table specification was finished, some fonts,
39    // notably Noto Emoji, have used version 2.0, but the final spec allows only 3.0.
40    // So there are perfectly valid fonts in the wild, which have an invalid version.
41    s.skip::<u32>(); // version
42
43    let size_table = select_bitmap_size_table(glyph_id, pixels_per_em, s)?;
44    let info = select_index_subtable(data, size_table, glyph_id)?;
45
46    let mut s = Stream::new_at(data, info.offset)?;
47    let index_format: u16 = s.read()?;
48    let image_format: u16 = s.read()?;
49    let mut image_offset = s.read::<Offset32>()?.to_usize();
50
51    let image_format = match image_format {
52        17 => BitmapFormat::Format17,
53        18 => BitmapFormat::Format18,
54        19 => BitmapFormat::Format19,
55        _ => return None, // Invalid format.
56    };
57
58    // TODO: I wasn't able to find fonts with index 4 and 5, so they are untested.
59
60    let glyph_diff = glyph_id.0.checked_sub(info.start_glyph_id.0)?;
61    let metrics = Metrics::default();
62    match index_format {
63        1 => {
64            s.advance(usize::from(glyph_diff) * Offset32::SIZE);
65            let offset: Offset32 = s.read()?;
66            image_offset += offset.to_usize();
67        }
68        2 => {
69            let image_size: u32 = s.read()?;
70            image_offset += usize::from(glyph_diff).checked_mul(usize::num_from(image_size))?;
71        }
72        3 => {
73            s.advance(usize::from(glyph_diff) * Offset16::SIZE);
74            let offset: Offset16 = s.read()?;
75            image_offset += offset.to_usize();
76        }
77        4 => {
78            let num_glyphs: u32 = s.read()?;
79            let num_glyphs = num_glyphs.checked_add(1)?;
80            let pairs = s.read_array32::<GlyphIdOffsetPair>(num_glyphs)?;
81            let pair = pairs.into_iter().find(|pair| pair.glyph_id == glyph_id)?;
82            image_offset += pair.offset.to_usize();
83        }
84        5 => {
85            let image_size: u32 = s.read()?;
86            s.advance(8); // big metrics
87            let num_glyphs: u32 = s.read()?;
88            let glyphs = s.read_array32::<GlyphId>(num_glyphs)?;
89            let (index, _) = glyphs.binary_search(&glyph_id)?;
90            image_offset = image_offset
91                .checked_add(usize::num_from(index).checked_mul(usize::num_from(image_size))?)?;
92        }
93        _ => return None, // Invalid format.
94    }
95
96    Some(Location {
97        format: image_format,
98        offset: image_offset,
99        metrics,
100        ppem: size_table.ppem,
101    })
102}
103
104
105#[derive(Clone, Copy)]
106struct BitmapSizeTable {
107    subtable_array_offset: Offset32,
108    number_of_subtables: u32,
109    ppem: u16,
110    // Many fields are omitted.
111}
112
113fn select_bitmap_size_table(
114    glyph_id: GlyphId,
115    pixels_per_em: u16,
116    mut s: Stream,
117) -> Option<BitmapSizeTable> {
118    let subtable_count: u32 = s.read()?;
119    let orig_s = s.clone();
120
121    let mut idx = None;
122    let mut max_ppem = 0;
123    for i in 0..subtable_count {
124        // The BitmapSize Table is larger than 32 bytes, so we cannot use scripts/gen-tables.py
125
126        // Check that the current subtable contains a provided glyph id.
127        s.advance(40); // Jump to `start_glyph_index`.
128        let start_glyph_id: GlyphId = s.read()?;
129        let end_glyph_id: GlyphId = s.read()?;
130        let ppem = u16::from(s.read::<u8>()?);
131
132        if !(start_glyph_id..=end_glyph_id).contains(&glyph_id) {
133            s.advance(4); // Jump to the end of the subtable.
134            continue;
135        }
136
137        // Select a best matching subtable based on `pixels_per_em`.
138        if (pixels_per_em <= ppem && ppem < max_ppem) || (pixels_per_em > max_ppem && ppem > max_ppem) {
139            idx = Some(usize::num_from(i));
140            max_ppem = ppem;
141        }
142    }
143
144    let mut s = orig_s;
145    s.advance(idx? * 48); // 48 is BitmapSize Table size
146
147    let subtable_array_offset: Offset32 = s.read()?;
148    s.skip::<u32>(); // index_tables_size
149    let number_of_subtables: u32 = s.read()?;
150
151    Some(BitmapSizeTable {
152        subtable_array_offset,
153        number_of_subtables,
154        ppem: max_ppem,
155    })
156}
157
158
159#[derive(Clone, Copy)]
160struct IndexSubtableInfo {
161    start_glyph_id: GlyphId,
162    offset: usize, // absolute offset
163}
164
165fn select_index_subtable(
166    data: &[u8],
167    size_table: BitmapSizeTable,
168    glyph_id: GlyphId,
169) -> Option<IndexSubtableInfo> {
170    let mut s = Stream::new_at(data, size_table.subtable_array_offset.to_usize())?;
171    for _ in 0..size_table.number_of_subtables {
172        let start_glyph_id: GlyphId = s.read()?;
173        let end_glyph_id: GlyphId = s.read()?;
174        let offset: Offset32 = s.read()?;
175
176        if (start_glyph_id..=end_glyph_id).contains(&glyph_id) {
177            let offset = size_table.subtable_array_offset.to_usize() + offset.to_usize();
178            return Some(IndexSubtableInfo {
179                start_glyph_id,
180                offset,
181            })
182        }
183    }
184
185    None
186}
187
188
189#[derive(Clone, Copy)]
190pub struct GlyphIdOffsetPair {
191    glyph_id: GlyphId,
192    offset: Offset16,
193}
194
195impl FromData for GlyphIdOffsetPair {
196    const SIZE: usize = 4;
197
198    #[inline]
199    fn parse(data: &[u8]) -> Option<Self> {
200        let mut s = Stream::new(data);
201        Some(GlyphIdOffsetPair {
202            glyph_id: s.read::<GlyphId>()?,
203            offset: s.read::<Offset16>()?,
204        })
205    }
206}