#[derive(Debug, Clone, Copy)]
pub struct BitmapFont {
data: &'static [u8],
glyph_width: u8,
glyph_height: u8,
glyph_count: u16,
charset: Option<&'static [(char, u8)]>,
}
impl BitmapFont {
#[must_use]
pub const fn new(
data: &'static [u8],
glyph_width: u8,
glyph_height: u8,
glyph_count: u16,
) -> Self {
Self {
data,
glyph_width,
glyph_height,
glyph_count,
charset: None,
}
}
#[must_use]
pub const fn with_charset(
data: &'static [u8],
glyph_width: u8,
glyph_height: u8,
glyph_count: u16,
charset: &'static [(char, u8)],
) -> Self {
Self {
data,
glyph_width,
glyph_height,
glyph_count,
charset: Some(charset),
}
}
#[must_use]
pub fn rows(&self, index: u8) -> &[u8] {
assert!(
u16::from(index) < self.glyph_count,
"glyph index {index} out of range ({})",
self.glyph_count,
);
let h = usize::from(self.glyph_height);
let start = usize::from(index) * h;
&self.data[start..start + h]
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub fn glyph_pixels(&self, index: u8) -> impl Iterator<Item = (u8, u8)> + '_ {
let width = self.glyph_width.min(8);
self.rows(index)
.iter()
.enumerate()
.flat_map(move |(y, &row)| {
#[allow(clippy::cast_possible_truncation)]
let y = y as u8;
(0..width)
.filter_map(move |x| ((row >> (width - 1 - x)) & 1 == 1).then_some((x, y)))
})
}
#[must_use]
pub const fn glyph_width(&self) -> u8 {
self.glyph_width
}
#[must_use]
pub const fn glyph_height(&self) -> u8 {
self.glyph_height
}
#[must_use]
pub const fn glyph_count(&self) -> u16 {
self.glyph_count
}
#[must_use]
pub const fn glyph_index(&self, ch: char) -> Option<u8> {
if let Some(table) = self.charset {
let mut i = 0;
while i < table.len() {
let (table_ch, index) = table[i];
if table_ch == ch && (index as u16) < self.glyph_count {
return Some(index);
}
i += 1;
}
return None;
}
match try_unicode_to_cp437(ch) {
Some(index) if (index as u16) < self.glyph_count => Some(index),
_ => None,
}
}
}
impl PartialEq for BitmapFont {
fn eq(&self, other: &Self) -> bool {
core::ptr::eq(self.data.as_ptr(), other.data.as_ptr())
&& self.glyph_width == other.glyph_width
&& self.glyph_height == other.glyph_height
&& self.glyph_count == other.glyph_count
}
}
impl Eq for BitmapFont {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResolvedGlyph {
font: BitmapFont,
font_index: usize,
index: u8,
notdef: bool,
}
impl ResolvedGlyph {
#[must_use]
pub const fn font(&self) -> BitmapFont {
self.font
}
#[must_use]
pub const fn font_index(&self) -> usize {
self.font_index
}
#[must_use]
pub const fn index(&self) -> u8 {
self.index
}
#[must_use]
pub const fn is_notdef(&self) -> bool {
self.notdef
}
#[must_use]
pub fn rows(&self) -> &[u8] {
self.font.rows(self.index)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FontChain<'a> {
primary: BitmapFont,
fallbacks: &'a [BitmapFont],
}
impl From<BitmapFont> for FontChain<'static> {
fn from(font: BitmapFont) -> Self {
Self::new(font, &[])
}
}
impl<'a> FontChain<'a> {
#[must_use]
pub const fn new(primary: BitmapFont, fallbacks: &'a [BitmapFont]) -> Self {
Self { primary, fallbacks }
}
pub fn fonts(&self) -> impl Iterator<Item = &BitmapFont> {
core::iter::once(&self.primary).chain(self.fallbacks.iter())
}
#[must_use]
pub const fn font_count(&self) -> usize {
1 + self.fallbacks.len()
}
#[must_use]
pub fn glyph_size(&self) -> Option<(u8, u8)> {
let size = (self.primary.glyph_width, self.primary.glyph_height);
self.fallbacks
.iter()
.all(|f| (f.glyph_width, f.glyph_height) == size)
.then_some(size)
}
#[must_use]
pub fn resolve(&self, ch: char) -> Option<ResolvedGlyph> {
self.lookup(ch, false).or_else(|| self.lookup(NOTDEF, true))
}
fn lookup(&self, ch: char, notdef: bool) -> Option<ResolvedGlyph> {
self.fonts()
.enumerate()
.find_map(|(font_index, font)| {
font.glyph_index(ch).map(|index| (font_index, font, index))
})
.map(|(font_index, font, index)| ResolvedGlyph {
font: *font,
font_index,
index,
notdef,
})
}
}
#[cfg(feature = "default-font")]
pub mod unscii16 {
use super::BitmapFont;
pub const FONT: BitmapFont = BitmapFont::new(&DATA, 8, 16, 256);
#[rustfmt::skip]
static DATA: [u8; 4096] = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x81, 0x81, 0xa5, 0x81, 0x81, 0xbd, 0x99, 0x81, 0x81, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xff, 0xff, 0xdb, 0xff, 0xff, 0xc3, 0xe7, 0xff, 0xff, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0xfe, 0xfe, 0xfe, 0xfe, 0x7c, 0x7c, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x38, 0x7c, 0xfe, 0x7c, 0x38, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x38, 0x54, 0xfe, 0xfe, 0x54, 0x10, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x7c, 0xfe, 0xfe, 0xfe, 0x38, 0x38, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x3c, 0x3c, 0x3c, 0x3c, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xe7, 0xc3, 0xc3, 0xc3, 0xc3, 0xe7, 0xe7, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3c, 0x3c, 0x66, 0x66, 0x42, 0x42, 0x42, 0x42, 0x66, 0x66, 0x3c, 0x3c, 0x00, 0x00, 0xff, 0xff, 0xc3, 0xc3, 0x99, 0x99, 0xbd, 0xbd, 0xbd, 0xbd, 0x99, 0x99, 0xc3, 0xc3, 0xff, 0xff, 0x00, 0x00, 0x00, 0x1e, 0x0e, 0x1a, 0x78, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x7e, 0x18, 0x00, 0x00, 0x00, 0x18, 0x1c, 0x1e, 0x1b, 0x18, 0x18, 0x78, 0xf8, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x63, 0x63, 0x63, 0x63, 0x63, 0x67, 0xe7, 0xe6, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x18, 0xbd, 0x7e, 0x7e, 0xbd, 0x18, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xf0, 0xfc, 0xff, 0xfc, 0xf0, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0f, 0x3f, 0xff, 0x3f, 0x0f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x7a, 0x7a, 0x7a, 0x7a, 0x3a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x60, 0x30, 0x38, 0x6c, 0x66, 0x36, 0x1c, 0x0c, 0x06, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x00, 0x00, 0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, 0xff, 0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0c, 0xfe, 0xfe, 0x0c, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x30, 0x7f, 0x7f, 0x30, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x06, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x66, 0xff, 0xff, 0x66, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x3c, 0x3c, 0x3c, 0x7e, 0x7e, 0x7e, 0x7e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7e, 0x7e, 0x7e, 0x7e, 0x3c, 0x3c, 0x3c, 0x3c, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x6c, 0x6c, 0xfe, 0x6c, 0x6c, 0x6c, 0xfe, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x3c, 0x66, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x66, 0x3c, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x06, 0xc6, 0xcc, 0xcc, 0x18, 0x18, 0x30, 0x30, 0x66, 0x66, 0xc6, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x38, 0x6c, 0x6c, 0x38, 0x30, 0x7a, 0xde, 0xcc, 0xcc, 0xcc, 0x76, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x18, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x18, 0x18, 0x0c, 0x00, 0x00, 0x00, 0x30, 0x18, 0x18, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x3c, 0xff, 0x3c, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x18, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x03, 0x03, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x18, 0x30, 0x30, 0x60, 0x60, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x38, 0x6c, 0xc6, 0xc6, 0xce, 0xd6, 0xe6, 0xc6, 0xc6, 0x6c, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x38, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x06, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x06, 0x06, 0x1c, 0x06, 0x06, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x1c, 0x3c, 0x6c, 0xcc, 0xcc, 0xfe, 0x0c, 0x0c, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x7c, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x30, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x06, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x76, 0x3c, 0x6e, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x06, 0x0c, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x38, 0x18, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x06, 0x0c, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xde, 0xde, 0xde, 0xdc, 0xc0, 0xc0, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3c, 0x66, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0x66, 0x66, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x60, 0x60, 0x60, 0x60, 0x60, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x6c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6c, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x60, 0x60, 0x6e, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xcc, 0xcc, 0xd8, 0xf0, 0xd8, 0xcc, 0xcc, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xee, 0xee, 0xfe, 0xd6, 0xd6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xe6, 0xe6, 0xf6, 0xfe, 0xde, 0xce, 0xce, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x0c, 0x06, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x6c, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x3c, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xd6, 0xd6, 0xfe, 0xee, 0xee, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, 0xc3, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x3c, 0x66, 0xc3, 0xc3, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, 0xc3, 0x66, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x30, 0x30, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3c, 0x00, 0x00, 0xc0, 0xc0, 0x60, 0x60, 0x30, 0x30, 0x18, 0x18, 0x0c, 0x0c, 0x06, 0x06, 0x03, 0x03, 0x00, 0x00, 0x00, 0x3c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x3c, 0x00, 0x00, 0x00, 0x10, 0x38, 0x6c, 0x6c, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x18, 0x18, 0x0c, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06, 0x06, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x30, 0x30, 0x30, 0x7e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x7c, 0x00, 0x00, 0x60, 0x60, 0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0c, 0x00, 0x00, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x78, 0x00, 0x00, 0x60, 0x60, 0x60, 0x60, 0x66, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0xfe, 0xd6, 0xd6, 0xd6, 0xd6, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x60, 0x60, 0x3c, 0x06, 0x06, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x30, 0x7e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xd6, 0xd6, 0xd6, 0x7c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0x6c, 0x38, 0x6c, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf0, 0x18, 0x18, 0x18, 0x18, 0x18, 0x0e, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0xe0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x1e, 0x30, 0x30, 0x30, 0x30, 0x30, 0xe0, 0x00, 0x00, 0x00, 0x72, 0xd6, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3c, 0x7e, 0xff, 0xc3, 0xc3, 0xc3, 0xdb, 0xdb, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x60, 0x60, 0x60, 0x60, 0x60, 0x66, 0x66, 0x3c, 0x0c, 0x06, 0x1c, 0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x3c, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3c, 0x0c, 0x06, 0x1c, 0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x60, 0x60, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x66, 0x66, 0x00, 0x18, 0x3c, 0x66, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x3c, 0x00, 0x18, 0x3c, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x0c, 0x18, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x60, 0x60, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x1b, 0x1b, 0x7f, 0xd8, 0xd8, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x7c, 0xfc, 0xcc, 0xcc, 0xfe, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x06, 0x3c, 0x66, 0x66, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x66, 0x66, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x3c, 0x66, 0x60, 0x60, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x6c, 0x6c, 0x60, 0x60, 0xf0, 0x60, 0x60, 0x66, 0x66, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, 0xc3, 0x66, 0x66, 0x3c, 0x18, 0x7e, 0x18, 0x7e, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x66, 0x66, 0x7c, 0x62, 0x66, 0x6f, 0x66, 0x66, 0x66, 0xf3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x1b, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x18, 0xd8, 0x70, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xdc, 0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x76, 0xdc, 0x00, 0xc6, 0xc6, 0xe6, 0xf6, 0xde, 0xce, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x06, 0x06, 0x3e, 0x66, 0x66, 0x3e, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x3c, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x30, 0x30, 0x60, 0x60, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x06, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xc6, 0x46, 0x4c, 0x4c, 0x18, 0x18, 0x30, 0x30, 0x6c, 0x62, 0xc4, 0xc8, 0x0e, 0x00, 0x00, 0x40, 0xc6, 0x46, 0x4c, 0x4c, 0x18, 0x18, 0x30, 0x30, 0x62, 0x66, 0xca, 0xcf, 0x02, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x66, 0xcc, 0x66, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x66, 0x33, 0x66, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0x18, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xec, 0xec, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfc, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x18, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xec, 0x0c, 0xec, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x0c, 0xec, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xec, 0x0c, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0x18, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xf8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x18, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6f, 0x6f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6f, 0x60, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x60, 0x6f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xef, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xef, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6f, 0x60, 0x6f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xef, 0x00, 0xef, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x7f, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1f, 0x18, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x18, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x7f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0xff, 0xff, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0x18, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xce, 0xc6, 0xc6, 0xc6, 0xce, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xcc, 0xcc, 0xcc, 0xd8, 0xcc, 0xc6, 0xc6, 0xc6, 0xc6, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xcc, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7c, 0x60, 0x60, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x7c, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0x7c, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xee, 0x6c, 0x6c, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xc0, 0xc0, 0x60, 0x30, 0x18, 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xdb, 0xdb, 0xdb, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc0, 0xdc, 0xd6, 0xd6, 0xd6, 0x7c, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x60, 0x60, 0x3c, 0x60, 0x60, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x18, 0x0c, 0x06, 0x0c, 0x18, 0x30, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0c, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x1b, 0x1b, 0x1b, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xd8, 0xd8, 0xd8, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x7e, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0xd6, 0x9c, 0x00, 0x72, 0xd6, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x06, 0x06, 0x06, 0x06, 0x06, 0xcc, 0xcc, 0x6c, 0x38, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x6c, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x6c, 0x0c, 0x18, 0x30, 0x60, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
}
#[cfg(feature = "legacy-computing")]
pub mod legacy_computing {
pub mod blocks {
use crate::font::BitmapFont;
const QUADRANT_COUNT: usize = 10;
const SEXTANT_COUNT: usize = 60;
const BAR_COUNT: usize = 6;
const BLOCK_COUNT: usize = 6;
const TOTAL: usize = QUADRANT_COUNT + SEXTANT_COUNT + BAR_COUNT + BLOCK_COUNT;
#[allow(clippy::cast_possible_truncation)]
pub const FONT: BitmapFont = BitmapFont::with_charset(&DATA, 8, 16, TOTAL as u16, &CHARSET);
#[rustfmt::skip]
const QUADRANTS: [(u8, char); QUADRANT_COUNT] = [
(1, '▘'), (2, '▝'), (4, '▖'), (6, '▞'), (7, '▛'),
(8, '▗'), (9, '▚'), (11, '▜'), (13, '▙'), (14, '▟'),
];
#[rustfmt::skip]
const BAR_LEVELS: [(u8, char); BAR_COUNT] = [
(1, '▁'), (2, '▂'), (3, '▃'), (5, '▅'), (6, '▆'), (7, '▇'),
];
#[rustfmt::skip]
const BLOCK_LEVELS: [(u8, char); BLOCK_COUNT] = [
(1, '▏'), (2, '▎'), (3, '▍'), (5, '▋'), (6, '▊'), (7, '▉'),
];
const fn sextant_masks() -> [u8; SEXTANT_COUNT] {
let mut masks = [0u8; SEXTANT_COUNT];
let mut m: u16 = 1;
let mut i = 0;
while m <= 62 {
if m != 21 && m != 42 {
#[allow(clippy::cast_possible_truncation)]
{
masks[i] = m as u8;
}
i += 1;
}
m += 1;
}
masks
}
const fn sextant_codepoint(mask: u8) -> u32 {
let gaps_below = if mask > 21 { 1 } else { 0 } + if mask > 42 { 1 } else { 0 };
0x1_FB00 + (mask as u32 - 1) - gaps_below
}
const fn set_pixel(data: &mut [u8; TOTAL * 16], index: usize, x: u8, y: u8) {
let row = index * 16 + y as usize;
data[row] |= 1 << (7 - x);
}
const fn build_data() -> [u8; TOTAL * 16] {
let mut data = [0u8; TOTAL * 16];
let mut qi = 0;
while qi < QUADRANT_COUNT {
let (mask, _) = QUADRANTS[qi];
let mut y = 0u8;
while y < 16 {
let mut x = 0u8;
while x < 8 {
let bit = if x < 4 {
if y < 8 { 0 } else { 2 }
} else if y < 8 {
1
} else {
3
};
if (mask >> bit) & 1 == 1 {
set_pixel(&mut data, qi, x, y);
}
x += 1;
}
y += 1;
}
qi += 1;
}
let masks = sextant_masks();
let mut si = 0;
while si < SEXTANT_COUNT {
let mask = masks[si];
let index = QUADRANT_COUNT + si;
let mut y = 0u8;
while y < 16 {
let row = if y < 5 {
0
} else if y < 11 {
1
} else {
2
};
let mut x = 0u8;
while x < 8 {
let col: usize = if x >= 4 { 1 } else { 0 };
let bit = row * 2 + col;
if (mask >> bit) & 1 == 1 {
set_pixel(&mut data, index, x, y);
}
x += 1;
}
y += 1;
}
si += 1;
}
let mut bi = 0;
while bi < BAR_COUNT {
let (eighths, _) = BAR_LEVELS[bi];
let index = QUADRANT_COUNT + SEXTANT_COUNT + bi;
let fill_from = 16 - eighths * 2;
let mut y = fill_from;
while y < 16 {
let mut x = 0u8;
while x < 8 {
set_pixel(&mut data, index, x, y);
x += 1;
}
y += 1;
}
bi += 1;
}
let mut bli = 0;
while bli < BLOCK_COUNT {
let (eighths, _) = BLOCK_LEVELS[bli];
let index = QUADRANT_COUNT + SEXTANT_COUNT + BAR_COUNT + bli;
let mut y = 0u8;
while y < 16 {
let mut x = 0u8;
while x < eighths {
set_pixel(&mut data, index, x, y);
x += 1;
}
y += 1;
}
bli += 1;
}
data
}
const fn build_charset() -> [(char, u8); TOTAL] {
let mut charset = [('\0', 0u8); TOTAL];
let mut qi = 0;
while qi < QUADRANT_COUNT {
let (_, ch) = QUADRANTS[qi];
#[allow(clippy::cast_possible_truncation)]
{
charset[qi] = (ch, qi as u8);
}
qi += 1;
}
let masks = sextant_masks();
let mut si = 0;
while si < SEXTANT_COUNT {
let cp = sextant_codepoint(masks[si]);
let Some(ch) = char::from_u32(cp) else {
panic!("sextant codepoint is not a valid char")
};
let index = QUADRANT_COUNT + si;
#[allow(clippy::cast_possible_truncation)]
{
charset[index] = (ch, index as u8);
}
si += 1;
}
let mut bi = 0;
while bi < BAR_COUNT {
let (_, ch) = BAR_LEVELS[bi];
let index = QUADRANT_COUNT + SEXTANT_COUNT + bi;
#[allow(clippy::cast_possible_truncation)]
{
charset[index] = (ch, index as u8);
}
bi += 1;
}
let mut bli = 0;
while bli < BLOCK_COUNT {
let (_, ch) = BLOCK_LEVELS[bli];
let index = QUADRANT_COUNT + SEXTANT_COUNT + BAR_COUNT + bli;
#[allow(clippy::cast_possible_truncation)]
{
charset[index] = (ch, index as u8);
}
bli += 1;
}
charset
}
static DATA: [u8; TOTAL * 16] = build_data();
static CHARSET: [(char, u8); TOTAL] = build_charset();
#[cfg(test)]
mod tests {
use super::{
BAR_LEVELS, BLOCK_LEVELS, CHARSET, FONT, QUADRANTS, SEXTANT_COUNT, TOTAL,
sextant_codepoint, sextant_masks,
};
use crate::font::FontChain;
use std::collections::HashSet;
#[test]
fn total_glyph_count_matches_quadrants_plus_sextants_plus_bar_plus_block() {
assert_eq!(TOTAL, 10 + 60 + 6 + 6);
assert_eq!(FONT.glyph_count(), u16::try_from(TOTAL).unwrap());
}
#[test]
fn no_charset_entry_duplicates_a_codepoint_cp437_already_serves() {
let already_cp437: HashSet<char> = [' ', '▀', '▄', '▌', '▐', '█', '░', '▒', '▓']
.into_iter()
.collect();
for &(ch, _) in &CHARSET {
assert!(
!already_cp437.contains(&ch),
"{ch:?} (U+{:04X}) duplicates existing CP437 coverage",
ch as u32
);
}
}
#[test]
fn every_charset_character_appears_exactly_once() {
let mut seen = HashSet::with_capacity(TOTAL);
for &(ch, _) in &CHARSET {
assert!(seen.insert(ch), "{ch:?} appears more than once in CHARSET");
}
assert_eq!(seen.len(), TOTAL);
}
#[test]
fn sextant_masks_skip_21_and_42() {
let masks = sextant_masks();
assert_eq!(masks.len(), SEXTANT_COUNT);
assert!(!masks.contains(&21));
assert!(!masks.contains(&42));
assert_eq!(masks[0], 1);
assert_eq!(masks[SEXTANT_COUNT - 1], 62);
}
#[test]
fn sextant_codepoint_shifts_down_after_each_gap() {
assert_eq!(sextant_codepoint(1), 0x1FB00);
assert_eq!(sextant_codepoint(22), 0x1FB00 + 21 - 1);
assert_eq!(sextant_codepoint(43), 0x1FB00 + 42 - 2);
}
#[test]
fn quadrant_table_round_trips_core_subcell_quadrants() {
const CP437_COVERED: [(u8, char); 6] = [
(0, ' '),
(3, '▀'),
(5, '▌'),
(10, '▐'),
(12, '▄'),
(15, '█'),
];
let mut by_mask: [Option<char>; 16] = [None; 16];
for &(mask, ch) in &CP437_COVERED {
by_mask[mask as usize] = Some(ch);
}
for &(mask, ch) in &QUADRANTS {
by_mask[mask as usize] = Some(ch);
}
for (mask, expected) in retroglyph_core::symbols::QUADRANTS.into_iter().enumerate()
{
assert_eq!(
by_mask[mask],
Some(expected),
"mask {mask}: this module's quadrant table disagrees with \
retroglyph_core::symbols::QUADRANTS[{mask}] ({expected:?})"
);
}
}
#[test]
fn sextant_table_round_trips_core_subcell_sextants() {
for (mask, expected) in retroglyph_core::symbols::SEXTANTS.into_iter().enumerate() {
let mask = u8::try_from(mask).unwrap();
let actual = match mask {
0 => ' ',
21 => '▌',
42 => '▐',
63 => '█',
_ => char::from_u32(sextant_codepoint(mask))
.expect("sextant_codepoint always yields a valid char"),
};
assert_eq!(
actual, expected,
"mask {mask}: sextant_codepoint disagrees with \
retroglyph_core::symbols::SEXTANTS[{mask}]"
);
}
}
#[test]
fn quadrant_top_left_mask_only_fills_the_top_left_quarter() {
let index = FONT.glyph_index('▘').expect("U+2598 is covered");
for (x, y) in FONT.glyph_pixels(index) {
assert!(x < 4 && y < 8, "({x}, {y}) outside the top-left quarter");
}
}
#[test]
fn font_chain_resolves_quadrant_via_blocks_fallback() {
static PRIMARY_DATA: [u8; 256 * 16] = [0; 256 * 16];
const PRIMARY: crate::font::BitmapFont =
crate::font::BitmapFont::new(&PRIMARY_DATA, 8, 16, 256);
static FALLBACKS: [crate::font::BitmapFont; 1] = [FONT];
let chain = FontChain::new(PRIMARY, &FALLBACKS);
let quadrant = chain
.resolve('▘')
.expect("covered by legacy_computing::blocks");
assert_eq!(quadrant.font_index(), 1);
assert!(!quadrant.is_notdef());
}
#[test]
fn bar_and_block_levels_are_covered_and_non_empty() {
static PRIMARY_DATA: [u8; 256 * 16] = [0; 256 * 16];
const PRIMARY: crate::font::BitmapFont =
crate::font::BitmapFont::new(&PRIMARY_DATA, 8, 16, 256);
static FALLBACKS: [crate::font::BitmapFont; 1] = [FONT];
let chain = FontChain::new(PRIMARY, &FALLBACKS);
for &(_, ch) in BAR_LEVELS.iter().chain(BLOCK_LEVELS.iter()) {
let resolved = chain
.resolve(ch)
.unwrap_or_else(|| panic!("{ch:?} covered by legacy_computing::blocks"));
assert_eq!(resolved.font_index(), 1);
assert!(!resolved.is_notdef(), "{ch:?} resolved to notdef");
assert!(
FONT.glyph_pixels(resolved.index()).count() > 0,
"{ch:?} has no set pixels"
);
}
}
#[test]
fn bar_levels_fill_monotonically_from_the_bottom() {
let mut last_count = 0usize;
for &(eighths, ch) in &BAR_LEVELS {
let index = FONT.glyph_index(ch).unwrap();
let pixels: Vec<(u8, u8)> = FONT.glyph_pixels(index).collect();
assert!(
pixels.iter().all(|&(_, y)| y >= 16 - eighths * 2),
"{ch:?} has a filled pixel above its {eighths}/8 fill line"
);
assert_eq!(pixels.len(), usize::from(eighths) * 2 * 8);
assert!(pixels.len() > last_count);
last_count = pixels.len();
}
}
#[test]
fn block_levels_fill_monotonically_from_the_left() {
let mut last_count = 0usize;
for &(eighths, ch) in &BLOCK_LEVELS {
let index = FONT.glyph_index(ch).unwrap();
let pixels: Vec<(u8, u8)> = FONT.glyph_pixels(index).collect();
assert!(
pixels.iter().all(|&(x, _)| x < eighths),
"{ch:?} has a filled pixel past its {eighths}/8 fill line"
);
assert_eq!(pixels.len(), usize::from(eighths) * 16);
assert!(pixels.len() > last_count);
last_count = pixels.len();
}
}
}
}
pub mod braille {
use crate::font::BitmapFont;
const TOTAL: usize = 256;
#[allow(clippy::cast_possible_truncation)]
pub const FONT: BitmapFont = BitmapFont::with_charset(&DATA, 8, 16, TOTAL as u16, &CHARSET);
const COL_X: [u8; 2] = [1, 4];
const ROW_Y: [u8; 4] = [1, 5, 9, 13];
const fn bit_index(col: usize, row: usize) -> u32 {
match (col, row) {
(0, 0) => 0,
(0, 1) => 1,
(0, 2) => 2,
(0, 3) => 6,
(1, 0) => 3,
(1, 1) => 4,
(1, 2) => 5,
(1, 3) => 7,
_ => panic!("braille dot position out of range"),
}
}
const fn set_pixel(data: &mut [u8; TOTAL * 16], index: usize, x: u8, y: u8) {
let row = index * 16 + y as usize;
data[row] |= 1 << (7 - x);
}
const fn build_data() -> [u8; TOTAL * 16] {
#[allow(clippy::cast_possible_truncation)]
const TOTAL_U32: u32 = TOTAL as u32;
let mut data = [0u8; TOTAL * 16];
let mut bits: u32 = 0;
while bits < TOTAL_U32 {
let index = bits as usize;
let mut col = 0usize;
while col < 2 {
let mut row = 0usize;
while row < 4 {
let bit = bit_index(col, row);
if (bits >> bit) & 1 == 1 {
let cx = COL_X[col];
let cy = ROW_Y[row];
let mut dy: i32 = -1;
while dy <= 1 {
let mut dx: i32 = -1;
while dx <= 1 {
let px = cx as i32 + dx;
let py = cy as i32 + dy;
if px >= 0 && px < 8 && py >= 0 && py < 16 {
#[allow(
clippy::cast_sign_loss,
clippy::cast_possible_truncation
)]
set_pixel(&mut data, index, px as u8, py as u8);
}
dx += 1;
}
dy += 1;
}
}
row += 1;
}
col += 1;
}
bits += 1;
}
data
}
const fn build_charset() -> [(char, u8); TOTAL] {
#[allow(clippy::cast_possible_truncation)]
const TOTAL_U32: u32 = TOTAL as u32;
let mut charset = [('\0', 0u8); TOTAL];
let mut bits: u32 = 0;
while bits < TOTAL_U32 {
let cp = 0x2800 + bits;
let Some(ch) = char::from_u32(cp) else {
panic!("braille codepoint is not a valid char")
};
let index = bits as usize;
#[allow(clippy::cast_possible_truncation)]
{
charset[index] = (ch, index as u8);
}
bits += 1;
}
charset
}
static DATA: [u8; TOTAL * 16] = build_data();
static CHARSET: [(char, u8); TOTAL] = build_charset();
#[cfg(test)]
mod tests {
use super::{CHARSET, FONT, TOTAL};
use crate::font::FontChain;
use std::collections::HashSet;
#[test]
fn total_glyph_count_is_256() {
assert_eq!(TOTAL, 256);
assert_eq!(FONT.glyph_count(), 256);
}
#[test]
fn every_charset_character_appears_exactly_once() {
let mut seen = HashSet::with_capacity(TOTAL);
for &(ch, _) in &CHARSET {
assert!(seen.insert(ch), "{ch:?} appears more than once in CHARSET");
}
assert_eq!(seen.len(), TOTAL);
}
#[test]
fn covers_the_full_u2800_block() {
for bits in 0u32..u32::try_from(TOTAL).unwrap() {
let ch = char::from_u32(0x2800 + bits).unwrap();
assert_eq!(CHARSET[bits as usize].0, ch);
assert_eq!(CHARSET[bits as usize].1, u8::try_from(bits).unwrap());
}
}
#[test]
fn charset_round_trips_core_symbols_braille_glyph() {
for bits in 0u8..=u8::MAX {
let expected = retroglyph_core::symbols::braille::glyph(bits);
assert_eq!(
CHARSET[bits as usize].0, expected,
"pattern {bits:#04x}: this module's CHARSET disagrees with \
retroglyph_core::symbols::braille::glyph"
);
}
}
#[test]
fn blank_glyph_is_all_zero_bits() {
let index = FONT.glyph_index('\u{2800}').expect("U+2800 is covered");
assert!(FONT.rows(index).iter().all(|&b| b == 0));
}
#[test]
fn full_glyph_has_all_dot_positions_set() {
let index = FONT.glyph_index('\u{28FF}').expect("U+28FF is covered");
let pixel_count = FONT.glyph_pixels(index).count();
assert_eq!(pixel_count, 72);
}
#[test]
fn font_chain_resolves_via_braille_fallback() {
static PRIMARY_DATA: [u8; 256 * 16] = [0; 256 * 16];
const PRIMARY: crate::font::BitmapFont =
crate::font::BitmapFont::new(&PRIMARY_DATA, 8, 16, 256);
static FALLBACKS: [crate::font::BitmapFont; 1] = [FONT];
let chain = FontChain::new(PRIMARY, &FALLBACKS);
let braille = chain
.resolve('\u{2837}')
.expect("covered by legacy_computing::braille");
assert_eq!(braille.font_index(), 1);
assert!(!braille.is_notdef());
let full_block = chain.resolve('█').expect("CP437 coverage");
assert_eq!(full_block.font(), PRIMARY);
assert!(!full_block.is_notdef());
}
}
}
}
const NOTDEF: char = '█';
#[allow(clippy::too_many_lines)]
const fn try_unicode_to_cp437(ch: char) -> Option<u8> {
let u = ch as u32;
if u < 0x80 {
#[allow(clippy::cast_possible_truncation)]
return Some(u as u8);
}
match ch {
'Ç' => Some(0x80),
'ü' => Some(0x81),
'é' => Some(0x82),
'â' => Some(0x83),
'ä' => Some(0x84),
'à' => Some(0x85),
'å' => Some(0x86),
'ç' => Some(0x87),
'ê' => Some(0x88),
'ë' => Some(0x89),
'è' => Some(0x8A),
'ï' => Some(0x8B),
'î' => Some(0x8C),
'ì' => Some(0x8D),
'Ä' => Some(0x8E),
'Å' => Some(0x8F),
'É' => Some(0x90),
'æ' => Some(0x91),
'Æ' => Some(0x92),
'ô' => Some(0x93),
'ö' => Some(0x94),
'ò' => Some(0x95),
'û' => Some(0x96),
'ù' => Some(0x97),
'ÿ' => Some(0x98),
'Ö' => Some(0x99),
'Ü' => Some(0x9A),
'¢' => Some(0x9B),
'£' => Some(0x9C),
'¥' => Some(0x9D),
'₧' => Some(0x9E),
'ƒ' => Some(0x9F),
'á' => Some(0xA0),
'í' => Some(0xA1),
'ó' => Some(0xA2),
'ú' => Some(0xA3),
'ñ' => Some(0xA4),
'Ñ' => Some(0xA5),
'ª' => Some(0xA6),
'º' => Some(0xA7),
'¿' => Some(0xA8),
'⌐' => Some(0xA9),
'¬' => Some(0xAA),
'½' => Some(0xAB),
'¼' => Some(0xAC),
'¡' => Some(0xAD),
'«' => Some(0xAE),
'»' => Some(0xAF),
'░' => Some(0xB0),
'▒' => Some(0xB1),
'▓' => Some(0xB2),
'│' => Some(0xB3),
'┤' => Some(0xB4),
'╡' => Some(0xB5),
'╢' => Some(0xB6),
'╖' => Some(0xB7),
'╕' => Some(0xB8),
'╣' => Some(0xB9),
'║' => Some(0xBA),
'╗' => Some(0xBB),
'╝' => Some(0xBC),
'╜' => Some(0xBD),
'╛' => Some(0xBE),
'┐' => Some(0xBF),
'└' => Some(0xC0),
'┴' => Some(0xC1),
'┬' => Some(0xC2),
'├' => Some(0xC3),
'─' => Some(0xC4),
'┼' => Some(0xC5),
'╞' => Some(0xC6),
'╟' => Some(0xC7),
'╚' => Some(0xC8),
'╔' => Some(0xC9),
'╩' => Some(0xCA),
'╦' => Some(0xCB),
'╠' => Some(0xCC),
'═' => Some(0xCD),
'╬' => Some(0xCE),
'╧' => Some(0xCF),
'╨' => Some(0xD0),
'╤' => Some(0xD1),
'╥' => Some(0xD2),
'╙' => Some(0xD3),
'╘' => Some(0xD4),
'╒' => Some(0xD5),
'╓' => Some(0xD6),
'╫' => Some(0xD7),
'╪' => Some(0xD8),
'┘' => Some(0xD9),
'┌' => Some(0xDA),
'█' => Some(0xDB),
'▄' => Some(0xDC),
'▌' => Some(0xDD),
'▐' => Some(0xDE),
'▀' => Some(0xDF),
'α' => Some(0xE0),
'ß' => Some(0xE1),
'Γ' => Some(0xE2),
'π' => Some(0xE3),
'Σ' => Some(0xE4),
'σ' => Some(0xE5),
'µ' | 'μ' => Some(0xE6),
'τ' => Some(0xE7),
'Φ' => Some(0xE8),
'Θ' => Some(0xE9),
'Ω' => Some(0xEA),
'δ' => Some(0xEB),
'∞' => Some(0xEC),
'φ' => Some(0xED),
'ε' => Some(0xEE),
'∩' => Some(0xEF),
'≡' => Some(0xF0),
'±' => Some(0xF1),
'≥' => Some(0xF2),
'≤' => Some(0xF3),
'⌠' => Some(0xF4),
'⌡' => Some(0xF5),
'÷' => Some(0xF6),
'≈' => Some(0xF7),
'°' => Some(0xF8),
'∙' => Some(0xF9),
'·' => Some(0xFA),
'√' => Some(0xFB),
'ⁿ' => Some(0xFC),
'²' => Some(0xFD),
'■' => Some(0xFE),
'\u{00A0}' => Some(0xFF),
'☺' => Some(0x01),
'•' => Some(0x07),
'☻' => Some(0x02),
'♥' => Some(0x03),
'♦' => Some(0x04),
'♣' => Some(0x05),
'♠' => Some(0x06),
'◘' => Some(0x08),
'○' => Some(0x09),
'◙' => Some(0x0A),
'♂' => Some(0x0B),
'♀' => Some(0x0C),
'♪' => Some(0x0D),
'♫' => Some(0x0E),
'☼' => Some(0x0F),
'►' => Some(0x10),
'◄' => Some(0x11),
'↕' => Some(0x12),
'‼' => Some(0x13),
'¶' => Some(0x14),
'§' => Some(0x15),
'▬' => Some(0x16),
'↨' => Some(0x17),
'↑' => Some(0x18),
'↓' => Some(0x19),
'→' => Some(0x1A),
'←' => Some(0x1B),
'∟' => Some(0x1C),
'↔' => Some(0x1D),
'▲' => Some(0x1E),
'▼' => Some(0x1F),
'⌂' => Some(0x7F),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::{BitmapFont, FontChain, try_unicode_to_cp437};
#[test]
fn patched_glyphs_are_reachable_by_char() {
assert_eq!(try_unicode_to_cp437('⌂'), Some(0x7F), "U+2302 HOUSE");
assert_eq!(
try_unicode_to_cp437('☼'),
Some(0x0F),
"U+263C WHITE SUN WITH RAYS"
);
assert_eq!(
try_unicode_to_cp437('⌐'),
Some(0xA9),
"U+2310 REVERSED NOT SIGN"
);
assert_eq!(
try_unicode_to_cp437('∙'),
Some(0xF9),
"U+2219 BULLET OPERATOR"
);
assert_eq!(
try_unicode_to_cp437('\u{00A0}'),
Some(0xFF),
"U+00A0 NO-BREAK SPACE"
);
assert_eq!(try_unicode_to_cp437('¬'), Some(0xAA), "U+00AC NOT SIGN");
assert_eq!(try_unicode_to_cp437('₧'), Some(0x9E), "U+20A7 PESETA SIGN");
assert_eq!(try_unicode_to_cp437('·'), Some(0xFA), "U+00B7 MIDDLE DOT");
}
#[cfg(feature = "tilesets")]
#[test]
fn cp437_to_unicode_round_trips_through_try_unicode_to_cp437() {
for (i, &ch) in crate::tileset::CP437_TO_UNICODE.iter().enumerate() {
#[allow(clippy::cast_possible_truncation)]
let expected = i as u8;
assert_eq!(
try_unicode_to_cp437(ch),
Some(expected),
"0x{i:02X} {ch:?} did not round-trip"
);
}
}
static PRIMARY_DATA: [u8; 128 * 16] = [0; 128 * 16];
const PRIMARY: BitmapFont = BitmapFont::new(&PRIMARY_DATA, 8, 16, 128);
static FALLBACK_DATA: [u8; 256 * 16] = [0; 256 * 16];
const FALLBACK_FONT: BitmapFont = BitmapFont::new(&FALLBACK_DATA, 8, 16, 256);
#[test]
fn chain_resolves_char_present_only_in_fallback_font() {
let chain = FontChain::new(PRIMARY, &[FALLBACK_FONT]);
let resolved = chain.resolve('Ç').expect("covered by the fallback font");
assert_eq!(resolved.font(), FALLBACK_FONT);
assert_eq!(resolved.font_index(), 1);
assert_eq!(resolved.index(), 0x80);
assert!(!resolved.is_notdef());
}
#[test]
fn chain_falls_back_to_solid_block_when_every_font_misses() {
let chain = FontChain::new(PRIMARY, &[FALLBACK_FONT]);
let resolved = chain.resolve('あ').expect("solid block substitute");
assert_eq!(resolved.font(), FALLBACK_FONT);
assert_eq!(resolved.index(), 0xDB);
assert!(resolved.is_notdef());
}
#[test]
fn chain_resolves_nothing_when_no_font_has_a_substitute() {
static DATA: [u8; 16] = [0; 16];
const CHARSET: [(char, u8); 1] = [('\u{2800}', 0)];
const BRAILLE: BitmapFont = BitmapFont::with_charset(&DATA, 8, 16, 1, &CHARSET);
let chain = FontChain::new(BRAILLE, &[]);
assert!(chain.resolve('\u{2800}').is_some());
assert!(chain.resolve('A').is_none());
}
#[test]
fn single_font_chain_resolves_that_font_directly() {
let chain = FontChain::from(FALLBACK_FONT);
assert_eq!(chain.font_count(), 1);
for ch in ['A', ' ', '█', '│', 'Ç', '☺'] {
let resolved = chain.resolve(ch).expect("CP437 coverage");
assert_eq!(resolved.font(), FALLBACK_FONT);
assert_eq!(resolved.font_index(), 0);
assert_eq!(resolved.index(), FALLBACK_FONT.glyph_index(ch).unwrap());
}
}
#[test]
fn glyph_size_is_none_for_a_chain_of_mismatched_fonts() {
static DATA: [u8; 8] = [0; 8];
const SHORT: BitmapFont = BitmapFont::new(&DATA, 8, 8, 1);
assert_eq!(
FontChain::new(PRIMARY, &[FALLBACK_FONT]).glyph_size(),
Some((8, 16))
);
assert_eq!(FontChain::new(PRIMARY, &[SHORT]).glyph_size(), None);
}
#[test]
fn glyph_pixels_decodes_msb_first_row_major() {
static DATA: [u8; 4] = [0b1000_0001, 0b0000_0000, 0b1111_1111, 0b0000_1000];
let font = BitmapFont::new(&DATA, 8, 2, 2);
let g0: Vec<(u8, u8)> = font.glyph_pixels(0).collect();
assert_eq!(
g0,
[(0, 0), (7, 0)],
"MSB is the leftmost pixel; row 0 first"
);
let g1: Vec<(u8, u8)> = font.glyph_pixels(1).collect();
let mut expected: Vec<(u8, u8)> = (0..8).map(|x| (x, 0)).collect();
expected.push((4, 1)); assert_eq!(g1, expected);
}
#[test]
fn glyph_pixels_is_parameterized_by_width_not_hardcoded_to_8() {
static DATA: [u8; 1] = [0b0001_0001];
let font = BitmapFont::new(&DATA, 5, 1, 1);
let pixels: Vec<(u8, u8)> = font.glyph_pixels(0).collect();
assert_eq!(pixels, [(0, 0), (4, 0)]);
}
#[test]
fn glyph_pixels_does_not_overflow_the_shift_for_width_above_8() {
static DATA: [u8; 1] = [0b1111_1111];
let font = BitmapFont::new(&DATA, 12, 1, 1);
let pixels: Vec<(u8, u8)> = font.glyph_pixels(0).collect();
assert_eq!(pixels, (0..8).map(|x| (x, 0)).collect::<Vec<_>>());
}
#[test]
fn chain_extends_past_cp437_via_charset_fallback_font() {
static BRAILLE_DATA: [u8; 16] = [0; 16];
const BRAILLE_CHARSET: [(char, u8); 1] = [('\u{2800}', 0)];
const BRAILLE_FONT: BitmapFont =
BitmapFont::with_charset(&BRAILLE_DATA, 8, 16, 1, &BRAILLE_CHARSET);
let primary = FALLBACK_FONT; let chain = FontChain::new(primary, &[BRAILLE_FONT]);
let braille = chain.resolve('\u{2800}').expect("charset coverage");
assert_eq!(braille.font(), BRAILLE_FONT);
assert_eq!(braille.index(), 0);
let full_block = chain.resolve('\u{2588}').expect("CP437 coverage"); assert_eq!(full_block.font(), primary);
assert_eq!(full_block.index(), 0xDB);
assert_ne!(braille.index(), full_block.index());
}
}
#[cfg(all(test, feature = "default-font", feature = "legacy-computing"))]
mod symbols_coverage {
use crate::font::{BitmapFont, FontChain, legacy_computing, unscii16};
use retroglyph_core::symbols::{bar, block, border, line};
use std::collections::HashSet;
fn chain() -> FontChain<'static> {
static FALLBACKS: [BitmapFont; 2] = [
legacy_computing::blocks::FONT,
legacy_computing::braille::FONT,
];
FontChain::new(unscii16::FONT, &FALLBACKS)
}
fn known_notdef_gaps() -> HashSet<char> {
[
border::ROUNDED.top_left,
border::ROUNDED.top_right,
border::ROUNDED.bottom_left,
border::ROUNDED.bottom_right,
border::THICK.top_left,
border::THICK.top_right,
border::THICK.bottom_left,
border::THICK.bottom_right,
border::THICK.horizontal,
border::THICK.vertical,
line::THICK.cross,
line::THICK.vertical_left,
line::THICK.vertical_right,
line::THICK.horizontal_down,
line::THICK.horizontal_up,
]
.into_iter()
.collect()
}
#[test]
fn every_symbols_entry_resolves_or_is_a_known_gap() {
let chain = chain();
let known = known_notdef_gaps();
let mut still_notdef = HashSet::new();
let mut check = |ch: char| {
let resolved = chain.resolve(ch);
let is_notdef = resolved.is_none_or(|g| g.is_notdef());
if is_notdef {
still_notdef.insert(ch);
}
};
for set in [
border::PLAIN,
border::ROUNDED,
border::DOUBLE,
border::THICK,
] {
check(set.top_left);
check(set.top_right);
check(set.bottom_left);
check(set.bottom_right);
check(set.horizontal);
check(set.vertical);
}
for set in [line::NORMAL, line::DOUBLE, line::THICK] {
check(set.horizontal);
check(set.vertical);
check(set.cross);
check(set.vertical_left);
check(set.vertical_right);
check(set.horizontal_down);
check(set.horizontal_up);
}
for ch in [
block::FULL,
block::SEVEN_EIGHTHS,
block::THREE_QUARTERS,
block::FIVE_EIGHTHS,
block::HALF,
block::THREE_EIGHTHS,
block::ONE_QUARTER,
block::ONE_EIGHTH,
] {
check(ch);
}
for ch in bar::NINE_LEVELS {
check(ch);
}
for pattern in 0u8..=u8::MAX {
check(retroglyph_core::symbols::braille::glyph(pattern));
}
for &ch in &still_notdef {
assert!(
known.contains(&ch),
"{ch:?} (U+{:04X}) newly falls back to notdef through the bundled chain; \
either fix its font coverage or add it to `known_notdef_gaps`",
ch as u32
);
}
assert_eq!(
still_notdef, known,
"`known_notdef_gaps` is stale: a previously-notdef glyph now resolves through the \
bundled chain. Shrink the allowlist to match."
);
}
}