Skip to main content

retroglyph_window/
font.rs

1//! Bitmap glyph fonts and CP437 mapping, shared by retroglyph's graphical backends.
2//!
3//! A [`BitmapFont`] holds a static 1-bit-per-pixel glyph table. Each glyph is stored as
4//! `glyph_height` bytes, one byte per row, MSB = leftmost pixel. For the standard 8-pixel-wide
5//! VGA format one byte covers all 8 pixels of a row; wider fonts would need two bytes per row,
6//! but that is not yet supported.
7//!
8//! This module is the dependency-free glyph-source layer both `retroglyph-software` (CPU
9//! rasterizer) and `retroglyph-gl` (GPU atlas) build on, so their text output stays
10//! pixel-identical. It lives here (rather than in a standalone crate) because both consumers
11//! already depend on `retroglyph-window` for [`Presenter`](crate::Presenter), and it needs none of
12//! winit: it is available with `default-features = false`. Enable the `default-font` feature for
13//! the embedded Unscii 16 font ([`unscii16::FONT`]); leave it off to supply your own via
14//! [`BitmapFont::new`].
15//!
16//! # Future work
17//!
18//! - **Expanded glyph cache:** For wider fonts (>8px), consider pre-computing expanded scanlines
19//!   to avoid per-frame bit extraction. Currently the bit extraction loop is not a bottleneck
20//!   for 8px-wide fonts at typical grid sizes, but wider fonts (10px, 16px) would benefit from
21//!   caching.
22//!
23//! - **Wider glyphs:** To support glyphs wider than 8px, change [`BitmapFont::rows`] to return
24//!   `ceil(glyph_width / 8)` bytes per row and update the consumers' bit extraction to index
25//!   across bytes. Tracked in retroglyph issue #164; deferred until a second, non-8px-wide font
26//!   is actually needed.
27
28// ── BitmapFont ─────────────────────────────────────────────────────────────
29
30/// A 1-bit-per-pixel bitmap glyph font.
31///
32/// `Copy` because it is just a static reference plus a few small fields.
33#[derive(Debug, Clone, Copy)]
34pub struct BitmapFont {
35    /// Glyph bitmap data: `glyph_count * glyph_height` bytes.
36    data: &'static [u8],
37    /// Width of each glyph in pixels (≤ 8 for single-byte rows).
38    pub glyph_width: u8,
39    /// Height of each glyph in pixels; also bytes per glyph.
40    pub glyph_height: u8,
41    /// Total number of glyphs stored in `data`.
42    glyph_count: u16,
43    /// The `char` -> glyph-index table used by [`glyph_index`](Self::glyph_index), or `None` to
44    /// use the built-in CP437 mapping.
45    ///
46    /// A font built with [`with_charset`](Self::with_charset) declares its own repertoire
47    /// instead of being routed through the CP437 table every other font shares: this is what
48    /// lets a [`FontChain`] extend coverage past CP437 (e.g. quadrants, sextants, braille)
49    /// rather than every font in the chain answering the identical CP437 question.
50    charset: Option<&'static [(char, u8)]>,
51}
52
53impl BitmapFont {
54    /// Constructs a bitmap font from a static byte slice, mapped through the built-in CP437
55    /// `char` encoding.
56    ///
57    /// `data` must contain exactly `glyph_count * glyph_height` bytes.
58    #[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    /// Constructs a bitmap font from a static byte slice, mapped through an explicit
75    /// `char` -> glyph-index `charset` instead of the built-in CP437 encoding.
76    ///
77    /// This is how a font extends coverage past CP437: [`glyph_index`](Self::glyph_index) looks a
78    /// `char` up in `charset` instead of the CP437 table, so a font built this way can answer for
79    /// codepoints (quadrants, sextants, braille, ...) that CP437 has no mapping for at all.
80    /// `data` must contain exactly `glyph_count * glyph_height` bytes.
81    ///
82    /// `charset` is scanned linearly, so it is meant for the focused repertoire a font actually
83    /// declares (a few dozen block or marker glyphs), not for a second general-purpose encoding
84    /// table.
85    #[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    /// Returns the row bytes for glyph `index`.
103    ///
104    /// Each byte is one row; bit 7 (MSB) is the leftmost pixel.
105    ///
106    /// # Panics
107    ///
108    /// Panics if `index as u16 >= self.glyph_count`.
109    #[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    /// Iterates the set ("on") pixels of glyph `index` as `(x, y)` coordinates, row-major from the
122    /// top: `x` in `0..glyph_width`, `y` in `0..glyph_height`.
123    ///
124    /// This is the single place the 1-bit format's MSB-first bit order lives (pixel `x` of a row
125    /// is bit `glyph_width - 1 - x` of that row's byte), so consumers (the GL atlas builder, the
126    /// software rasterizer's glyph blit) decode through it instead of each re-deriving the shift
127    /// and risking disagreement. It is also the one seam that has to change for wider-than-8px
128    /// glyphs (multi-byte rows, #164): today a row is a single byte (`glyph_width <= 8`), so its
129    /// bits are read straight out of that byte.
130    ///
131    /// # Panics
132    ///
133    /// Panics if `index as u16 >= self.glyph_count` (via [`rows`](Self::rows)).
134    #[must_use = "iterators are lazy and do nothing unless consumed"]
135    pub fn glyph_pixels(&self, index: u8) -> impl Iterator<Item = (u8, u8)> + '_ {
136        let width = self.glyph_width;
137        self.rows(index)
138            .iter()
139            .enumerate()
140            .flat_map(move |(y, &row)| {
141                #[allow(clippy::cast_possible_truncation)]
142                let y = y as u8;
143                (0..width)
144                    .filter_map(move |x| ((row >> (width - 1 - x)) & 1 == 1).then_some((x, y)))
145            })
146    }
147
148    /// The total number of glyphs stored in this font.
149    ///
150    /// Glyph indices `0..glyph_count()` are valid arguments to [`rows`](Self::rows). A GPU
151    /// backend uses this to size its glyph atlas (one texture-array layer per glyph).
152    #[must_use]
153    pub const fn glyph_count(&self) -> u16 {
154        self.glyph_count
155    }
156
157    /// Maps a Unicode `char` to a glyph index in this font, or `None` if this font does not
158    /// cover `ch`.
159    ///
160    /// If this font was built with [`with_charset`](Self::with_charset), `ch` is looked up in
161    /// that explicit table; otherwise it goes through the built-in CP437 mapping. A miss is
162    /// either `ch` not being in this font's repertoire at all, or its mapped index falling
163    /// outside this font's `glyph_count` (e.g. a font built with fewer than 256 glyphs).
164    ///
165    /// A returned index is always `< glyph_count()`, so it is always a valid argument to
166    /// [`rows`](Self::rows) and [`glyph_pixels`](Self::glyph_pixels).
167    ///
168    /// Substituting something drawable for a miss is [`FontChain::resolve`]'s job, not this
169    /// one's: a font cannot answer for a character it has no glyph for, and pretending otherwise
170    /// is what hides a chain's later fonts from ever being consulted.
171    #[must_use]
172    pub const fn glyph_index(&self, ch: char) -> Option<u8> {
173        if let Some(table) = self.charset {
174            let mut i = 0;
175            while i < table.len() {
176                let (table_ch, index) = table[i];
177                if table_ch == ch && (index as u16) < self.glyph_count {
178                    return Some(index);
179                }
180                i += 1;
181            }
182            return None;
183        }
184        match try_unicode_to_cp437(ch) {
185            Some(index) if (index as u16) < self.glyph_count => Some(index),
186            _ => None,
187        }
188    }
189}
190
191// Two `BitmapFont`s are equal when they point at the same static data and
192// share the same dimensions.  Comparing the full 4 KB slice on every draw
193// call would be wasteful, so we compare the data pointer instead.
194impl PartialEq for BitmapFont {
195    fn eq(&self, other: &Self) -> bool {
196        core::ptr::eq(self.data.as_ptr(), other.data.as_ptr())
197            && self.glyph_width == other.glyph_width
198            && self.glyph_height == other.glyph_height
199            && self.glyph_count == other.glyph_count
200    }
201}
202
203impl Eq for BitmapFont {}
204
205// ── Font chain ──────────────────────────────────────────────────────────────
206
207/// A glyph resolved from a [`FontChain`]: the glyph index plus the specific [`BitmapFont`] it
208/// came from, since each font in a chain owns its own bitmap data.
209#[derive(Debug, Clone, Copy, PartialEq, Eq)]
210pub struct ResolvedGlyph {
211    font: BitmapFont,
212    font_index: usize,
213    index: u8,
214    notdef: bool,
215}
216
217impl ResolvedGlyph {
218    /// The font this glyph was resolved from.
219    #[must_use]
220    pub const fn font(&self) -> &BitmapFont {
221        &self.font
222    }
223
224    /// The position of [`font`](Self::font) within the chain that resolved it: `0` is the
225    /// primary font, `1..` the fallbacks in order.
226    ///
227    /// A GPU backend packs every font in the chain into one atlas and addresses a glyph by a flat
228    /// slot, so it needs the font's position (a stable index into [`FontChain::fonts`]) rather
229    /// than the font value, which carries no identity of its own.
230    #[must_use]
231    pub const fn font_index(&self) -> usize {
232        self.font_index
233    }
234
235    /// The glyph index within [`font`](Self::font), always `< font().glyph_count()`.
236    #[must_use]
237    pub const fn index(&self) -> u8 {
238        self.index
239    }
240
241    /// Whether this is the substituted "no glyph" box rather than a glyph for the character that
242    /// was asked for: `true` when no font in the chain covered that character and
243    /// [`FontChain::resolve`] fell back to the solid block.
244    #[must_use]
245    pub const fn is_notdef(&self) -> bool {
246        self.notdef
247    }
248
249    /// Returns the row bytes for this glyph; see [`BitmapFont::rows`].
250    #[must_use]
251    pub fn rows(&self) -> &[u8] {
252        self.font.rows(self.index)
253    }
254}
255
256/// The glyph source a backend draws from: a primary [`BitmapFont`] plus an ordered list of
257/// fallback fonts.
258///
259/// This is the only character-to-glyph path the bundled pixel backends have. A single font is a
260/// chain of one (`FontChain::from(font)`), so `SoftwareBackendBuilder::font` and
261/// `GlBackendBuilder::font` both take an `impl Into<FontChain<'static>>` and there is no second,
262/// chain-blind route that could quietly ignore a font's declared repertoire.
263///
264/// [`resolve`](Self::resolve) tries the primary font first, then each fallback in order, and only
265/// if every font misses substitutes the solid block (`'█'`) from the first font in the chain that
266/// has one. This lets a caller layer, say, an ASCII or partial-coverage primary font with one or
267/// more broader fallback fonts, so a char missing from the primary doesn't automatically become a
268/// solid block if some other font in the chain actually has it.
269///
270/// This type ships **no bundled fallback font data**: every font in the chain, primary or
271/// fallback, is supplied by the caller. Bundling a ready-to-use Latin-1/Extended or sub-cell
272/// (quadrant/sextant/braille) fallback font is a natural follow-up now that this mechanism is
273/// reachable end to end, but is out of scope here.
274///
275/// A fallback font only extends the chain's repertoire if it declares coverage for the
276/// characters it is meant to answer for. A [`BitmapFont::new`] font is always resolved through
277/// the built-in CP437 table, so stacking several CP437 fonts in a chain never reaches past CP437:
278/// every font in the chain answers the identical question. To actually extend coverage (e.g.
279/// quadrants, sextants, braille, none of which CP437 has a mapping for), build the fallback font
280/// with [`BitmapFont::with_charset`] and an explicit table covering those codepoints. Until a
281/// chain does, `retroglyph_core::subcell`'s `quantize_quadrant`/`quantize_sextant` glyphs render
282/// as a solid block on the pixel backends; see those functions' docs.
283///
284/// # Examples
285///
286/// ```
287/// use retroglyph_window::font::{BitmapFont, FontChain};
288///
289/// static ASCII: [u8; 128 * 16] = [0; 128 * 16];
290/// static QUADRANTS: [u8; 3 * 16] = [0; 3 * 16];
291/// const QUADRANT_CHARSET: [(char, u8); 3] = [('▘', 0), ('▝', 1), ('▖', 2)];
292///
293/// const PRIMARY: BitmapFont = BitmapFont::new(&ASCII, 8, 16, 128);
294/// const SUBCELL: BitmapFont = BitmapFont::with_charset(&QUADRANTS, 8, 16, 3, &QUADRANT_CHARSET);
295/// static FALLBACKS: [BitmapFont; 1] = [SUBCELL];
296///
297/// let chain = FontChain::new(PRIMARY, &FALLBACKS);
298/// let quadrant = chain.resolve('▘').expect("covered by the fallback font");
299/// assert_eq!(quadrant.font_index(), 1);
300/// assert!(!quadrant.is_notdef());
301/// ```
302#[derive(Debug, Clone, Copy, PartialEq, Eq)]
303pub struct FontChain<'a> {
304    primary: BitmapFont,
305    fallbacks: &'a [BitmapFont],
306}
307
308impl From<BitmapFont> for FontChain<'static> {
309    fn from(font: BitmapFont) -> Self {
310        Self::new(font, &[])
311    }
312}
313
314impl<'a> FontChain<'a> {
315    /// Constructs a chain from a primary font and an ordered list of fallback fonts.
316    #[must_use]
317    pub const fn new(primary: BitmapFont, fallbacks: &'a [BitmapFont]) -> Self {
318        Self { primary, fallbacks }
319    }
320
321    /// The fonts in resolution order: the primary font first, then each fallback.
322    ///
323    /// The position of a font in this iterator is its [`ResolvedGlyph::font_index`].
324    pub fn fonts(&self) -> impl Iterator<Item = &BitmapFont> {
325        core::iter::once(&self.primary).chain(self.fallbacks.iter())
326    }
327
328    /// The number of fonts in the chain (always at least one).
329    #[must_use]
330    pub const fn font_count(&self) -> usize {
331        1 + self.fallbacks.len()
332    }
333
334    /// The glyph cell size (`(width, height)` in unscaled pixels) shared by every font in the
335    /// chain, or `None` if the fonts disagree.
336    ///
337    /// A grid has one cell size, so a chain whose fonts don't agree on theirs has no single
338    /// answer for how big a cell is; backends reject such a chain at build time rather than
339    /// picking one font's size and letting the others overflow or under-fill their cells.
340    #[must_use]
341    pub fn glyph_size(&self) -> Option<(u8, u8)> {
342        let size = (self.primary.glyph_width, self.primary.glyph_height);
343        self.fallbacks
344            .iter()
345            .all(|f| (f.glyph_width, f.glyph_height) == size)
346            .then_some(size)
347    }
348
349    /// Resolves `ch` to a drawable glyph, trying the primary font first, then each fallback font
350    /// in order.
351    ///
352    /// If no font covers `ch`, this substitutes the solid block (`'█'`) from the first font in
353    /// the chain that covers *it*, flagged as [`ResolvedGlyph::is_notdef`]. `None` means the
354    /// chain cannot draw `ch` at all, not even a substitute box, and the caller should draw
355    /// nothing: a chain of narrow `with_charset` fonts (say, braille only) legitimately has no
356    /// solid block to fall back to.
357    ///
358    /// A returned glyph is always in range for its font, so [`ResolvedGlyph::rows`] and
359    /// [`BitmapFont::glyph_pixels`] cannot panic on it.
360    #[must_use]
361    pub fn resolve(&self, ch: char) -> Option<ResolvedGlyph> {
362        self.lookup(ch, false).or_else(|| self.lookup(NOTDEF, true))
363    }
364
365    /// The first font in the chain covering `ch`, tagged with `notdef`.
366    fn lookup(&self, ch: char, notdef: bool) -> Option<ResolvedGlyph> {
367        self.fonts()
368            .enumerate()
369            .find_map(|(font_index, font)| {
370                font.glyph_index(ch).map(|index| (font_index, font, index))
371            })
372            .map(|(font_index, font, index)| ResolvedGlyph {
373                font: *font,
374                font_index,
375                index,
376                notdef,
377            })
378    }
379}
380
381// ── Default embedded font ──────────────────────────────────────────────────
382
383/// The Unscii 16 font, embedded when the `default-font` feature is enabled.
384///
385/// 256 glyphs laid out in CP437 order (matching `unicode_to_cp437`), each
386/// 16 bytes (1 bit per pixel, MSB = leftmost). Source: unscii's
387/// public-domain/CC0 `unscii-16.hex` (<https://github.com/viznut/unscii>),
388/// re-laid-out from Unicode codepoints to CP437 glyph indices.
389///
390/// Four CP437 codepoints that plain `unscii-16.hex` doesn't cover (U+2302
391/// HOUSE, U+263C WHITE SUN WITH RAYS, U+2310 REVERSED NOT SIGN, U+2219
392/// BULLET OPERATOR) are filled in with original pixel art or mechanical
393/// transforms of neighboring unscii glyphs (e.g. REVERSED NOT SIGN is a
394/// horizontal mirror of unscii's own NOT SIGN) rather than pulling in
395/// unscii's GPL-licensed `-full` variant (which adds GNU Unifont glyphs).
396/// `unicode_to_cp437` carries matching reverse-mapping arms for all four
397/// (`☼` already had one; the `⌂`/`⌐`/`∙` arms are new here) so all four
398/// are reachable through the normal char-to-glyph path, not just by raw
399/// glyph index.
400#[cfg(feature = "default-font")]
401pub mod unscii16 {
402    use super::BitmapFont;
403
404    /// A [`BitmapFont`] backed by the embedded Unscii 16 glyph data.
405    pub const FONT: BitmapFont = BitmapFont::new(&DATA, 8, 16, 256);
406
407    /// Unscii 16 glyph bitmaps: 256 CP437-ordered glyphs, 16 bytes each.
408    ///
409    /// Each byte is one row of 8 pixels; bit 7 (MSB) is the leftmost pixel.
410    #[rustfmt::skip]
411    static DATA: [u8; 4096] = [
412        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x00
413        0x00, 0x00, 0x7e, 0x81, 0x81, 0xa5, 0x81, 0x81, 0xbd, 0x99, 0x81, 0x81, 0x7e, 0x00, 0x00, 0x00, // 0x01
414        0x00, 0x00, 0x7e, 0xff, 0xff, 0xdb, 0xff, 0xff, 0xc3, 0xe7, 0xff, 0xff, 0x7e, 0x00, 0x00, 0x00, // 0x02
415        0x00, 0x00, 0x00, 0x6c, 0xfe, 0xfe, 0xfe, 0xfe, 0x7c, 0x7c, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, // 0x03
416        0x00, 0x00, 0x00, 0x10, 0x38, 0x38, 0x7c, 0xfe, 0x7c, 0x38, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, // 0x04
417        0x00, 0x00, 0x00, 0x10, 0x38, 0x38, 0x54, 0xfe, 0xfe, 0x54, 0x10, 0x38, 0x00, 0x00, 0x00, 0x00, // 0x05
418        0x00, 0x00, 0x00, 0x10, 0x38, 0x7c, 0xfe, 0xfe, 0xfe, 0x38, 0x38, 0x7c, 0x00, 0x00, 0x00, 0x00, // 0x06
419        0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x3c, 0x3c, 0x3c, 0x3c, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, // 0x07
420        0xff, 0xff, 0xff, 0xff, 0xe7, 0xe7, 0xc3, 0xc3, 0xc3, 0xc3, 0xe7, 0xe7, 0xff, 0xff, 0xff, 0xff, // 0x08
421        0x00, 0x00, 0x3c, 0x3c, 0x66, 0x66, 0x42, 0x42, 0x42, 0x42, 0x66, 0x66, 0x3c, 0x3c, 0x00, 0x00, // 0x09
422        0xff, 0xff, 0xc3, 0xc3, 0x99, 0x99, 0xbd, 0xbd, 0xbd, 0xbd, 0x99, 0x99, 0xc3, 0xc3, 0xff, 0xff, // 0x0a
423        0x00, 0x00, 0x00, 0x1e, 0x0e, 0x1a, 0x78, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x78, 0x00, 0x00, 0x00, // 0x0b
424        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x7e, 0x18, // 0x0c
425        0x00, 0x00, 0x00, 0x18, 0x1c, 0x1e, 0x1b, 0x18, 0x18, 0x78, 0xf8, 0x70, 0x00, 0x00, 0x00, 0x00, // 0x0d
426        0x00, 0x00, 0x00, 0x7f, 0x63, 0x63, 0x63, 0x63, 0x63, 0x67, 0xe7, 0xe6, 0xc0, 0x00, 0x00, 0x00, // 0x0e
427        0x00, 0x00, 0x00, 0x24, 0x18, 0xbd, 0x7e, 0x7e, 0xbd, 0x18, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x0f
428        0x00, 0x00, 0x00, 0x00, 0xc0, 0xf0, 0xfc, 0xff, 0xfc, 0xf0, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x10
429        0x00, 0x00, 0x00, 0x00, 0x03, 0x0f, 0x3f, 0xff, 0x3f, 0x0f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x11
430        0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, // 0x12
431        0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x66, 0x66, 0x00, 0x00, // 0x13
432        0x00, 0x00, 0x3e, 0x7a, 0x7a, 0x7a, 0x7a, 0x3a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x00, 0x00, 0x00, // 0x14
433        0x00, 0x3c, 0x66, 0x60, 0x30, 0x38, 0x6c, 0x66, 0x36, 0x1c, 0x0c, 0x06, 0x66, 0x3c, 0x00, 0x00, // 0x15
434        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x00, 0x00, // 0x16
435        0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, 0xff, // 0x17
436        0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0x18
437        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, // 0x19
438        0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0c, 0xfe, 0xfe, 0x0c, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1a
439        0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x30, 0x7f, 0x7f, 0x30, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1b
440        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x06, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1c
441        0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x66, 0xff, 0xff, 0x66, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1d
442        0x18, 0x18, 0x18, 0x18, 0x3c, 0x3c, 0x3c, 0x3c, 0x7e, 0x7e, 0x7e, 0x7e, 0xff, 0xff, 0xff, 0xff, // 0x1e
443        0xff, 0xff, 0xff, 0xff, 0x7e, 0x7e, 0x7e, 0x7e, 0x3c, 0x3c, 0x3c, 0x3c, 0x18, 0x18, 0x18, 0x18, // 0x1f
444        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20
445        0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x21
446        0x00, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x22
447        0x00, 0x00, 0x6c, 0x6c, 0x6c, 0xfe, 0x6c, 0x6c, 0x6c, 0xfe, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, // 0x23
448        0x00, 0x18, 0x18, 0x3c, 0x66, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x66, 0x3c, 0x18, 0x18, 0x00, 0x00, // 0x24
449        0x00, 0x00, 0x06, 0xc6, 0xcc, 0xcc, 0x18, 0x18, 0x30, 0x30, 0x66, 0x66, 0xc6, 0xc0, 0x00, 0x00, // 0x25
450        0x00, 0x00, 0x38, 0x6c, 0x6c, 0x38, 0x30, 0x7a, 0xde, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, // 0x26
451        0x00, 0x18, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x27
452        0x00, 0x0c, 0x18, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x18, 0x18, 0x0c, 0x00, 0x00, // 0x28
453        0x00, 0x30, 0x18, 0x18, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x18, 0x18, 0x30, 0x00, 0x00, // 0x29
454        0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x3c, 0xff, 0x3c, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2a
455        0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2b
456        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x18, 0x18, 0x30, 0x60, 0x00, // 0x2c
457        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2d
458        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x2e
459        0x03, 0x03, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x18, 0x30, 0x30, 0x60, 0x60, 0xc0, 0xc0, 0x00, 0x00, // 0x2f
460        0x00, 0x00, 0x38, 0x6c, 0xc6, 0xc6, 0xce, 0xd6, 0xe6, 0xc6, 0xc6, 0x6c, 0x38, 0x00, 0x00, 0x00, // 0x30
461        0x00, 0x00, 0x18, 0x38, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, 0x00, 0x00, // 0x31
462        0x00, 0x00, 0x3c, 0x66, 0x66, 0x06, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, // 0x32
463        0x00, 0x00, 0x3c, 0x66, 0x66, 0x06, 0x06, 0x1c, 0x06, 0x06, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x33
464        0x00, 0x00, 0x0c, 0x1c, 0x3c, 0x6c, 0xcc, 0xcc, 0xfe, 0x0c, 0x0c, 0x0c, 0x0c, 0x00, 0x00, 0x00, // 0x34
465        0x00, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x7c, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x35
466        0x00, 0x00, 0x1c, 0x30, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x36
467        0x00, 0x00, 0x7e, 0x06, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x37
468        0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x76, 0x3c, 0x6e, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x38
469        0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x06, 0x0c, 0x38, 0x00, 0x00, 0x00, // 0x39
470        0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x3a
471        0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x38, 0x18, 0x18, 0x30, 0x60, 0x00, // 0x3b
472        0x00, 0x00, 0x00, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x00, 0x00, 0x00, 0x00, // 0x3c
473        0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x3d
474        0x00, 0x00, 0x00, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, // 0x3e
475        0x00, 0x3c, 0x66, 0x66, 0x06, 0x0c, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x3f
476        0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xde, 0xde, 0xde, 0xdc, 0xc0, 0xc0, 0x7c, 0x00, 0x00, 0x00, // 0x40
477        0x00, 0x00, 0x18, 0x3c, 0x66, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x41
478        0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0x66, 0x66, 0x7c, 0x00, 0x00, 0x00, // 0x42
479        0x00, 0x00, 0x3c, 0x66, 0x66, 0x60, 0x60, 0x60, 0x60, 0x60, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x43
480        0x00, 0x00, 0x78, 0x6c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6c, 0x78, 0x00, 0x00, 0x00, // 0x44
481        0x00, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, // 0x45
482        0x00, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, // 0x46
483        0x00, 0x00, 0x3c, 0x66, 0x66, 0x60, 0x60, 0x6e, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x47
484        0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x48
485        0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, 0x00, 0x00, // 0x49
486        0x00, 0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x4a
487        0x00, 0x00, 0xc6, 0xc6, 0xcc, 0xcc, 0xd8, 0xf0, 0xd8, 0xcc, 0xcc, 0xc6, 0xc6, 0x00, 0x00, 0x00, // 0x4b
488        0x00, 0x00, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, // 0x4c
489        0x00, 0x00, 0xc6, 0xee, 0xee, 0xfe, 0xd6, 0xd6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, // 0x4d
490        0x00, 0x00, 0xc6, 0xc6, 0xe6, 0xe6, 0xf6, 0xfe, 0xde, 0xce, 0xce, 0xc6, 0xc6, 0x00, 0x00, 0x00, // 0x4e
491        0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x4f
492        0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, // 0x50
493        0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x0c, 0x06, 0x00, // 0x51
494        0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x6c, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x52
495        0x00, 0x00, 0x3c, 0x66, 0x66, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x53
496        0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x54
497        0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x55
498        0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x3c, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x56
499        0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xd6, 0xd6, 0xfe, 0xee, 0xee, 0xc6, 0x00, 0x00, 0x00, // 0x57
500        0x00, 0x00, 0xc3, 0xc3, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x3c, 0x66, 0xc3, 0xc3, 0x00, 0x00, 0x00, // 0x58
501        0x00, 0x00, 0xc3, 0xc3, 0x66, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x59
502        0x00, 0x00, 0x7e, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x30, 0x30, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, // 0x5a
503        0x00, 0x3c, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3c, 0x00, 0x00, // 0x5b
504        0xc0, 0xc0, 0x60, 0x60, 0x30, 0x30, 0x18, 0x18, 0x0c, 0x0c, 0x06, 0x06, 0x03, 0x03, 0x00, 0x00, // 0x5c
505        0x00, 0x3c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x3c, 0x00, 0x00, // 0x5d
506        0x00, 0x10, 0x38, 0x6c, 0x6c, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x5e
507        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, // 0x5f
508        0x00, 0x18, 0x18, 0x0c, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x60
509        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x61
510        0x00, 0x00, 0x60, 0x60, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x00, 0x00, 0x00, // 0x62
511        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x63
512        0x00, 0x00, 0x06, 0x06, 0x06, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x64
513        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, // 0x65
514        0x00, 0x00, 0x1e, 0x30, 0x30, 0x30, 0x7e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, // 0x66
515        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x7c, // 0x67
516        0x00, 0x00, 0x60, 0x60, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x68
517        0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1e, 0x00, 0x00, 0x00, // 0x69
518        0x00, 0x00, 0x0c, 0x0c, 0x00, 0x00, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x78, // 0x6a
519        0x00, 0x00, 0x60, 0x60, 0x60, 0x60, 0x66, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x6b
520        0x00, 0x00, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1e, 0x00, 0x00, 0x00, // 0x6c
521        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0xfe, 0xd6, 0xd6, 0xd6, 0xd6, 0xc6, 0x00, 0x00, 0x00, // 0x6d
522        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x6e
523        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x6f
524        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x60, // 0x70
525        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x06, // 0x71
526        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, // 0x72
527        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x60, 0x60, 0x3c, 0x06, 0x06, 0x7c, 0x00, 0x00, 0x00, // 0x73
528        0x00, 0x00, 0x00, 0x30, 0x30, 0x30, 0x7e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x1e, 0x00, 0x00, 0x00, // 0x74
529        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x75
530        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x00, 0x00, 0x00, // 0x76
531        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xd6, 0xd6, 0xd6, 0x7c, 0x6c, 0x00, 0x00, 0x00, // 0x77
532        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0x6c, 0x38, 0x6c, 0xc6, 0xc6, 0x00, 0x00, 0x00, // 0x78
533        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x3c, // 0x79
534        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x7e, 0x00, 0x00, 0x00, // 0x7a
535        0x00, 0x0e, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf0, 0x18, 0x18, 0x18, 0x18, 0x18, 0x0e, 0x00, 0x00, // 0x7b
536        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, // 0x7c
537        0x00, 0xe0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x1e, 0x30, 0x30, 0x30, 0x30, 0x30, 0xe0, 0x00, 0x00, // 0x7d
538        0x00, 0x72, 0xd6, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x7e
539        0x00, 0x18, 0x3c, 0x7e, 0xff, 0xc3, 0xc3, 0xc3, 0xdb, 0xdb, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x7f
540        0x00, 0x00, 0x3c, 0x66, 0x66, 0x60, 0x60, 0x60, 0x60, 0x60, 0x66, 0x66, 0x3c, 0x0c, 0x06, 0x1c, // 0x80
541        0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x81
542        0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, // 0x82
543        0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x83
544        0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x84
545        0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x85
546        0x00, 0x00, 0x3c, 0x66, 0x3c, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x86
547        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3c, 0x0c, 0x06, 0x1c, // 0x87
548        0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, // 0x88
549        0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, // 0x89
550        0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, // 0x8a
551        0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, // 0x8b
552        0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, // 0x8c
553        0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, // 0x8d
554        0x66, 0x66, 0x00, 0x18, 0x3c, 0x66, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x8e
555        0x3c, 0x66, 0x3c, 0x00, 0x18, 0x3c, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x8f
556        0x0c, 0x18, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, // 0x90
557        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x1b, 0x1b, 0x7f, 0xd8, 0xd8, 0x77, 0x00, 0x00, 0x00, // 0x91
558        0x00, 0x00, 0x3f, 0x7c, 0xfc, 0xcc, 0xcc, 0xfe, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0x00, 0x00, 0x00, // 0x92
559        0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x93
560        0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x94
561        0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x95
562        0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x96
563        0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x97
564        0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x3c, // 0x98
565        0x66, 0x66, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x99
566        0x66, 0x66, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x9a
567        0x00, 0x18, 0x18, 0x18, 0x3c, 0x66, 0x60, 0x60, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x9b
568        0x00, 0x00, 0x38, 0x6c, 0x6c, 0x60, 0x60, 0xf0, 0x60, 0x60, 0x66, 0x66, 0xfc, 0x00, 0x00, 0x00, // 0x9c
569        0x00, 0x00, 0xc3, 0xc3, 0x66, 0x66, 0x3c, 0x18, 0x7e, 0x18, 0x7e, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x9d
570        0x00, 0xfc, 0x66, 0x66, 0x7c, 0x62, 0x66, 0x6f, 0x66, 0x66, 0x66, 0xf3, 0x00, 0x00, 0x00, 0x00, // 0x9e
571        0x00, 0x0e, 0x1b, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x18, 0xd8, 0x70, 0x00, 0x00, // 0x9f
572        0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0xa0
573        0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, // 0xa1
574        0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0xa2
575        0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0xa3
576        0x00, 0x00, 0x76, 0xdc, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0xa4
577        0x76, 0xdc, 0x00, 0xc6, 0xc6, 0xe6, 0xf6, 0xde, 0xce, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, // 0xa5
578        0x00, 0x3c, 0x06, 0x06, 0x3e, 0x66, 0x66, 0x3e, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xa6
579        0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xa7
580        0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x30, 0x30, 0x60, 0x60, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0xa8
581        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xa9
582        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x06, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xaa
583        0x00, 0x40, 0xc6, 0x46, 0x4c, 0x4c, 0x18, 0x18, 0x30, 0x30, 0x6c, 0x62, 0xc4, 0xc8, 0x0e, 0x00, // 0xab
584        0x00, 0x40, 0xc6, 0x46, 0x4c, 0x4c, 0x18, 0x18, 0x30, 0x30, 0x62, 0x66, 0xca, 0xcf, 0x02, 0x00, // 0xac
585        0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // 0xad
586        0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x66, 0xcc, 0x66, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xae
587        0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x66, 0x33, 0x66, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xaf
588        0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, // 0xb0
589        0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, // 0xb1
590        0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, // 0xb2
591        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xb3
592        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xb4
593        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0x18, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xb5
594        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xec, 0xec, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xb6
595        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfc, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xb7
596        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x18, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xb8
597        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xec, 0x0c, 0xec, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xb9
598        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xba
599        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x0c, 0xec, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xbb
600        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xec, 0x0c, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xbc
601        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xbd
602        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0x18, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xbe
603        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xbf
604        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xc0
605        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xc1
606        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xc2
607        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xc3
608        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xc4
609        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xc5
610        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x18, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xc6
611        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6f, 0x6f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xc7
612        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6f, 0x60, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xc8
613        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x60, 0x6f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xc9
614        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xef, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xca
615        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xef, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xcb
616        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6f, 0x60, 0x6f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xcc
617        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xcd
618        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xef, 0x00, 0xef, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xce
619        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xcf
620        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xd0
621        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xd1
622        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xd2
623        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x7f, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xd3
624        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x18, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xd4
625        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x18, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xd5
626        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x7f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xd6
627        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xff, 0xff, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xd7
628        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0x18, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xd8
629        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xd9
630        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xda
631        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 0xdb
632        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 0xdc
633        0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0xdd
634        0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, // 0xde
635        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xdf
636        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xce, 0xc6, 0xc6, 0xc6, 0xce, 0x76, 0x00, 0x00, 0x00, // 0xe0
637        0x00, 0x00, 0x78, 0xcc, 0xcc, 0xcc, 0xd8, 0xcc, 0xc6, 0xc6, 0xc6, 0xc6, 0xcc, 0x00, 0x00, 0x00, // 0xe1
638        0x00, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, // 0xe2
639        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0xe3
640        0x00, 0x00, 0xfe, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0xfe, 0x00, 0x00, 0x00, // 0xe4
641        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xcc, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, // 0xe5
642        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0xc0, // 0xe6
643        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x0c, 0x00, 0x00, 0x00, // 0xe7
644        0x10, 0x10, 0x10, 0x7c, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0x7c, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, // 0xe8
645        0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, // 0xe9
646        0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xee, 0x6c, 0x6c, 0xee, 0x00, 0x00, 0x00, // 0xea
647        0x00, 0x00, 0xfe, 0xc0, 0xc0, 0x60, 0x30, 0x18, 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, // 0xeb
648        0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xdb, 0xdb, 0xdb, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xec
649        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc0, 0xdc, 0xd6, 0xd6, 0xd6, 0x7c, 0x10, 0x10, 0x00, // 0xed
650        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x60, 0x60, 0x3c, 0x60, 0x60, 0x3e, 0x00, 0x00, 0x00, // 0xee
651        0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, // 0xef
652        0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xf0
653        0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x00, 0x7e, 0x00, 0x00, 0x00, // 0xf1
654        0x00, 0x00, 0x00, 0x00, 0x30, 0x18, 0x0c, 0x06, 0x0c, 0x18, 0x30, 0x00, 0x7e, 0x00, 0x00, 0x00, // 0xf2
655        0x00, 0x00, 0x00, 0x00, 0x0c, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0c, 0x00, 0x7e, 0x00, 0x00, 0x00, // 0xf3
656        0x00, 0x00, 0x0e, 0x1b, 0x1b, 0x1b, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xf4
657        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xd8, 0xd8, 0xd8, 0x70, 0x00, 0x00, 0x00, // 0xf5
658        0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x7e, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xf6
659        0x00, 0x00, 0x00, 0x00, 0x72, 0xd6, 0x9c, 0x00, 0x72, 0xd6, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xf7
660        0x00, 0x3c, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xf8
661        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xf9
662        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xfa
663        0x00, 0x03, 0x03, 0x06, 0x06, 0x06, 0x06, 0x06, 0xcc, 0xcc, 0x6c, 0x38, 0x18, 0x00, 0x00, 0x00, // 0xfb
664        0x00, 0x00, 0x00, 0x78, 0x6c, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xfc
665        0x00, 0x38, 0x6c, 0x0c, 0x18, 0x30, 0x60, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xfd
666        0x00, 0x00, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x00, 0x00, // 0xfe
667        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xff
668    ];
669}
670
671// ── Generated block/braille fallback font ──────────────────────────────────
672
673/// Generated fallback [`BitmapFont`]s, embedded when the `legacy-computing` feature is enabled.
674///
675/// Covers the 10 quadrant block characters, the 60 addressable Unicode "Symbols for Legacy
676/// Computing" sextant characters, and the full 256-glyph Braille Patterns block
677/// (U+2800–U+28FF). None of these are covered by CP437 (and so not by [`unscii16`] either): quadrants and
678/// sextants exist to give [`retroglyph_core::subcell::quantize_quadrant`] and
679/// [`retroglyph_core::subcell::quantize_sextant`] a font that actually renders their glyphs as
680/// blocks instead of a CP437 solid-block substitute, and braille is a common terminal-UI density
681/// trick with no CP437 equivalent at all. All three repertoires are pure geometry -- rectangular
682/// quadrants, banded sextants, a 2x4 dot grid -- so both fonts below are computed at compile time
683/// by a `const fn` rather than transcribed from an external font file; there is no font asset
684/// backing this module and no `image`/build-script dependency.
685///
686/// This is two [`BitmapFont`]s ([`legacy_computing::blocks::FONT`] and
687/// [`legacy_computing::braille::FONT`]), not one: a [`BitmapFont`] addresses its glyphs with a
688/// `u8` index (see [`BitmapFont::rows`], [`BitmapFont::glyph_index`]), capping any single font at
689/// 256 glyphs. Braille alone needs the full 256, so folding quadrants and sextants into the same
690/// font would silently wrap their indices mod 256 and collide with braille glyphs. Splitting at
691/// the geometry boundary (block elements vs. braille) keeps every font within that limit with
692/// room to spare (70 for [`legacy_computing::blocks::FONT`]) instead of splitting mid-repertoire.
693///
694/// Add either or both as [`FontChain`] fallbacks alongside a primary CP437 font (e.g.
695/// [`unscii16`]) to extend a chain's coverage past CP437:
696///
697/// ```
698/// # #[cfg(feature = "legacy-computing")]
699/// # {
700/// use retroglyph_window::font::{FontChain, legacy_computing, unscii16};
701///
702/// static FALLBACKS: [retroglyph_window::font::BitmapFont; 2] =
703///     [legacy_computing::blocks::FONT, legacy_computing::braille::FONT];
704/// let chain = FontChain::new(unscii16::FONT, &FALLBACKS);
705/// let quadrant = chain.resolve('▘').expect("covered by legacy_computing::blocks");
706/// assert_eq!(quadrant.font_index(), 1);
707/// let braille = chain.resolve('\u{2837}').expect("covered by legacy_computing::braille");
708/// assert_eq!(braille.font_index(), 2);
709/// # }
710/// ```
711#[cfg(feature = "legacy-computing")]
712pub mod legacy_computing {
713    /// The 10 quadrant block glyphs and 60 addressable sextant glyphs CP437 has no mapping for.
714    ///
715    /// See [`super::legacy_computing`]'s module docs for why this is a separate [`BitmapFont`]
716    /// from [`super::legacy_computing::braille`] rather than one combined font.
717    ///
718    /// [`BitmapFont`]: crate::font::BitmapFont
719    pub mod blocks {
720        use crate::font::BitmapFont;
721
722        /// Number of quadrant block glyphs (the 10 not already covered by CP437).
723        const QUADRANT_COUNT: usize = 10;
724        /// Number of sextant glyphs (the 60 addressable masks not already covered by CP437).
725        const SEXTANT_COUNT: usize = 60;
726        /// Total glyph count: quadrants, then sextants, in that index order.
727        const TOTAL: usize = QUADRANT_COUNT + SEXTANT_COUNT;
728
729        /// A [`BitmapFont`] backed by the generated quadrant/sextant glyph data.
730        ///
731        /// Built with [`BitmapFont::with_charset`] (not [`BitmapFont::new`]): none of these
732        /// codepoints are in the CP437 table this crate's default mapping uses, so this font
733        /// declares its own explicit `char` -> glyph-index table instead.
734        #[allow(clippy::cast_possible_truncation)]
735        pub const FONT: BitmapFont = BitmapFont::with_charset(&DATA, 8, 16, TOTAL as u16, &CHARSET);
736
737        /// The 10 quadrant block glyphs not already covered by CP437, as `(mask, char)` pairs.
738        ///
739        /// `mask` is a 4-bit pattern, bit 0 = top-left, bit 1 = top-right, bit 2 = bottom-left,
740        /// bit 3 = bottom-right (matching `retroglyph_core::subcell::QUADRANTS`'s own bit
741        /// order), skipping the 6 masks CP437 already serves (`0` space, `3` `▀`, `5` `▌`,
742        /// `10` `▐`, `12` `▄`, `15` `█`).
743        #[rustfmt::skip]
744        const QUADRANTS: [(u8, char); QUADRANT_COUNT] = [
745            (1, '▘'), (2, '▝'), (4, '▖'), (6, '▞'), (7, '▛'),
746            (8, '▗'), (9, '▚'), (11, '▜'), (13, '▙'), (14, '▟'),
747        ];
748
749        /// The 60 addressable sextant masks, in ascending order: every 6-bit pattern `1..=62`
750        /// (`0` and `63` would be space/full-block, already CP437) except `21` and `42` (a fully
751        /// filled left/right column respectively -- CP437's own `▌`/`▐` -- which have no
752        /// codepoint of their own in the Sextants block).
753        ///
754        /// Bit order: 0 = top-left, 1 = top-right, 2 = mid-left, 3 = mid-right, 4 = bottom-left,
755        /// 5 = bottom-right (matching `retroglyph_core::subcell::SEXTANTS`'s own bit order).
756        const fn sextant_masks() -> [u8; SEXTANT_COUNT] {
757            let mut masks = [0u8; SEXTANT_COUNT];
758            let mut m: u16 = 1;
759            let mut i = 0;
760            while m <= 62 {
761                if m != 21 && m != 42 {
762                    #[allow(clippy::cast_possible_truncation)]
763                    {
764                        masks[i] = m as u8;
765                    }
766                    i += 1;
767                }
768                m += 1;
769            }
770            masks
771        }
772
773        /// Maps a sextant `mask` (`1..=62`, excluding `21`/`42`) to its codepoint in the
774        /// Symbols for Legacy Computing block.
775        ///
776        /// Sextant codepoints are not `0x1FB00 + mask`: masks `21` and `42` are gaps (see
777        /// [`QUADRANTS`] -- they're CP437's `▌`/`▐` instead), so every mask above each gap
778        /// shifts its codepoint down by one relative to a naive offset. Mask `1` -> U+1FB00;
779        /// mask `22` (one gap below it, at `21`) -> U+1FB00 + 21 - 1 = U+1FB14.
780        const fn sextant_codepoint(mask: u8) -> u32 {
781            let gaps_below = if mask > 21 { 1 } else { 0 } + if mask > 42 { 1 } else { 0 };
782            0x1_FB00 + (mask as u32 - 1) - gaps_below
783        }
784
785        /// Sets pixel `(x, y)` of glyph `index` in `data` (a full `[u8; TOTAL * 16]` glyph
786        /// table).
787        const fn set_pixel(data: &mut [u8; TOTAL * 16], index: usize, x: u8, y: u8) {
788            let row = index * 16 + y as usize;
789            data[row] |= 1 << (7 - x);
790        }
791
792        /// Computes the full glyph bitmap table: quadrants, then sextants, matching
793        /// [`CHARSET`]'s glyph-index order.
794        const fn build_data() -> [u8; TOTAL * 16] {
795            let mut data = [0u8; TOTAL * 16];
796
797            // Quadrants: each glyph is one quarter-rectangle of the 8x16 cell (mx=4, my=8
798            // split).
799            let mut qi = 0;
800            while qi < QUADRANT_COUNT {
801                let (mask, _) = QUADRANTS[qi];
802                let mut y = 0u8;
803                while y < 16 {
804                    let mut x = 0u8;
805                    while x < 8 {
806                        let bit = if x < 4 {
807                            if y < 8 { 0 } else { 2 }
808                        } else if y < 8 {
809                            1
810                        } else {
811                            3
812                        };
813                        if (mask >> bit) & 1 == 1 {
814                            set_pixel(&mut data, qi, x, y);
815                        }
816                        x += 1;
817                    }
818                    y += 1;
819                }
820                qi += 1;
821            }
822
823            // Sextants: 2 columns (mx=4) x 3 row bands (y=0,5,11,16 -- uneven, to avoid a 1px
824            // seam between vertically stacked filled cells).
825            let masks = sextant_masks();
826            let mut si = 0;
827            while si < SEXTANT_COUNT {
828                let mask = masks[si];
829                let index = QUADRANT_COUNT + si;
830                let mut y = 0u8;
831                while y < 16 {
832                    let row = if y < 5 {
833                        0
834                    } else if y < 11 {
835                        1
836                    } else {
837                        2
838                    };
839                    let mut x = 0u8;
840                    while x < 8 {
841                        let col: usize = if x >= 4 { 1 } else { 0 };
842                        let bit = row * 2 + col;
843                        if (mask >> bit) & 1 == 1 {
844                            set_pixel(&mut data, index, x, y);
845                        }
846                        x += 1;
847                    }
848                    y += 1;
849                }
850                si += 1;
851            }
852
853            data
854        }
855
856        /// Computes the `char` -> glyph-index charset table, matching [`build_data`]'s glyph
857        /// order.
858        const fn build_charset() -> [(char, u8); TOTAL] {
859            let mut charset = [('\0', 0u8); TOTAL];
860
861            let mut qi = 0;
862            while qi < QUADRANT_COUNT {
863                let (_, ch) = QUADRANTS[qi];
864                #[allow(clippy::cast_possible_truncation)]
865                {
866                    charset[qi] = (ch, qi as u8);
867                }
868                qi += 1;
869            }
870
871            let masks = sextant_masks();
872            let mut si = 0;
873            while si < SEXTANT_COUNT {
874                let cp = sextant_codepoint(masks[si]);
875                let Some(ch) = char::from_u32(cp) else {
876                    panic!("sextant codepoint is not a valid char")
877                };
878                let index = QUADRANT_COUNT + si;
879                #[allow(clippy::cast_possible_truncation)]
880                {
881                    charset[index] = (ch, index as u8);
882                }
883                si += 1;
884            }
885
886            charset
887        }
888
889        /// Glyph bitmap data for [`FONT`]: `TOTAL` glyphs, 16 bytes each, computed at compile
890        /// time.
891        static DATA: [u8; TOTAL * 16] = build_data();
892
893        /// The `char` -> glyph-index table for [`FONT`], computed at compile time.
894        static CHARSET: [(char, u8); TOTAL] = build_charset();
895
896        #[cfg(test)]
897        mod tests {
898            use super::{CHARSET, FONT, SEXTANT_COUNT, TOTAL, sextant_codepoint, sextant_masks};
899            use crate::font::FontChain;
900            use std::collections::HashSet;
901
902            #[test]
903            fn total_glyph_count_matches_quadrants_plus_sextants() {
904                assert_eq!(TOTAL, 10 + 60);
905                assert_eq!(FONT.glyph_count(), u16::try_from(TOTAL).unwrap());
906            }
907
908            #[test]
909            fn no_charset_entry_duplicates_a_codepoint_cp437_already_serves() {
910                // Space, the 4 CP437 half/quadrant blocks, the full block, and the shade ramp
911                // are all already reachable through `unscii16`/CP437; this font must not
912                // re-supply them.
913                let already_cp437: HashSet<char> = [' ', '▀', '▄', '▌', '▐', '█', '░', '▒', '▓']
914                    .into_iter()
915                    .collect();
916                for &(ch, _) in &CHARSET {
917                    assert!(
918                        !already_cp437.contains(&ch),
919                        "{ch:?} (U+{:04X}) duplicates existing CP437 coverage",
920                        ch as u32
921                    );
922                }
923            }
924
925            #[test]
926            fn every_charset_character_appears_exactly_once() {
927                let mut seen = HashSet::with_capacity(TOTAL);
928                for &(ch, _) in &CHARSET {
929                    assert!(seen.insert(ch), "{ch:?} appears more than once in CHARSET");
930                }
931                assert_eq!(seen.len(), TOTAL);
932            }
933
934            #[test]
935            fn sextant_masks_skip_21_and_42() {
936                let masks = sextant_masks();
937                assert_eq!(masks.len(), SEXTANT_COUNT);
938                assert!(!masks.contains(&21));
939                assert!(!masks.contains(&42));
940                assert_eq!(masks[0], 1);
941                assert_eq!(masks[SEXTANT_COUNT - 1], 62);
942            }
943
944            #[test]
945            fn sextant_codepoint_shifts_down_after_each_gap() {
946                assert_eq!(sextant_codepoint(1), 0x1FB00);
947                // One gap below (mask 21) has already been skipped by the time mask 22 is
948                // reached.
949                assert_eq!(sextant_codepoint(22), 0x1FB00 + 21 - 1);
950                // Two gaps below (masks 21 and 42) have been skipped by mask 43.
951                assert_eq!(sextant_codepoint(43), 0x1FB00 + 42 - 2);
952            }
953
954            /// Every quadrant glyph's set pixels fall in the correct quarter of the 8x16 cell.
955            #[test]
956            fn quadrant_top_left_mask_only_fills_the_top_left_quarter() {
957                let index = FONT.glyph_index('▘').expect("U+2598 is covered");
958                for (x, y) in FONT.glyph_pixels(index) {
959                    assert!(x < 4 && y < 8, "({x}, {y}) outside the top-left quarter");
960                }
961            }
962
963            #[test]
964            fn font_chain_resolves_quadrant_via_blocks_fallback() {
965                static PRIMARY_DATA: [u8; 256 * 16] = [0; 256 * 16];
966                const PRIMARY: crate::font::BitmapFont =
967                    crate::font::BitmapFont::new(&PRIMARY_DATA, 8, 16, 256);
968
969                static FALLBACKS: [crate::font::BitmapFont; 1] = [FONT];
970                let chain = FontChain::new(PRIMARY, &FALLBACKS);
971
972                let quadrant = chain
973                    .resolve('▘')
974                    .expect("covered by legacy_computing::blocks");
975                assert_eq!(quadrant.font_index(), 1);
976                assert!(!quadrant.is_notdef());
977            }
978        }
979    }
980
981    /// The full 256-glyph Braille Patterns block (U+2800–U+28FF) CP437 has no mapping for.
982    ///
983    /// See [`super::legacy_computing`]'s module docs for why this is a separate [`BitmapFont`]
984    /// from [`super::legacy_computing::blocks`] rather than one combined font.
985    ///
986    /// [`BitmapFont`]: crate::font::BitmapFont
987    pub mod braille {
988        use crate::font::BitmapFont;
989
990        /// Total glyph count: the full U+2800..=U+28FF block.
991        const TOTAL: usize = 256;
992
993        /// A [`BitmapFont`] backed by the generated braille glyph data.
994        ///
995        /// Built with [`BitmapFont::with_charset`] (not [`BitmapFont::new`]): braille
996        /// codepoints are not in the CP437 table this crate's default mapping uses, so this
997        /// font declares its own explicit `char` -> glyph-index table instead.
998        #[allow(clippy::cast_possible_truncation)]
999        pub const FONT: BitmapFont = BitmapFont::with_charset(&DATA, 8, 16, TOTAL as u16, &CHARSET);
1000
1001        /// Dot-column pixel centers for braille glyphs (`x`), in cell-pixel coordinates.
1002        const COL_X: [u8; 2] = [1, 4];
1003        /// Dot-row pixel centers for braille glyphs (`y`), in cell-pixel coordinates.
1004        const ROW_Y: [u8; 4] = [1, 5, 9, 13];
1005
1006        /// Maps a braille dot's `(col, row)` position (`col` in `0..2`, `row` in `0..4`) to its
1007        /// bit index in the U+2800 block's `u8` payload, per historical braille dot numbering:
1008        /// column 0 is dots 1,2,3,7 (bit indices 0,1,2,6), column 1 is dots 4,5,6,8 (bit indices
1009        /// 3,4,5,7).
1010        const fn bit_index(col: usize, row: usize) -> u32 {
1011            match (col, row) {
1012                (0, 0) => 0,
1013                (0, 1) => 1,
1014                (0, 2) => 2,
1015                (0, 3) => 6,
1016                (1, 0) => 3,
1017                (1, 1) => 4,
1018                (1, 2) => 5,
1019                (1, 3) => 7,
1020                _ => panic!("braille dot position out of range"),
1021            }
1022        }
1023
1024        /// Sets pixel `(x, y)` of glyph `index` in `data` (a full `[u8; TOTAL * 16]` glyph
1025        /// table).
1026        const fn set_pixel(data: &mut [u8; TOTAL * 16], index: usize, x: u8, y: u8) {
1027            let row = index * 16 + y as usize;
1028            data[row] |= 1 << (7 - x);
1029        }
1030
1031        /// Computes the full glyph bitmap table: 2 columns x 4 rows of dots per glyph, each dot
1032        /// a 3x3 filled square, matching [`CHARSET`]'s glyph-index order.
1033        const fn build_data() -> [u8; TOTAL * 16] {
1034            #[allow(clippy::cast_possible_truncation)]
1035            const TOTAL_U32: u32 = TOTAL as u32;
1036
1037            let mut data = [0u8; TOTAL * 16];
1038
1039            let mut bits: u32 = 0;
1040            while bits < TOTAL_U32 {
1041                let index = bits as usize;
1042                let mut col = 0usize;
1043                while col < 2 {
1044                    let mut row = 0usize;
1045                    while row < 4 {
1046                        let bit = bit_index(col, row);
1047                        if (bits >> bit) & 1 == 1 {
1048                            let cx = COL_X[col];
1049                            let cy = ROW_Y[row];
1050                            let mut dy: i32 = -1;
1051                            while dy <= 1 {
1052                                let mut dx: i32 = -1;
1053                                while dx <= 1 {
1054                                    let px = cx as i32 + dx;
1055                                    let py = cy as i32 + dy;
1056                                    if px >= 0 && px < 8 && py >= 0 && py < 16 {
1057                                        #[allow(
1058                                            clippy::cast_sign_loss,
1059                                            clippy::cast_possible_truncation
1060                                        )]
1061                                        set_pixel(&mut data, index, px as u8, py as u8);
1062                                    }
1063                                    dx += 1;
1064                                }
1065                                dy += 1;
1066                            }
1067                        }
1068                        row += 1;
1069                    }
1070                    col += 1;
1071                }
1072                bits += 1;
1073            }
1074
1075            data
1076        }
1077
1078        /// Computes the `char` -> glyph-index charset table, matching [`build_data`]'s glyph
1079        /// order: `CHARSET[i] == (char::from_u32(0x2800 + i).unwrap(), i as u8)`.
1080        const fn build_charset() -> [(char, u8); TOTAL] {
1081            #[allow(clippy::cast_possible_truncation)]
1082            const TOTAL_U32: u32 = TOTAL as u32;
1083
1084            let mut charset = [('\0', 0u8); TOTAL];
1085
1086            let mut bits: u32 = 0;
1087            while bits < TOTAL_U32 {
1088                let cp = 0x2800 + bits;
1089                let Some(ch) = char::from_u32(cp) else {
1090                    panic!("braille codepoint is not a valid char")
1091                };
1092                let index = bits as usize;
1093                #[allow(clippy::cast_possible_truncation)]
1094                {
1095                    charset[index] = (ch, index as u8);
1096                }
1097                bits += 1;
1098            }
1099
1100            charset
1101        }
1102
1103        /// Glyph bitmap data for [`FONT`]: `TOTAL` glyphs, 16 bytes each, computed at compile
1104        /// time.
1105        static DATA: [u8; TOTAL * 16] = build_data();
1106
1107        /// The `char` -> glyph-index table for [`FONT`], computed at compile time.
1108        static CHARSET: [(char, u8); TOTAL] = build_charset();
1109
1110        #[cfg(test)]
1111        mod tests {
1112            use super::{CHARSET, FONT, TOTAL};
1113            use crate::font::FontChain;
1114            use std::collections::HashSet;
1115
1116            #[test]
1117            fn total_glyph_count_is_256() {
1118                assert_eq!(TOTAL, 256);
1119                assert_eq!(FONT.glyph_count(), 256);
1120            }
1121
1122            #[test]
1123            fn every_charset_character_appears_exactly_once() {
1124                let mut seen = HashSet::with_capacity(TOTAL);
1125                for &(ch, _) in &CHARSET {
1126                    assert!(seen.insert(ch), "{ch:?} appears more than once in CHARSET");
1127                }
1128                assert_eq!(seen.len(), TOTAL);
1129            }
1130
1131            #[test]
1132            fn covers_the_full_u2800_block() {
1133                for bits in 0u32..u32::try_from(TOTAL).unwrap() {
1134                    let ch = char::from_u32(0x2800 + bits).unwrap();
1135                    assert_eq!(CHARSET[bits as usize].0, ch);
1136                    assert_eq!(CHARSET[bits as usize].1, u8::try_from(bits).unwrap());
1137                }
1138            }
1139
1140            #[test]
1141            fn blank_glyph_is_all_zero_bits() {
1142                let index = FONT.glyph_index('\u{2800}').expect("U+2800 is covered");
1143                assert!(FONT.rows(index).iter().all(|&b| b == 0));
1144            }
1145
1146            #[test]
1147            fn full_glyph_has_all_dot_positions_set() {
1148                let index = FONT.glyph_index('\u{28FF}').expect("U+28FF is covered");
1149                let pixel_count = FONT.glyph_pixels(index).count();
1150                // 8 dots, 3x3 each, none clipped by the 8x16 cell at these centers: 8 * 9 = 72
1151                // lit pixels.
1152                assert_eq!(pixel_count, 72);
1153            }
1154
1155            /// Mirrors `FontChain`'s own doc example: a chain resolving a character none of
1156            /// CP437 has a mapping for at all through this generated fallback font.
1157            #[test]
1158            fn font_chain_resolves_via_braille_fallback() {
1159                static PRIMARY_DATA: [u8; 256 * 16] = [0; 256 * 16];
1160                const PRIMARY: crate::font::BitmapFont =
1161                    crate::font::BitmapFont::new(&PRIMARY_DATA, 8, 16, 256);
1162
1163                static FALLBACKS: [crate::font::BitmapFont; 1] = [FONT];
1164                let chain = FontChain::new(PRIMARY, &FALLBACKS);
1165
1166                // A mid-range braille char: not reachable through CP437 at all.
1167                let braille = chain
1168                    .resolve('\u{2837}')
1169                    .expect("covered by legacy_computing::braille");
1170                assert_eq!(braille.font_index(), 1);
1171                assert!(!braille.is_notdef());
1172
1173                // The primary font's own CP437 coverage answers directly for the solid block;
1174                // this chain never needs to fall back to `notdef` for it.
1175                let full_block = chain.resolve('█').expect("CP437 coverage");
1176                assert_eq!(*full_block.font(), PRIMARY);
1177                assert!(!full_block.is_notdef());
1178            }
1179        }
1180    }
1181}
1182
1183// ── Unicode → CP437 mapping ────────────────────────────────────────────────
1184
1185/// The substitute drawn for a character no font in a chain covers: the solid block, whichever
1186/// glyph index the font that has it stores it at.
1187///
1188/// Naming the substitute as a `char` rather than a fixed index is what keeps
1189/// [`FontChain::resolve`] total: CP437's own `0xDB` is out of range for a font with fewer than
1190/// 220 glyphs, so an index constant would resolve to a glyph that font does not have.
1191const NOTDEF: char = '█';
1192
1193/// Attempts to map a Unicode scalar to its CP437 glyph index.
1194///
1195/// ASCII (U+0020–U+007E) maps identically.  Common box-drawing characters,
1196/// block-elements, and roguelike symbols are mapped explicitly.  Returns
1197/// `None` for anything else, distinguishing "not in the CP437 table" from a
1198/// character that legitimately maps to the solid-block glyph (`'█'`) --
1199/// [`FontChain`] relies on that distinction to keep trying further
1200/// fonts on a miss instead of stopping at a false-positive solid-block hit.
1201#[allow(clippy::too_many_lines)]
1202const fn try_unicode_to_cp437(ch: char) -> Option<u8> {
1203    // Direct ASCII pass-through (the most common path for roguelikes).
1204    let u = ch as u32;
1205    if u < 0x80 {
1206        #[allow(clippy::cast_possible_truncation)]
1207        return Some(u as u8);
1208    }
1209
1210    // Named mappings for the characters roguelikes actually use.
1211    match ch {
1212        // ── Latin-1 accented letters that overlap CP437 ──────────────────
1213        'Ç' => Some(0x80),
1214        'ü' => Some(0x81),
1215        'é' => Some(0x82),
1216        'â' => Some(0x83),
1217        'ä' => Some(0x84),
1218        'à' => Some(0x85),
1219        'å' => Some(0x86),
1220        'ç' => Some(0x87),
1221        'ê' => Some(0x88),
1222        'ë' => Some(0x89),
1223        'è' => Some(0x8A),
1224        'ï' => Some(0x8B),
1225        'î' => Some(0x8C),
1226        'ì' => Some(0x8D),
1227        'Ä' => Some(0x8E),
1228        'Å' => Some(0x8F),
1229        'É' => Some(0x90),
1230        'æ' => Some(0x91),
1231        'Æ' => Some(0x92),
1232        'ô' => Some(0x93),
1233        'ö' => Some(0x94),
1234        'ò' => Some(0x95),
1235        'û' => Some(0x96),
1236        'ù' => Some(0x97),
1237        'ÿ' => Some(0x98),
1238        'Ö' => Some(0x99),
1239        'Ü' => Some(0x9A),
1240        '¢' => Some(0x9B),
1241        '£' => Some(0x9C),
1242        '¥' => Some(0x9D),
1243        'ƒ' => Some(0x9F),
1244        'á' => Some(0xA0),
1245        'í' => Some(0xA1),
1246        'ó' => Some(0xA2),
1247        'ú' => Some(0xA3),
1248        'ñ' => Some(0xA4),
1249        'Ñ' => Some(0xA5),
1250        'ª' => Some(0xA6),
1251        'º' => Some(0xA7),
1252        '¿' => Some(0xA8),
1253        '⌐' => Some(0xA9),
1254        '½' => Some(0xAB),
1255        '¼' => Some(0xAC),
1256        '¡' => Some(0xAD),
1257        '«' => Some(0xAE),
1258        '»' => Some(0xAF),
1259
1260        // ── Shade characters ─────────────────────────────────────────────
1261        '░' => Some(0xB0),
1262        '▒' => Some(0xB1),
1263        '▓' => Some(0xB2),
1264
1265        // ── Single-line box drawing ───────────────────────────────────────
1266        '│' => Some(0xB3),
1267        '┤' => Some(0xB4),
1268        '╡' => Some(0xB5),
1269        '╢' => Some(0xB6),
1270        '╖' => Some(0xB7),
1271        '╕' => Some(0xB8),
1272        '╣' => Some(0xB9),
1273        '║' => Some(0xBA),
1274        '╗' => Some(0xBB),
1275        '╝' => Some(0xBC),
1276        '╜' => Some(0xBD),
1277        '╛' => Some(0xBE),
1278        '┐' => Some(0xBF),
1279        '└' => Some(0xC0),
1280        '┴' => Some(0xC1),
1281        '┬' => Some(0xC2),
1282        '├' => Some(0xC3),
1283        '─' => Some(0xC4),
1284        '┼' => Some(0xC5),
1285        '╞' => Some(0xC6),
1286        '╟' => Some(0xC7),
1287        '╚' => Some(0xC8),
1288        '╔' => Some(0xC9),
1289        '╩' => Some(0xCA),
1290        '╦' => Some(0xCB),
1291        '╠' => Some(0xCC),
1292        '═' => Some(0xCD),
1293        '╬' => Some(0xCE),
1294        '╧' => Some(0xCF),
1295        '╨' => Some(0xD0),
1296        '╤' => Some(0xD1),
1297        '╥' => Some(0xD2),
1298        '╙' => Some(0xD3),
1299        '╘' => Some(0xD4),
1300        '╒' => Some(0xD5),
1301        '╓' => Some(0xD6),
1302        '╫' => Some(0xD7),
1303        '╪' => Some(0xD8),
1304        '┘' => Some(0xD9),
1305        '┌' => Some(0xDA),
1306
1307        // ── Block elements ────────────────────────────────────────────────
1308        '█' => Some(0xDB),
1309        '▄' => Some(0xDC),
1310        '▌' => Some(0xDD),
1311        '▐' => Some(0xDE),
1312        '▀' => Some(0xDF),
1313
1314        // ── Greek / math ──────────────────────────────────────────────────
1315        'α' => Some(0xE0),
1316        'ß' => Some(0xE1),
1317        'Γ' => Some(0xE2),
1318        'π' => Some(0xE3),
1319        'Σ' => Some(0xE4),
1320        'σ' => Some(0xE5),
1321        'µ' | 'μ' => Some(0xE6),
1322        'τ' => Some(0xE7),
1323        'Φ' => Some(0xE8),
1324        'Θ' => Some(0xE9),
1325        'Ω' => Some(0xEA),
1326        'δ' => Some(0xEB),
1327        '∞' => Some(0xEC),
1328        'φ' => Some(0xED),
1329        'ε' => Some(0xEE),
1330        '∩' => Some(0xEF),
1331        '≡' => Some(0xF0),
1332        '±' => Some(0xF1),
1333        '≥' => Some(0xF2),
1334        '≤' => Some(0xF3),
1335        '⌠' => Some(0xF4),
1336        '⌡' => Some(0xF5),
1337        '÷' => Some(0xF6),
1338        '≈' => Some(0xF7),
1339        '°' => Some(0xF8),
1340        '·' | '∙' => Some(0xF9),
1341        '√' => Some(0xFB),
1342        'ⁿ' => Some(0xFC),
1343        '²' => Some(0xFD),
1344        '■' => Some(0xFE),
1345
1346        // ── Roguelike / Unicode symbols ───────────────────────────────────
1347        '☺' => Some(0x01),
1348        '•' => Some(0x07),
1349        '☻' => Some(0x02),
1350        '♥' => Some(0x03),
1351        '♦' => Some(0x04),
1352        '♣' => Some(0x05),
1353        '♠' => Some(0x06),
1354        '◘' => Some(0x08),
1355        '○' => Some(0x09),
1356        '◙' => Some(0x0A),
1357        '♂' => Some(0x0B),
1358        '♀' => Some(0x0C),
1359        '♪' => Some(0x0D),
1360        '♫' => Some(0x0E),
1361        '☼' => Some(0x0F),
1362        '►' => Some(0x10),
1363        '◄' => Some(0x11),
1364        '↕' => Some(0x12),
1365        '‼' => Some(0x13),
1366        '¶' => Some(0x14),
1367        '§' => Some(0x15),
1368        '▬' => Some(0x16),
1369        '↨' => Some(0x17),
1370        '↑' => Some(0x18),
1371        '↓' => Some(0x19),
1372        '→' => Some(0x1A),
1373        '←' => Some(0x1B),
1374        '∟' => Some(0x1C),
1375        '↔' => Some(0x1D),
1376        '▲' => Some(0x1E),
1377        '▼' => Some(0x1F),
1378        '⌂' => Some(0x7F),
1379
1380        _ => None,
1381    }
1382}
1383
1384#[cfg(test)]
1385mod tests {
1386    use super::{BitmapFont, FontChain, try_unicode_to_cp437};
1387
1388    /// The four codepoints patched into `unscii16`'s `DATA` (see that module's doc comment)
1389    /// must actually be reachable through the char-to-glyph path, not just present at their
1390    /// raw glyph index: otherwise they're invisible to anything that goes through
1391    /// [`FontChain::resolve`]/`Surface::print`, which is every real caller.
1392    #[test]
1393    fn patched_glyphs_are_reachable_by_char() {
1394        assert_eq!(try_unicode_to_cp437('⌂'), Some(0x7F), "U+2302 HOUSE");
1395        assert_eq!(
1396            try_unicode_to_cp437('☼'),
1397            Some(0x0F),
1398            "U+263C WHITE SUN WITH RAYS"
1399        );
1400        assert_eq!(
1401            try_unicode_to_cp437('⌐'),
1402            Some(0xA9),
1403            "U+2310 REVERSED NOT SIGN"
1404        );
1405        assert_eq!(
1406            try_unicode_to_cp437('∙'),
1407            Some(0xF9),
1408            "U+2219 BULLET OPERATOR"
1409        );
1410    }
1411
1412    /// A primary font that only covers the ASCII half of CP437 (glyph indices 0..128), so
1413    /// any character mapping into the extended range (128..256) is a miss for it.
1414    static PRIMARY_DATA: [u8; 128 * 16] = [0; 128 * 16];
1415    const PRIMARY: BitmapFont = BitmapFont::new(&PRIMARY_DATA, 8, 16, 128);
1416
1417    /// A fallback font with full CP437 coverage (glyph indices 0..256).
1418    static FALLBACK_DATA: [u8; 256 * 16] = [0; 256 * 16];
1419    const FALLBACK_FONT: BitmapFont = BitmapFont::new(&FALLBACK_DATA, 8, 16, 256);
1420
1421    #[test]
1422    fn chain_resolves_char_present_only_in_fallback_font() {
1423        // 'Ç' maps to CP437 index 0x80, which is out of range for `PRIMARY`
1424        // (glyph_count == 128) but present in `FALLBACK_FONT` (glyph_count == 256).
1425        let chain = FontChain::new(PRIMARY, &[FALLBACK_FONT]);
1426        let resolved = chain.resolve('Ç').expect("covered by the fallback font");
1427        assert_eq!(*resolved.font(), FALLBACK_FONT);
1428        assert_eq!(resolved.font_index(), 1);
1429        assert_eq!(resolved.index(), 0x80);
1430        assert!(!resolved.is_notdef());
1431    }
1432
1433    #[test]
1434    fn chain_falls_back_to_solid_block_when_every_font_misses() {
1435        // 'あ' (U+3042 HIRAGANA LETTER A) isn't in the CP437 table at all, so both fonts in the
1436        // chain miss and resolution must substitute the solid block. `PRIMARY` stops at glyph 128
1437        // and so doesn't have one, which is exactly the case a fixed 0xDB fallback index used to
1438        // resolve to an out-of-range glyph for.
1439        let chain = FontChain::new(PRIMARY, &[FALLBACK_FONT]);
1440        let resolved = chain.resolve('あ').expect("solid block substitute");
1441        assert_eq!(*resolved.font(), FALLBACK_FONT);
1442        assert_eq!(resolved.index(), 0xDB);
1443        assert!(resolved.is_notdef());
1444    }
1445
1446    #[test]
1447    fn chain_resolves_nothing_when_no_font_has_a_substitute() {
1448        // A chain that covers braille and nothing else: an uncovered character has no solid block
1449        // to fall back to anywhere in the chain, so resolution reports "undrawable" instead of
1450        // pointing at a glyph the font doesn't have.
1451        static DATA: [u8; 16] = [0; 16];
1452        const CHARSET: [(char, u8); 1] = [('\u{2800}', 0)];
1453        const BRAILLE: BitmapFont = BitmapFont::with_charset(&DATA, 8, 16, 1, &CHARSET);
1454
1455        let chain = FontChain::new(BRAILLE, &[]);
1456        assert!(chain.resolve('\u{2800}').is_some());
1457        assert!(chain.resolve('A').is_none());
1458    }
1459
1460    #[test]
1461    fn single_font_chain_resolves_that_font_directly() {
1462        let chain = FontChain::from(FALLBACK_FONT);
1463        assert_eq!(chain.font_count(), 1);
1464        for ch in ['A', ' ', '█', '│', 'Ç', '☺'] {
1465            let resolved = chain.resolve(ch).expect("CP437 coverage");
1466            assert_eq!(*resolved.font(), FALLBACK_FONT);
1467            assert_eq!(resolved.font_index(), 0);
1468            assert_eq!(resolved.index(), FALLBACK_FONT.glyph_index(ch).unwrap());
1469        }
1470    }
1471
1472    #[test]
1473    fn glyph_size_is_none_for_a_chain_of_mismatched_fonts() {
1474        static DATA: [u8; 8] = [0; 8];
1475        const SHORT: BitmapFont = BitmapFont::new(&DATA, 8, 8, 1);
1476
1477        assert_eq!(
1478            FontChain::new(PRIMARY, &[FALLBACK_FONT]).glyph_size(),
1479            Some((8, 16))
1480        );
1481        assert_eq!(FontChain::new(PRIMARY, &[SHORT]).glyph_size(), None);
1482    }
1483
1484    #[test]
1485    fn glyph_pixels_decodes_msb_first_row_major() {
1486        // Two 8x2 glyphs. Glyph 0: corners of the top row set; glyph 1: full top row plus one
1487        // interior pixel on the second row.
1488        static DATA: [u8; 4] = [0b1000_0001, 0b0000_0000, 0b1111_1111, 0b0000_1000];
1489        let font = BitmapFont::new(&DATA, 8, 2, 2);
1490
1491        let g0: Vec<(u8, u8)> = font.glyph_pixels(0).collect();
1492        assert_eq!(
1493            g0,
1494            [(0, 0), (7, 0)],
1495            "MSB is the leftmost pixel; row 0 first"
1496        );
1497
1498        let g1: Vec<(u8, u8)> = font.glyph_pixels(1).collect();
1499        let mut expected: Vec<(u8, u8)> = (0..8).map(|x| (x, 0)).collect();
1500        expected.push((4, 1)); // bit 3 of 0b0000_1000 -> x = width-1-3 = 4
1501        assert_eq!(g1, expected);
1502    }
1503
1504    #[test]
1505    fn glyph_pixels_is_parameterized_by_width_not_hardcoded_to_8() {
1506        // A 5px-wide glyph: set pixels must come from bits (width-1-x), i.e. bit 4 and bit 0, not
1507        // bit 7 and bit 3. This guards against a consumer re-introducing a hardcoded `7 - x`.
1508        static DATA: [u8; 1] = [0b0001_0001];
1509        let font = BitmapFont::new(&DATA, 5, 1, 1);
1510        let pixels: Vec<(u8, u8)> = font.glyph_pixels(0).collect();
1511        assert_eq!(pixels, [(0, 0), (4, 0)]);
1512    }
1513
1514    /// Reproduces retroglyph#507: a fallback font built with [`BitmapFont::with_charset`] can
1515    /// declare coverage for a codepoint CP437 has no mapping for at all (here U+2800 BRAILLE
1516    /// PATTERN BLANK), and a [`FontChain`] resolves it to that font's own distinct glyph
1517    /// index instead of colliding with CP437's solid-block fallback (`chain.resolve('\u{2588}')`,
1518    /// i.e. `'█'`).
1519    #[test]
1520    fn chain_extends_past_cp437_via_charset_fallback_font() {
1521        static BRAILLE_DATA: [u8; 16] = [0; 16];
1522        const BRAILLE_CHARSET: [(char, u8); 1] = [('\u{2800}', 0)];
1523        const BRAILLE_FONT: BitmapFont =
1524            BitmapFont::with_charset(&BRAILLE_DATA, 8, 16, 1, &BRAILLE_CHARSET);
1525
1526        let primary = FALLBACK_FONT; // full CP437 coverage, glyph_count == 256
1527        let chain = FontChain::new(primary, &[BRAILLE_FONT]);
1528
1529        let braille = chain.resolve('\u{2800}').expect("charset coverage");
1530        assert_eq!(*braille.font(), BRAILLE_FONT);
1531        assert_eq!(braille.index(), 0);
1532
1533        let full_block = chain.resolve('\u{2588}').expect("CP437 coverage"); // '█', index 0xDB
1534        assert_eq!(*full_block.font(), primary);
1535        assert_eq!(full_block.index(), 0xDB);
1536
1537        assert_ne!(braille.index(), full_block.index());
1538    }
1539}