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