1use crate::parser::read_u32;
30use crate::tables::cblc::{BigGlyphMetrics, CblcEntry, SmallGlyphMetrics};
31use crate::Error;
32
33#[derive(Debug, Clone, Copy)]
35pub struct ColorBitmap<'a> {
36 pub width: u8,
38 pub height: u8,
40 pub bearing_x: i8,
43 pub bearing_y: i8,
46 pub advance: u8,
48 pub ppem: u8,
50 pub png_bytes: &'a [u8],
53}
54
55#[derive(Debug, Clone)]
57#[doc(hidden)]
59pub struct CbdtTable<'a> {
60 bytes: &'a [u8],
61}
62
63impl<'a> CbdtTable<'a> {
64 pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
67 if bytes.len() < 4 {
68 return Err(Error::UnexpectedEof);
69 }
70 let major = u16::from_be_bytes([bytes[0], bytes[1]]);
71 if major != 2 && major != 3 {
76 return Err(Error::BadStructure("CBDT: unknown major version"));
77 }
78 Ok(Self { bytes })
79 }
80
81 pub fn lookup(&self, entry: &CblcEntry) -> Result<Option<ColorBitmap<'a>>, Error> {
87 let off = entry.image_data_offset as usize;
88 let end = off
89 .checked_add(entry.data_len as usize)
90 .ok_or(Error::BadStructure("CBDT: entry overflow"))?;
91 if end > self.bytes.len() {
92 return Err(Error::BadOffset);
93 }
94 let blob = &self.bytes[off..end];
95 match entry.image_format {
96 17 => {
97 let metrics = SmallGlyphMetrics::parse(blob, 0)?;
99 if blob.len() < 5 + 4 {
100 return Err(Error::UnexpectedEof);
101 }
102 let data_len = read_u32(blob, 5)? as usize;
103 let png = blob.get(9..9 + data_len).ok_or(Error::BadOffset)?;
104 Ok(Some(ColorBitmap {
105 width: metrics.width,
106 height: metrics.height,
107 bearing_x: metrics.bearing_x,
108 bearing_y: metrics.bearing_y,
109 advance: metrics.advance,
110 ppem: entry.ppem_y,
111 png_bytes: png,
112 }))
113 }
114 18 => {
115 let metrics = BigGlyphMetrics::parse(blob, 0)?;
117 if blob.len() < 8 + 4 {
118 return Err(Error::UnexpectedEof);
119 }
120 let data_len = read_u32(blob, 8)? as usize;
121 let png = blob.get(12..12 + data_len).ok_or(Error::BadOffset)?;
122 Ok(Some(ColorBitmap {
123 width: metrics.width,
124 height: metrics.height,
125 bearing_x: metrics.hori_bearing_x,
126 bearing_y: metrics.hori_bearing_y,
127 advance: metrics.hori_advance,
128 ppem: entry.ppem_y,
129 png_bytes: png,
130 }))
131 }
132 19 => {
133 let metrics = entry.fixed_metrics.ok_or(Error::BadStructure(
135 "CBDT format 19 needs CBLC fixed metrics",
136 ))?;
137 if blob.len() < 4 {
138 return Err(Error::UnexpectedEof);
139 }
140 let data_len = read_u32(blob, 0)? as usize;
141 let png = blob.get(4..4 + data_len).ok_or(Error::BadOffset)?;
142 Ok(Some(ColorBitmap {
143 width: metrics.width,
144 height: metrics.height,
145 bearing_x: metrics.hori_bearing_x,
146 bearing_y: metrics.hori_bearing_y,
147 advance: metrics.hori_advance,
148 ppem: entry.ppem_y,
149 png_bytes: png,
150 }))
151 }
152 _ => Ok(None),
153 }
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn parses_format17_entry() {
163 let mut bytes = vec![0u8; 64];
171 bytes[0..2].copy_from_slice(&3u16.to_be_bytes());
172 bytes[16] = 10; bytes[17] = 12; bytes[18] = (-1i8) as u8; bytes[19] = 8; bytes[20] = 14; bytes[21..25].copy_from_slice(&5u32.to_be_bytes()); bytes[25..30].copy_from_slice(&[0x89, b'P', b'N', b'G', 0x0D]);
179 let cbdt = CbdtTable::parse(&bytes).expect("parse");
180 let entry = CblcEntry {
181 image_format: 17,
182 image_data_offset: 16,
183 data_len: 30 - 16,
184 ppem_x: 96,
185 ppem_y: 96,
186 bit_depth: 32,
187 fixed_metrics: None,
188 };
189 let cb = cbdt.lookup(&entry).expect("lookup ok").expect("entry");
190 assert_eq!(cb.width, 12);
191 assert_eq!(cb.height, 10);
192 assert_eq!(cb.bearing_x, -1);
193 assert_eq!(cb.bearing_y, 8);
194 assert_eq!(cb.advance, 14);
195 assert_eq!(cb.png_bytes.len(), 5);
196 assert_eq!(cb.png_bytes[0], 0x89);
197 }
198
199 #[test]
200 fn parses_format18_entry() {
201 let mut bytes = vec![0u8; 64];
203 bytes[0..2].copy_from_slice(&3u16.to_be_bytes());
204 bytes[12] = 20; bytes[13] = 24; bytes[14] = 2; bytes[15] = 18; bytes[16] = 26; bytes[17..20].copy_from_slice(&[0; 3]); bytes[20..24].copy_from_slice(&3u32.to_be_bytes()); bytes[24..27].copy_from_slice(&[0xA1, 0xB2, 0xC3]);
213 let cbdt = CbdtTable::parse(&bytes).expect("parse");
214 let entry = CblcEntry {
215 image_format: 18,
216 image_data_offset: 12,
217 data_len: 27 - 12,
218 ppem_x: 109,
219 ppem_y: 109,
220 bit_depth: 32,
221 fixed_metrics: None,
222 };
223 let cb = cbdt.lookup(&entry).expect("lookup ok").expect("entry");
224 assert_eq!(cb.width, 24);
225 assert_eq!(cb.height, 20);
226 assert_eq!(cb.bearing_x, 2);
227 assert_eq!(cb.bearing_y, 18);
228 assert_eq!(cb.advance, 26);
229 assert_eq!(cb.ppem, 109);
230 assert_eq!(cb.png_bytes, &[0xA1, 0xB2, 0xC3]);
231 }
232
233 #[test]
234 fn returns_none_for_unsupported_format() {
235 let mut bytes = vec![0u8; 16];
236 bytes[0..2].copy_from_slice(&3u16.to_be_bytes());
237 let cbdt = CbdtTable::parse(&bytes).expect("parse");
238 let entry = CblcEntry {
239 image_format: 1, image_data_offset: 4,
241 data_len: 4,
242 ppem_x: 32,
243 ppem_y: 32,
244 bit_depth: 1,
245 fixed_metrics: None,
246 };
247 assert!(cbdt.lookup(&entry).expect("lookup ok").is_none());
248 }
249
250 #[test]
251 fn parses_format19_with_cblc_metrics() {
252 let mut bytes = vec![0u8; 32];
253 bytes[0..2].copy_from_slice(&3u16.to_be_bytes());
254 bytes[8..12].copy_from_slice(&4u32.to_be_bytes());
256 bytes[12..16].copy_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
257 let cbdt = CbdtTable::parse(&bytes).expect("parse");
258 let entry = CblcEntry {
259 image_format: 19,
260 image_data_offset: 8,
261 data_len: 16 - 8,
262 ppem_x: 64,
263 ppem_y: 64,
264 bit_depth: 32,
265 fixed_metrics: Some(BigGlyphMetrics {
266 height: 7,
267 width: 9,
268 hori_bearing_x: 3,
269 hori_bearing_y: 11,
270 hori_advance: 13,
271 vert_bearing_x: 0,
272 vert_bearing_y: 0,
273 vert_advance: 0,
274 }),
275 };
276 let cb = cbdt.lookup(&entry).expect("lookup ok").expect("entry");
277 assert_eq!(cb.width, 9);
278 assert_eq!(cb.height, 7);
279 assert_eq!(cb.bearing_x, 3);
280 assert_eq!(cb.bearing_y, 11);
281 assert_eq!(cb.advance, 13);
282 assert_eq!(cb.png_bytes, &[0xDE, 0xAD, 0xBE, 0xEF]);
283 }
284}