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::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    glyph_width: u8,
39    /// Height of each glyph in pixels; also bytes per glyph.
40    glyph_height: u8,
41    /// Total number of glyphs stored in `data`.
42    glyph_count: u16,
43    /// The `char` repertoire used by [`glyph_index`](Self::glyph_index), or `None` to use the
44    /// built-in CP437 mapping. Slot `i` in this table is glyph index `i`.
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]>,
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 `charset`
75    /// repertoire instead of the built-in CP437 encoding. `charset[i]` is the `char` for glyph
76    /// index `i`.
77    ///
78    /// This is how a font extends coverage past CP437: [`glyph_index`](Self::glyph_index) looks a
79    /// `char` up in `charset` instead of the CP437 table, so a font built this way can answer for
80    /// codepoints (quadrants, sextants, braille, ...) that CP437 has no mapping for at all.
81    /// `data` must contain exactly `glyph_count * glyph_height` bytes.
82    ///
83    /// `charset` is scanned linearly, so it is meant for the focused repertoire a font actually
84    /// declares (a few dozen block or marker glyphs), not for a second general-purpose encoding
85    /// table.
86    #[must_use]
87    pub const fn with_charset(
88        data: &'static [u8],
89        glyph_width: u8,
90        glyph_height: u8,
91        glyph_count: u16,
92        charset: &'static [char],
93    ) -> Self {
94        Self {
95            data,
96            glyph_width,
97            glyph_height,
98            glyph_count,
99            charset: Some(charset),
100        }
101    }
102
103    /// Returns the row bytes for glyph `index`.
104    ///
105    /// Each byte is one row; bit 7 (MSB) is the leftmost pixel.
106    ///
107    /// # Panics
108    ///
109    /// Panics if `index as u16 >= self.glyph_count`.
110    #[must_use]
111    pub fn rows(&self, index: u8) -> &[u8] {
112        assert!(
113            u16::from(index) < self.glyph_count,
114            "glyph index {index} out of range ({})",
115            self.glyph_count,
116        );
117        let h = usize::from(self.glyph_height);
118        let start = usize::from(index) * h;
119        &self.data[start..start + h]
120    }
121
122    /// Iterates the set ("on") pixels of glyph `index` as `(x, y)` coordinates, row-major from the
123    /// top: `x` in `0..glyph_width`, `y` in `0..glyph_height`.
124    ///
125    /// This is the single place the 1-bit format's MSB-first bit order lives (pixel `x` of a row
126    /// is bit `glyph_width - 1 - x` of that row's byte), so consumers (the GL atlas builder, the
127    /// software rasterizer's glyph blit) decode through it instead of each re-deriving the shift
128    /// and risking disagreement. It is also the one seam that has to change for wider-than-8px
129    /// glyphs (multi-byte rows, #164): today a row is a single byte (`glyph_width <= 8`), so its
130    /// bits are read straight out of that byte.
131    ///
132    /// A `glyph_width` above 8 is out of this format's contract (rows are one byte, so only bits
133    /// 0..8 exist); it is clamped to 8 here rather than shifting past the byte's width, so columns
134    /// 8.. of an oversized font are simply never yielded instead of panicking.
135    ///
136    /// # Panics
137    ///
138    /// Panics if `index as u16 >= self.glyph_count` (via [`rows`](Self::rows)).
139    #[must_use = "iterators are lazy and do nothing unless consumed"]
140    pub fn glyph_pixels(&self, index: u8) -> impl Iterator<Item = (u8, u8)> + '_ {
141        let width = self.glyph_width.min(8);
142        self.rows(index)
143            .iter()
144            .enumerate()
145            .flat_map(move |(y, &row)| {
146                #[allow(clippy::cast_possible_truncation)]
147                let y = y as u8;
148                (0..width)
149                    .filter_map(move |x| ((row >> (width - 1 - x)) & 1 == 1).then_some((x, y)))
150            })
151    }
152
153    /// The width of each glyph in pixels (≤ 8 for single-byte rows).
154    #[must_use]
155    pub const fn glyph_width(&self) -> u8 {
156        self.glyph_width
157    }
158
159    /// The height of each glyph in pixels; also bytes per glyph.
160    #[must_use]
161    pub const fn glyph_height(&self) -> u8 {
162        self.glyph_height
163    }
164
165    /// The total number of glyphs stored in this font.
166    ///
167    /// Glyph indices `0..glyph_count()` are valid arguments to [`rows`](Self::rows). A GPU
168    /// backend uses this to size its glyph atlas (one texture-array layer per glyph).
169    #[must_use]
170    pub const fn glyph_count(&self) -> u16 {
171        self.glyph_count
172    }
173
174    /// Maps a Unicode `char` to a glyph index in this font, or `None` if this font does not
175    /// cover `ch`.
176    ///
177    /// If this font was built with [`with_charset`](Self::with_charset), `ch` is looked up in
178    /// that explicit table; otherwise it goes through the built-in CP437 mapping. A miss is
179    /// either `ch` not being in this font's repertoire at all, or its mapped index falling
180    /// outside this font's `glyph_count` (e.g. a font built with fewer than 256 glyphs).
181    ///
182    /// A returned index is always `< glyph_count()`, so it is always a valid argument to
183    /// [`rows`](Self::rows) and [`glyph_pixels`](Self::glyph_pixels).
184    ///
185    /// Substituting something drawable for a miss is [`FontChain::resolve`]'s job, not this
186    /// one's: a font cannot answer for a character it has no glyph for, and pretending otherwise
187    /// is what hides a chain's later fonts from ever being consulted.
188    #[must_use]
189    pub const fn glyph_index(&self, ch: char) -> Option<u8> {
190        if let Some(table) = self.charset {
191            let mut i = 0;
192            while i < table.len() {
193                if table[i] == ch && i < self.glyph_count as usize {
194                    #[allow(clippy::cast_possible_truncation)]
195                    return Some(i as u8);
196                }
197                i += 1;
198            }
199            return None;
200        }
201        match try_unicode_to_cp437(ch) {
202            Some(index) if (index as u16) < self.glyph_count => Some(index),
203            _ => None,
204        }
205    }
206}
207
208// Two `BitmapFont`s are equal when they point at the same static data and
209// share the same dimensions.  Comparing the full 4 KB slice on every draw
210// call would be wasteful, so we compare the data pointer instead.
211impl PartialEq for BitmapFont {
212    fn eq(&self, other: &Self) -> bool {
213        core::ptr::eq(self.data.as_ptr(), other.data.as_ptr())
214            && self.glyph_width == other.glyph_width
215            && self.glyph_height == other.glyph_height
216            && self.glyph_count == other.glyph_count
217    }
218}
219
220impl Eq for BitmapFont {}
221
222// ── Font chain ──────────────────────────────────────────────────────────────
223
224/// A glyph resolved from a [`FontChain`]: the glyph index plus the specific [`BitmapFont`] it
225/// came from, since each font in a chain owns its own bitmap data.
226#[derive(Debug, Clone, Copy, PartialEq, Eq)]
227pub struct ResolvedGlyph {
228    font: BitmapFont,
229    font_index: usize,
230    index: u8,
231    notdef: bool,
232}
233
234impl ResolvedGlyph {
235    /// The font this glyph was resolved from.
236    #[must_use]
237    pub const fn font(&self) -> BitmapFont {
238        self.font
239    }
240
241    /// The position of [`font`](Self::font) within the chain that resolved it: `0` is the
242    /// primary font, `1..` the fallbacks in order.
243    ///
244    /// A GPU backend packs every font in the chain into one atlas and addresses a glyph by a flat
245    /// slot, so it needs the font's position (a stable index into [`FontChain::fonts`]) rather
246    /// than the font value, which carries no identity of its own.
247    #[must_use]
248    pub const fn font_index(&self) -> usize {
249        self.font_index
250    }
251
252    /// The glyph index within [`font`](Self::font), always `< font().glyph_count()`.
253    #[must_use]
254    pub const fn index(&self) -> u8 {
255        self.index
256    }
257
258    /// Whether this is the substituted "no glyph" box rather than a glyph for the character that
259    /// was asked for: `true` when no font in the chain covered that character and
260    /// [`FontChain::resolve`] fell back to the solid block.
261    #[must_use]
262    pub const fn is_notdef(&self) -> bool {
263        self.notdef
264    }
265
266    /// Returns the row bytes for this glyph; see [`BitmapFont::rows`].
267    #[must_use]
268    pub fn rows(&self) -> &[u8] {
269        self.font.rows(self.index)
270    }
271}
272
273/// The glyph source a backend draws from: a primary [`BitmapFont`] plus an ordered list of
274/// fallback fonts.
275///
276/// This is the only character-to-glyph path the bundled pixel backends have. A single font is a
277/// chain of one (`FontChain::from(font)`), so `SoftwareBackendBuilder::font` and
278/// `GlBackendBuilder::font` both take an `impl Into<FontChain<'static>>` and there is no second,
279/// chain-blind route that could quietly ignore a font's declared repertoire.
280///
281/// [`resolve`](Self::resolve) tries the primary font first, then each fallback in order, and only
282/// if every font misses substitutes the solid block (`'█'`) from the first font in the chain that
283/// has one. This lets a caller layer, say, an ASCII or partial-coverage primary font with one or
284/// more broader fallback fonts, so a char missing from the primary doesn't automatically become a
285/// solid block if some other font in the chain actually has it.
286///
287/// This type ships **no bundled fallback font data**: every font in the chain, primary or
288/// fallback, is supplied by the caller. Bundling a ready-to-use Latin-1/Extended or sub-cell
289/// (quadrant/sextant/braille) fallback font is a natural follow-up now that this mechanism is
290/// reachable end to end, but is out of scope here.
291///
292/// A fallback font only extends the chain's repertoire if it declares coverage for the
293/// characters it is meant to answer for. A [`BitmapFont::new`] font is always resolved through
294/// the built-in CP437 table, so stacking several CP437 fonts in a chain never reaches past CP437:
295/// every font in the chain answers the identical question. To actually extend coverage (e.g.
296/// quadrants, sextants, braille, none of which CP437 has a mapping for), build the fallback font
297/// with [`BitmapFont::with_charset`] and an explicit table covering those codepoints. Until a
298/// chain does, `retroglyph_core::symbols`'s `quantize_quadrant`/`quantize_sextant` glyphs render
299/// as a solid block on the pixel backends; see those functions' docs.
300///
301/// # Examples
302///
303/// ```
304/// use retroglyph_window::font::{BitmapFont, FontChain};
305///
306/// static ASCII: [u8; 128 * 16] = [0; 128 * 16];
307/// static QUADRANTS: [u8; 3 * 16] = [0; 3 * 16];
308/// const QUADRANT_CHARSET: [char; 3] = ['▘', '▝', '▖'];
309///
310/// const PRIMARY: BitmapFont = BitmapFont::new(&ASCII, 8, 16, 128);
311/// const SUBCELL: BitmapFont = BitmapFont::with_charset(&QUADRANTS, 8, 16, 3, &QUADRANT_CHARSET);
312/// static FALLBACKS: [BitmapFont; 1] = [SUBCELL];
313///
314/// let chain = FontChain::new(PRIMARY, &FALLBACKS);
315/// let quadrant = chain.resolve('▘').expect("covered by the fallback font");
316/// assert_eq!(quadrant.font_index(), 1);
317/// assert!(!quadrant.is_notdef());
318/// ```
319#[derive(Debug, Clone, Copy, PartialEq, Eq)]
320pub struct FontChain<'a> {
321    primary: BitmapFont,
322    fallbacks: &'a [BitmapFont],
323}
324
325impl From<BitmapFont> for FontChain<'static> {
326    fn from(font: BitmapFont) -> Self {
327        Self::new(font, &[])
328    }
329}
330
331impl<'a> FontChain<'a> {
332    /// Constructs a chain from a primary font and an ordered list of fallback fonts.
333    #[must_use]
334    pub const fn new(primary: BitmapFont, fallbacks: &'a [BitmapFont]) -> Self {
335        Self { primary, fallbacks }
336    }
337
338    /// The fonts in resolution order: the primary font first, then each fallback.
339    ///
340    /// The position of a font in this iterator is its [`ResolvedGlyph::font_index`].
341    pub fn fonts(&self) -> impl Iterator<Item = &BitmapFont> {
342        core::iter::once(&self.primary).chain(self.fallbacks.iter())
343    }
344
345    /// The number of fonts in the chain (always at least one).
346    #[must_use]
347    pub const fn font_count(&self) -> usize {
348        1 + self.fallbacks.len()
349    }
350
351    /// The glyph cell size (`(width, height)` in unscaled pixels) shared by every font in the
352    /// chain, or `None` if the fonts disagree.
353    ///
354    /// A grid has one cell size, so a chain whose fonts don't agree on theirs has no single
355    /// answer for how big a cell is; backends reject such a chain at build time rather than
356    /// picking one font's size and letting the others overflow or under-fill their cells.
357    #[must_use]
358    pub fn glyph_size(&self) -> Option<(u8, u8)> {
359        let size = (self.primary.glyph_width, self.primary.glyph_height);
360        self.fallbacks
361            .iter()
362            .all(|f| (f.glyph_width, f.glyph_height) == size)
363            .then_some(size)
364    }
365
366    /// Resolves `ch` to a drawable glyph, trying the primary font first, then each fallback font
367    /// in order.
368    ///
369    /// If no font covers `ch`, this substitutes the solid block (`'█'`) from the first font in
370    /// the chain that covers *it*, flagged as [`ResolvedGlyph::is_notdef`]. `None` means the
371    /// chain cannot draw `ch` at all, not even a substitute box, and the caller should draw
372    /// nothing: a chain of narrow `with_charset` fonts (say, braille only) legitimately has no
373    /// solid block to fall back to.
374    ///
375    /// A returned glyph is always in range for its font, so [`ResolvedGlyph::rows`] and
376    /// [`BitmapFont::glyph_pixels`] cannot panic on it.
377    #[must_use]
378    pub fn resolve(&self, ch: char) -> Option<ResolvedGlyph> {
379        self.lookup(ch, false).or_else(|| self.lookup(NOTDEF, true))
380    }
381
382    /// The first font in the chain covering `ch`, tagged with `notdef`.
383    fn lookup(&self, ch: char, notdef: bool) -> Option<ResolvedGlyph> {
384        self.fonts()
385            .enumerate()
386            .find_map(|(font_index, font)| {
387                font.glyph_index(ch).map(|index| (font_index, font, index))
388            })
389            .map(|(font_index, font, index)| ResolvedGlyph {
390                font: *font,
391                font_index,
392                index,
393                notdef,
394            })
395    }
396}
397
398// ── Default embedded font ──────────────────────────────────────────────────
399
400/// The Unscii 16 font, embedded when the `default-font` feature is enabled.
401///
402/// 256 glyphs laid out in CP437 order (matching `unicode_to_cp437`), each
403/// 16 bytes (1 bit per pixel, MSB = leftmost). Source: unscii's
404/// public-domain/CC0 `unscii-16.hex` (<https://github.com/viznut/unscii>),
405/// re-laid-out from Unicode codepoints to CP437 glyph indices.
406///
407/// Four CP437 codepoints that plain `unscii-16.hex` doesn't cover (U+2302
408/// HOUSE, U+263C WHITE SUN WITH RAYS, U+2310 REVERSED NOT SIGN, U+2219
409/// BULLET OPERATOR) are filled in with original pixel art or mechanical
410/// transforms of neighboring unscii glyphs (e.g. REVERSED NOT SIGN is a
411/// horizontal mirror of unscii's own NOT SIGN) rather than pulling in
412/// unscii's GPL-licensed `-full` variant (which adds GNU Unifont glyphs).
413/// `unicode_to_cp437` carries matching reverse-mapping arms for all four
414/// (`☼` already had one; the `⌂`/`⌐`/`∙` arms are new here) so all four
415/// are reachable through the normal char-to-glyph path, not just by raw
416/// glyph index.
417#[cfg(feature = "default-font")]
418pub mod unscii16 {
419    use super::BitmapFont;
420
421    /// A [`BitmapFont`] backed by the embedded Unscii 16 glyph data.
422    pub const FONT: BitmapFont = BitmapFont::new(&DATA, 8, 16, 256);
423
424    /// Unscii 16 glyph bitmaps: 256 CP437-ordered glyphs, 16 bytes each.
425    ///
426    /// Each byte is one row of 8 pixels; bit 7 (MSB) is the leftmost pixel.
427    #[rustfmt::skip]
428    static DATA: [u8; 4096] = [
429        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x00
430        0x00, 0x00, 0x7e, 0x81, 0x81, 0xa5, 0x81, 0x81, 0xbd, 0x99, 0x81, 0x81, 0x7e, 0x00, 0x00, 0x00, // 0x01
431        0x00, 0x00, 0x7e, 0xff, 0xff, 0xdb, 0xff, 0xff, 0xc3, 0xe7, 0xff, 0xff, 0x7e, 0x00, 0x00, 0x00, // 0x02
432        0x00, 0x00, 0x00, 0x6c, 0xfe, 0xfe, 0xfe, 0xfe, 0x7c, 0x7c, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, // 0x03
433        0x00, 0x00, 0x00, 0x10, 0x38, 0x38, 0x7c, 0xfe, 0x7c, 0x38, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, // 0x04
434        0x00, 0x00, 0x00, 0x10, 0x38, 0x38, 0x54, 0xfe, 0xfe, 0x54, 0x10, 0x38, 0x00, 0x00, 0x00, 0x00, // 0x05
435        0x00, 0x00, 0x00, 0x10, 0x38, 0x7c, 0xfe, 0xfe, 0xfe, 0x38, 0x38, 0x7c, 0x00, 0x00, 0x00, 0x00, // 0x06
436        0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x3c, 0x3c, 0x3c, 0x3c, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, // 0x07
437        0xff, 0xff, 0xff, 0xff, 0xe7, 0xe7, 0xc3, 0xc3, 0xc3, 0xc3, 0xe7, 0xe7, 0xff, 0xff, 0xff, 0xff, // 0x08
438        0x00, 0x00, 0x3c, 0x3c, 0x66, 0x66, 0x42, 0x42, 0x42, 0x42, 0x66, 0x66, 0x3c, 0x3c, 0x00, 0x00, // 0x09
439        0xff, 0xff, 0xc3, 0xc3, 0x99, 0x99, 0xbd, 0xbd, 0xbd, 0xbd, 0x99, 0x99, 0xc3, 0xc3, 0xff, 0xff, // 0x0a
440        0x00, 0x00, 0x00, 0x1e, 0x0e, 0x1a, 0x78, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x78, 0x00, 0x00, 0x00, // 0x0b
441        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x7e, 0x18, // 0x0c
442        0x00, 0x00, 0x00, 0x18, 0x1c, 0x1e, 0x1b, 0x18, 0x18, 0x78, 0xf8, 0x70, 0x00, 0x00, 0x00, 0x00, // 0x0d
443        0x00, 0x00, 0x00, 0x7f, 0x63, 0x63, 0x63, 0x63, 0x63, 0x67, 0xe7, 0xe6, 0xc0, 0x00, 0x00, 0x00, // 0x0e
444        0x00, 0x00, 0x00, 0x24, 0x18, 0xbd, 0x7e, 0x7e, 0xbd, 0x18, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x0f
445        0x00, 0x00, 0x00, 0x00, 0xc0, 0xf0, 0xfc, 0xff, 0xfc, 0xf0, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x10
446        0x00, 0x00, 0x00, 0x00, 0x03, 0x0f, 0x3f, 0xff, 0x3f, 0x0f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x11
447        0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, // 0x12
448        0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x66, 0x66, 0x00, 0x00, // 0x13
449        0x00, 0x00, 0x3e, 0x7a, 0x7a, 0x7a, 0x7a, 0x3a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x00, 0x00, 0x00, // 0x14
450        0x00, 0x3c, 0x66, 0x60, 0x30, 0x38, 0x6c, 0x66, 0x36, 0x1c, 0x0c, 0x06, 0x66, 0x3c, 0x00, 0x00, // 0x15
451        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x00, 0x00, // 0x16
452        0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, 0xff, // 0x17
453        0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0x18
454        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, // 0x19
455        0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0c, 0xfe, 0xfe, 0x0c, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1a
456        0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x30, 0x7f, 0x7f, 0x30, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1b
457        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x06, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1c
458        0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x66, 0xff, 0xff, 0x66, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1d
459        0x18, 0x18, 0x18, 0x18, 0x3c, 0x3c, 0x3c, 0x3c, 0x7e, 0x7e, 0x7e, 0x7e, 0xff, 0xff, 0xff, 0xff, // 0x1e
460        0xff, 0xff, 0xff, 0xff, 0x7e, 0x7e, 0x7e, 0x7e, 0x3c, 0x3c, 0x3c, 0x3c, 0x18, 0x18, 0x18, 0x18, // 0x1f
461        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20
462        0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x21
463        0x00, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x22
464        0x00, 0x00, 0x6c, 0x6c, 0x6c, 0xfe, 0x6c, 0x6c, 0x6c, 0xfe, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, // 0x23
465        0x00, 0x18, 0x18, 0x3c, 0x66, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x66, 0x3c, 0x18, 0x18, 0x00, 0x00, // 0x24
466        0x00, 0x00, 0x06, 0xc6, 0xcc, 0xcc, 0x18, 0x18, 0x30, 0x30, 0x66, 0x66, 0xc6, 0xc0, 0x00, 0x00, // 0x25
467        0x00, 0x00, 0x38, 0x6c, 0x6c, 0x38, 0x30, 0x7a, 0xde, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, // 0x26
468        0x00, 0x18, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x27
469        0x00, 0x0c, 0x18, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x18, 0x18, 0x0c, 0x00, 0x00, // 0x28
470        0x00, 0x30, 0x18, 0x18, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x18, 0x18, 0x30, 0x00, 0x00, // 0x29
471        0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x3c, 0xff, 0x3c, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2a
472        0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2b
473        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x18, 0x18, 0x30, 0x60, 0x00, // 0x2c
474        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2d
475        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x2e
476        0x03, 0x03, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x18, 0x30, 0x30, 0x60, 0x60, 0xc0, 0xc0, 0x00, 0x00, // 0x2f
477        0x00, 0x00, 0x38, 0x6c, 0xc6, 0xc6, 0xce, 0xd6, 0xe6, 0xc6, 0xc6, 0x6c, 0x38, 0x00, 0x00, 0x00, // 0x30
478        0x00, 0x00, 0x18, 0x38, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, 0x00, 0x00, // 0x31
479        0x00, 0x00, 0x3c, 0x66, 0x66, 0x06, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, // 0x32
480        0x00, 0x00, 0x3c, 0x66, 0x66, 0x06, 0x06, 0x1c, 0x06, 0x06, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x33
481        0x00, 0x00, 0x0c, 0x1c, 0x3c, 0x6c, 0xcc, 0xcc, 0xfe, 0x0c, 0x0c, 0x0c, 0x0c, 0x00, 0x00, 0x00, // 0x34
482        0x00, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x7c, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x35
483        0x00, 0x00, 0x1c, 0x30, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x36
484        0x00, 0x00, 0x7e, 0x06, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x37
485        0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x76, 0x3c, 0x6e, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x38
486        0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x06, 0x0c, 0x38, 0x00, 0x00, 0x00, // 0x39
487        0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x3a
488        0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x38, 0x18, 0x18, 0x30, 0x60, 0x00, // 0x3b
489        0x00, 0x00, 0x00, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x00, 0x00, 0x00, 0x00, // 0x3c
490        0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x3d
491        0x00, 0x00, 0x00, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, // 0x3e
492        0x00, 0x3c, 0x66, 0x66, 0x06, 0x0c, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x3f
493        0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xde, 0xde, 0xde, 0xdc, 0xc0, 0xc0, 0x7c, 0x00, 0x00, 0x00, // 0x40
494        0x00, 0x00, 0x18, 0x3c, 0x66, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x41
495        0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0x66, 0x66, 0x7c, 0x00, 0x00, 0x00, // 0x42
496        0x00, 0x00, 0x3c, 0x66, 0x66, 0x60, 0x60, 0x60, 0x60, 0x60, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x43
497        0x00, 0x00, 0x78, 0x6c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6c, 0x78, 0x00, 0x00, 0x00, // 0x44
498        0x00, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, // 0x45
499        0x00, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, // 0x46
500        0x00, 0x00, 0x3c, 0x66, 0x66, 0x60, 0x60, 0x6e, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x47
501        0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x48
502        0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, 0x00, 0x00, // 0x49
503        0x00, 0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x4a
504        0x00, 0x00, 0xc6, 0xc6, 0xcc, 0xcc, 0xd8, 0xf0, 0xd8, 0xcc, 0xcc, 0xc6, 0xc6, 0x00, 0x00, 0x00, // 0x4b
505        0x00, 0x00, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, // 0x4c
506        0x00, 0x00, 0xc6, 0xee, 0xee, 0xfe, 0xd6, 0xd6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, // 0x4d
507        0x00, 0x00, 0xc6, 0xc6, 0xe6, 0xe6, 0xf6, 0xfe, 0xde, 0xce, 0xce, 0xc6, 0xc6, 0x00, 0x00, 0x00, // 0x4e
508        0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x4f
509        0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, // 0x50
510        0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x0c, 0x06, 0x00, // 0x51
511        0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x6c, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x52
512        0x00, 0x00, 0x3c, 0x66, 0x66, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x53
513        0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x54
514        0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x55
515        0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x3c, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x56
516        0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xd6, 0xd6, 0xfe, 0xee, 0xee, 0xc6, 0x00, 0x00, 0x00, // 0x57
517        0x00, 0x00, 0xc3, 0xc3, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x3c, 0x66, 0xc3, 0xc3, 0x00, 0x00, 0x00, // 0x58
518        0x00, 0x00, 0xc3, 0xc3, 0x66, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x59
519        0x00, 0x00, 0x7e, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x30, 0x30, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, // 0x5a
520        0x00, 0x3c, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3c, 0x00, 0x00, // 0x5b
521        0xc0, 0xc0, 0x60, 0x60, 0x30, 0x30, 0x18, 0x18, 0x0c, 0x0c, 0x06, 0x06, 0x03, 0x03, 0x00, 0x00, // 0x5c
522        0x00, 0x3c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x3c, 0x00, 0x00, // 0x5d
523        0x00, 0x10, 0x38, 0x6c, 0x6c, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x5e
524        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, // 0x5f
525        0x00, 0x18, 0x18, 0x0c, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x60
526        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x61
527        0x00, 0x00, 0x60, 0x60, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x00, 0x00, 0x00, // 0x62
528        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x63
529        0x00, 0x00, 0x06, 0x06, 0x06, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x64
530        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, // 0x65
531        0x00, 0x00, 0x1e, 0x30, 0x30, 0x30, 0x7e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, // 0x66
532        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x7c, // 0x67
533        0x00, 0x00, 0x60, 0x60, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x68
534        0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1e, 0x00, 0x00, 0x00, // 0x69
535        0x00, 0x00, 0x0c, 0x0c, 0x00, 0x00, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x78, // 0x6a
536        0x00, 0x00, 0x60, 0x60, 0x60, 0x60, 0x66, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x6b
537        0x00, 0x00, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1e, 0x00, 0x00, 0x00, // 0x6c
538        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0xfe, 0xd6, 0xd6, 0xd6, 0xd6, 0xc6, 0x00, 0x00, 0x00, // 0x6d
539        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x6e
540        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x6f
541        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x60, // 0x70
542        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x06, // 0x71
543        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, // 0x72
544        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x60, 0x60, 0x3c, 0x06, 0x06, 0x7c, 0x00, 0x00, 0x00, // 0x73
545        0x00, 0x00, 0x00, 0x30, 0x30, 0x30, 0x7e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x1e, 0x00, 0x00, 0x00, // 0x74
546        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x75
547        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x00, 0x00, 0x00, // 0x76
548        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xd6, 0xd6, 0xd6, 0x7c, 0x6c, 0x00, 0x00, 0x00, // 0x77
549        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0x6c, 0x38, 0x6c, 0xc6, 0xc6, 0x00, 0x00, 0x00, // 0x78
550        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x3c, // 0x79
551        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x7e, 0x00, 0x00, 0x00, // 0x7a
552        0x00, 0x0e, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf0, 0x18, 0x18, 0x18, 0x18, 0x18, 0x0e, 0x00, 0x00, // 0x7b
553        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, // 0x7c
554        0x00, 0xe0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x1e, 0x30, 0x30, 0x30, 0x30, 0x30, 0xe0, 0x00, 0x00, // 0x7d
555        0x00, 0x72, 0xd6, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x7e
556        0x00, 0x18, 0x3c, 0x7e, 0xff, 0xc3, 0xc3, 0xc3, 0xdb, 0xdb, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x7f
557        0x00, 0x00, 0x3c, 0x66, 0x66, 0x60, 0x60, 0x60, 0x60, 0x60, 0x66, 0x66, 0x3c, 0x0c, 0x06, 0x1c, // 0x80
558        0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x81
559        0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, // 0x82
560        0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x83
561        0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x84
562        0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x85
563        0x00, 0x00, 0x3c, 0x66, 0x3c, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x86
564        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3c, 0x0c, 0x06, 0x1c, // 0x87
565        0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, // 0x88
566        0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, // 0x89
567        0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, // 0x8a
568        0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, // 0x8b
569        0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, // 0x8c
570        0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, // 0x8d
571        0x66, 0x66, 0x00, 0x18, 0x3c, 0x66, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x8e
572        0x3c, 0x66, 0x3c, 0x00, 0x18, 0x3c, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x8f
573        0x0c, 0x18, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, // 0x90
574        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x1b, 0x1b, 0x7f, 0xd8, 0xd8, 0x77, 0x00, 0x00, 0x00, // 0x91
575        0x00, 0x00, 0x3f, 0x7c, 0xfc, 0xcc, 0xcc, 0xfe, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0x00, 0x00, 0x00, // 0x92
576        0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x93
577        0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x94
578        0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x95
579        0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x96
580        0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x97
581        0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x3c, // 0x98
582        0x66, 0x66, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x99
583        0x66, 0x66, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x9a
584        0x00, 0x18, 0x18, 0x18, 0x3c, 0x66, 0x60, 0x60, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x9b
585        0x00, 0x00, 0x38, 0x6c, 0x6c, 0x60, 0x60, 0xf0, 0x60, 0x60, 0x66, 0x66, 0xfc, 0x00, 0x00, 0x00, // 0x9c
586        0x00, 0x00, 0xc3, 0xc3, 0x66, 0x66, 0x3c, 0x18, 0x7e, 0x18, 0x7e, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x9d
587        0x00, 0xfc, 0x66, 0x66, 0x7c, 0x62, 0x66, 0x6f, 0x66, 0x66, 0x66, 0xf3, 0x00, 0x00, 0x00, 0x00, // 0x9e
588        0x00, 0x0e, 0x1b, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x18, 0xd8, 0x70, 0x00, 0x00, // 0x9f
589        0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0xa0
590        0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, // 0xa1
591        0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0xa2
592        0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0xa3
593        0x00, 0x00, 0x76, 0xdc, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0xa4
594        0x76, 0xdc, 0x00, 0xc6, 0xc6, 0xe6, 0xf6, 0xde, 0xce, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, // 0xa5
595        0x00, 0x3c, 0x06, 0x06, 0x3e, 0x66, 0x66, 0x3e, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xa6
596        0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xa7
597        0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x30, 0x30, 0x60, 0x60, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0xa8
598        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xa9
599        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x06, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xaa
600        0x00, 0x40, 0xc6, 0x46, 0x4c, 0x4c, 0x18, 0x18, 0x30, 0x30, 0x6c, 0x62, 0xc4, 0xc8, 0x0e, 0x00, // 0xab
601        0x00, 0x40, 0xc6, 0x46, 0x4c, 0x4c, 0x18, 0x18, 0x30, 0x30, 0x62, 0x66, 0xca, 0xcf, 0x02, 0x00, // 0xac
602        0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // 0xad
603        0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x66, 0xcc, 0x66, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xae
604        0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x66, 0x33, 0x66, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xaf
605        0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, // 0xb0
606        0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, // 0xb1
607        0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, // 0xb2
608        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xb3
609        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xb4
610        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0x18, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xb5
611        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xec, 0xec, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xb6
612        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfc, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xb7
613        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x18, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xb8
614        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xec, 0x0c, 0xec, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xb9
615        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xba
616        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x0c, 0xec, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xbb
617        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xec, 0x0c, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xbc
618        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xbd
619        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0x18, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xbe
620        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xbf
621        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xc0
622        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xc1
623        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xc2
624        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xc3
625        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xc4
626        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xc5
627        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x18, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xc6
628        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6f, 0x6f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xc7
629        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6f, 0x60, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xc8
630        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x60, 0x6f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xc9
631        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xef, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xca
632        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xef, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xcb
633        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6f, 0x60, 0x6f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xcc
634        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xcd
635        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xef, 0x00, 0xef, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xce
636        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xcf
637        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xd0
638        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xd1
639        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xd2
640        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x7f, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xd3
641        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x18, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xd4
642        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x18, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xd5
643        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x7f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xd6
644        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xff, 0xff, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xd7
645        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0x18, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xd8
646        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xd9
647        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xda
648        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 0xdb
649        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 0xdc
650        0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0xdd
651        0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, // 0xde
652        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xdf
653        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xce, 0xc6, 0xc6, 0xc6, 0xce, 0x76, 0x00, 0x00, 0x00, // 0xe0
654        0x00, 0x00, 0x78, 0xcc, 0xcc, 0xcc, 0xd8, 0xcc, 0xc6, 0xc6, 0xc6, 0xc6, 0xcc, 0x00, 0x00, 0x00, // 0xe1
655        0x00, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, // 0xe2
656        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0xe3
657        0x00, 0x00, 0xfe, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0xfe, 0x00, 0x00, 0x00, // 0xe4
658        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xcc, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, // 0xe5
659        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0xc0, // 0xe6
660        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x0c, 0x00, 0x00, 0x00, // 0xe7
661        0x10, 0x10, 0x10, 0x7c, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0x7c, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, // 0xe8
662        0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, // 0xe9
663        0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xee, 0x6c, 0x6c, 0xee, 0x00, 0x00, 0x00, // 0xea
664        0x00, 0x00, 0xfe, 0xc0, 0xc0, 0x60, 0x30, 0x18, 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, // 0xeb
665        0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xdb, 0xdb, 0xdb, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xec
666        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc0, 0xdc, 0xd6, 0xd6, 0xd6, 0x7c, 0x10, 0x10, 0x00, // 0xed
667        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x60, 0x60, 0x3c, 0x60, 0x60, 0x3e, 0x00, 0x00, 0x00, // 0xee
668        0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, // 0xef
669        0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xf0
670        0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x00, 0x7e, 0x00, 0x00, 0x00, // 0xf1
671        0x00, 0x00, 0x00, 0x00, 0x30, 0x18, 0x0c, 0x06, 0x0c, 0x18, 0x30, 0x00, 0x7e, 0x00, 0x00, 0x00, // 0xf2
672        0x00, 0x00, 0x00, 0x00, 0x0c, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0c, 0x00, 0x7e, 0x00, 0x00, 0x00, // 0xf3
673        0x00, 0x00, 0x0e, 0x1b, 0x1b, 0x1b, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xf4
674        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xd8, 0xd8, 0xd8, 0x70, 0x00, 0x00, 0x00, // 0xf5
675        0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x7e, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xf6
676        0x00, 0x00, 0x00, 0x00, 0x72, 0xd6, 0x9c, 0x00, 0x72, 0xd6, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xf7
677        0x00, 0x3c, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xf8
678        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xf9
679        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xfa
680        0x00, 0x03, 0x03, 0x06, 0x06, 0x06, 0x06, 0x06, 0xcc, 0xcc, 0x6c, 0x38, 0x18, 0x00, 0x00, 0x00, // 0xfb
681        0x00, 0x00, 0x00, 0x78, 0x6c, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xfc
682        0x00, 0x38, 0x6c, 0x0c, 0x18, 0x30, 0x60, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xfd
683        0x00, 0x00, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x00, 0x00, // 0xfe
684        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xff
685    ];
686}
687
688// ── Generated block/braille fallback font ──────────────────────────────────
689
690/// Generated fallback [`BitmapFont`]s, embedded when the `legacy-computing` feature is enabled.
691///
692/// Covers the 10 quadrant block characters, the 60 addressable Unicode "Symbols for Legacy
693/// Computing" sextant characters, and the full 256-glyph Braille Patterns block
694/// (U+2800–U+28FF). None of these are covered by CP437 (and so not by [`unscii16`] either): quadrants and
695/// sextants exist to give [`retroglyph_core::symbols::quantize_quadrant`] and
696/// [`retroglyph_core::symbols::quantize_sextant`] a font that actually renders their glyphs as
697/// blocks instead of a CP437 solid-block substitute, and braille is a common terminal-UI density
698/// trick with no CP437 equivalent at all. All three repertoires are pure geometry (rectangular
699/// quadrants, banded sextants, a 2x4 dot grid), so both fonts below are computed at compile time
700/// by a `const fn` rather than transcribed from an external font file; there is no font asset
701/// backing this module and no `image`/build-script dependency.
702///
703/// This is two [`BitmapFont`]s ([`legacy_computing::blocks::FONT`] and
704/// [`legacy_computing::braille::FONT`]), not one: a [`BitmapFont`] addresses its glyphs with a
705/// `u8` index (see [`BitmapFont::rows`], [`BitmapFont::glyph_index`]), capping any single font at
706/// 256 glyphs. Braille alone needs the full 256, so folding quadrants and sextants into the same
707/// font would silently wrap their indices mod 256 and collide with braille glyphs. Splitting at
708/// the geometry boundary (block elements vs. braille) keeps every font within that limit with
709/// room to spare (70 for [`legacy_computing::blocks::FONT`]) instead of splitting mid-repertoire.
710///
711/// Add either or both as [`FontChain`] fallbacks alongside a primary CP437 font (e.g.
712/// [`unscii16`]) to extend a chain's coverage past CP437:
713///
714/// ```
715/// # #[cfg(feature = "legacy-computing")]
716/// # {
717/// use retroglyph_window::font::{FontChain, legacy_computing, unscii16};
718///
719/// static FALLBACKS: [retroglyph_window::font::BitmapFont; 2] =
720///     [legacy_computing::blocks::FONT, legacy_computing::braille::FONT];
721/// let chain = FontChain::new(unscii16::FONT, &FALLBACKS);
722/// let quadrant = chain.resolve('▘').expect("covered by legacy_computing::blocks");
723/// assert_eq!(quadrant.font_index(), 1);
724/// let braille = chain.resolve('\u{2837}').expect("covered by legacy_computing::braille");
725/// assert_eq!(braille.font_index(), 2);
726/// # }
727/// ```
728#[cfg(feature = "legacy-computing")]
729pub mod legacy_computing {
730    /// The 10 quadrant block glyphs and 60 addressable sextant glyphs CP437 has no mapping for.
731    ///
732    /// See [`super::legacy_computing`]'s module docs for why this is a separate [`BitmapFont`]
733    /// from [`super::legacy_computing::braille`] rather than one combined font.
734    ///
735    /// [`BitmapFont`]: crate::font::BitmapFont
736    pub mod blocks {
737        use crate::font::BitmapFont;
738
739        /// Number of quadrant block glyphs (the 10 not already covered by CP437).
740        const QUADRANT_COUNT: usize = 10;
741        /// Number of sextant glyphs (the 60 addressable masks not already covered by CP437).
742        const SEXTANT_COUNT: usize = 60;
743        /// Number of `retroglyph_core::symbols::bar` eighth-fraction glyphs not already covered
744        /// by CP437 (`ONE_EIGHTH`, `ONE_QUARTER`, `THREE_EIGHTHS`, `FIVE_EIGHTHS`,
745        /// `THREE_QUARTERS`, `SEVEN_EIGHTHS`; `HALF` and `FULL` are CP437's own `▄`/`█`).
746        const BAR_COUNT: usize = 6;
747        /// Number of `retroglyph_core::symbols::block` eighth-fraction glyphs not already
748        /// covered by CP437 (`ONE_EIGHTH`, `ONE_QUARTER`, `THREE_EIGHTHS`, `FIVE_EIGHTHS`,
749        /// `THREE_QUARTERS`, `SEVEN_EIGHTHS`; `HALF` and `FULL` are CP437's own `▌`/`█`).
750        const BLOCK_COUNT: usize = 6;
751        /// Total glyph count: quadrants, then sextants, then bar levels, then block levels, in
752        /// that index order.
753        const TOTAL: usize = QUADRANT_COUNT + SEXTANT_COUNT + BAR_COUNT + BLOCK_COUNT;
754
755        /// A [`BitmapFont`] backed by the generated quadrant/sextant/bar/block glyph data.
756        ///
757        /// Built with [`BitmapFont::with_charset`] (not [`BitmapFont::new`]): none of these
758        /// codepoints are in the CP437 table this crate's default mapping uses, so this font
759        /// declares its own explicit `char` charset instead.
760        #[allow(clippy::cast_possible_truncation)]
761        pub const FONT: BitmapFont = BitmapFont::with_charset(&DATA, 8, 16, TOTAL as u16, &CHARSET);
762
763        /// The 10 quadrant block glyphs not already covered by CP437, as `(mask, char)` pairs.
764        ///
765        /// `mask` is a 4-bit pattern, bit 0 = top-left, bit 1 = top-right, bit 2 = bottom-left,
766        /// bit 3 = bottom-right (matching `retroglyph_core::symbols::QUADRANTS`'s own bit
767        /// order), skipping the 6 masks CP437 already serves (`0` space, `3` `▀`, `5` `▌`,
768        /// `10` `▐`, `12` `▄`, `15` `█`).
769        #[rustfmt::skip]
770        const QUADRANTS: [(u8, char); QUADRANT_COUNT] = [
771            (1, '▘'), (2, '▝'), (4, '▖'), (6, '▞'), (7, '▛'),
772            (8, '▗'), (9, '▚'), (11, '▜'), (13, '▙'), (14, '▟'),
773        ];
774
775        /// `retroglyph_core::symbols::bar`'s 6 eighth-fraction levels CP437 doesn't cover, as
776        /// `(eighths, char)` pairs: a bottom-anchored vertical fill, `eighths` rows out of 8
777        /// filled from the bottom of the cell (matching `bar::NINE_LEVELS`'s ordering).
778        #[rustfmt::skip]
779        const BAR_LEVELS: [(u8, char); BAR_COUNT] = [
780            (1, '▁'), (2, '▂'), (3, '▃'), (5, '▅'), (6, '▆'), (7, '▇'),
781        ];
782
783        /// `retroglyph_core::symbols::block`'s 6 eighth-fraction levels CP437 doesn't cover, as
784        /// `(eighths, char)` pairs: a left-anchored horizontal fill, `eighths` columns out of 8
785        /// filled from the left of the cell.
786        #[rustfmt::skip]
787        const BLOCK_LEVELS: [(u8, char); BLOCK_COUNT] = [
788            (1, '▏'), (2, '▎'), (3, '▍'), (5, '▋'), (6, '▊'), (7, '▉'),
789        ];
790
791        /// The 60 addressable sextant masks, in ascending order: every 6-bit pattern `1..=62`
792        /// (`0` and `63` would be space/full-block, already CP437) except `21` and `42` (a fully
793        /// filled left/right column respectively, CP437's own `▌`/`▐`, which have no
794        /// codepoint of their own in the Sextants block).
795        ///
796        /// Bit order: 0 = top-left, 1 = top-right, 2 = mid-left, 3 = mid-right, 4 = bottom-left,
797        /// 5 = bottom-right (matching `retroglyph_core::symbols::SEXTANTS`'s own bit order).
798        const fn sextant_masks() -> [u8; SEXTANT_COUNT] {
799            let mut masks = [0u8; SEXTANT_COUNT];
800            let mut m: u16 = 1;
801            let mut i = 0;
802            while m <= 62 {
803                if m != 21 && m != 42 {
804                    #[allow(clippy::cast_possible_truncation)]
805                    {
806                        masks[i] = m as u8;
807                    }
808                    i += 1;
809                }
810                m += 1;
811            }
812            masks
813        }
814
815        /// Maps a sextant `mask` (`1..=62`, excluding `21`/`42`) to its codepoint in the
816        /// Symbols for Legacy Computing block.
817        ///
818        /// Sextant codepoints are not `0x1FB00 + mask`: masks `21` and `42` are gaps (see
819        /// [`QUADRANTS`], they're CP437's `▌`/`▐` instead), so every mask above each gap
820        /// shifts its codepoint down by one relative to a naive offset. Mask `1` -> U+1FB00;
821        /// mask `22` (one gap below it, at `21`) -> U+1FB00 + 21 - 1 = U+1FB14.
822        const fn sextant_codepoint(mask: u8) -> u32 {
823            let gaps_below = if mask > 21 { 1 } else { 0 } + if mask > 42 { 1 } else { 0 };
824            0x1_FB00 + (mask as u32 - 1) - gaps_below
825        }
826
827        /// Which drawing routine fills in a [`GLYPHS`] entry's bitmap, and the mask or fill
828        /// level that routine needs. Kept private: it exists only to give [`build_data`] and
829        /// [`GLYPHS`] a single shared description of "how to draw glyph `i`" instead of two
830        /// hand-synchronized base-offset computations.
831        #[derive(Clone, Copy)]
832        enum Shape {
833            Quadrant(u8),
834            Sextant(u8),
835            Bar(u8),
836            Block(u8),
837        }
838
839        /// Every glyph this font draws, in glyph-index order: quadrants, then sextants, then
840        /// bar levels, then block levels. `GLYPHS[i]` is glyph index `i`'s `(char, Shape)`, so
841        /// this one array is both [`build_data`]'s draw order and [`FONT`]'s charset
842        /// ([`CHARSET`] is just `GLYPHS[i].0` for each `i`).
843        const GLYPHS: [(char, Shape); TOTAL] = build_glyphs();
844
845        const fn build_glyphs() -> [(char, Shape); TOTAL] {
846            let mut glyphs = [('\0', Shape::Quadrant(0)); TOTAL];
847
848            let mut qi = 0;
849            while qi < QUADRANT_COUNT {
850                let (mask, ch) = QUADRANTS[qi];
851                glyphs[qi] = (ch, Shape::Quadrant(mask));
852                qi += 1;
853            }
854
855            let masks = sextant_masks();
856            let mut si = 0;
857            while si < SEXTANT_COUNT {
858                let mask = masks[si];
859                let cp = sextant_codepoint(mask);
860                let Some(ch) = char::from_u32(cp) else {
861                    panic!("sextant codepoint is not a valid char")
862                };
863                glyphs[QUADRANT_COUNT + si] = (ch, Shape::Sextant(mask));
864                si += 1;
865            }
866
867            let mut bi = 0;
868            while bi < BAR_COUNT {
869                let (eighths, ch) = BAR_LEVELS[bi];
870                glyphs[QUADRANT_COUNT + SEXTANT_COUNT + bi] = (ch, Shape::Bar(eighths));
871                bi += 1;
872            }
873
874            let mut bli = 0;
875            while bli < BLOCK_COUNT {
876                let (eighths, ch) = BLOCK_LEVELS[bli];
877                glyphs[QUADRANT_COUNT + SEXTANT_COUNT + BAR_COUNT + bli] =
878                    (ch, Shape::Block(eighths));
879                bli += 1;
880            }
881
882            glyphs
883        }
884
885        /// Sets pixel `(x, y)` of glyph `index` in `data` (a full `[u8; TOTAL * 16]` glyph
886        /// table).
887        const fn set_pixel(data: &mut [u8; TOTAL * 16], index: usize, x: u8, y: u8) {
888            let row = index * 16 + y as usize;
889            data[row] |= 1 << (7 - x);
890        }
891
892        /// Computes the full glyph bitmap table by drawing each of [`GLYPHS`]' shapes at its
893        /// own index.
894        const fn build_data() -> [u8; TOTAL * 16] {
895            let mut data = [0u8; TOTAL * 16];
896
897            let mut index = 0;
898            while index < TOTAL {
899                let (_, shape) = GLYPHS[index];
900                match shape {
901                    // Each glyph is one quarter-rectangle of the 8x16 cell (mx=4, my=8 split).
902                    Shape::Quadrant(mask) => {
903                        let mut y = 0u8;
904                        while y < 16 {
905                            let mut x = 0u8;
906                            while x < 8 {
907                                let bit = if x < 4 {
908                                    if y < 8 { 0 } else { 2 }
909                                } else if y < 8 {
910                                    1
911                                } else {
912                                    3
913                                };
914                                if (mask >> bit) & 1 == 1 {
915                                    set_pixel(&mut data, index, x, y);
916                                }
917                                x += 1;
918                            }
919                            y += 1;
920                        }
921                    }
922                    // 2 columns (mx=4) x 3 row bands (y=0,5,11,16, uneven, to avoid a 1px seam
923                    // between vertically stacked filled cells).
924                    Shape::Sextant(mask) => {
925                        let mut y = 0u8;
926                        while y < 16 {
927                            let row = if y < 5 {
928                                0
929                            } else if y < 11 {
930                                1
931                            } else {
932                                2
933                            };
934                            let mut x = 0u8;
935                            while x < 8 {
936                                let col: usize = if x >= 4 { 1 } else { 0 };
937                                let bit = row * 2 + col;
938                                if (mask >> bit) & 1 == 1 {
939                                    set_pixel(&mut data, index, x, y);
940                                }
941                                x += 1;
942                            }
943                            y += 1;
944                        }
945                    }
946                    // Bottom-anchored, `eighths` rows out of 16 (2px per eighth) filled from the
947                    // bottom of the cell.
948                    Shape::Bar(eighths) => {
949                        let fill_from = 16 - eighths * 2;
950                        let mut y = fill_from;
951                        while y < 16 {
952                            let mut x = 0u8;
953                            while x < 8 {
954                                set_pixel(&mut data, index, x, y);
955                                x += 1;
956                            }
957                            y += 1;
958                        }
959                    }
960                    // Left-anchored, `eighths` columns out of 8 (1px per eighth) filled from the
961                    // left of the cell.
962                    Shape::Block(eighths) => {
963                        let mut y = 0u8;
964                        while y < 16 {
965                            let mut x = 0u8;
966                            while x < eighths {
967                                set_pixel(&mut data, index, x, y);
968                                x += 1;
969                            }
970                            y += 1;
971                        }
972                    }
973                }
974                index += 1;
975            }
976
977            data
978        }
979
980        /// Computes the `char` charset table for [`FONT`]: `GLYPHS[i].0` for each `i`.
981        const fn build_charset() -> [char; TOTAL] {
982            let mut charset = ['\0'; TOTAL];
983            let mut i = 0;
984            while i < TOTAL {
985                charset[i] = GLYPHS[i].0;
986                i += 1;
987            }
988            charset
989        }
990
991        /// Glyph bitmap data for [`FONT`]: `TOTAL` glyphs, 16 bytes each, computed at compile
992        /// time.
993        static DATA: [u8; TOTAL * 16] = build_data();
994
995        /// The `char` charset table for [`FONT`], computed at compile time.
996        static CHARSET: [char; TOTAL] = build_charset();
997
998        #[cfg(test)]
999        mod tests {
1000            use super::{
1001                BAR_LEVELS, BLOCK_LEVELS, CHARSET, FONT, QUADRANTS, SEXTANT_COUNT, TOTAL,
1002                sextant_codepoint, sextant_masks,
1003            };
1004            use crate::font::FontChain;
1005            use std::collections::HashSet;
1006
1007            #[test]
1008            fn total_glyph_count_matches_quadrants_plus_sextants_plus_bar_plus_block() {
1009                assert_eq!(TOTAL, 10 + 60 + 6 + 6);
1010                assert_eq!(FONT.glyph_count(), u16::try_from(TOTAL).unwrap());
1011            }
1012
1013            #[test]
1014            fn no_charset_entry_duplicates_a_codepoint_cp437_already_serves() {
1015                // Space, the 4 CP437 half/quadrant blocks, the full block, and the shade ramp
1016                // are all already reachable through `unscii16`/CP437; this font must not
1017                // re-supply them.
1018                let already_cp437: HashSet<char> = [' ', '▀', '▄', '▌', '▐', '█', '░', '▒', '▓']
1019                    .into_iter()
1020                    .collect();
1021                for &ch in &CHARSET {
1022                    assert!(
1023                        !already_cp437.contains(&ch),
1024                        "{ch:?} (U+{:04X}) duplicates existing CP437 coverage",
1025                        ch as u32
1026                    );
1027                }
1028            }
1029
1030            #[test]
1031            fn every_charset_character_appears_exactly_once() {
1032                let mut seen = HashSet::with_capacity(TOTAL);
1033                for &ch in &CHARSET {
1034                    assert!(seen.insert(ch), "{ch:?} appears more than once in CHARSET");
1035                }
1036                assert_eq!(seen.len(), TOTAL);
1037            }
1038
1039            #[test]
1040            fn sextant_masks_skip_21_and_42() {
1041                let masks = sextant_masks();
1042                assert_eq!(masks.len(), SEXTANT_COUNT);
1043                assert!(!masks.contains(&21));
1044                assert!(!masks.contains(&42));
1045                assert_eq!(masks[0], 1);
1046                assert_eq!(masks[SEXTANT_COUNT - 1], 62);
1047            }
1048
1049            #[test]
1050            fn sextant_codepoint_shifts_down_after_each_gap() {
1051                assert_eq!(sextant_codepoint(1), 0x1FB00);
1052                // One gap below (mask 21) has already been skipped by the time mask 22 is
1053                // reached.
1054                assert_eq!(sextant_codepoint(22), 0x1FB00 + 21 - 1);
1055                // Two gaps below (masks 21 and 42) have been skipped by mask 43.
1056                assert_eq!(sextant_codepoint(43), 0x1FB00 + 42 - 2);
1057            }
1058
1059            /// Round-trips this module's `QUADRANTS` table against
1060            /// `retroglyph_core::symbols::QUADRANTS`, the table it exists to invert (retroglyph#769).
1061            /// The two are maintained by hand in separate crates with nothing but a doc-comment
1062            /// claim tying them together; a wrong bit order or codepoint here would silently
1063            /// scramble any posterized image rendered through `quantize_quadrant`, invisible to
1064            /// ordinary code review.
1065            #[test]
1066            fn quadrant_table_round_trips_core_subcell_quadrants() {
1067                // The 6 masks CP437 already serves directly, per `QUADRANTS`'s own doc comment;
1068                // not present in this module's `QUADRANTS` (which only covers the other 10).
1069                const CP437_COVERED: [(u8, char); 6] = [
1070                    (0, ' '),
1071                    (3, '▀'),
1072                    (5, '▌'),
1073                    (10, '▐'),
1074                    (12, '▄'),
1075                    (15, '█'),
1076                ];
1077
1078                let mut by_mask: [Option<char>; 16] = [None; 16];
1079                for &(mask, ch) in &CP437_COVERED {
1080                    by_mask[mask as usize] = Some(ch);
1081                }
1082                for &(mask, ch) in &QUADRANTS {
1083                    by_mask[mask as usize] = Some(ch);
1084                }
1085
1086                for (mask, expected) in retroglyph_core::symbols::QUADRANTS.into_iter().enumerate()
1087                {
1088                    assert_eq!(
1089                        by_mask[mask],
1090                        Some(expected),
1091                        "mask {mask}: this module's quadrant table disagrees with \
1092                         retroglyph_core::symbols::QUADRANTS[{mask}] ({expected:?})"
1093                    );
1094                }
1095            }
1096
1097            /// Round-trips this module's sextant generation (`sextant_masks` plus
1098            /// `sextant_codepoint`, and the 4 masks CP437 already serves) against
1099            /// `retroglyph_core::symbols::SEXTANTS`, the table it exists to invert (retroglyph#769).
1100            /// The Symbols for Legacy Computing block is non-contiguous (which is exactly why
1101            /// `sextant_codepoint`'s gap-correction exists), so this is the class of table where a
1102            /// hand-review-only guarantee is weakest.
1103            #[test]
1104            fn sextant_table_round_trips_core_subcell_sextants() {
1105                for (mask, expected) in retroglyph_core::symbols::SEXTANTS.into_iter().enumerate() {
1106                    let mask = u8::try_from(mask).unwrap();
1107                    let actual = match mask {
1108                        0 => ' ',
1109                        21 => '▌',
1110                        42 => '▐',
1111                        63 => '█',
1112                        _ => char::from_u32(sextant_codepoint(mask))
1113                            .expect("sextant_codepoint always yields a valid char"),
1114                    };
1115                    assert_eq!(
1116                        actual, expected,
1117                        "mask {mask}: sextant_codepoint disagrees with \
1118                         retroglyph_core::symbols::SEXTANTS[{mask}]"
1119                    );
1120                }
1121            }
1122
1123            /// Every quadrant glyph's set pixels fall in the correct quarter of the 8x16 cell.
1124            #[test]
1125            fn quadrant_top_left_mask_only_fills_the_top_left_quarter() {
1126                let index = FONT.glyph_index('▘').expect("U+2598 is covered");
1127                for (x, y) in FONT.glyph_pixels(index) {
1128                    assert!(x < 4 && y < 8, "({x}, {y}) outside the top-left quarter");
1129                }
1130            }
1131
1132            #[test]
1133            fn font_chain_resolves_quadrant_via_blocks_fallback() {
1134                static PRIMARY_DATA: [u8; 256 * 16] = [0; 256 * 16];
1135                const PRIMARY: crate::font::BitmapFont =
1136                    crate::font::BitmapFont::new(&PRIMARY_DATA, 8, 16, 256);
1137
1138                static FALLBACKS: [crate::font::BitmapFont; 1] = [FONT];
1139                let chain = FontChain::new(PRIMARY, &FALLBACKS);
1140
1141                let quadrant = chain
1142                    .resolve('▘')
1143                    .expect("covered by legacy_computing::blocks");
1144                assert_eq!(quadrant.font_index(), 1);
1145                assert!(!quadrant.is_notdef());
1146            }
1147
1148            /// Every `bar`/`block` eighth-fraction glyph this module generates is reachable by
1149            /// its own `char` and resolves to a non-empty, non-`notdef` glyph through a
1150            /// [`FontChain`] (retroglyph#832).
1151            #[test]
1152            fn bar_and_block_levels_are_covered_and_non_empty() {
1153                static PRIMARY_DATA: [u8; 256 * 16] = [0; 256 * 16];
1154                const PRIMARY: crate::font::BitmapFont =
1155                    crate::font::BitmapFont::new(&PRIMARY_DATA, 8, 16, 256);
1156
1157                static FALLBACKS: [crate::font::BitmapFont; 1] = [FONT];
1158                let chain = FontChain::new(PRIMARY, &FALLBACKS);
1159
1160                for &(_, ch) in BAR_LEVELS.iter().chain(BLOCK_LEVELS.iter()) {
1161                    let resolved = chain
1162                        .resolve(ch)
1163                        .unwrap_or_else(|| panic!("{ch:?} covered by legacy_computing::blocks"));
1164                    assert_eq!(resolved.font_index(), 1);
1165                    assert!(!resolved.is_notdef(), "{ch:?} resolved to notdef");
1166                    assert!(
1167                        FONT.glyph_pixels(resolved.index()).count() > 0,
1168                        "{ch:?} has no set pixels"
1169                    );
1170                }
1171            }
1172
1173            /// `BAR_LEVELS`' fill grows monotonically with `eighths`: level `n` must be a strict
1174            /// pixel-count superset of level `n - 1` (bottom-anchored), matching
1175            /// `bar::NINE_LEVELS`' intended ramp semantics.
1176            #[test]
1177            fn bar_levels_fill_monotonically_from_the_bottom() {
1178                let mut last_count = 0usize;
1179                for &(eighths, ch) in &BAR_LEVELS {
1180                    let index = FONT.glyph_index(ch).unwrap();
1181                    let pixels: Vec<(u8, u8)> = FONT.glyph_pixels(index).collect();
1182                    assert!(
1183                        pixels.iter().all(|&(_, y)| y >= 16 - eighths * 2),
1184                        "{ch:?} has a filled pixel above its {eighths}/8 fill line"
1185                    );
1186                    assert_eq!(pixels.len(), usize::from(eighths) * 2 * 8);
1187                    assert!(pixels.len() > last_count);
1188                    last_count = pixels.len();
1189                }
1190            }
1191
1192            /// `BLOCK_LEVELS`' fill grows monotonically with `eighths`: level `n` must be a
1193            /// strict pixel-count superset of level `n - 1` (left-anchored).
1194            #[test]
1195            fn block_levels_fill_monotonically_from_the_left() {
1196                let mut last_count = 0usize;
1197                for &(eighths, ch) in &BLOCK_LEVELS {
1198                    let index = FONT.glyph_index(ch).unwrap();
1199                    let pixels: Vec<(u8, u8)> = FONT.glyph_pixels(index).collect();
1200                    assert!(
1201                        pixels.iter().all(|&(x, _)| x < eighths),
1202                        "{ch:?} has a filled pixel past its {eighths}/8 fill line"
1203                    );
1204                    assert_eq!(pixels.len(), usize::from(eighths) * 16);
1205                    assert!(pixels.len() > last_count);
1206                    last_count = pixels.len();
1207                }
1208            }
1209        }
1210    }
1211
1212    /// The full 256-glyph Braille Patterns block (U+2800–U+28FF) CP437 has no mapping for.
1213    ///
1214    /// See [`super::legacy_computing`]'s module docs for why this is a separate [`BitmapFont`]
1215    /// from [`super::legacy_computing::blocks`] rather than one combined font.
1216    ///
1217    /// [`BitmapFont`]: crate::font::BitmapFont
1218    pub mod braille {
1219        use crate::font::BitmapFont;
1220
1221        /// Total glyph count: the full U+2800..=U+28FF block.
1222        const TOTAL: usize = 256;
1223
1224        /// A [`BitmapFont`] backed by the generated braille glyph data.
1225        ///
1226        /// Built with [`BitmapFont::with_charset`] (not [`BitmapFont::new`]): braille
1227        /// codepoints are not in the CP437 table this crate's default mapping uses, so this
1228        /// font declares its own explicit `char` charset instead.
1229        #[allow(clippy::cast_possible_truncation)]
1230        pub const FONT: BitmapFont = BitmapFont::with_charset(&DATA, 8, 16, TOTAL as u16, &CHARSET);
1231
1232        /// Dot-column pixel centers for braille glyphs (`x`), in cell-pixel coordinates.
1233        const COL_X: [u8; 2] = [1, 4];
1234        /// Dot-row pixel centers for braille glyphs (`y`), in cell-pixel coordinates.
1235        const ROW_Y: [u8; 4] = [1, 5, 9, 13];
1236
1237        /// Maps a braille dot's `(col, row)` position (`col` in `0..2`, `row` in `0..4`) to its
1238        /// bit index in the U+2800 block's `u8` payload, per historical braille dot numbering:
1239        /// column 0 is dots 1,2,3,7 (bit indices 0,1,2,6), column 1 is dots 4,5,6,8 (bit indices
1240        /// 3,4,5,7).
1241        const fn bit_index(col: usize, row: usize) -> u32 {
1242            match (col, row) {
1243                (0, 0) => 0,
1244                (0, 1) => 1,
1245                (0, 2) => 2,
1246                (0, 3) => 6,
1247                (1, 0) => 3,
1248                (1, 1) => 4,
1249                (1, 2) => 5,
1250                (1, 3) => 7,
1251                _ => panic!("braille dot position out of range"),
1252            }
1253        }
1254
1255        /// Sets pixel `(x, y)` of glyph `index` in `data` (a full `[u8; TOTAL * 16]` glyph
1256        /// table).
1257        const fn set_pixel(data: &mut [u8; TOTAL * 16], index: usize, x: u8, y: u8) {
1258            let row = index * 16 + y as usize;
1259            data[row] |= 1 << (7 - x);
1260        }
1261
1262        /// Computes the full glyph bitmap table: 2 columns x 4 rows of dots per glyph, each dot
1263        /// a 3x3 filled square, matching [`CHARSET`]'s glyph-index order.
1264        const fn build_data() -> [u8; TOTAL * 16] {
1265            #[allow(clippy::cast_possible_truncation)]
1266            const TOTAL_U32: u32 = TOTAL as u32;
1267
1268            let mut data = [0u8; TOTAL * 16];
1269
1270            let mut bits: u32 = 0;
1271            while bits < TOTAL_U32 {
1272                let index = bits as usize;
1273                let mut col = 0usize;
1274                while col < 2 {
1275                    let mut row = 0usize;
1276                    while row < 4 {
1277                        let bit = bit_index(col, row);
1278                        if (bits >> bit) & 1 == 1 {
1279                            let cx = COL_X[col];
1280                            let cy = ROW_Y[row];
1281                            let mut dy: i32 = -1;
1282                            while dy <= 1 {
1283                                let mut dx: i32 = -1;
1284                                while dx <= 1 {
1285                                    let px = cx as i32 + dx;
1286                                    let py = cy as i32 + dy;
1287                                    if px >= 0 && px < 8 && py >= 0 && py < 16 {
1288                                        #[allow(
1289                                            clippy::cast_sign_loss,
1290                                            clippy::cast_possible_truncation
1291                                        )]
1292                                        set_pixel(&mut data, index, px as u8, py as u8);
1293                                    }
1294                                    dx += 1;
1295                                }
1296                                dy += 1;
1297                            }
1298                        }
1299                        row += 1;
1300                    }
1301                    col += 1;
1302                }
1303                bits += 1;
1304            }
1305
1306            data
1307        }
1308
1309        /// Computes the `char` charset table, matching [`build_data`]'s glyph order:
1310        /// `CHARSET[i] == char::from_u32(0x2800 + i).unwrap()`.
1311        const fn build_charset() -> [char; TOTAL] {
1312            #[allow(clippy::cast_possible_truncation)]
1313            const TOTAL_U32: u32 = TOTAL as u32;
1314
1315            let mut charset = ['\0'; TOTAL];
1316
1317            let mut bits: u32 = 0;
1318            while bits < TOTAL_U32 {
1319                let cp = 0x2800 + bits;
1320                let Some(ch) = char::from_u32(cp) else {
1321                    panic!("braille codepoint is not a valid char")
1322                };
1323                charset[bits as usize] = ch;
1324                bits += 1;
1325            }
1326
1327            charset
1328        }
1329
1330        /// Glyph bitmap data for [`FONT`]: `TOTAL` glyphs, 16 bytes each, computed at compile
1331        /// time.
1332        static DATA: [u8; TOTAL * 16] = build_data();
1333
1334        /// The `char` charset table for [`FONT`], computed at compile time.
1335        static CHARSET: [char; TOTAL] = build_charset();
1336
1337        #[cfg(test)]
1338        mod tests {
1339            use super::{CHARSET, FONT, TOTAL};
1340            use crate::font::FontChain;
1341            use std::collections::HashSet;
1342
1343            #[test]
1344            fn total_glyph_count_is_256() {
1345                assert_eq!(TOTAL, 256);
1346                assert_eq!(FONT.glyph_count(), 256);
1347            }
1348
1349            #[test]
1350            fn every_charset_character_appears_exactly_once() {
1351                let mut seen = HashSet::with_capacity(TOTAL);
1352                for &ch in &CHARSET {
1353                    assert!(seen.insert(ch), "{ch:?} appears more than once in CHARSET");
1354                }
1355                assert_eq!(seen.len(), TOTAL);
1356            }
1357
1358            #[test]
1359            fn covers_the_full_u2800_block() {
1360                for bits in 0u32..u32::try_from(TOTAL).unwrap() {
1361                    let ch = char::from_u32(0x2800 + bits).unwrap();
1362                    assert_eq!(CHARSET[bits as usize], ch);
1363                }
1364            }
1365
1366            /// Round-trips this module's `CHARSET` against `retroglyph_core::symbols::braille`'s
1367            /// own `glyph` function, the independent implementation it exists to render
1368            /// (retroglyph#769): every one of the 256 braille patterns must map to the identical
1369            /// codepoint through both.
1370            #[test]
1371            fn charset_round_trips_core_symbols_braille_glyph() {
1372                for bits in 0u8..=u8::MAX {
1373                    let expected = retroglyph_core::symbols::braille::glyph(bits);
1374                    assert_eq!(
1375                        CHARSET[bits as usize], expected,
1376                        "pattern {bits:#04x}: this module's CHARSET disagrees with \
1377                         retroglyph_core::symbols::braille::glyph"
1378                    );
1379                }
1380            }
1381
1382            #[test]
1383            fn blank_glyph_is_all_zero_bits() {
1384                let index = FONT.glyph_index('\u{2800}').expect("U+2800 is covered");
1385                assert!(FONT.rows(index).iter().all(|&b| b == 0));
1386            }
1387
1388            #[test]
1389            fn full_glyph_has_all_dot_positions_set() {
1390                let index = FONT.glyph_index('\u{28FF}').expect("U+28FF is covered");
1391                let pixel_count = FONT.glyph_pixels(index).count();
1392                // 8 dots, 3x3 each, none clipped by the 8x16 cell at these centers: 8 * 9 = 72
1393                // lit pixels.
1394                assert_eq!(pixel_count, 72);
1395            }
1396
1397            /// Mirrors `FontChain`'s own doc example: a chain resolving a character none of
1398            /// CP437 has a mapping for at all through this generated fallback font.
1399            #[test]
1400            fn font_chain_resolves_via_braille_fallback() {
1401                static PRIMARY_DATA: [u8; 256 * 16] = [0; 256 * 16];
1402                const PRIMARY: crate::font::BitmapFont =
1403                    crate::font::BitmapFont::new(&PRIMARY_DATA, 8, 16, 256);
1404
1405                static FALLBACKS: [crate::font::BitmapFont; 1] = [FONT];
1406                let chain = FontChain::new(PRIMARY, &FALLBACKS);
1407
1408                // A mid-range braille char: not reachable through CP437 at all.
1409                let braille = chain
1410                    .resolve('\u{2837}')
1411                    .expect("covered by legacy_computing::braille");
1412                assert_eq!(braille.font_index(), 1);
1413                assert!(!braille.is_notdef());
1414
1415                // The primary font's own CP437 coverage answers directly for the solid block;
1416                // this chain never needs to fall back to `notdef` for it.
1417                let full_block = chain.resolve('█').expect("CP437 coverage");
1418                assert_eq!(full_block.font(), PRIMARY);
1419                assert!(!full_block.is_notdef());
1420            }
1421        }
1422    }
1423}
1424
1425// ── Unicode → CP437 mapping ────────────────────────────────────────────────
1426
1427/// The substitute drawn for a character no font in a chain covers: the solid block, whichever
1428/// glyph index the font that has it stores it at.
1429///
1430/// Naming the substitute as a `char` rather than a fixed index is what keeps
1431/// [`FontChain::resolve`] total: CP437's own `0xDB` is out of range for a font with fewer than
1432/// 220 glyphs, so an index constant would resolve to a glyph that font does not have.
1433const NOTDEF: char = '█';
1434
1435/// Attempts to map a Unicode scalar to its CP437 glyph index.
1436///
1437/// ASCII (U+0020–U+007E) maps identically.  Common box-drawing characters,
1438/// block-elements, and roguelike symbols are mapped explicitly.  Returns
1439/// `None` for anything else, distinguishing "not in the CP437 table" from a
1440/// character that legitimately maps to the solid-block glyph (`'█'`) --
1441/// [`FontChain`] relies on that distinction to keep trying further
1442/// fonts on a miss instead of stopping at a false-positive solid-block hit.
1443#[allow(clippy::too_many_lines)]
1444const fn try_unicode_to_cp437(ch: char) -> Option<u8> {
1445    // Direct ASCII pass-through (the most common path for roguelikes).
1446    let u = ch as u32;
1447    if u < 0x80 {
1448        #[allow(clippy::cast_possible_truncation)]
1449        return Some(u as u8);
1450    }
1451
1452    // Named mappings for the characters roguelikes actually use.
1453    match ch {
1454        // ── Latin-1 accented letters that overlap CP437 ──────────────────
1455        'Ç' => Some(0x80),
1456        'ü' => Some(0x81),
1457        'é' => Some(0x82),
1458        'â' => Some(0x83),
1459        'ä' => Some(0x84),
1460        'à' => Some(0x85),
1461        'å' => Some(0x86),
1462        'ç' => Some(0x87),
1463        'ê' => Some(0x88),
1464        'ë' => Some(0x89),
1465        'è' => Some(0x8A),
1466        'ï' => Some(0x8B),
1467        'î' => Some(0x8C),
1468        'ì' => Some(0x8D),
1469        'Ä' => Some(0x8E),
1470        'Å' => Some(0x8F),
1471        'É' => Some(0x90),
1472        'æ' => Some(0x91),
1473        'Æ' => Some(0x92),
1474        'ô' => Some(0x93),
1475        'ö' => Some(0x94),
1476        'ò' => Some(0x95),
1477        'û' => Some(0x96),
1478        'ù' => Some(0x97),
1479        'ÿ' => Some(0x98),
1480        'Ö' => Some(0x99),
1481        'Ü' => Some(0x9A),
1482        '¢' => Some(0x9B),
1483        '£' => Some(0x9C),
1484        '¥' => Some(0x9D),
1485        '₧' => Some(0x9E),
1486        'ƒ' => Some(0x9F),
1487        'á' => Some(0xA0),
1488        'í' => Some(0xA1),
1489        'ó' => Some(0xA2),
1490        'ú' => Some(0xA3),
1491        'ñ' => Some(0xA4),
1492        'Ñ' => Some(0xA5),
1493        'ª' => Some(0xA6),
1494        'º' => Some(0xA7),
1495        '¿' => Some(0xA8),
1496        '⌐' => Some(0xA9),
1497        '¬' => Some(0xAA),
1498        '½' => Some(0xAB),
1499        '¼' => Some(0xAC),
1500        '¡' => Some(0xAD),
1501        '«' => Some(0xAE),
1502        '»' => Some(0xAF),
1503
1504        // ── Shade characters ─────────────────────────────────────────────
1505        '░' => Some(0xB0),
1506        '▒' => Some(0xB1),
1507        '▓' => Some(0xB2),
1508
1509        // ── Single-line box drawing ───────────────────────────────────────
1510        '│' => Some(0xB3),
1511        '┤' => Some(0xB4),
1512        '╡' => Some(0xB5),
1513        '╢' => Some(0xB6),
1514        '╖' => Some(0xB7),
1515        '╕' => Some(0xB8),
1516        '╣' => Some(0xB9),
1517        '║' => Some(0xBA),
1518        '╗' => Some(0xBB),
1519        '╝' => Some(0xBC),
1520        '╜' => Some(0xBD),
1521        '╛' => Some(0xBE),
1522        '┐' => Some(0xBF),
1523        '└' => Some(0xC0),
1524        '┴' => Some(0xC1),
1525        '┬' => Some(0xC2),
1526        '├' => Some(0xC3),
1527        '─' => Some(0xC4),
1528        '┼' => Some(0xC5),
1529        '╞' => Some(0xC6),
1530        '╟' => Some(0xC7),
1531        '╚' => Some(0xC8),
1532        '╔' => Some(0xC9),
1533        '╩' => Some(0xCA),
1534        '╦' => Some(0xCB),
1535        '╠' => Some(0xCC),
1536        '═' => Some(0xCD),
1537        '╬' => Some(0xCE),
1538        '╧' => Some(0xCF),
1539        '╨' => Some(0xD0),
1540        '╤' => Some(0xD1),
1541        '╥' => Some(0xD2),
1542        '╙' => Some(0xD3),
1543        '╘' => Some(0xD4),
1544        '╒' => Some(0xD5),
1545        '╓' => Some(0xD6),
1546        '╫' => Some(0xD7),
1547        '╪' => Some(0xD8),
1548        '┘' => Some(0xD9),
1549        '┌' => Some(0xDA),
1550
1551        // ── Block elements ────────────────────────────────────────────────
1552        '█' => Some(0xDB),
1553        '▄' => Some(0xDC),
1554        '▌' => Some(0xDD),
1555        '▐' => Some(0xDE),
1556        '▀' => Some(0xDF),
1557
1558        // ── Greek / math ──────────────────────────────────────────────────
1559        'α' => Some(0xE0),
1560        'ß' => Some(0xE1),
1561        'Γ' => Some(0xE2),
1562        'π' => Some(0xE3),
1563        'Σ' => Some(0xE4),
1564        'σ' => Some(0xE5),
1565        'µ' | 'μ' => Some(0xE6),
1566        'τ' => Some(0xE7),
1567        'Φ' => Some(0xE8),
1568        'Θ' => Some(0xE9),
1569        'Ω' => Some(0xEA),
1570        'δ' => Some(0xEB),
1571        '∞' => Some(0xEC),
1572        'φ' => Some(0xED),
1573        'ε' => Some(0xEE),
1574        '∩' => Some(0xEF),
1575        '≡' => Some(0xF0),
1576        '±' => Some(0xF1),
1577        '≥' => Some(0xF2),
1578        '≤' => Some(0xF3),
1579        '⌠' => Some(0xF4),
1580        '⌡' => Some(0xF5),
1581        '÷' => Some(0xF6),
1582        '≈' => Some(0xF7),
1583        '°' => Some(0xF8),
1584        '∙' => Some(0xF9),
1585        '·' => Some(0xFA),
1586        '√' => Some(0xFB),
1587        'ⁿ' => Some(0xFC),
1588        '²' => Some(0xFD),
1589        '■' => Some(0xFE),
1590        '\u{00A0}' => Some(0xFF),
1591
1592        // ── Roguelike / Unicode symbols ───────────────────────────────────
1593        '☺' => Some(0x01),
1594        '•' => Some(0x07),
1595        '☻' => Some(0x02),
1596        '♥' => Some(0x03),
1597        '♦' => Some(0x04),
1598        '♣' => Some(0x05),
1599        '♠' => Some(0x06),
1600        '◘' => Some(0x08),
1601        '○' => Some(0x09),
1602        '◙' => Some(0x0A),
1603        '♂' => Some(0x0B),
1604        '♀' => Some(0x0C),
1605        '♪' => Some(0x0D),
1606        '♫' => Some(0x0E),
1607        '☼' => Some(0x0F),
1608        '►' => Some(0x10),
1609        '◄' => Some(0x11),
1610        '↕' => Some(0x12),
1611        '‼' => Some(0x13),
1612        '¶' => Some(0x14),
1613        '§' => Some(0x15),
1614        '▬' => Some(0x16),
1615        '↨' => Some(0x17),
1616        '↑' => Some(0x18),
1617        '↓' => Some(0x19),
1618        '→' => Some(0x1A),
1619        '←' => Some(0x1B),
1620        '∟' => Some(0x1C),
1621        '↔' => Some(0x1D),
1622        '▲' => Some(0x1E),
1623        '▼' => Some(0x1F),
1624        '⌂' => Some(0x7F),
1625
1626        _ => None,
1627    }
1628}
1629
1630#[cfg(test)]
1631mod tests {
1632    use super::{BitmapFont, FontChain, try_unicode_to_cp437};
1633
1634    /// The four codepoints patched into `unscii16`'s `DATA` (see that module's doc comment)
1635    /// must actually be reachable through the char-to-glyph path, not just present at their
1636    /// raw glyph index: otherwise they're invisible to anything that goes through
1637    /// [`FontChain::resolve`]/`Surface::print`, which is every real caller.
1638    #[test]
1639    fn patched_glyphs_are_reachable_by_char() {
1640        assert_eq!(try_unicode_to_cp437('⌂'), Some(0x7F), "U+2302 HOUSE");
1641        assert_eq!(
1642            try_unicode_to_cp437('☼'),
1643            Some(0x0F),
1644            "U+263C WHITE SUN WITH RAYS"
1645        );
1646        assert_eq!(
1647            try_unicode_to_cp437('⌐'),
1648            Some(0xA9),
1649            "U+2310 REVERSED NOT SIGN"
1650        );
1651        assert_eq!(
1652            try_unicode_to_cp437('∙'),
1653            Some(0xF9),
1654            "U+2219 BULLET OPERATOR"
1655        );
1656        assert_eq!(
1657            try_unicode_to_cp437('\u{00A0}'),
1658            Some(0xFF),
1659            "U+00A0 NO-BREAK SPACE"
1660        );
1661        assert_eq!(try_unicode_to_cp437('¬'), Some(0xAA), "U+00AC NOT SIGN");
1662        assert_eq!(try_unicode_to_cp437('₧'), Some(0x9E), "U+20A7 PESETA SIGN");
1663        assert_eq!(try_unicode_to_cp437('·'), Some(0xFA), "U+00B7 MIDDLE DOT");
1664    }
1665
1666    /// Every entry in [`crate::tileset::CP437_TO_UNICODE`] must round-trip back through
1667    /// [`try_unicode_to_cp437`] to its own index: the reverse map is supposed to be a clean
1668    /// inverse of the crate's own canonical forward table.
1669    #[cfg(feature = "tilesets")]
1670    #[test]
1671    fn cp437_to_unicode_round_trips_through_try_unicode_to_cp437() {
1672        for (i, &ch) in crate::tileset::CP437_TO_UNICODE.iter().enumerate() {
1673            #[allow(clippy::cast_possible_truncation)]
1674            let expected = i as u8;
1675            assert_eq!(
1676                try_unicode_to_cp437(ch),
1677                Some(expected),
1678                "0x{i:02X} {ch:?} did not round-trip"
1679            );
1680        }
1681    }
1682
1683    /// A primary font that only covers the ASCII half of CP437 (glyph indices 0..128), so
1684    /// any character mapping into the extended range (128..256) is a miss for it.
1685    static PRIMARY_DATA: [u8; 128 * 16] = [0; 128 * 16];
1686    const PRIMARY: BitmapFont = BitmapFont::new(&PRIMARY_DATA, 8, 16, 128);
1687
1688    /// A fallback font with full CP437 coverage (glyph indices 0..256).
1689    static FALLBACK_DATA: [u8; 256 * 16] = [0; 256 * 16];
1690    const FALLBACK_FONT: BitmapFont = BitmapFont::new(&FALLBACK_DATA, 8, 16, 256);
1691
1692    #[test]
1693    fn chain_resolves_char_present_only_in_fallback_font() {
1694        // 'Ç' maps to CP437 index 0x80, which is out of range for `PRIMARY`
1695        // (glyph_count == 128) but present in `FALLBACK_FONT` (glyph_count == 256).
1696        let chain = FontChain::new(PRIMARY, &[FALLBACK_FONT]);
1697        let resolved = chain.resolve('Ç').expect("covered by the fallback font");
1698        assert_eq!(resolved.font(), FALLBACK_FONT);
1699        assert_eq!(resolved.font_index(), 1);
1700        assert_eq!(resolved.index(), 0x80);
1701        assert!(!resolved.is_notdef());
1702    }
1703
1704    #[test]
1705    fn chain_falls_back_to_solid_block_when_every_font_misses() {
1706        // 'あ' (U+3042 HIRAGANA LETTER A) isn't in the CP437 table at all, so both fonts in the
1707        // chain miss and resolution must substitute the solid block. `PRIMARY` stops at glyph 128
1708        // and so doesn't have one, which is exactly the case a fixed 0xDB fallback index used to
1709        // resolve to an out-of-range glyph for.
1710        let chain = FontChain::new(PRIMARY, &[FALLBACK_FONT]);
1711        let resolved = chain.resolve('あ').expect("solid block substitute");
1712        assert_eq!(resolved.font(), FALLBACK_FONT);
1713        assert_eq!(resolved.index(), 0xDB);
1714        assert!(resolved.is_notdef());
1715    }
1716
1717    #[test]
1718    fn chain_resolves_nothing_when_no_font_has_a_substitute() {
1719        // A chain that covers braille and nothing else: an uncovered character has no solid block
1720        // to fall back to anywhere in the chain, so resolution reports "undrawable" instead of
1721        // pointing at a glyph the font doesn't have.
1722        static DATA: [u8; 16] = [0; 16];
1723        const CHARSET: [char; 1] = ['\u{2800}'];
1724        const BRAILLE: BitmapFont = BitmapFont::with_charset(&DATA, 8, 16, 1, &CHARSET);
1725
1726        let chain = FontChain::new(BRAILLE, &[]);
1727        assert!(chain.resolve('\u{2800}').is_some());
1728        assert!(chain.resolve('A').is_none());
1729    }
1730
1731    #[test]
1732    fn single_font_chain_resolves_that_font_directly() {
1733        let chain = FontChain::from(FALLBACK_FONT);
1734        assert_eq!(chain.font_count(), 1);
1735        for ch in ['A', ' ', '█', '│', 'Ç', '☺'] {
1736            let resolved = chain.resolve(ch).expect("CP437 coverage");
1737            assert_eq!(resolved.font(), FALLBACK_FONT);
1738            assert_eq!(resolved.font_index(), 0);
1739            assert_eq!(resolved.index(), FALLBACK_FONT.glyph_index(ch).unwrap());
1740        }
1741    }
1742
1743    #[test]
1744    fn glyph_size_is_none_for_a_chain_of_mismatched_fonts() {
1745        static DATA: [u8; 8] = [0; 8];
1746        const SHORT: BitmapFont = BitmapFont::new(&DATA, 8, 8, 1);
1747
1748        assert_eq!(
1749            FontChain::new(PRIMARY, &[FALLBACK_FONT]).glyph_size(),
1750            Some((8, 16))
1751        );
1752        assert_eq!(FontChain::new(PRIMARY, &[SHORT]).glyph_size(), None);
1753    }
1754
1755    #[test]
1756    fn glyph_pixels_decodes_msb_first_row_major() {
1757        // Two 8x2 glyphs. Glyph 0: corners of the top row set; glyph 1: full top row plus one
1758        // interior pixel on the second row.
1759        static DATA: [u8; 4] = [0b1000_0001, 0b0000_0000, 0b1111_1111, 0b0000_1000];
1760        let font = BitmapFont::new(&DATA, 8, 2, 2);
1761
1762        let g0: Vec<(u8, u8)> = font.glyph_pixels(0).collect();
1763        assert_eq!(
1764            g0,
1765            [(0, 0), (7, 0)],
1766            "MSB is the leftmost pixel; row 0 first"
1767        );
1768
1769        let g1: Vec<(u8, u8)> = font.glyph_pixels(1).collect();
1770        let mut expected: Vec<(u8, u8)> = (0..8).map(|x| (x, 0)).collect();
1771        expected.push((4, 1)); // bit 3 of 0b0000_1000 -> x = width-1-3 = 4
1772        assert_eq!(g1, expected);
1773    }
1774
1775    #[test]
1776    fn glyph_pixels_is_parameterized_by_width_not_hardcoded_to_8() {
1777        // A 5px-wide glyph: set pixels must come from bits (width-1-x), i.e. bit 4 and bit 0, not
1778        // bit 7 and bit 3. This guards against a consumer re-introducing a hardcoded `7 - x`.
1779        static DATA: [u8; 1] = [0b0001_0001];
1780        let font = BitmapFont::new(&DATA, 5, 1, 1);
1781        let pixels: Vec<(u8, u8)> = font.glyph_pixels(0).collect();
1782        assert_eq!(pixels, [(0, 0), (4, 0)]);
1783    }
1784
1785    #[test]
1786    fn glyph_pixels_does_not_overflow_the_shift_for_width_above_8() {
1787        // retroglyph#729: `row >> (width - 1 - x)` used to shift past the byte's width once
1788        // `glyph_width` exceeded 8, which this 1-bit-per-row format never actually supports.
1789        static DATA: [u8; 1] = [0b1111_1111];
1790        let font = BitmapFont::new(&DATA, 12, 1, 1);
1791        let pixels: Vec<(u8, u8)> = font.glyph_pixels(0).collect();
1792        assert_eq!(pixels, (0..8).map(|x| (x, 0)).collect::<Vec<_>>());
1793    }
1794
1795    /// Reproduces retroglyph#507: a fallback font built with [`BitmapFont::with_charset`] can
1796    /// declare coverage for a codepoint CP437 has no mapping for at all (here U+2800 BRAILLE
1797    /// PATTERN BLANK), and a [`FontChain`] resolves it to that font's own distinct glyph
1798    /// index instead of colliding with CP437's solid-block fallback (`chain.resolve('\u{2588}')`,
1799    /// i.e. `'█'`).
1800    #[test]
1801    fn chain_extends_past_cp437_via_charset_fallback_font() {
1802        static BRAILLE_DATA: [u8; 16] = [0; 16];
1803        const BRAILLE_CHARSET: [char; 1] = ['\u{2800}'];
1804        const BRAILLE_FONT: BitmapFont =
1805            BitmapFont::with_charset(&BRAILLE_DATA, 8, 16, 1, &BRAILLE_CHARSET);
1806
1807        let primary = FALLBACK_FONT; // full CP437 coverage, glyph_count == 256
1808        let chain = FontChain::new(primary, &[BRAILLE_FONT]);
1809
1810        let braille = chain.resolve('\u{2800}').expect("charset coverage");
1811        assert_eq!(braille.font(), BRAILLE_FONT);
1812        assert_eq!(braille.index(), 0);
1813
1814        let full_block = chain.resolve('\u{2588}').expect("CP437 coverage"); // '█', index 0xDB
1815        assert_eq!(full_block.font(), primary);
1816        assert_eq!(full_block.index(), 0xDB);
1817
1818        assert_ne!(braille.index(), full_block.index());
1819    }
1820}
1821
1822/// Coverage test for `retroglyph_core::symbols`'s hand-maintained glyph tables against the
1823/// fullest bundled [`FontChain`] this crate can build (`unscii16` plus every `legacy_computing`
1824/// fallback font).
1825///
1826/// `core::symbols` promises a repertoire that no font is required to actually draw; nothing
1827/// checked, before this, that any bundled font could render a given entry (retroglyph#769). This
1828/// only records the gap (asserting each entry is either drawable or a documented exception): the
1829/// fix (generating the missing eighth-block glyphs and adding "falls back to notdef" doc notes
1830/// for the rest) is tracked as a follow-up, deliberately out of scope here.
1831#[cfg(all(test, feature = "default-font", feature = "legacy-computing"))]
1832mod symbols_coverage {
1833    use crate::font::{BitmapFont, FontChain, legacy_computing, unscii16};
1834    use retroglyph_core::symbols::{bar, block, border, line};
1835    use std::collections::HashSet;
1836
1837    /// The bundled `unscii16` primary font plus every `legacy_computing` fallback: the fullest
1838    /// font coverage this crate can build without a caller supplying custom glyph art.
1839    fn chain() -> FontChain<'static> {
1840        static FALLBACKS: [BitmapFont; 2] = [
1841            legacy_computing::blocks::FONT,
1842            legacy_computing::braille::FONT,
1843        ];
1844        FontChain::new(unscii16::FONT, &FALLBACKS)
1845    }
1846
1847    /// Every `core::symbols` entry that currently falls back to the notdef substitute through
1848    /// [`chain`] (or that no font in the chain can draw at all), as found by retroglyph#769's
1849    /// audit and narrowed by retroglyph#832's fix for the `bar`/`block` eighth-fraction gaps: 4
1850    /// of `border::ROUNDED`, all 6 of `border::THICK`, and 5 of `line::THICK`.
1851    ///
1852    /// None of these are fixed here: `border::ROUNDED`'s corners, `border::THICK`, and
1853    /// `line::THICK`'s tees/cross need real glyph art rather than a mechanical eighth-block
1854    /// generator (see their own doc comments in `retroglyph_core::symbols` for the same note).
1855    /// This list exists so a *regression* (a currently-covered glyph losing coverage) fails
1856    /// loudly, and so this test starts failing (forcing the list to shrink) the moment a
1857    /// future change closes any of these gaps.
1858    fn known_notdef_gaps() -> HashSet<char> {
1859        [
1860            // border::ROUNDED: 4 of 6 (the corners; horizontal/vertical are shared with PLAIN).
1861            border::ROUNDED.top_left,
1862            border::ROUNDED.top_right,
1863            border::ROUNDED.bottom_left,
1864            border::ROUNDED.bottom_right,
1865            // border::THICK: all 6.
1866            border::THICK.top_left,
1867            border::THICK.top_right,
1868            border::THICK.bottom_left,
1869            border::THICK.bottom_right,
1870            border::THICK.horizontal,
1871            border::THICK.vertical,
1872            // line::THICK: 5 of 7. `horizontal`/`vertical` are the same glyphs as
1873            // `border::THICK`'s (already counted above); the 4 tees and the cross are not.
1874            line::THICK.cross,
1875            line::THICK.vertical_left,
1876            line::THICK.vertical_right,
1877            line::THICK.horizontal_down,
1878            line::THICK.horizontal_up,
1879        ]
1880        .into_iter()
1881        .collect()
1882    }
1883
1884    #[test]
1885    fn every_symbols_entry_resolves_or_is_a_known_gap() {
1886        let chain = chain();
1887        let known = known_notdef_gaps();
1888        let mut still_notdef = HashSet::new();
1889
1890        let mut check = |ch: char| {
1891            let resolved = chain.resolve(ch);
1892            let is_notdef = resolved.is_none_or(|g| g.is_notdef());
1893            if is_notdef {
1894                still_notdef.insert(ch);
1895            }
1896        };
1897
1898        for set in [
1899            border::PLAIN,
1900            border::ROUNDED,
1901            border::DOUBLE,
1902            border::THICK,
1903        ] {
1904            check(set.top_left);
1905            check(set.top_right);
1906            check(set.bottom_left);
1907            check(set.bottom_right);
1908            check(set.horizontal);
1909            check(set.vertical);
1910        }
1911
1912        for set in [line::NORMAL, line::DOUBLE, line::THICK] {
1913            check(set.horizontal);
1914            check(set.vertical);
1915            check(set.cross);
1916            check(set.vertical_left);
1917            check(set.vertical_right);
1918            check(set.horizontal_down);
1919            check(set.horizontal_up);
1920        }
1921
1922        for ch in [
1923            block::FULL,
1924            block::SEVEN_EIGHTHS,
1925            block::THREE_QUARTERS,
1926            block::FIVE_EIGHTHS,
1927            block::HALF,
1928            block::THREE_EIGHTHS,
1929            block::ONE_QUARTER,
1930            block::ONE_EIGHTH,
1931        ] {
1932            check(ch);
1933        }
1934
1935        for ch in bar::NINE_LEVELS {
1936            check(ch);
1937        }
1938
1939        for pattern in 0u8..=u8::MAX {
1940            check(retroglyph_core::symbols::braille::glyph(pattern));
1941        }
1942
1943        for &ch in &still_notdef {
1944            assert!(
1945                known.contains(&ch),
1946                "{ch:?} (U+{:04X}) newly falls back to notdef through the bundled chain; \
1947                 either fix its font coverage or add it to `known_notdef_gaps`",
1948                ch as u32
1949            );
1950        }
1951        assert_eq!(
1952            still_notdef, known,
1953            "`known_notdef_gaps` is stale: a previously-notdef glyph now resolves through the \
1954             bundled chain. Shrink the allowlist to match."
1955        );
1956    }
1957}