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