pub const END: u8 = 0x50;
#[derive(Debug)]
pub struct Charmap {
pub(crate) runs: &'static [(u8, &'static str)],
pub(crate) words: &'static [(u8, &'static str)],
pub(crate) borrows_kana: bool,
}
const CONTROL: &[(u8, &str)] = &[
(0x49, "<PAGE>"),
(0x4B, "<_CONT>"),
(0x4C, "<SCROLL>"),
(0x4E, "\n"),
(0x4F, "\n"),
(0x51, "\n\n"),
(0x52, "<PLAYER>"),
(0x53, "<RIVAL>"),
(0x55, "\n"),
(0x57, "<DONE>"),
(0x58, "<PROMPT>"),
(0x59, "<TARGET>"),
(0x5A, "<USER>"),
(0x5F, "<DEXEND>"),
];
pub const ENGLISH: Charmap = Charmap {
runs: &[
(0x60, "ABCDEFGHIVSLM:ぃぅ‘’“”·…ぁぇぉ┌─┐│└┘ "),
(
0x80,
"ABCDEFGHIJKLMNOPQRSTUVWXYZ():;[]abcdefghijklmnopqrstuvwxyzé",
),
(0xE0, "'"),
(0xE3, "-"),
(0xE6, "?!.ァゥェ▷▶▼♂¥×./,♀0123456789"),
],
words: &[
(0x4A, "PKMN"),
(0x54, "POKé"),
(0x56, "……"),
(0x5B, "PC"),
(0x5C, "TM"),
(0x5D, "TRAINER"),
(0x5E, "ROCKET"),
(0xBB, "'d"),
(0xBC, "'l"),
(0xBD, "'s"),
(0xBE, "'t"),
(0xBF, "'v"),
(0xE1, "PK"),
(0xE2, "MN"),
(0xE4, "'r"),
(0xE5, "'m"),
],
borrows_kana: false,
};
pub const JAPANESE: Charmap = Charmap {
runs: &[
(0x05, "ガギグゲゴザジズゼゾダヂヅデド"),
(0x19, "バビブボ"),
(0x26, "がぎぐげござじずぜぞだぢづでど"),
(0x3A, "ばびぶべぼ"),
(0x40, "パピプポぱぴぷぺぽ"),
(
0x60,
"ABCDEFGHIVSLM:ぃぅ「」『』・⋯ぁぇぉ┌─┐│└┘ ",
),
(
0x80,
"アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフホマミムメモヤユヨラルレロワヲンッャュョィ",
),
(
0xB1,
"あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをんっゃゅょー゜゛?!。ァゥェ▷▶▼♂円×./ォ♀0123456789",
),
],
words: &[(0x4A, "ポケモン"), (0x54, "ポケモン"), (0x56, "⋯⋯")],
borrows_kana: true,
};
impl Charmap {
pub fn decode(&self, bytes: &[u8]) -> String {
let end = bytes.iter().position(|&byte| byte == END);
let mut text: Vec<char> = Vec::new();
for &byte in &bytes[..end.unwrap_or(bytes.len())] {
match self.character(byte) {
Some(character) => text.push(character),
None => text.extend(self.word(byte).chars()),
}
}
if self.borrows_kana {
restore_katakana(&mut text);
}
text.into_iter().collect()
}
fn character(&self, byte: u8) -> Option<char> {
self.runs.iter().find_map(|&(start, run)| {
byte.checked_sub(start)
.and_then(|index| run.chars().nth(index.into()))
})
}
fn word(&self, byte: u8) -> String {
self.words
.iter()
.chain(CONTROL)
.find(|&&(code, _)| code == byte)
.map_or_else(|| format!("<${byte:02x}>"), |(_, word)| (*word).to_owned())
}
pub fn fixed(&self, bytes: &[u8], len: usize) -> Vec<String> {
bytes
.chunks_exact(len)
.map(|name| self.decode(name))
.collect()
}
pub fn list(&self, bytes: &[u8], count: usize) -> Vec<String> {
bytes
.split(|&byte| byte == END)
.take(count)
.map(|text| self.decode(text))
.collect()
}
}
fn restore_katakana(text: &mut [char]) {
let katakana = |c: Option<&char>| c.is_some_and(|&c| ('ァ'..='ヺ').contains(&c) || c == 'ー');
for index in 0..text.len() {
let twin = match text[index] {
'へ' => 'ヘ',
'べ' => 'ベ',
'ぺ' => 'ペ',
'り' => 'リ',
_ => continue,
};
let before = index.checked_sub(1).and_then(|before| text.get(before));
if katakana(before) || katakana(text.get(index + 1)) {
text[index] = twin;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decodes_english_names_words_and_markup() {
let name = [0x8D, 0x88, 0x83, 0x8E, 0x91, 0x80, 0x8D, 0xEF, 0x50, 0x50];
assert_eq!(ENGLISH.decode(&name), "NIDORAN♂");
assert_eq!(
ENGLISH.decode(&[0x54, 0x7F, 0x81, 0x4E, 0x8E, 0xBD, 0x52, 0x01]),
"POKé B\nO's<PLAYER><$01>"
);
assert_eq!(ENGLISH.fixed(&[0x80, 0x50, 0x81, 0x50], 2), ["A", "B"]);
assert_eq!(ENGLISH.list(&[0x80, 0x50, 0x81, 0x50, 0x82], 2), ["A", "B"]);
}
#[test]
fn decodes_japanese_with_borrowed_kana() {
assert_eq!(JAPANESE.decode(&[0x8A, 0x81, 0x13, 0xAB, 0x50]), "サイドン");
assert_eq!(
JAPANESE.decode(&[0xD8, 0x0A, 0xE3, 0x13, 0xAB]),
"リザードン"
);
assert_eq!(JAPANESE.decode(&[0xD8, 0xDE, 0x2A]), "りんご");
}
}