1#[derive(Debug, Clone, Copy)]
34pub struct BitmapFont {
35 data: &'static [u8],
37 glyph_width: u8,
39 glyph_height: u8,
41 glyph_count: u16,
43 charset: Option<&'static [char]>,
51}
52
53impl BitmapFont {
54 #[must_use]
59 pub const fn new(
60 data: &'static [u8],
61 glyph_width: u8,
62 glyph_height: u8,
63 glyph_count: u16,
64 ) -> Self {
65 Self {
66 data,
67 glyph_width,
68 glyph_height,
69 glyph_count,
70 charset: None,
71 }
72 }
73
74 #[must_use]
87 pub const fn with_charset(
88 data: &'static [u8],
89 glyph_width: u8,
90 glyph_height: u8,
91 glyph_count: u16,
92 charset: &'static [char],
93 ) -> Self {
94 Self {
95 data,
96 glyph_width,
97 glyph_height,
98 glyph_count,
99 charset: Some(charset),
100 }
101 }
102
103 #[must_use]
111 pub fn rows(&self, index: u8) -> &[u8] {
112 assert!(
113 u16::from(index) < self.glyph_count,
114 "glyph index {index} out of range ({})",
115 self.glyph_count,
116 );
117 let h = usize::from(self.glyph_height);
118 let start = usize::from(index) * h;
119 &self.data[start..start + h]
120 }
121
122 #[must_use = "iterators are lazy and do nothing unless consumed"]
140 pub fn glyph_pixels(&self, index: u8) -> impl Iterator<Item = (u8, u8)> + '_ {
141 let width = self.glyph_width.min(8);
142 self.rows(index)
143 .iter()
144 .enumerate()
145 .flat_map(move |(y, &row)| {
146 #[allow(clippy::cast_possible_truncation)]
147 let y = y as u8;
148 (0..width)
149 .filter_map(move |x| ((row >> (width - 1 - x)) & 1 == 1).then_some((x, y)))
150 })
151 }
152
153 #[must_use]
155 pub const fn glyph_width(&self) -> u8 {
156 self.glyph_width
157 }
158
159 #[must_use]
161 pub const fn glyph_height(&self) -> u8 {
162 self.glyph_height
163 }
164
165 #[must_use]
170 pub const fn glyph_count(&self) -> u16 {
171 self.glyph_count
172 }
173
174 #[must_use]
189 pub const fn glyph_index(&self, ch: char) -> Option<u8> {
190 if let Some(table) = self.charset {
191 let mut i = 0;
192 while i < table.len() {
193 if table[i] == ch && i < self.glyph_count as usize {
194 #[allow(clippy::cast_possible_truncation)]
195 return Some(i as u8);
196 }
197 i += 1;
198 }
199 return None;
200 }
201 match try_unicode_to_cp437(ch) {
202 Some(index) if (index as u16) < self.glyph_count => Some(index),
203 _ => None,
204 }
205 }
206}
207
208impl PartialEq for BitmapFont {
212 fn eq(&self, other: &Self) -> bool {
213 core::ptr::eq(self.data.as_ptr(), other.data.as_ptr())
214 && self.glyph_width == other.glyph_width
215 && self.glyph_height == other.glyph_height
216 && self.glyph_count == other.glyph_count
217 }
218}
219
220impl Eq for BitmapFont {}
221
222#[derive(Debug, Clone, Copy, PartialEq, Eq)]
227pub struct ResolvedGlyph {
228 font: BitmapFont,
229 font_index: usize,
230 index: u8,
231 notdef: bool,
232}
233
234impl ResolvedGlyph {
235 #[must_use]
237 pub const fn font(&self) -> BitmapFont {
238 self.font
239 }
240
241 #[must_use]
248 pub const fn font_index(&self) -> usize {
249 self.font_index
250 }
251
252 #[must_use]
254 pub const fn index(&self) -> u8 {
255 self.index
256 }
257
258 #[must_use]
262 pub const fn is_notdef(&self) -> bool {
263 self.notdef
264 }
265
266 #[must_use]
268 pub fn rows(&self) -> &[u8] {
269 self.font.rows(self.index)
270 }
271}
272
273#[derive(Debug, Clone, Copy, PartialEq, Eq)]
320pub struct FontChain<'a> {
321 primary: BitmapFont,
322 fallbacks: &'a [BitmapFont],
323}
324
325impl From<BitmapFont> for FontChain<'static> {
326 fn from(font: BitmapFont) -> Self {
327 Self::new(font, &[])
328 }
329}
330
331impl<'a> FontChain<'a> {
332 #[must_use]
334 pub const fn new(primary: BitmapFont, fallbacks: &'a [BitmapFont]) -> Self {
335 Self { primary, fallbacks }
336 }
337
338 pub fn fonts(&self) -> impl Iterator<Item = &BitmapFont> {
342 core::iter::once(&self.primary).chain(self.fallbacks.iter())
343 }
344
345 #[must_use]
347 pub const fn font_count(&self) -> usize {
348 1 + self.fallbacks.len()
349 }
350
351 #[must_use]
358 pub fn glyph_size(&self) -> Option<(u8, u8)> {
359 let size = (self.primary.glyph_width, self.primary.glyph_height);
360 self.fallbacks
361 .iter()
362 .all(|f| (f.glyph_width, f.glyph_height) == size)
363 .then_some(size)
364 }
365
366 #[must_use]
378 pub fn resolve(&self, ch: char) -> Option<ResolvedGlyph> {
379 self.lookup(ch, false).or_else(|| self.lookup(NOTDEF, true))
380 }
381
382 fn lookup(&self, ch: char, notdef: bool) -> Option<ResolvedGlyph> {
384 self.fonts()
385 .enumerate()
386 .find_map(|(font_index, font)| {
387 font.glyph_index(ch).map(|index| (font_index, font, index))
388 })
389 .map(|(font_index, font, index)| ResolvedGlyph {
390 font: *font,
391 font_index,
392 index,
393 notdef,
394 })
395 }
396}
397
398#[cfg(feature = "default-font")]
418pub mod unscii16 {
419 use super::BitmapFont;
420
421 pub const FONT: BitmapFont = BitmapFont::new(&DATA, 8, 16, 256);
423
424 #[rustfmt::skip]
428 static DATA: [u8; 4096] = [
429 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x81, 0x81, 0xa5, 0x81, 0x81, 0xbd, 0x99, 0x81, 0x81, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xff, 0xff, 0xdb, 0xff, 0xff, 0xc3, 0xe7, 0xff, 0xff, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0xfe, 0xfe, 0xfe, 0xfe, 0x7c, 0x7c, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x38, 0x7c, 0xfe, 0x7c, 0x38, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x38, 0x54, 0xfe, 0xfe, 0x54, 0x10, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x7c, 0xfe, 0xfe, 0xfe, 0x38, 0x38, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x3c, 0x3c, 0x3c, 0x3c, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xe7, 0xc3, 0xc3, 0xc3, 0xc3, 0xe7, 0xe7, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3c, 0x3c, 0x66, 0x66, 0x42, 0x42, 0x42, 0x42, 0x66, 0x66, 0x3c, 0x3c, 0x00, 0x00, 0xff, 0xff, 0xc3, 0xc3, 0x99, 0x99, 0xbd, 0xbd, 0xbd, 0xbd, 0x99, 0x99, 0xc3, 0xc3, 0xff, 0xff, 0x00, 0x00, 0x00, 0x1e, 0x0e, 0x1a, 0x78, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x7e, 0x18, 0x00, 0x00, 0x00, 0x18, 0x1c, 0x1e, 0x1b, 0x18, 0x18, 0x78, 0xf8, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x63, 0x63, 0x63, 0x63, 0x63, 0x67, 0xe7, 0xe6, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x18, 0xbd, 0x7e, 0x7e, 0xbd, 0x18, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xf0, 0xfc, 0xff, 0xfc, 0xf0, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0f, 0x3f, 0xff, 0x3f, 0x0f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x7a, 0x7a, 0x7a, 0x7a, 0x3a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x60, 0x30, 0x38, 0x6c, 0x66, 0x36, 0x1c, 0x0c, 0x06, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x00, 0x00, 0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, 0xff, 0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0c, 0xfe, 0xfe, 0x0c, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x30, 0x7f, 0x7f, 0x30, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x06, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x66, 0xff, 0xff, 0x66, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x3c, 0x3c, 0x3c, 0x7e, 0x7e, 0x7e, 0x7e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7e, 0x7e, 0x7e, 0x7e, 0x3c, 0x3c, 0x3c, 0x3c, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x6c, 0x6c, 0xfe, 0x6c, 0x6c, 0x6c, 0xfe, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x3c, 0x66, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x66, 0x3c, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x06, 0xc6, 0xcc, 0xcc, 0x18, 0x18, 0x30, 0x30, 0x66, 0x66, 0xc6, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x38, 0x6c, 0x6c, 0x38, 0x30, 0x7a, 0xde, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x18, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x18, 0x18, 0x0c, 0x00, 0x00, 0x00, 0x30, 0x18, 0x18, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x3c, 0xff, 0x3c, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x18, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x03, 0x03, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x18, 0x30, 0x30, 0x60, 0x60, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x38, 0x6c, 0xc6, 0xc6, 0xce, 0xd6, 0xe6, 0xc6, 0xc6, 0x6c, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x38, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x06, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x06, 0x06, 0x1c, 0x06, 0x06, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x1c, 0x3c, 0x6c, 0xcc, 0xcc, 0xfe, 0x0c, 0x0c, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x7c, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x30, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x06, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x76, 0x3c, 0x6e, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x06, 0x0c, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x38, 0x18, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x06, 0x0c, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xde, 0xde, 0xde, 0xdc, 0xc0, 0xc0, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3c, 0x66, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0x66, 0x66, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x60, 0x60, 0x60, 0x60, 0x60, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x6c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6c, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x60, 0x60, 0x6e, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xcc, 0xcc, 0xd8, 0xf0, 0xd8, 0xcc, 0xcc, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xee, 0xee, 0xfe, 0xd6, 0xd6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xe6, 0xe6, 0xf6, 0xfe, 0xde, 0xce, 0xce, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x0c, 0x06, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x6c, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x3c, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xd6, 0xd6, 0xfe, 0xee, 0xee, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, 0xc3, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x3c, 0x66, 0xc3, 0xc3, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, 0xc3, 0x66, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x30, 0x30, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3c, 0x00, 0x00, 0xc0, 0xc0, 0x60, 0x60, 0x30, 0x30, 0x18, 0x18, 0x0c, 0x0c, 0x06, 0x06, 0x03, 0x03, 0x00, 0x00, 0x00, 0x3c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x3c, 0x00, 0x00, 0x00, 0x10, 0x38, 0x6c, 0x6c, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x18, 0x18, 0x0c, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06, 0x06, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x30, 0x30, 0x30, 0x7e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x7c, 0x00, 0x00, 0x60, 0x60, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0c, 0x00, 0x00, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x78, 0x00, 0x00, 0x60, 0x60, 0x60, 0x60, 0x66, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0xfe, 0xd6, 0xd6, 0xd6, 0xd6, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x60, 0x60, 0x3c, 0x06, 0x06, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x30, 0x7e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xd6, 0xd6, 0xd6, 0x7c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0x6c, 0x38, 0x6c, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf0, 0x18, 0x18, 0x18, 0x18, 0x18, 0x0e, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0xe0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x1e, 0x30, 0x30, 0x30, 0x30, 0x30, 0xe0, 0x00, 0x00, 0x00, 0x72, 0xd6, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3c, 0x7e, 0xff, 0xc3, 0xc3, 0xc3, 0xdb, 0xdb, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x60, 0x60, 0x60, 0x60, 0x60, 0x66, 0x66, 0x3c, 0x0c, 0x06, 0x1c, 0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x3c, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3c, 0x0c, 0x06, 0x1c, 0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x66, 0x66, 0x00, 0x18, 0x3c, 0x66, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x3c, 0x00, 0x18, 0x3c, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x0c, 0x18, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x1b, 0x1b, 0x7f, 0xd8, 0xd8, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x7c, 0xfc, 0xcc, 0xcc, 0xfe, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x3c, 0x66, 0x66, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x66, 0x66, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x3c, 0x66, 0x60, 0x60, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x6c, 0x6c, 0x60, 0x60, 0xf0, 0x60, 0x60, 0x66, 0x66, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, 0xc3, 0x66, 0x66, 0x3c, 0x18, 0x7e, 0x18, 0x7e, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x66, 0x66, 0x7c, 0x62, 0x66, 0x6f, 0x66, 0x66, 0x66, 0xf3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x1b, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x18, 0xd8, 0x70, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xdc, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x76, 0xdc, 0x00, 0xc6, 0xc6, 0xe6, 0xf6, 0xde, 0xce, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x06, 0x06, 0x3e, 0x66, 0x66, 0x3e, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x30, 0x30, 0x60, 0x60, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x06, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xc6, 0x46, 0x4c, 0x4c, 0x18, 0x18, 0x30, 0x30, 0x6c, 0x62, 0xc4, 0xc8, 0x0e, 0x00, 0x00, 0x40, 0xc6, 0x46, 0x4c, 0x4c, 0x18, 0x18, 0x30, 0x30, 0x62, 0x66, 0xca, 0xcf, 0x02, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x66, 0xcc, 0x66, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x66, 0x33, 0x66, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0x18, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xec, 0xec, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfc, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x18, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xec, 0x0c, 0xec, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x0c, 0xec, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xec, 0x0c, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0x18, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x18, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6f, 0x6f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6f, 0x60, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x60, 0x6f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xef, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xef, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6f, 0x60, 0x6f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xef, 0x00, 0xef, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x7f, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x18, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x18, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x7f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xff, 0xff, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0x18, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xce, 0xc6, 0xc6, 0xc6, 0xce, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xcc, 0xcc, 0xcc, 0xd8, 0xcc, 0xc6, 0xc6, 0xc6, 0xc6, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xcc, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x7c, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0x7c, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xee, 0x6c, 0x6c, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xc0, 0xc0, 0x60, 0x30, 0x18, 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xdb, 0xdb, 0xdb, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc0, 0xdc, 0xd6, 0xd6, 0xd6, 0x7c, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x60, 0x60, 0x3c, 0x60, 0x60, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x18, 0x0c, 0x06, 0x0c, 0x18, 0x30, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0c, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x1b, 0x1b, 0x1b, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xd8, 0xd8, 0xd8, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x7e, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0xd6, 0x9c, 0x00, 0x72, 0xd6, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x06, 0x06, 0x06, 0x06, 0x06, 0xcc, 0xcc, 0x6c, 0x38, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x6c, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x6c, 0x0c, 0x18, 0x30, 0x60, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
686}
687
688#[cfg(feature = "legacy-computing")]
729pub mod legacy_computing {
730 pub mod blocks {
737 use crate::font::BitmapFont;
738
739 const QUADRANT_COUNT: usize = 10;
741 const SEXTANT_COUNT: usize = 60;
743 const BAR_COUNT: usize = 6;
747 const BLOCK_COUNT: usize = 6;
751 const TOTAL: usize = QUADRANT_COUNT + SEXTANT_COUNT + BAR_COUNT + BLOCK_COUNT;
754
755 #[allow(clippy::cast_possible_truncation)]
761 pub const FONT: BitmapFont = BitmapFont::with_charset(&DATA, 8, 16, TOTAL as u16, &CHARSET);
762
763 #[rustfmt::skip]
770 const QUADRANTS: [(u8, char); QUADRANT_COUNT] = [
771 (1, '▘'), (2, '▝'), (4, '▖'), (6, '▞'), (7, '▛'),
772 (8, '▗'), (9, '▚'), (11, '▜'), (13, '▙'), (14, '▟'),
773 ];
774
775 #[rustfmt::skip]
779 const BAR_LEVELS: [(u8, char); BAR_COUNT] = [
780 (1, '▁'), (2, '▂'), (3, '▃'), (5, '▅'), (6, '▆'), (7, '▇'),
781 ];
782
783 #[rustfmt::skip]
787 const BLOCK_LEVELS: [(u8, char); BLOCK_COUNT] = [
788 (1, '▏'), (2, '▎'), (3, '▍'), (5, '▋'), (6, '▊'), (7, '▉'),
789 ];
790
791 const fn sextant_masks() -> [u8; SEXTANT_COUNT] {
799 let mut masks = [0u8; SEXTANT_COUNT];
800 let mut m: u16 = 1;
801 let mut i = 0;
802 while m <= 62 {
803 if m != 21 && m != 42 {
804 #[allow(clippy::cast_possible_truncation)]
805 {
806 masks[i] = m as u8;
807 }
808 i += 1;
809 }
810 m += 1;
811 }
812 masks
813 }
814
815 const fn sextant_codepoint(mask: u8) -> u32 {
823 let gaps_below = if mask > 21 { 1 } else { 0 } + if mask > 42 { 1 } else { 0 };
824 0x1_FB00 + (mask as u32 - 1) - gaps_below
825 }
826
827 #[derive(Clone, Copy)]
832 enum Shape {
833 Quadrant(u8),
834 Sextant(u8),
835 Bar(u8),
836 Block(u8),
837 }
838
839 const GLYPHS: [(char, Shape); TOTAL] = build_glyphs();
844
845 const fn build_glyphs() -> [(char, Shape); TOTAL] {
846 let mut glyphs = [('\0', Shape::Quadrant(0)); TOTAL];
847
848 let mut qi = 0;
849 while qi < QUADRANT_COUNT {
850 let (mask, ch) = QUADRANTS[qi];
851 glyphs[qi] = (ch, Shape::Quadrant(mask));
852 qi += 1;
853 }
854
855 let masks = sextant_masks();
856 let mut si = 0;
857 while si < SEXTANT_COUNT {
858 let mask = masks[si];
859 let cp = sextant_codepoint(mask);
860 let Some(ch) = char::from_u32(cp) else {
861 panic!("sextant codepoint is not a valid char")
862 };
863 glyphs[QUADRANT_COUNT + si] = (ch, Shape::Sextant(mask));
864 si += 1;
865 }
866
867 let mut bi = 0;
868 while bi < BAR_COUNT {
869 let (eighths, ch) = BAR_LEVELS[bi];
870 glyphs[QUADRANT_COUNT + SEXTANT_COUNT + bi] = (ch, Shape::Bar(eighths));
871 bi += 1;
872 }
873
874 let mut bli = 0;
875 while bli < BLOCK_COUNT {
876 let (eighths, ch) = BLOCK_LEVELS[bli];
877 glyphs[QUADRANT_COUNT + SEXTANT_COUNT + BAR_COUNT + bli] =
878 (ch, Shape::Block(eighths));
879 bli += 1;
880 }
881
882 glyphs
883 }
884
885 const fn set_pixel(data: &mut [u8; TOTAL * 16], index: usize, x: u8, y: u8) {
888 let row = index * 16 + y as usize;
889 data[row] |= 1 << (7 - x);
890 }
891
892 const fn build_data() -> [u8; TOTAL * 16] {
895 let mut data = [0u8; TOTAL * 16];
896
897 let mut index = 0;
898 while index < TOTAL {
899 let (_, shape) = GLYPHS[index];
900 match shape {
901 Shape::Quadrant(mask) => {
903 let mut y = 0u8;
904 while y < 16 {
905 let mut x = 0u8;
906 while x < 8 {
907 let bit = if x < 4 {
908 if y < 8 { 0 } else { 2 }
909 } else if y < 8 {
910 1
911 } else {
912 3
913 };
914 if (mask >> bit) & 1 == 1 {
915 set_pixel(&mut data, index, x, y);
916 }
917 x += 1;
918 }
919 y += 1;
920 }
921 }
922 Shape::Sextant(mask) => {
925 let mut y = 0u8;
926 while y < 16 {
927 let row = if y < 5 {
928 0
929 } else if y < 11 {
930 1
931 } else {
932 2
933 };
934 let mut x = 0u8;
935 while x < 8 {
936 let col: usize = if x >= 4 { 1 } else { 0 };
937 let bit = row * 2 + col;
938 if (mask >> bit) & 1 == 1 {
939 set_pixel(&mut data, index, x, y);
940 }
941 x += 1;
942 }
943 y += 1;
944 }
945 }
946 Shape::Bar(eighths) => {
949 let fill_from = 16 - eighths * 2;
950 let mut y = fill_from;
951 while y < 16 {
952 let mut x = 0u8;
953 while x < 8 {
954 set_pixel(&mut data, index, x, y);
955 x += 1;
956 }
957 y += 1;
958 }
959 }
960 Shape::Block(eighths) => {
963 let mut y = 0u8;
964 while y < 16 {
965 let mut x = 0u8;
966 while x < eighths {
967 set_pixel(&mut data, index, x, y);
968 x += 1;
969 }
970 y += 1;
971 }
972 }
973 }
974 index += 1;
975 }
976
977 data
978 }
979
980 const fn build_charset() -> [char; TOTAL] {
982 let mut charset = ['\0'; TOTAL];
983 let mut i = 0;
984 while i < TOTAL {
985 charset[i] = GLYPHS[i].0;
986 i += 1;
987 }
988 charset
989 }
990
991 static DATA: [u8; TOTAL * 16] = build_data();
994
995 static CHARSET: [char; TOTAL] = build_charset();
997
998 #[cfg(test)]
999 mod tests {
1000 use super::{
1001 BAR_LEVELS, BLOCK_LEVELS, CHARSET, FONT, QUADRANTS, SEXTANT_COUNT, TOTAL,
1002 sextant_codepoint, sextant_masks,
1003 };
1004 use crate::font::FontChain;
1005 use std::collections::HashSet;
1006
1007 #[test]
1008 fn total_glyph_count_matches_quadrants_plus_sextants_plus_bar_plus_block() {
1009 assert_eq!(TOTAL, 10 + 60 + 6 + 6);
1010 assert_eq!(FONT.glyph_count(), u16::try_from(TOTAL).unwrap());
1011 }
1012
1013 #[test]
1014 fn no_charset_entry_duplicates_a_codepoint_cp437_already_serves() {
1015 let already_cp437: HashSet<char> = [' ', '▀', '▄', '▌', '▐', '█', '░', '▒', '▓']
1019 .into_iter()
1020 .collect();
1021 for &ch in &CHARSET {
1022 assert!(
1023 !already_cp437.contains(&ch),
1024 "{ch:?} (U+{:04X}) duplicates existing CP437 coverage",
1025 ch as u32
1026 );
1027 }
1028 }
1029
1030 #[test]
1031 fn every_charset_character_appears_exactly_once() {
1032 let mut seen = HashSet::with_capacity(TOTAL);
1033 for &ch in &CHARSET {
1034 assert!(seen.insert(ch), "{ch:?} appears more than once in CHARSET");
1035 }
1036 assert_eq!(seen.len(), TOTAL);
1037 }
1038
1039 #[test]
1040 fn sextant_masks_skip_21_and_42() {
1041 let masks = sextant_masks();
1042 assert_eq!(masks.len(), SEXTANT_COUNT);
1043 assert!(!masks.contains(&21));
1044 assert!(!masks.contains(&42));
1045 assert_eq!(masks[0], 1);
1046 assert_eq!(masks[SEXTANT_COUNT - 1], 62);
1047 }
1048
1049 #[test]
1050 fn sextant_codepoint_shifts_down_after_each_gap() {
1051 assert_eq!(sextant_codepoint(1), 0x1FB00);
1052 assert_eq!(sextant_codepoint(22), 0x1FB00 + 21 - 1);
1055 assert_eq!(sextant_codepoint(43), 0x1FB00 + 42 - 2);
1057 }
1058
1059 #[test]
1066 fn quadrant_table_round_trips_core_subcell_quadrants() {
1067 const CP437_COVERED: [(u8, char); 6] = [
1070 (0, ' '),
1071 (3, '▀'),
1072 (5, '▌'),
1073 (10, '▐'),
1074 (12, '▄'),
1075 (15, '█'),
1076 ];
1077
1078 let mut by_mask: [Option<char>; 16] = [None; 16];
1079 for &(mask, ch) in &CP437_COVERED {
1080 by_mask[mask as usize] = Some(ch);
1081 }
1082 for &(mask, ch) in &QUADRANTS {
1083 by_mask[mask as usize] = Some(ch);
1084 }
1085
1086 for (mask, expected) in retroglyph_core::symbols::QUADRANTS.into_iter().enumerate()
1087 {
1088 assert_eq!(
1089 by_mask[mask],
1090 Some(expected),
1091 "mask {mask}: this module's quadrant table disagrees with \
1092 retroglyph_core::symbols::QUADRANTS[{mask}] ({expected:?})"
1093 );
1094 }
1095 }
1096
1097 #[test]
1104 fn sextant_table_round_trips_core_subcell_sextants() {
1105 for (mask, expected) in retroglyph_core::symbols::SEXTANTS.into_iter().enumerate() {
1106 let mask = u8::try_from(mask).unwrap();
1107 let actual = match mask {
1108 0 => ' ',
1109 21 => '▌',
1110 42 => '▐',
1111 63 => '█',
1112 _ => char::from_u32(sextant_codepoint(mask))
1113 .expect("sextant_codepoint always yields a valid char"),
1114 };
1115 assert_eq!(
1116 actual, expected,
1117 "mask {mask}: sextant_codepoint disagrees with \
1118 retroglyph_core::symbols::SEXTANTS[{mask}]"
1119 );
1120 }
1121 }
1122
1123 #[test]
1125 fn quadrant_top_left_mask_only_fills_the_top_left_quarter() {
1126 let index = FONT.glyph_index('▘').expect("U+2598 is covered");
1127 for (x, y) in FONT.glyph_pixels(index) {
1128 assert!(x < 4 && y < 8, "({x}, {y}) outside the top-left quarter");
1129 }
1130 }
1131
1132 #[test]
1133 fn font_chain_resolves_quadrant_via_blocks_fallback() {
1134 static PRIMARY_DATA: [u8; 256 * 16] = [0; 256 * 16];
1135 const PRIMARY: crate::font::BitmapFont =
1136 crate::font::BitmapFont::new(&PRIMARY_DATA, 8, 16, 256);
1137
1138 static FALLBACKS: [crate::font::BitmapFont; 1] = [FONT];
1139 let chain = FontChain::new(PRIMARY, &FALLBACKS);
1140
1141 let quadrant = chain
1142 .resolve('▘')
1143 .expect("covered by legacy_computing::blocks");
1144 assert_eq!(quadrant.font_index(), 1);
1145 assert!(!quadrant.is_notdef());
1146 }
1147
1148 #[test]
1152 fn bar_and_block_levels_are_covered_and_non_empty() {
1153 static PRIMARY_DATA: [u8; 256 * 16] = [0; 256 * 16];
1154 const PRIMARY: crate::font::BitmapFont =
1155 crate::font::BitmapFont::new(&PRIMARY_DATA, 8, 16, 256);
1156
1157 static FALLBACKS: [crate::font::BitmapFont; 1] = [FONT];
1158 let chain = FontChain::new(PRIMARY, &FALLBACKS);
1159
1160 for &(_, ch) in BAR_LEVELS.iter().chain(BLOCK_LEVELS.iter()) {
1161 let resolved = chain
1162 .resolve(ch)
1163 .unwrap_or_else(|| panic!("{ch:?} covered by legacy_computing::blocks"));
1164 assert_eq!(resolved.font_index(), 1);
1165 assert!(!resolved.is_notdef(), "{ch:?} resolved to notdef");
1166 assert!(
1167 FONT.glyph_pixels(resolved.index()).count() > 0,
1168 "{ch:?} has no set pixels"
1169 );
1170 }
1171 }
1172
1173 #[test]
1177 fn bar_levels_fill_monotonically_from_the_bottom() {
1178 let mut last_count = 0usize;
1179 for &(eighths, ch) in &BAR_LEVELS {
1180 let index = FONT.glyph_index(ch).unwrap();
1181 let pixels: Vec<(u8, u8)> = FONT.glyph_pixels(index).collect();
1182 assert!(
1183 pixels.iter().all(|&(_, y)| y >= 16 - eighths * 2),
1184 "{ch:?} has a filled pixel above its {eighths}/8 fill line"
1185 );
1186 assert_eq!(pixels.len(), usize::from(eighths) * 2 * 8);
1187 assert!(pixels.len() > last_count);
1188 last_count = pixels.len();
1189 }
1190 }
1191
1192 #[test]
1195 fn block_levels_fill_monotonically_from_the_left() {
1196 let mut last_count = 0usize;
1197 for &(eighths, ch) in &BLOCK_LEVELS {
1198 let index = FONT.glyph_index(ch).unwrap();
1199 let pixels: Vec<(u8, u8)> = FONT.glyph_pixels(index).collect();
1200 assert!(
1201 pixels.iter().all(|&(x, _)| x < eighths),
1202 "{ch:?} has a filled pixel past its {eighths}/8 fill line"
1203 );
1204 assert_eq!(pixels.len(), usize::from(eighths) * 16);
1205 assert!(pixels.len() > last_count);
1206 last_count = pixels.len();
1207 }
1208 }
1209 }
1210 }
1211
1212 pub mod braille {
1219 use crate::font::BitmapFont;
1220
1221 const TOTAL: usize = 256;
1223
1224 #[allow(clippy::cast_possible_truncation)]
1230 pub const FONT: BitmapFont = BitmapFont::with_charset(&DATA, 8, 16, TOTAL as u16, &CHARSET);
1231
1232 const COL_X: [u8; 2] = [1, 4];
1234 const ROW_Y: [u8; 4] = [1, 5, 9, 13];
1236
1237 const fn bit_index(col: usize, row: usize) -> u32 {
1242 match (col, row) {
1243 (0, 0) => 0,
1244 (0, 1) => 1,
1245 (0, 2) => 2,
1246 (0, 3) => 6,
1247 (1, 0) => 3,
1248 (1, 1) => 4,
1249 (1, 2) => 5,
1250 (1, 3) => 7,
1251 _ => panic!("braille dot position out of range"),
1252 }
1253 }
1254
1255 const fn set_pixel(data: &mut [u8; TOTAL * 16], index: usize, x: u8, y: u8) {
1258 let row = index * 16 + y as usize;
1259 data[row] |= 1 << (7 - x);
1260 }
1261
1262 const fn build_data() -> [u8; TOTAL * 16] {
1265 #[allow(clippy::cast_possible_truncation)]
1266 const TOTAL_U32: u32 = TOTAL as u32;
1267
1268 let mut data = [0u8; TOTAL * 16];
1269
1270 let mut bits: u32 = 0;
1271 while bits < TOTAL_U32 {
1272 let index = bits as usize;
1273 let mut col = 0usize;
1274 while col < 2 {
1275 let mut row = 0usize;
1276 while row < 4 {
1277 let bit = bit_index(col, row);
1278 if (bits >> bit) & 1 == 1 {
1279 let cx = COL_X[col];
1280 let cy = ROW_Y[row];
1281 let mut dy: i32 = -1;
1282 while dy <= 1 {
1283 let mut dx: i32 = -1;
1284 while dx <= 1 {
1285 let px = cx as i32 + dx;
1286 let py = cy as i32 + dy;
1287 if px >= 0 && px < 8 && py >= 0 && py < 16 {
1288 #[allow(
1289 clippy::cast_sign_loss,
1290 clippy::cast_possible_truncation
1291 )]
1292 set_pixel(&mut data, index, px as u8, py as u8);
1293 }
1294 dx += 1;
1295 }
1296 dy += 1;
1297 }
1298 }
1299 row += 1;
1300 }
1301 col += 1;
1302 }
1303 bits += 1;
1304 }
1305
1306 data
1307 }
1308
1309 const fn build_charset() -> [char; TOTAL] {
1312 #[allow(clippy::cast_possible_truncation)]
1313 const TOTAL_U32: u32 = TOTAL as u32;
1314
1315 let mut charset = ['\0'; TOTAL];
1316
1317 let mut bits: u32 = 0;
1318 while bits < TOTAL_U32 {
1319 let cp = 0x2800 + bits;
1320 let Some(ch) = char::from_u32(cp) else {
1321 panic!("braille codepoint is not a valid char")
1322 };
1323 charset[bits as usize] = ch;
1324 bits += 1;
1325 }
1326
1327 charset
1328 }
1329
1330 static DATA: [u8; TOTAL * 16] = build_data();
1333
1334 static CHARSET: [char; TOTAL] = build_charset();
1336
1337 #[cfg(test)]
1338 mod tests {
1339 use super::{CHARSET, FONT, TOTAL};
1340 use crate::font::FontChain;
1341 use std::collections::HashSet;
1342
1343 #[test]
1344 fn total_glyph_count_is_256() {
1345 assert_eq!(TOTAL, 256);
1346 assert_eq!(FONT.glyph_count(), 256);
1347 }
1348
1349 #[test]
1350 fn every_charset_character_appears_exactly_once() {
1351 let mut seen = HashSet::with_capacity(TOTAL);
1352 for &ch in &CHARSET {
1353 assert!(seen.insert(ch), "{ch:?} appears more than once in CHARSET");
1354 }
1355 assert_eq!(seen.len(), TOTAL);
1356 }
1357
1358 #[test]
1359 fn covers_the_full_u2800_block() {
1360 for bits in 0u32..u32::try_from(TOTAL).unwrap() {
1361 let ch = char::from_u32(0x2800 + bits).unwrap();
1362 assert_eq!(CHARSET[bits as usize], ch);
1363 }
1364 }
1365
1366 #[test]
1371 fn charset_round_trips_core_symbols_braille_glyph() {
1372 for bits in 0u8..=u8::MAX {
1373 let expected = retroglyph_core::symbols::braille::glyph(bits);
1374 assert_eq!(
1375 CHARSET[bits as usize], expected,
1376 "pattern {bits:#04x}: this module's CHARSET disagrees with \
1377 retroglyph_core::symbols::braille::glyph"
1378 );
1379 }
1380 }
1381
1382 #[test]
1383 fn blank_glyph_is_all_zero_bits() {
1384 let index = FONT.glyph_index('\u{2800}').expect("U+2800 is covered");
1385 assert!(FONT.rows(index).iter().all(|&b| b == 0));
1386 }
1387
1388 #[test]
1389 fn full_glyph_has_all_dot_positions_set() {
1390 let index = FONT.glyph_index('\u{28FF}').expect("U+28FF is covered");
1391 let pixel_count = FONT.glyph_pixels(index).count();
1392 assert_eq!(pixel_count, 72);
1395 }
1396
1397 #[test]
1400 fn font_chain_resolves_via_braille_fallback() {
1401 static PRIMARY_DATA: [u8; 256 * 16] = [0; 256 * 16];
1402 const PRIMARY: crate::font::BitmapFont =
1403 crate::font::BitmapFont::new(&PRIMARY_DATA, 8, 16, 256);
1404
1405 static FALLBACKS: [crate::font::BitmapFont; 1] = [FONT];
1406 let chain = FontChain::new(PRIMARY, &FALLBACKS);
1407
1408 let braille = chain
1410 .resolve('\u{2837}')
1411 .expect("covered by legacy_computing::braille");
1412 assert_eq!(braille.font_index(), 1);
1413 assert!(!braille.is_notdef());
1414
1415 let full_block = chain.resolve('█').expect("CP437 coverage");
1418 assert_eq!(full_block.font(), PRIMARY);
1419 assert!(!full_block.is_notdef());
1420 }
1421 }
1422 }
1423}
1424
1425const NOTDEF: char = '█';
1434
1435#[allow(clippy::too_many_lines)]
1444const fn try_unicode_to_cp437(ch: char) -> Option<u8> {
1445 let u = ch as u32;
1447 if u < 0x80 {
1448 #[allow(clippy::cast_possible_truncation)]
1449 return Some(u as u8);
1450 }
1451
1452 match ch {
1454 'Ç' => Some(0x80),
1456 'ü' => Some(0x81),
1457 'é' => Some(0x82),
1458 'â' => Some(0x83),
1459 'ä' => Some(0x84),
1460 'à' => Some(0x85),
1461 'å' => Some(0x86),
1462 'ç' => Some(0x87),
1463 'ê' => Some(0x88),
1464 'ë' => Some(0x89),
1465 'è' => Some(0x8A),
1466 'ï' => Some(0x8B),
1467 'î' => Some(0x8C),
1468 'ì' => Some(0x8D),
1469 'Ä' => Some(0x8E),
1470 'Å' => Some(0x8F),
1471 'É' => Some(0x90),
1472 'æ' => Some(0x91),
1473 'Æ' => Some(0x92),
1474 'ô' => Some(0x93),
1475 'ö' => Some(0x94),
1476 'ò' => Some(0x95),
1477 'û' => Some(0x96),
1478 'ù' => Some(0x97),
1479 'ÿ' => Some(0x98),
1480 'Ö' => Some(0x99),
1481 'Ü' => Some(0x9A),
1482 '¢' => Some(0x9B),
1483 '£' => Some(0x9C),
1484 '¥' => Some(0x9D),
1485 '₧' => Some(0x9E),
1486 'ƒ' => Some(0x9F),
1487 'á' => Some(0xA0),
1488 'í' => Some(0xA1),
1489 'ó' => Some(0xA2),
1490 'ú' => Some(0xA3),
1491 'ñ' => Some(0xA4),
1492 'Ñ' => Some(0xA5),
1493 'ª' => Some(0xA6),
1494 'º' => Some(0xA7),
1495 '¿' => Some(0xA8),
1496 '⌐' => Some(0xA9),
1497 '¬' => Some(0xAA),
1498 '½' => Some(0xAB),
1499 '¼' => Some(0xAC),
1500 '¡' => Some(0xAD),
1501 '«' => Some(0xAE),
1502 '»' => Some(0xAF),
1503
1504 '░' => Some(0xB0),
1506 '▒' => Some(0xB1),
1507 '▓' => Some(0xB2),
1508
1509 '│' => Some(0xB3),
1511 '┤' => Some(0xB4),
1512 '╡' => Some(0xB5),
1513 '╢' => Some(0xB6),
1514 '╖' => Some(0xB7),
1515 '╕' => Some(0xB8),
1516 '╣' => Some(0xB9),
1517 '║' => Some(0xBA),
1518 '╗' => Some(0xBB),
1519 '╝' => Some(0xBC),
1520 '╜' => Some(0xBD),
1521 '╛' => Some(0xBE),
1522 '┐' => Some(0xBF),
1523 '└' => Some(0xC0),
1524 '┴' => Some(0xC1),
1525 '┬' => Some(0xC2),
1526 '├' => Some(0xC3),
1527 '─' => Some(0xC4),
1528 '┼' => Some(0xC5),
1529 '╞' => Some(0xC6),
1530 '╟' => Some(0xC7),
1531 '╚' => Some(0xC8),
1532 '╔' => Some(0xC9),
1533 '╩' => Some(0xCA),
1534 '╦' => Some(0xCB),
1535 '╠' => Some(0xCC),
1536 '═' => Some(0xCD),
1537 '╬' => Some(0xCE),
1538 '╧' => Some(0xCF),
1539 '╨' => Some(0xD0),
1540 '╤' => Some(0xD1),
1541 '╥' => Some(0xD2),
1542 '╙' => Some(0xD3),
1543 '╘' => Some(0xD4),
1544 '╒' => Some(0xD5),
1545 '╓' => Some(0xD6),
1546 '╫' => Some(0xD7),
1547 '╪' => Some(0xD8),
1548 '┘' => Some(0xD9),
1549 '┌' => Some(0xDA),
1550
1551 '█' => Some(0xDB),
1553 '▄' => Some(0xDC),
1554 '▌' => Some(0xDD),
1555 '▐' => Some(0xDE),
1556 '▀' => Some(0xDF),
1557
1558 'α' => Some(0xE0),
1560 'ß' => Some(0xE1),
1561 'Γ' => Some(0xE2),
1562 'π' => Some(0xE3),
1563 'Σ' => Some(0xE4),
1564 'σ' => Some(0xE5),
1565 'µ' | 'μ' => Some(0xE6),
1566 'τ' => Some(0xE7),
1567 'Φ' => Some(0xE8),
1568 'Θ' => Some(0xE9),
1569 'Ω' => Some(0xEA),
1570 'δ' => Some(0xEB),
1571 '∞' => Some(0xEC),
1572 'φ' => Some(0xED),
1573 'ε' => Some(0xEE),
1574 '∩' => Some(0xEF),
1575 '≡' => Some(0xF0),
1576 '±' => Some(0xF1),
1577 '≥' => Some(0xF2),
1578 '≤' => Some(0xF3),
1579 '⌠' => Some(0xF4),
1580 '⌡' => Some(0xF5),
1581 '÷' => Some(0xF6),
1582 '≈' => Some(0xF7),
1583 '°' => Some(0xF8),
1584 '∙' => Some(0xF9),
1585 '·' => Some(0xFA),
1586 '√' => Some(0xFB),
1587 'ⁿ' => Some(0xFC),
1588 '²' => Some(0xFD),
1589 '■' => Some(0xFE),
1590 '\u{00A0}' => Some(0xFF),
1591
1592 '☺' => Some(0x01),
1594 '•' => Some(0x07),
1595 '☻' => Some(0x02),
1596 '♥' => Some(0x03),
1597 '♦' => Some(0x04),
1598 '♣' => Some(0x05),
1599 '♠' => Some(0x06),
1600 '◘' => Some(0x08),
1601 '○' => Some(0x09),
1602 '◙' => Some(0x0A),
1603 '♂' => Some(0x0B),
1604 '♀' => Some(0x0C),
1605 '♪' => Some(0x0D),
1606 '♫' => Some(0x0E),
1607 '☼' => Some(0x0F),
1608 '►' => Some(0x10),
1609 '◄' => Some(0x11),
1610 '↕' => Some(0x12),
1611 '‼' => Some(0x13),
1612 '¶' => Some(0x14),
1613 '§' => Some(0x15),
1614 '▬' => Some(0x16),
1615 '↨' => Some(0x17),
1616 '↑' => Some(0x18),
1617 '↓' => Some(0x19),
1618 '→' => Some(0x1A),
1619 '←' => Some(0x1B),
1620 '∟' => Some(0x1C),
1621 '↔' => Some(0x1D),
1622 '▲' => Some(0x1E),
1623 '▼' => Some(0x1F),
1624 '⌂' => Some(0x7F),
1625
1626 _ => None,
1627 }
1628}
1629
1630#[cfg(test)]
1631mod tests {
1632 use super::{BitmapFont, FontChain, try_unicode_to_cp437};
1633
1634 #[test]
1639 fn patched_glyphs_are_reachable_by_char() {
1640 assert_eq!(try_unicode_to_cp437('⌂'), Some(0x7F), "U+2302 HOUSE");
1641 assert_eq!(
1642 try_unicode_to_cp437('☼'),
1643 Some(0x0F),
1644 "U+263C WHITE SUN WITH RAYS"
1645 );
1646 assert_eq!(
1647 try_unicode_to_cp437('⌐'),
1648 Some(0xA9),
1649 "U+2310 REVERSED NOT SIGN"
1650 );
1651 assert_eq!(
1652 try_unicode_to_cp437('∙'),
1653 Some(0xF9),
1654 "U+2219 BULLET OPERATOR"
1655 );
1656 assert_eq!(
1657 try_unicode_to_cp437('\u{00A0}'),
1658 Some(0xFF),
1659 "U+00A0 NO-BREAK SPACE"
1660 );
1661 assert_eq!(try_unicode_to_cp437('¬'), Some(0xAA), "U+00AC NOT SIGN");
1662 assert_eq!(try_unicode_to_cp437('₧'), Some(0x9E), "U+20A7 PESETA SIGN");
1663 assert_eq!(try_unicode_to_cp437('·'), Some(0xFA), "U+00B7 MIDDLE DOT");
1664 }
1665
1666 #[cfg(feature = "tilesets")]
1670 #[test]
1671 fn cp437_to_unicode_round_trips_through_try_unicode_to_cp437() {
1672 for (i, &ch) in crate::tileset::CP437_TO_UNICODE.iter().enumerate() {
1673 #[allow(clippy::cast_possible_truncation)]
1674 let expected = i as u8;
1675 assert_eq!(
1676 try_unicode_to_cp437(ch),
1677 Some(expected),
1678 "0x{i:02X} {ch:?} did not round-trip"
1679 );
1680 }
1681 }
1682
1683 static PRIMARY_DATA: [u8; 128 * 16] = [0; 128 * 16];
1686 const PRIMARY: BitmapFont = BitmapFont::new(&PRIMARY_DATA, 8, 16, 128);
1687
1688 static FALLBACK_DATA: [u8; 256 * 16] = [0; 256 * 16];
1690 const FALLBACK_FONT: BitmapFont = BitmapFont::new(&FALLBACK_DATA, 8, 16, 256);
1691
1692 #[test]
1693 fn chain_resolves_char_present_only_in_fallback_font() {
1694 let chain = FontChain::new(PRIMARY, &[FALLBACK_FONT]);
1697 let resolved = chain.resolve('Ç').expect("covered by the fallback font");
1698 assert_eq!(resolved.font(), FALLBACK_FONT);
1699 assert_eq!(resolved.font_index(), 1);
1700 assert_eq!(resolved.index(), 0x80);
1701 assert!(!resolved.is_notdef());
1702 }
1703
1704 #[test]
1705 fn chain_falls_back_to_solid_block_when_every_font_misses() {
1706 let chain = FontChain::new(PRIMARY, &[FALLBACK_FONT]);
1711 let resolved = chain.resolve('あ').expect("solid block substitute");
1712 assert_eq!(resolved.font(), FALLBACK_FONT);
1713 assert_eq!(resolved.index(), 0xDB);
1714 assert!(resolved.is_notdef());
1715 }
1716
1717 #[test]
1718 fn chain_resolves_nothing_when_no_font_has_a_substitute() {
1719 static DATA: [u8; 16] = [0; 16];
1723 const CHARSET: [char; 1] = ['\u{2800}'];
1724 const BRAILLE: BitmapFont = BitmapFont::with_charset(&DATA, 8, 16, 1, &CHARSET);
1725
1726 let chain = FontChain::new(BRAILLE, &[]);
1727 assert!(chain.resolve('\u{2800}').is_some());
1728 assert!(chain.resolve('A').is_none());
1729 }
1730
1731 #[test]
1732 fn single_font_chain_resolves_that_font_directly() {
1733 let chain = FontChain::from(FALLBACK_FONT);
1734 assert_eq!(chain.font_count(), 1);
1735 for ch in ['A', ' ', '█', '│', 'Ç', '☺'] {
1736 let resolved = chain.resolve(ch).expect("CP437 coverage");
1737 assert_eq!(resolved.font(), FALLBACK_FONT);
1738 assert_eq!(resolved.font_index(), 0);
1739 assert_eq!(resolved.index(), FALLBACK_FONT.glyph_index(ch).unwrap());
1740 }
1741 }
1742
1743 #[test]
1744 fn glyph_size_is_none_for_a_chain_of_mismatched_fonts() {
1745 static DATA: [u8; 8] = [0; 8];
1746 const SHORT: BitmapFont = BitmapFont::new(&DATA, 8, 8, 1);
1747
1748 assert_eq!(
1749 FontChain::new(PRIMARY, &[FALLBACK_FONT]).glyph_size(),
1750 Some((8, 16))
1751 );
1752 assert_eq!(FontChain::new(PRIMARY, &[SHORT]).glyph_size(), None);
1753 }
1754
1755 #[test]
1756 fn glyph_pixels_decodes_msb_first_row_major() {
1757 static DATA: [u8; 4] = [0b1000_0001, 0b0000_0000, 0b1111_1111, 0b0000_1000];
1760 let font = BitmapFont::new(&DATA, 8, 2, 2);
1761
1762 let g0: Vec<(u8, u8)> = font.glyph_pixels(0).collect();
1763 assert_eq!(
1764 g0,
1765 [(0, 0), (7, 0)],
1766 "MSB is the leftmost pixel; row 0 first"
1767 );
1768
1769 let g1: Vec<(u8, u8)> = font.glyph_pixels(1).collect();
1770 let mut expected: Vec<(u8, u8)> = (0..8).map(|x| (x, 0)).collect();
1771 expected.push((4, 1)); assert_eq!(g1, expected);
1773 }
1774
1775 #[test]
1776 fn glyph_pixels_is_parameterized_by_width_not_hardcoded_to_8() {
1777 static DATA: [u8; 1] = [0b0001_0001];
1780 let font = BitmapFont::new(&DATA, 5, 1, 1);
1781 let pixels: Vec<(u8, u8)> = font.glyph_pixels(0).collect();
1782 assert_eq!(pixels, [(0, 0), (4, 0)]);
1783 }
1784
1785 #[test]
1786 fn glyph_pixels_does_not_overflow_the_shift_for_width_above_8() {
1787 static DATA: [u8; 1] = [0b1111_1111];
1790 let font = BitmapFont::new(&DATA, 12, 1, 1);
1791 let pixels: Vec<(u8, u8)> = font.glyph_pixels(0).collect();
1792 assert_eq!(pixels, (0..8).map(|x| (x, 0)).collect::<Vec<_>>());
1793 }
1794
1795 #[test]
1801 fn chain_extends_past_cp437_via_charset_fallback_font() {
1802 static BRAILLE_DATA: [u8; 16] = [0; 16];
1803 const BRAILLE_CHARSET: [char; 1] = ['\u{2800}'];
1804 const BRAILLE_FONT: BitmapFont =
1805 BitmapFont::with_charset(&BRAILLE_DATA, 8, 16, 1, &BRAILLE_CHARSET);
1806
1807 let primary = FALLBACK_FONT; let chain = FontChain::new(primary, &[BRAILLE_FONT]);
1809
1810 let braille = chain.resolve('\u{2800}').expect("charset coverage");
1811 assert_eq!(braille.font(), BRAILLE_FONT);
1812 assert_eq!(braille.index(), 0);
1813
1814 let full_block = chain.resolve('\u{2588}').expect("CP437 coverage"); assert_eq!(full_block.font(), primary);
1816 assert_eq!(full_block.index(), 0xDB);
1817
1818 assert_ne!(braille.index(), full_block.index());
1819 }
1820}
1821
1822#[cfg(all(test, feature = "default-font", feature = "legacy-computing"))]
1832mod symbols_coverage {
1833 use crate::font::{BitmapFont, FontChain, legacy_computing, unscii16};
1834 use retroglyph_core::symbols::{bar, block, border, line};
1835 use std::collections::HashSet;
1836
1837 fn chain() -> FontChain<'static> {
1840 static FALLBACKS: [BitmapFont; 2] = [
1841 legacy_computing::blocks::FONT,
1842 legacy_computing::braille::FONT,
1843 ];
1844 FontChain::new(unscii16::FONT, &FALLBACKS)
1845 }
1846
1847 fn known_notdef_gaps() -> HashSet<char> {
1859 [
1860 border::ROUNDED.top_left,
1862 border::ROUNDED.top_right,
1863 border::ROUNDED.bottom_left,
1864 border::ROUNDED.bottom_right,
1865 border::THICK.top_left,
1867 border::THICK.top_right,
1868 border::THICK.bottom_left,
1869 border::THICK.bottom_right,
1870 border::THICK.horizontal,
1871 border::THICK.vertical,
1872 line::THICK.cross,
1875 line::THICK.vertical_left,
1876 line::THICK.vertical_right,
1877 line::THICK.horizontal_down,
1878 line::THICK.horizontal_up,
1879 ]
1880 .into_iter()
1881 .collect()
1882 }
1883
1884 #[test]
1885 fn every_symbols_entry_resolves_or_is_a_known_gap() {
1886 let chain = chain();
1887 let known = known_notdef_gaps();
1888 let mut still_notdef = HashSet::new();
1889
1890 let mut check = |ch: char| {
1891 let resolved = chain.resolve(ch);
1892 let is_notdef = resolved.is_none_or(|g| g.is_notdef());
1893 if is_notdef {
1894 still_notdef.insert(ch);
1895 }
1896 };
1897
1898 for set in [
1899 border::PLAIN,
1900 border::ROUNDED,
1901 border::DOUBLE,
1902 border::THICK,
1903 ] {
1904 check(set.top_left);
1905 check(set.top_right);
1906 check(set.bottom_left);
1907 check(set.bottom_right);
1908 check(set.horizontal);
1909 check(set.vertical);
1910 }
1911
1912 for set in [line::NORMAL, line::DOUBLE, line::THICK] {
1913 check(set.horizontal);
1914 check(set.vertical);
1915 check(set.cross);
1916 check(set.vertical_left);
1917 check(set.vertical_right);
1918 check(set.horizontal_down);
1919 check(set.horizontal_up);
1920 }
1921
1922 for ch in [
1923 block::FULL,
1924 block::SEVEN_EIGHTHS,
1925 block::THREE_QUARTERS,
1926 block::FIVE_EIGHTHS,
1927 block::HALF,
1928 block::THREE_EIGHTHS,
1929 block::ONE_QUARTER,
1930 block::ONE_EIGHTH,
1931 ] {
1932 check(ch);
1933 }
1934
1935 for ch in bar::NINE_LEVELS {
1936 check(ch);
1937 }
1938
1939 for pattern in 0u8..=u8::MAX {
1940 check(retroglyph_core::symbols::braille::glyph(pattern));
1941 }
1942
1943 for &ch in &still_notdef {
1944 assert!(
1945 known.contains(&ch),
1946 "{ch:?} (U+{:04X}) newly falls back to notdef through the bundled chain; \
1947 either fix its font coverage or add it to `known_notdef_gaps`",
1948 ch as u32
1949 );
1950 }
1951 assert_eq!(
1952 still_notdef, known,
1953 "`known_notdef_gaps` is stale: a previously-notdef glyph now resolves through the \
1954 bundled chain. Shrink the allowlist to match."
1955 );
1956 }
1957}