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