Skip to main content

retroglyph_window/
font.rs

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