1 | //! A [Color Bitmap Location Table]( |
2 | //! https://docs.microsoft.com/en-us/typography/opentype/spec/cblc) implementation. |
3 | |
4 | use crate::parser::{FromData, NumFrom, Offset, Offset16, Offset32, Stream}; |
5 | use crate::GlyphId; |
6 | |
7 | #[derive (Clone, Copy, PartialEq, Debug)] |
8 | pub(crate) enum BitmapFormat { |
9 | Format17, |
10 | Format18, |
11 | Format19, |
12 | } |
13 | |
14 | #[derive (Clone, Copy, Default, Debug)] |
15 | pub(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)] |
23 | pub(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)] |
31 | struct BitmapSizeTable { |
32 | subtable_array_offset: Offset32, |
33 | number_of_subtables: u32, |
34 | ppem: u16, |
35 | // Many fields are omitted. |
36 | } |
37 | |
38 | fn 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)] |
84 | struct IndexSubtableInfo { |
85 | start_glyph_id: GlyphId, |
86 | offset: usize, // absolute offset |
87 | } |
88 | |
89 | fn 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)] |
113 | struct GlyphIdOffsetPair { |
114 | glyph_id: GlyphId, |
115 | offset: Offset16, |
116 | } |
117 | |
118 | impl 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)] |
136 | pub struct Table<'a> { |
137 | data: &'a [u8], |
138 | } |
139 | |
140 | impl<'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 | |
219 | impl core::fmt::Debug for Table<'_> { |
220 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
221 | write!(f, "Table {{ ... }}" ) |
222 | } |
223 | } |
224 | |