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 three small integers.
33#[derive(Debug, Clone, Copy)]
34pub struct BitmapFont {
35    /// Glyph bitmap data: `glyph_count * glyph_height` bytes.
36    data: &'static [u8],
37    /// Width of each glyph in pixels (≤ 8 for single-byte rows).
38    pub glyph_width: u8,
39    /// Height of each glyph in pixels; also bytes per glyph.
40    pub glyph_height: u8,
41    /// Total number of glyphs stored in `data`.
42    glyph_count: u16,
43}
44
45impl BitmapFont {
46    /// Constructs a bitmap font from a static byte slice.
47    ///
48    /// `data` must contain exactly `glyph_count * glyph_height` bytes.
49    #[must_use]
50    pub const fn new(
51        data: &'static [u8],
52        glyph_width: u8,
53        glyph_height: u8,
54        glyph_count: u16,
55    ) -> Self {
56        Self {
57            data,
58            glyph_width,
59            glyph_height,
60            glyph_count,
61        }
62    }
63
64    /// Returns the row bytes for glyph `index`.
65    ///
66    /// Each byte is one row; bit 7 (MSB) is the leftmost pixel.
67    ///
68    /// # Panics
69    ///
70    /// Panics if `index as u16 >= self.glyph_count`.
71    #[must_use]
72    pub fn rows(&self, index: u8) -> &[u8] {
73        assert!(
74            u16::from(index) < self.glyph_count,
75            "glyph index {index} out of range ({})",
76            self.glyph_count,
77        );
78        let h = usize::from(self.glyph_height);
79        let start = usize::from(index) * h;
80        &self.data[start..start + h]
81    }
82
83    /// Iterates the set ("on") pixels of glyph `index` as `(x, y)` coordinates, row-major from the
84    /// top: `x` in `0..glyph_width`, `y` in `0..glyph_height`.
85    ///
86    /// This is the single place the 1-bit format's MSB-first bit order lives -- pixel `x` of a row
87    /// is bit `glyph_width - 1 - x` of that row's byte -- so consumers (the GL atlas builder, the
88    /// software rasterizer's glyph blit) decode through it instead of each re-deriving the shift
89    /// and risking disagreement. It is also the one seam that has to change for wider-than-8px
90    /// glyphs (multi-byte rows, #164): today a row is a single byte (`glyph_width <= 8`), so its
91    /// bits are read straight out of that byte.
92    ///
93    /// # Panics
94    ///
95    /// Panics if `index as u16 >= self.glyph_count` (via [`rows`](Self::rows)).
96    #[must_use = "iterators are lazy and do nothing unless consumed"]
97    pub fn glyph_pixels(&self, index: u8) -> impl Iterator<Item = (u8, u8)> + '_ {
98        let width = self.glyph_width;
99        self.rows(index)
100            .iter()
101            .enumerate()
102            .flat_map(move |(y, &row)| {
103                #[allow(clippy::cast_possible_truncation)]
104                let y = y as u8;
105                (0..width)
106                    .filter_map(move |x| ((row >> (width - 1 - x)) & 1 == 1).then_some((x, y)))
107            })
108    }
109
110    /// The total number of glyphs stored in this font.
111    ///
112    /// Glyph indices `0..glyph_count()` are valid arguments to [`rows`](Self::rows). A GPU
113    /// backend uses this to size its glyph atlas (one texture-array layer per glyph).
114    #[must_use]
115    pub const fn glyph_count(&self) -> u16 {
116        self.glyph_count
117    }
118
119    /// Maps a Unicode `char` to a glyph index in this font using the CP437
120    /// encoding, falling back to a solid-block glyph for unmapped characters.
121    #[must_use]
122    pub const fn char_to_index(&self, ch: char) -> u8 {
123        unicode_to_cp437(ch)
124    }
125
126    /// Attempts to map a Unicode `char` to a glyph index in this font, returning `None`
127    /// instead of substituting the solid-block fallback glyph on a miss.
128    ///
129    /// A miss is either `ch` not being in the CP437 mapping table at all, or its mapped
130    /// index falling outside this font's `glyph_count` (e.g. a font built with fewer than
131    /// 256 glyphs). [`FallbackFontChain::resolve`] uses this to keep trying further fonts
132    /// in the chain rather than settling for [`char_to_index`](Self::char_to_index)'s
133    /// unconditional solid-block substitution.
134    #[must_use]
135    pub const fn try_char_to_index(&self, ch: char) -> Option<u8> {
136        match try_unicode_to_cp437(ch) {
137            Some(index) if (index as u16) < self.glyph_count => Some(index),
138            _ => None,
139        }
140    }
141}
142
143// Two `BitmapFont`s are equal when they point at the same static data and
144// share the same dimensions.  Comparing the full 4 KB slice on every draw
145// call would be wasteful, so we compare the data pointer instead.
146impl PartialEq for BitmapFont {
147    fn eq(&self, other: &Self) -> bool {
148        core::ptr::eq(self.data.as_ptr(), other.data.as_ptr())
149            && self.glyph_width == other.glyph_width
150            && self.glyph_height == other.glyph_height
151            && self.glyph_count == other.glyph_count
152    }
153}
154
155impl Eq for BitmapFont {}
156
157// ── Fallback font chain ─────────────────────────────────────────────────────
158
159/// A glyph resolved from a [`FallbackFontChain`]: the glyph index plus the specific
160/// [`BitmapFont`] it came from, since each font in a chain owns its own bitmap data.
161#[derive(Debug, Clone, Copy, PartialEq, Eq)]
162pub struct ResolvedGlyph {
163    font: BitmapFont,
164    index: u8,
165}
166
167impl ResolvedGlyph {
168    /// The font this glyph was resolved from.
169    #[must_use]
170    pub const fn font(&self) -> &BitmapFont {
171        &self.font
172    }
173
174    /// The glyph index within [`font`](Self::font).
175    #[must_use]
176    pub const fn index(&self) -> u8 {
177        self.index
178    }
179
180    /// Returns the row bytes for this glyph; see [`BitmapFont::rows`].
181    #[must_use]
182    pub fn rows(&self) -> &[u8] {
183        self.font.rows(self.index)
184    }
185}
186
187/// Combines a primary [`BitmapFont`] with an ordered list of fallback fonts.
188///
189/// [`resolve`](Self::resolve) tries the primary font's char mapping first; on a miss, it
190/// tries each fallback font in order; only if every font in the chain misses does it fall
191/// back to the primary font's solid-block glyph. This lets a caller layer, say, an ASCII
192/// or partial-coverage primary font with one or more broader fallback fonts, so a char
193/// missing from the primary doesn't automatically become a solid block if some other font
194/// in the chain actually has it.
195///
196/// This type ships **no bundled fallback font data** -- every font in the chain, primary
197/// or fallback, is supplied by the caller. Bundling a ready-to-use Latin-1/Extended
198/// fallback font is a natural follow-up now that this mechanism exists, but is out of
199/// scope here.
200///
201/// Callers who don't need a fallback chain can keep using a bare [`BitmapFont`] and
202/// [`BitmapFont::char_to_index`]/[`BitmapFont::rows`] directly, exactly as before --
203/// this type is purely additive and opt-in.
204#[derive(Debug, Clone, Copy)]
205pub struct FallbackFontChain<'a> {
206    primary: BitmapFont,
207    fallbacks: &'a [BitmapFont],
208}
209
210impl<'a> FallbackFontChain<'a> {
211    /// Constructs a chain from a primary font and an ordered list of fallback fonts.
212    #[must_use]
213    pub const fn new(primary: BitmapFont, fallbacks: &'a [BitmapFont]) -> Self {
214        Self { primary, fallbacks }
215    }
216
217    /// Resolves `ch` to a glyph, trying the primary font first, then each fallback font
218    /// in order, only substituting the primary font's solid-block glyph if every font in
219    /// the chain misses.
220    #[must_use]
221    pub fn resolve(&self, ch: char) -> ResolvedGlyph {
222        if let Some(index) = self.primary.try_char_to_index(ch) {
223            return ResolvedGlyph {
224                font: self.primary,
225                index,
226            };
227        }
228        for font in self.fallbacks {
229            if let Some(index) = font.try_char_to_index(ch) {
230                return ResolvedGlyph { font: *font, index };
231            }
232        }
233        ResolvedGlyph {
234            font: self.primary,
235            index: FALLBACK,
236        }
237    }
238}
239
240// ── Default embedded font ──────────────────────────────────────────────────
241
242/// The Unscii 16 font, embedded when the `default-font` feature is enabled.
243///
244/// 256 glyphs laid out in CP437 order (matching `unicode_to_cp437`), each
245/// 16 bytes (1 bit per pixel, MSB = leftmost). Source: unscii's
246/// public-domain/CC0 `unscii-16.hex` (<https://github.com/viznut/unscii>),
247/// re-laid-out from Unicode codepoints to CP437 glyph indices.
248///
249/// Four CP437 codepoints that plain `unscii-16.hex` doesn't cover (U+2302
250/// HOUSE, U+263C WHITE SUN WITH RAYS, U+2310 REVERSED NOT SIGN, U+2219
251/// BULLET OPERATOR) are filled in with original pixel art or mechanical
252/// transforms of neighboring unscii glyphs (e.g. REVERSED NOT SIGN is a
253/// horizontal mirror of unscii's own NOT SIGN) rather than pulling in
254/// unscii's GPL-licensed `-full` variant (which adds GNU Unifont glyphs).
255/// `unicode_to_cp437` carries matching reverse-mapping arms for all four
256/// (`☼` already had one; the `⌂`/`⌐`/`∙` arms are new here) so all four
257/// are reachable through the normal char-to-glyph path, not just by raw
258/// glyph index.
259#[cfg(feature = "default-font")]
260pub mod unscii16 {
261    use super::BitmapFont;
262
263    /// A [`BitmapFont`] backed by the embedded Unscii 16 glyph data.
264    pub const FONT: BitmapFont = BitmapFont::new(&DATA, 8, 16, 256);
265
266    /// Unscii 16 glyph bitmaps: 256 CP437-ordered glyphs, 16 bytes each.
267    ///
268    /// Each byte is one row of 8 pixels; bit 7 (MSB) is the leftmost pixel.
269    #[rustfmt::skip]
270    static DATA: [u8; 4096] = [
271        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x00
272        0x00, 0x00, 0x7e, 0x81, 0x81, 0xa5, 0x81, 0x81, 0xbd, 0x99, 0x81, 0x81, 0x7e, 0x00, 0x00, 0x00, // 0x01
273        0x00, 0x00, 0x7e, 0xff, 0xff, 0xdb, 0xff, 0xff, 0xc3, 0xe7, 0xff, 0xff, 0x7e, 0x00, 0x00, 0x00, // 0x02
274        0x00, 0x00, 0x00, 0x6c, 0xfe, 0xfe, 0xfe, 0xfe, 0x7c, 0x7c, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, // 0x03
275        0x00, 0x00, 0x00, 0x10, 0x38, 0x38, 0x7c, 0xfe, 0x7c, 0x38, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, // 0x04
276        0x00, 0x00, 0x00, 0x10, 0x38, 0x38, 0x54, 0xfe, 0xfe, 0x54, 0x10, 0x38, 0x00, 0x00, 0x00, 0x00, // 0x05
277        0x00, 0x00, 0x00, 0x10, 0x38, 0x7c, 0xfe, 0xfe, 0xfe, 0x38, 0x38, 0x7c, 0x00, 0x00, 0x00, 0x00, // 0x06
278        0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x3c, 0x3c, 0x3c, 0x3c, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, // 0x07
279        0xff, 0xff, 0xff, 0xff, 0xe7, 0xe7, 0xc3, 0xc3, 0xc3, 0xc3, 0xe7, 0xe7, 0xff, 0xff, 0xff, 0xff, // 0x08
280        0x00, 0x00, 0x3c, 0x3c, 0x66, 0x66, 0x42, 0x42, 0x42, 0x42, 0x66, 0x66, 0x3c, 0x3c, 0x00, 0x00, // 0x09
281        0xff, 0xff, 0xc3, 0xc3, 0x99, 0x99, 0xbd, 0xbd, 0xbd, 0xbd, 0x99, 0x99, 0xc3, 0xc3, 0xff, 0xff, // 0x0a
282        0x00, 0x00, 0x00, 0x1e, 0x0e, 0x1a, 0x78, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x78, 0x00, 0x00, 0x00, // 0x0b
283        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x7e, 0x18, // 0x0c
284        0x00, 0x00, 0x00, 0x18, 0x1c, 0x1e, 0x1b, 0x18, 0x18, 0x78, 0xf8, 0x70, 0x00, 0x00, 0x00, 0x00, // 0x0d
285        0x00, 0x00, 0x00, 0x7f, 0x63, 0x63, 0x63, 0x63, 0x63, 0x67, 0xe7, 0xe6, 0xc0, 0x00, 0x00, 0x00, // 0x0e
286        0x00, 0x00, 0x00, 0x24, 0x18, 0xbd, 0x7e, 0x7e, 0xbd, 0x18, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x0f
287        0x00, 0x00, 0x00, 0x00, 0xc0, 0xf0, 0xfc, 0xff, 0xfc, 0xf0, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x10
288        0x00, 0x00, 0x00, 0x00, 0x03, 0x0f, 0x3f, 0xff, 0x3f, 0x0f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x11
289        0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, // 0x12
290        0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x66, 0x66, 0x00, 0x00, // 0x13
291        0x00, 0x00, 0x3e, 0x7a, 0x7a, 0x7a, 0x7a, 0x3a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x00, 0x00, 0x00, // 0x14
292        0x00, 0x3c, 0x66, 0x60, 0x30, 0x38, 0x6c, 0x66, 0x36, 0x1c, 0x0c, 0x06, 0x66, 0x3c, 0x00, 0x00, // 0x15
293        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x00, 0x00, // 0x16
294        0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, 0xff, // 0x17
295        0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0x18
296        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, // 0x19
297        0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0c, 0xfe, 0xfe, 0x0c, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1a
298        0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x30, 0x7f, 0x7f, 0x30, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1b
299        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x06, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1c
300        0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x66, 0xff, 0xff, 0x66, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x1d
301        0x18, 0x18, 0x18, 0x18, 0x3c, 0x3c, 0x3c, 0x3c, 0x7e, 0x7e, 0x7e, 0x7e, 0xff, 0xff, 0xff, 0xff, // 0x1e
302        0xff, 0xff, 0xff, 0xff, 0x7e, 0x7e, 0x7e, 0x7e, 0x3c, 0x3c, 0x3c, 0x3c, 0x18, 0x18, 0x18, 0x18, // 0x1f
303        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20
304        0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x21
305        0x00, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x22
306        0x00, 0x00, 0x6c, 0x6c, 0x6c, 0xfe, 0x6c, 0x6c, 0x6c, 0xfe, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, // 0x23
307        0x00, 0x18, 0x18, 0x3c, 0x66, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x66, 0x3c, 0x18, 0x18, 0x00, 0x00, // 0x24
308        0x00, 0x00, 0x06, 0xc6, 0xcc, 0xcc, 0x18, 0x18, 0x30, 0x30, 0x66, 0x66, 0xc6, 0xc0, 0x00, 0x00, // 0x25
309        0x00, 0x00, 0x38, 0x6c, 0x6c, 0x38, 0x30, 0x7a, 0xde, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, // 0x26
310        0x00, 0x18, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x27
311        0x00, 0x0c, 0x18, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x18, 0x18, 0x0c, 0x00, 0x00, // 0x28
312        0x00, 0x30, 0x18, 0x18, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x18, 0x18, 0x30, 0x00, 0x00, // 0x29
313        0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x3c, 0xff, 0x3c, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2a
314        0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2b
315        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x18, 0x18, 0x30, 0x60, 0x00, // 0x2c
316        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x2d
317        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x2e
318        0x03, 0x03, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x18, 0x30, 0x30, 0x60, 0x60, 0xc0, 0xc0, 0x00, 0x00, // 0x2f
319        0x00, 0x00, 0x38, 0x6c, 0xc6, 0xc6, 0xce, 0xd6, 0xe6, 0xc6, 0xc6, 0x6c, 0x38, 0x00, 0x00, 0x00, // 0x30
320        0x00, 0x00, 0x18, 0x38, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, 0x00, 0x00, // 0x31
321        0x00, 0x00, 0x3c, 0x66, 0x66, 0x06, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, // 0x32
322        0x00, 0x00, 0x3c, 0x66, 0x66, 0x06, 0x06, 0x1c, 0x06, 0x06, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x33
323        0x00, 0x00, 0x0c, 0x1c, 0x3c, 0x6c, 0xcc, 0xcc, 0xfe, 0x0c, 0x0c, 0x0c, 0x0c, 0x00, 0x00, 0x00, // 0x34
324        0x00, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x7c, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x35
325        0x00, 0x00, 0x1c, 0x30, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x36
326        0x00, 0x00, 0x7e, 0x06, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x37
327        0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x76, 0x3c, 0x6e, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x38
328        0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x06, 0x0c, 0x38, 0x00, 0x00, 0x00, // 0x39
329        0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x3a
330        0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x38, 0x18, 0x18, 0x30, 0x60, 0x00, // 0x3b
331        0x00, 0x00, 0x00, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x00, 0x00, 0x00, 0x00, // 0x3c
332        0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x3d
333        0x00, 0x00, 0x00, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, // 0x3e
334        0x00, 0x3c, 0x66, 0x66, 0x06, 0x0c, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x3f
335        0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xde, 0xde, 0xde, 0xdc, 0xc0, 0xc0, 0x7c, 0x00, 0x00, 0x00, // 0x40
336        0x00, 0x00, 0x18, 0x3c, 0x66, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x41
337        0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0x66, 0x66, 0x7c, 0x00, 0x00, 0x00, // 0x42
338        0x00, 0x00, 0x3c, 0x66, 0x66, 0x60, 0x60, 0x60, 0x60, 0x60, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x43
339        0x00, 0x00, 0x78, 0x6c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6c, 0x78, 0x00, 0x00, 0x00, // 0x44
340        0x00, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, // 0x45
341        0x00, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, // 0x46
342        0x00, 0x00, 0x3c, 0x66, 0x66, 0x60, 0x60, 0x6e, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x47
343        0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x48
344        0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, 0x00, 0x00, // 0x49
345        0x00, 0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x4a
346        0x00, 0x00, 0xc6, 0xc6, 0xcc, 0xcc, 0xd8, 0xf0, 0xd8, 0xcc, 0xcc, 0xc6, 0xc6, 0x00, 0x00, 0x00, // 0x4b
347        0x00, 0x00, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, // 0x4c
348        0x00, 0x00, 0xc6, 0xee, 0xee, 0xfe, 0xd6, 0xd6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, // 0x4d
349        0x00, 0x00, 0xc6, 0xc6, 0xe6, 0xe6, 0xf6, 0xfe, 0xde, 0xce, 0xce, 0xc6, 0xc6, 0x00, 0x00, 0x00, // 0x4e
350        0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x4f
351        0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, // 0x50
352        0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x0c, 0x06, 0x00, // 0x51
353        0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x6c, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x52
354        0x00, 0x00, 0x3c, 0x66, 0x66, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x53
355        0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x54
356        0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x55
357        0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x3c, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x56
358        0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xd6, 0xd6, 0xfe, 0xee, 0xee, 0xc6, 0x00, 0x00, 0x00, // 0x57
359        0x00, 0x00, 0xc3, 0xc3, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x3c, 0x66, 0xc3, 0xc3, 0x00, 0x00, 0x00, // 0x58
360        0x00, 0x00, 0xc3, 0xc3, 0x66, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x59
361        0x00, 0x00, 0x7e, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x30, 0x30, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, // 0x5a
362        0x00, 0x3c, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3c, 0x00, 0x00, // 0x5b
363        0xc0, 0xc0, 0x60, 0x60, 0x30, 0x30, 0x18, 0x18, 0x0c, 0x0c, 0x06, 0x06, 0x03, 0x03, 0x00, 0x00, // 0x5c
364        0x00, 0x3c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x3c, 0x00, 0x00, // 0x5d
365        0x00, 0x10, 0x38, 0x6c, 0x6c, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x5e
366        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, // 0x5f
367        0x00, 0x18, 0x18, 0x0c, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x60
368        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x61
369        0x00, 0x00, 0x60, 0x60, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x00, 0x00, 0x00, // 0x62
370        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x63
371        0x00, 0x00, 0x06, 0x06, 0x06, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x64
372        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, // 0x65
373        0x00, 0x00, 0x1e, 0x30, 0x30, 0x30, 0x7e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, // 0x66
374        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x7c, // 0x67
375        0x00, 0x00, 0x60, 0x60, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x68
376        0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1e, 0x00, 0x00, 0x00, // 0x69
377        0x00, 0x00, 0x0c, 0x0c, 0x00, 0x00, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x78, // 0x6a
378        0x00, 0x00, 0x60, 0x60, 0x60, 0x60, 0x66, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x6b
379        0x00, 0x00, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1e, 0x00, 0x00, 0x00, // 0x6c
380        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0xfe, 0xd6, 0xd6, 0xd6, 0xd6, 0xc6, 0x00, 0x00, 0x00, // 0x6d
381        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x6e
382        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x6f
383        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x60, // 0x70
384        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x06, // 0x71
385        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, // 0x72
386        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x60, 0x60, 0x3c, 0x06, 0x06, 0x7c, 0x00, 0x00, 0x00, // 0x73
387        0x00, 0x00, 0x00, 0x30, 0x30, 0x30, 0x7e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x1e, 0x00, 0x00, 0x00, // 0x74
388        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x75
389        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x00, 0x00, 0x00, // 0x76
390        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xd6, 0xd6, 0xd6, 0x7c, 0x6c, 0x00, 0x00, 0x00, // 0x77
391        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0x6c, 0x38, 0x6c, 0xc6, 0xc6, 0x00, 0x00, 0x00, // 0x78
392        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x3c, // 0x79
393        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x7e, 0x00, 0x00, 0x00, // 0x7a
394        0x00, 0x0e, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf0, 0x18, 0x18, 0x18, 0x18, 0x18, 0x0e, 0x00, 0x00, // 0x7b
395        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, // 0x7c
396        0x00, 0xe0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x1e, 0x30, 0x30, 0x30, 0x30, 0x30, 0xe0, 0x00, 0x00, // 0x7d
397        0x00, 0x72, 0xd6, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x7e
398        0x00, 0x18, 0x3c, 0x7e, 0xff, 0xc3, 0xc3, 0xc3, 0xdb, 0xdb, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x7f
399        0x00, 0x00, 0x3c, 0x66, 0x66, 0x60, 0x60, 0x60, 0x60, 0x60, 0x66, 0x66, 0x3c, 0x0c, 0x06, 0x1c, // 0x80
400        0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x81
401        0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, // 0x82
402        0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x83
403        0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x84
404        0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x85
405        0x00, 0x00, 0x3c, 0x66, 0x3c, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x86
406        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3c, 0x0c, 0x06, 0x1c, // 0x87
407        0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, // 0x88
408        0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, // 0x89
409        0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, // 0x8a
410        0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, // 0x8b
411        0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, // 0x8c
412        0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, // 0x8d
413        0x66, 0x66, 0x00, 0x18, 0x3c, 0x66, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x8e
414        0x3c, 0x66, 0x3c, 0x00, 0x18, 0x3c, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0x8f
415        0x0c, 0x18, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, // 0x90
416        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x1b, 0x1b, 0x7f, 0xd8, 0xd8, 0x77, 0x00, 0x00, 0x00, // 0x91
417        0x00, 0x00, 0x3f, 0x7c, 0xfc, 0xcc, 0xcc, 0xfe, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0x00, 0x00, 0x00, // 0x92
418        0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x93
419        0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x94
420        0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x95
421        0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x96
422        0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0x97
423        0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x3c, // 0x98
424        0x66, 0x66, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x99
425        0x66, 0x66, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0x9a
426        0x00, 0x18, 0x18, 0x18, 0x3c, 0x66, 0x60, 0x60, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x9b
427        0x00, 0x00, 0x38, 0x6c, 0x6c, 0x60, 0x60, 0xf0, 0x60, 0x60, 0x66, 0x66, 0xfc, 0x00, 0x00, 0x00, // 0x9c
428        0x00, 0x00, 0xc3, 0xc3, 0x66, 0x66, 0x3c, 0x18, 0x7e, 0x18, 0x7e, 0x18, 0x18, 0x00, 0x00, 0x00, // 0x9d
429        0x00, 0xfc, 0x66, 0x66, 0x7c, 0x62, 0x66, 0x6f, 0x66, 0x66, 0x66, 0xf3, 0x00, 0x00, 0x00, 0x00, // 0x9e
430        0x00, 0x0e, 0x1b, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x18, 0xd8, 0x70, 0x00, 0x00, // 0x9f
431        0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0xa0
432        0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, // 0xa1
433        0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0xa2
434        0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, // 0xa3
435        0x00, 0x00, 0x76, 0xdc, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0xa4
436        0x76, 0xdc, 0x00, 0xc6, 0xc6, 0xe6, 0xf6, 0xde, 0xce, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, // 0xa5
437        0x00, 0x3c, 0x06, 0x06, 0x3e, 0x66, 0x66, 0x3e, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xa6
438        0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xa7
439        0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x30, 0x30, 0x60, 0x60, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, // 0xa8
440        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xa9
441        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x06, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xaa
442        0x00, 0x40, 0xc6, 0x46, 0x4c, 0x4c, 0x18, 0x18, 0x30, 0x30, 0x6c, 0x62, 0xc4, 0xc8, 0x0e, 0x00, // 0xab
443        0x00, 0x40, 0xc6, 0x46, 0x4c, 0x4c, 0x18, 0x18, 0x30, 0x30, 0x62, 0x66, 0xca, 0xcf, 0x02, 0x00, // 0xac
444        0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, // 0xad
445        0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x66, 0xcc, 0x66, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xae
446        0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x66, 0x33, 0x66, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xaf
447        0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, // 0xb0
448        0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, // 0xb1
449        0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, // 0xb2
450        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xb3
451        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xb4
452        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0x18, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xb5
453        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xec, 0xec, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xb6
454        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfc, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xb7
455        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x18, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xb8
456        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xec, 0x0c, 0xec, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xb9
457        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xba
458        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x0c, 0xec, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xbb
459        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xec, 0x0c, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xbc
460        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xbd
461        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0x18, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xbe
462        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xbf
463        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xc0
464        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xc1
465        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xc2
466        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xc3
467        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xc4
468        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xc5
469        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x18, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xc6
470        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6f, 0x6f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xc7
471        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6f, 0x60, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xc8
472        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x60, 0x6f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xc9
473        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xef, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xca
474        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xef, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xcb
475        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6f, 0x60, 0x6f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xcc
476        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xcd
477        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xef, 0x00, 0xef, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xce
478        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xcf
479        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xd0
480        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xd1
481        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xd2
482        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x7f, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xd3
483        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x18, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xd4
484        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x18, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xd5
485        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x7f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xd6
486        0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xff, 0xff, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, // 0xd7
487        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0x18, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xd8
488        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xd9
489        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xda
490        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 0xdb
491        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 0xdc
492        0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0xdd
493        0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, // 0xde
494        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xdf
495        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xce, 0xc6, 0xc6, 0xc6, 0xce, 0x76, 0x00, 0x00, 0x00, // 0xe0
496        0x00, 0x00, 0x78, 0xcc, 0xcc, 0xcc, 0xd8, 0xcc, 0xc6, 0xc6, 0xc6, 0xc6, 0xcc, 0x00, 0x00, 0x00, // 0xe1
497        0x00, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, // 0xe2
498        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, // 0xe3
499        0x00, 0x00, 0xfe, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0xfe, 0x00, 0x00, 0x00, // 0xe4
500        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xcc, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, // 0xe5
501        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0xc0, // 0xe6
502        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x0c, 0x00, 0x00, 0x00, // 0xe7
503        0x10, 0x10, 0x10, 0x7c, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0x7c, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, // 0xe8
504        0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, // 0xe9
505        0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xee, 0x6c, 0x6c, 0xee, 0x00, 0x00, 0x00, // 0xea
506        0x00, 0x00, 0xfe, 0xc0, 0xc0, 0x60, 0x30, 0x18, 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, // 0xeb
507        0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xdb, 0xdb, 0xdb, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xec
508        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc0, 0xdc, 0xd6, 0xd6, 0xd6, 0x7c, 0x10, 0x10, 0x00, // 0xed
509        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x60, 0x60, 0x3c, 0x60, 0x60, 0x3e, 0x00, 0x00, 0x00, // 0xee
510        0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, // 0xef
511        0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xf0
512        0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x00, 0x7e, 0x00, 0x00, 0x00, // 0xf1
513        0x00, 0x00, 0x00, 0x00, 0x30, 0x18, 0x0c, 0x06, 0x0c, 0x18, 0x30, 0x00, 0x7e, 0x00, 0x00, 0x00, // 0xf2
514        0x00, 0x00, 0x00, 0x00, 0x0c, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0c, 0x00, 0x7e, 0x00, 0x00, 0x00, // 0xf3
515        0x00, 0x00, 0x0e, 0x1b, 0x1b, 0x1b, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0xf4
516        0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xd8, 0xd8, 0xd8, 0x70, 0x00, 0x00, 0x00, // 0xf5
517        0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x7e, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xf6
518        0x00, 0x00, 0x00, 0x00, 0x72, 0xd6, 0x9c, 0x00, 0x72, 0xd6, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xf7
519        0x00, 0x3c, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xf8
520        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xf9
521        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xfa
522        0x00, 0x03, 0x03, 0x06, 0x06, 0x06, 0x06, 0x06, 0xcc, 0xcc, 0x6c, 0x38, 0x18, 0x00, 0x00, 0x00, // 0xfb
523        0x00, 0x00, 0x00, 0x78, 0x6c, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xfc
524        0x00, 0x38, 0x6c, 0x0c, 0x18, 0x30, 0x60, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xfd
525        0x00, 0x00, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x00, 0x00, // 0xfe
526        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xff
527    ];
528}
529
530// ── Unicode → CP437 mapping ────────────────────────────────────────────────
531
532/// Solid block, the fallback for any unmapped character.
533const FALLBACK: u8 = 0xDB;
534
535/// Maps a Unicode scalar to its CP437 glyph index, falling back to a solid block
536/// (CP437 `0xDB`) for characters not covered by [`try_unicode_to_cp437`].
537pub(crate) const fn unicode_to_cp437(ch: char) -> u8 {
538    match try_unicode_to_cp437(ch) {
539        Some(index) => index,
540        None => FALLBACK,
541    }
542}
543
544/// Attempts to map a Unicode scalar to its CP437 glyph index.
545///
546/// ASCII (U+0020–U+007E) maps identically.  Common box-drawing characters,
547/// block-elements, and roguelike symbols are mapped explicitly.  Returns
548/// `None` for anything else, distinguishing "not in the CP437 table" from a
549/// character that legitimately maps to the solid-block glyph (`'█'`) --
550/// [`FallbackFontChain`] relies on that distinction to keep trying further
551/// fonts on a miss instead of stopping at a false-positive solid-block hit.
552#[allow(clippy::too_many_lines)]
553const fn try_unicode_to_cp437(ch: char) -> Option<u8> {
554    // Direct ASCII pass-through (the most common path for roguelikes).
555    let u = ch as u32;
556    if u < 0x80 {
557        #[allow(clippy::cast_possible_truncation)]
558        return Some(u as u8);
559    }
560
561    // Named mappings for the characters roguelikes actually use.
562    match ch {
563        // ── Latin-1 accented letters that overlap CP437 ──────────────────
564        'Ç' => Some(0x80),
565        'ü' => Some(0x81),
566        'é' => Some(0x82),
567        'â' => Some(0x83),
568        'ä' => Some(0x84),
569        'à' => Some(0x85),
570        'å' => Some(0x86),
571        'ç' => Some(0x87),
572        'ê' => Some(0x88),
573        'ë' => Some(0x89),
574        'è' => Some(0x8A),
575        'ï' => Some(0x8B),
576        'î' => Some(0x8C),
577        'ì' => Some(0x8D),
578        'Ä' => Some(0x8E),
579        'Å' => Some(0x8F),
580        'É' => Some(0x90),
581        'æ' => Some(0x91),
582        'Æ' => Some(0x92),
583        'ô' => Some(0x93),
584        'ö' => Some(0x94),
585        'ò' => Some(0x95),
586        'û' => Some(0x96),
587        'ù' => Some(0x97),
588        'ÿ' => Some(0x98),
589        'Ö' => Some(0x99),
590        'Ü' => Some(0x9A),
591        '¢' => Some(0x9B),
592        '£' => Some(0x9C),
593        '¥' => Some(0x9D),
594        'ƒ' => Some(0x9F),
595        'á' => Some(0xA0),
596        'í' => Some(0xA1),
597        'ó' => Some(0xA2),
598        'ú' => Some(0xA3),
599        'ñ' => Some(0xA4),
600        'Ñ' => Some(0xA5),
601        'ª' => Some(0xA6),
602        'º' => Some(0xA7),
603        '¿' => Some(0xA8),
604        '⌐' => Some(0xA9),
605        '½' => Some(0xAB),
606        '¼' => Some(0xAC),
607        '¡' => Some(0xAD),
608        '«' => Some(0xAE),
609        '»' => Some(0xAF),
610
611        // ── Shade characters ─────────────────────────────────────────────
612        '░' => Some(0xB0),
613        '▒' => Some(0xB1),
614        '▓' => Some(0xB2),
615
616        // ── Single-line box drawing ───────────────────────────────────────
617        '│' => Some(0xB3),
618        '┤' => Some(0xB4),
619        '╡' => Some(0xB5),
620        '╢' => Some(0xB6),
621        '╖' => Some(0xB7),
622        '╕' => Some(0xB8),
623        '╣' => Some(0xB9),
624        '║' => Some(0xBA),
625        '╗' => Some(0xBB),
626        '╝' => Some(0xBC),
627        '╜' => Some(0xBD),
628        '╛' => Some(0xBE),
629        '┐' => Some(0xBF),
630        '└' => Some(0xC0),
631        '┴' => Some(0xC1),
632        '┬' => Some(0xC2),
633        '├' => Some(0xC3),
634        '─' => Some(0xC4),
635        '┼' => Some(0xC5),
636        '╞' => Some(0xC6),
637        '╟' => Some(0xC7),
638        '╚' => Some(0xC8),
639        '╔' => Some(0xC9),
640        '╩' => Some(0xCA),
641        '╦' => Some(0xCB),
642        '╠' => Some(0xCC),
643        '═' => Some(0xCD),
644        '╬' => Some(0xCE),
645        '╧' => Some(0xCF),
646        '╨' => Some(0xD0),
647        '╤' => Some(0xD1),
648        '╥' => Some(0xD2),
649        '╙' => Some(0xD3),
650        '╘' => Some(0xD4),
651        '╒' => Some(0xD5),
652        '╓' => Some(0xD6),
653        '╫' => Some(0xD7),
654        '╪' => Some(0xD8),
655        '┘' => Some(0xD9),
656        '┌' => Some(0xDA),
657
658        // ── Block elements ────────────────────────────────────────────────
659        '█' => Some(0xDB),
660        '▄' => Some(0xDC),
661        '▌' => Some(0xDD),
662        '▐' => Some(0xDE),
663        '▀' => Some(0xDF),
664
665        // ── Greek / math ──────────────────────────────────────────────────
666        'α' => Some(0xE0),
667        'ß' => Some(0xE1),
668        'Γ' => Some(0xE2),
669        'π' => Some(0xE3),
670        'Σ' => Some(0xE4),
671        'σ' => Some(0xE5),
672        'µ' | 'μ' => Some(0xE6),
673        'τ' => Some(0xE7),
674        'Φ' => Some(0xE8),
675        'Θ' => Some(0xE9),
676        'Ω' => Some(0xEA),
677        'δ' => Some(0xEB),
678        '∞' => Some(0xEC),
679        'φ' => Some(0xED),
680        'ε' => Some(0xEE),
681        '∩' => Some(0xEF),
682        '≡' => Some(0xF0),
683        '±' => Some(0xF1),
684        '≥' => Some(0xF2),
685        '≤' => Some(0xF3),
686        '⌠' => Some(0xF4),
687        '⌡' => Some(0xF5),
688        '÷' => Some(0xF6),
689        '≈' => Some(0xF7),
690        '°' => Some(0xF8),
691        '·' | '∙' => Some(0xF9),
692        '√' => Some(0xFB),
693        'ⁿ' => Some(0xFC),
694        '²' => Some(0xFD),
695        '■' => Some(0xFE),
696
697        // ── Roguelike / Unicode symbols ───────────────────────────────────
698        '☺' => Some(0x01),
699        '•' => Some(0x07),
700        '☻' => Some(0x02),
701        '♥' => Some(0x03),
702        '♦' => Some(0x04),
703        '♣' => Some(0x05),
704        '♠' => Some(0x06),
705        '◘' => Some(0x08),
706        '○' => Some(0x09),
707        '◙' => Some(0x0A),
708        '♂' => Some(0x0B),
709        '♀' => Some(0x0C),
710        '♪' => Some(0x0D),
711        '♫' => Some(0x0E),
712        '☼' => Some(0x0F),
713        '►' => Some(0x10),
714        '◄' => Some(0x11),
715        '↕' => Some(0x12),
716        '‼' => Some(0x13),
717        '¶' => Some(0x14),
718        '§' => Some(0x15),
719        '▬' => Some(0x16),
720        '↨' => Some(0x17),
721        '↑' => Some(0x18),
722        '↓' => Some(0x19),
723        '→' => Some(0x1A),
724        '←' => Some(0x1B),
725        '∟' => Some(0x1C),
726        '↔' => Some(0x1D),
727        '▲' => Some(0x1E),
728        '▼' => Some(0x1F),
729        '⌂' => Some(0x7F),
730
731        _ => None,
732    }
733}
734
735#[cfg(test)]
736mod tests {
737    use super::{BitmapFont, FALLBACK, FallbackFontChain, unicode_to_cp437};
738
739    /// The four codepoints patched into `unscii16`'s `DATA` (see that module's doc comment)
740    /// must actually be reachable through the char-to-glyph path, not just present at their
741    /// raw glyph index -- otherwise they're invisible to anything that goes through
742    /// `BitmapFont::char_to_index`/`term.print`, which is every real caller.
743    #[test]
744    fn patched_glyphs_are_reachable_by_char() {
745        assert_eq!(unicode_to_cp437('⌂'), 0x7F, "U+2302 HOUSE");
746        assert_eq!(unicode_to_cp437('☼'), 0x0F, "U+263C WHITE SUN WITH RAYS");
747        assert_eq!(unicode_to_cp437('⌐'), 0xA9, "U+2310 REVERSED NOT SIGN");
748        assert_eq!(unicode_to_cp437('∙'), 0xF9, "U+2219 BULLET OPERATOR");
749    }
750
751    /// A primary font that only covers the ASCII half of CP437 (glyph indices 0..128), so
752    /// any character mapping into the extended range (128..256) is a miss for it.
753    static PRIMARY_DATA: [u8; 128 * 16] = [0; 128 * 16];
754    const PRIMARY: BitmapFont = BitmapFont::new(&PRIMARY_DATA, 8, 16, 128);
755
756    /// A fallback font with full CP437 coverage (glyph indices 0..256).
757    static FALLBACK_DATA: [u8; 256 * 16] = [0; 256 * 16];
758    const FALLBACK_FONT: BitmapFont = BitmapFont::new(&FALLBACK_DATA, 8, 16, 256);
759
760    #[test]
761    fn chain_resolves_char_present_only_in_fallback_font() {
762        // 'Ç' maps to CP437 index 0x80, which is out of range for `PRIMARY`
763        // (glyph_count == 128) but present in `FALLBACK_FONT` (glyph_count == 256).
764        let chain = FallbackFontChain::new(PRIMARY, &[FALLBACK_FONT]);
765        let resolved = chain.resolve('Ç');
766        assert_eq!(*resolved.font(), FALLBACK_FONT);
767        assert_eq!(resolved.index(), 0x80);
768    }
769
770    #[test]
771    fn chain_falls_back_to_solid_block_when_every_font_misses() {
772        // 'あ' (U+3042 HIRAGANA LETTER A) isn't in the CP437 table at all, so both
773        // fonts in the chain miss and resolution must fall back to the documented
774        // solid-block glyph, taken from the primary font.
775        let chain = FallbackFontChain::new(PRIMARY, &[FALLBACK_FONT]);
776        let resolved = chain.resolve('あ');
777        assert_eq!(*resolved.font(), PRIMARY);
778        assert_eq!(resolved.index(), FALLBACK);
779    }
780
781    #[test]
782    fn zero_fallback_chain_matches_plain_char_to_index() {
783        let chain = FallbackFontChain::new(FALLBACK_FONT, &[]);
784        for ch in ['A', ' ', '█', '│', 'Ç', 'あ', '☺'] {
785            let resolved = chain.resolve(ch);
786            assert_eq!(*resolved.font(), FALLBACK_FONT);
787            assert_eq!(resolved.index(), FALLBACK_FONT.char_to_index(ch));
788        }
789    }
790
791    #[test]
792    fn glyph_pixels_decodes_msb_first_row_major() {
793        // Two 8x2 glyphs. Glyph 0: corners of the top row set; glyph 1: full top row plus one
794        // interior pixel on the second row.
795        static DATA: [u8; 4] = [0b1000_0001, 0b0000_0000, 0b1111_1111, 0b0000_1000];
796        let font = BitmapFont::new(&DATA, 8, 2, 2);
797
798        let g0: Vec<(u8, u8)> = font.glyph_pixels(0).collect();
799        assert_eq!(
800            g0,
801            [(0, 0), (7, 0)],
802            "MSB is the leftmost pixel; row 0 first"
803        );
804
805        let g1: Vec<(u8, u8)> = font.glyph_pixels(1).collect();
806        let mut expected: Vec<(u8, u8)> = (0..8).map(|x| (x, 0)).collect();
807        expected.push((4, 1)); // bit 3 of 0b0000_1000 -> x = width-1-3 = 4
808        assert_eq!(g1, expected);
809    }
810
811    #[test]
812    fn glyph_pixels_is_parameterized_by_width_not_hardcoded_to_8() {
813        // A 5px-wide glyph: set pixels must come from bits (width-1-x), i.e. bit 4 and bit 0, not
814        // bit 7 and bit 3. This guards against a consumer re-introducing a hardcoded `7 - x`.
815        static DATA: [u8; 1] = [0b0001_0001];
816        let font = BitmapFont::new(&DATA, 5, 1, 1);
817        let pixels: Vec<(u8, u8)> = font.glyph_pixels(0).collect();
818        assert_eq!(pixels, [(0, 0), (4, 0)]);
819    }
820}