#![warn(missing_docs)]
#![warn(unsafe_code)]
use enumset::{EnumSet, EnumSetType};
use std::{
fmt::{Debug, Display, Formatter, Result},
sync::RwLock,
};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use crate::{config_data, Configuration, SystemConfig};
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct ScreenCodeValue {
pub set: u8,
pub value: u8,
}
#[derive(Debug, EnumSetType, Serialize, Deserialize)]
#[enumset(serialize_repr = "u8", repr = "u8")]
pub enum PetsciiCharacterAttributes {
Shifted,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PetsciiCodeValue {
pub attributes: u8,
pub value: u8,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct PetsciiConfig {
pub version: String,
pub c64_petscii_shifted_codes_to_screen_codes: Map<String, Value>,
pub c64_petscii_unshifted_codes_to_screen_codes: Map<String, Value>,
pub c64_screen_codes_set_1_to_unicode_codes: Map<String, Value>,
pub c64_screen_codes_set_2_to_unicode_codes: Map<String, Value>,
pub c64_screen_codes_set_3_to_unicode_codes: Map<String, Value>,
pub unicode_codes_to_c64_screen_codes: Map<String, Value>,
pub c64_screen_codes_set_1_to_petscii_codes: Map<String, Value>,
pub c64_screen_codes_set_2_to_petscii_codes: Map<String, Value>,
pub c64_screen_codes_set_3_to_petscii_codes: Map<String, Value>,
}
pub static CONFIG: RwLock<Option<PetsciiConfig>> = RwLock::new(None);
impl Configuration for PetsciiConfig {
fn load() -> std::result::Result<crate::Config, crate::error::Error> {
let crate_config = crate::Config::load()?;
{
let binding = CONFIG.read().expect("Should be able to get reader lock");
let test = binding.as_ref();
if let Some(petscii_config) = test {
return Ok(crate::Config {
version: crate_config.version,
petscii: crate::SystemConfig {
version: crate_config.petscii.version,
character_set_map: petscii_config.clone(),
},
});
}
}
let json_str = config_data::C64_PETSCII_MAP;
let petscii_config: PetsciiConfig =
serde_json::from_str(json_str).expect("Couldn't load embedded config");
{
let mut lock_res = CONFIG
.write()
.expect("Should be able to acquire config lock");
*lock_res = Some(petscii_config.clone());
}
Ok(crate::Config {
version: crate_config.version,
petscii: crate::SystemConfig {
version: crate_config.petscii.version,
character_set_map: petscii_config.clone(),
},
})
}
fn load_from_file(filename: &str) -> std::result::Result<crate::Config, crate::error::Error> {
let crate_config = crate::Config::load_from_file(filename)?;
Ok(crate_config)
}
}
#[derive(Debug, EnumSetType)]
pub enum CharacterAttributes {
Normal = 0,
Reversed = 1,
}
pub struct PetsciiCharacter {
pub attributes: CharacterAttributes,
pub character: u8,
}
#[derive(Clone, Copy)]
pub struct PetsciiString<'a, const L: usize> {
pub len: u32,
pub data: [u8; L],
pub character_map: Option<&'a SystemConfig>,
pub strip_shifted_space: bool,
}
impl<const L: usize> Debug for PetsciiString<'_, L> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "length: {:?}, ", self.len)?;
write!(f, "data: {:?}, ", self.data)?;
write!(f, "display: {}", self)
}
}
impl<const L: usize> Display for PetsciiString<'_, L> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{}", String::from(self))
}
}
pub struct IntoIter<'a, const L: usize> {
index: usize,
data: PetsciiString<'a, L>,
}
impl<'a, const L: usize> IntoIterator for PetsciiString<'a, L> {
type Item = u8;
type IntoIter = IntoIter<'a, L>;
fn into_iter(self) -> IntoIter<'a, L> {
IntoIter {
index: 0,
data: self,
}
}
}
impl<const L: usize> Iterator for IntoIter<'_, L> {
type Item = u8;
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.data.len.try_into().unwrap() {
self.index += 1;
Some(self.data.data[self.index - 1])
} else {
None
}
}
}
impl<'a, const L: usize> From<&'a [u8]> for PetsciiString<'a, L> {
fn from(s: &'a [u8]) -> PetsciiString<'a, L> {
let mut bytes: [u8; L] = [0; L];
if s.len() > L {
panic!("u8 slice is too large");
}
bytes[..L].copy_from_slice(&s[..L]);
PetsciiString {
len: L as u32,
data: bytes,
character_map: None,
strip_shifted_space: false,
}
}
}
fn unicode_to_petscii_bytes(s: &str) -> Vec<u8> {
let mut attributes = EnumSet::new();
let mut shifted = false;
let config = PetsciiConfig::load().expect("Error loading config");
let uc_map = config
.petscii
.character_set_map
.unicode_codes_to_c64_screen_codes;
let sc1_map = config
.petscii
.character_set_map
.c64_screen_codes_set_1_to_petscii_codes;
let sc2_map = config
.petscii
.character_set_map
.c64_screen_codes_set_2_to_petscii_codes;
let sc3_map = config
.petscii
.character_set_map
.c64_screen_codes_set_3_to_petscii_codes;
attributes.insert(CharacterAttributes::Normal);
let mut bytes: Vec<u8> = s
.chars()
.filter_map(|c| {
let key = u32::from(c).to_string();
let screen_code_opt = uc_map.get(&key);
let screen_code_value = match screen_code_opt {
Some(s) => s,
None => {
return None;
}
};
let screen_code_res = ScreenCodeValue::deserialize(screen_code_value);
let screen_code = match screen_code_res {
Ok(s) => s,
Err(_) => {
return None;
}
};
let key = screen_code.value.to_string();
let petscii_code_opt = if screen_code.set == 1 {
sc1_map.get(&key)
} else if screen_code.set == 2 {
sc2_map.get(&key)
} else if screen_code.set == 3 {
sc3_map.get(&key)
} else {
return None;
};
let petscii_code_value = match petscii_code_opt {
Some(s) => s,
None => {
return None;
}
};
let petscii_code_res = PetsciiCodeValue::deserialize(petscii_code_value);
let petscii_code = match petscii_code_res {
Ok(s) => s,
Err(_) => {
return None;
}
};
Some(petscii_code)
})
.flat_map(|petscii_code| {
let mut codes: Vec<u8> = Vec::new();
let eset: EnumSet<PetsciiCharacterAttributes> =
EnumSet::from_repr(petscii_code.attributes);
if eset.contains(PetsciiCharacterAttributes::Shifted) {
if !shifted {
codes.push(0x0E);
shifted = true;
}
} else if shifted {
codes.push(0x8E);
shifted = false;
}
codes.push(petscii_code.value);
codes
})
.collect();
if shifted {
bytes.push(0x8E);
}
bytes
}
impl<'a, const L: usize> From<&str> for PetsciiString<'a, L> {
fn from(s: &str) -> PetsciiString<'a, L> {
let mut final_bytes: [u8; L] = [0; L];
let bytes = unicode_to_petscii_bytes(s);
if bytes.len() > L {
panic!("u8 slice is too large");
}
let b = bytes.as_slice();
final_bytes[..b.len()].copy_from_slice(&b[..b.len()]);
PetsciiString {
len: b.len() as u32,
data: final_bytes,
character_map: None,
strip_shifted_space: false,
}
}
}
impl<const L: usize> From<PetsciiString<'_, L>> for String {
fn from(s: PetsciiString<L>) -> String {
String::from(&s)
}
}
impl<const L: usize> From<&PetsciiString<'_, L>> for String {
fn from(s: &PetsciiString<L>) -> String {
let mut attributes = EnumSet::new();
let mut shifted = false;
attributes.insert(CharacterAttributes::Normal);
s.into_iter()
.filter(|c| !s.strip_shifted_space || (*c != 0xA0))
.filter_map(|c| {
match c {
0x0E => {
shifted = true;
return None;
},
0x12 => {
attributes.remove(CharacterAttributes::Normal);
attributes.insert(CharacterAttributes::Reversed);
return None;
},
0x8E => {
shifted = false;
return None;
},
0x92 => {
attributes.remove(CharacterAttributes::Reversed);
attributes.insert(CharacterAttributes::Normal);
return None;
},
_ => {}
}
let cm = match &s.character_map {
Some(s) => s,
None => { return Some(char::from_u32(c as u32).unwrap()); },
};
let c = match c {
0..=191 => c,
192..=223 => c - 96,
224..=254 => c - 64,
255 => 126,
};
let petscii_to_screen_codes = if !shifted {
&cm.character_set_map.c64_petscii_unshifted_codes_to_screen_codes
} else {
&cm.character_set_map.c64_petscii_shifted_codes_to_screen_codes
};
let key = c.to_string();
let screen_code_opt: Option<ScreenCodeValue> =
petscii_to_screen_codes
.get(&key)
.and_then(|screen_code_value| {
ScreenCodeValue::deserialize(screen_code_value).ok()
});
let screen_code = match screen_code_opt {
Some(s) => s,
None => return None,
};
if screen_code.value > 127 {
panic!("Should not have a screen code greater than 127 before applying reverse video transform");
}
let screen_code_value: u32 =
if attributes.contains(CharacterAttributes::Reversed) {
(screen_code.value as u32) + 128
} else {
screen_code.value.into()
};
let screen_codes_to_unicode = match screen_code.set {
1 =>
&cm.character_set_map.c64_screen_codes_set_1_to_unicode_codes,
2 =>
&cm.character_set_map.c64_screen_codes_set_2_to_unicode_codes,
3 =>
&cm.character_set_map.c64_screen_codes_set_3_to_unicode_codes,
_ => {
panic!("Invalid screen code set");
}
};
let key = screen_code_value.to_string();
let d = if screen_codes_to_unicode.contains_key(&key) {
match screen_codes_to_unicode.get(&key).unwrap() {
serde_json::Value::Number(v) => v.as_u64().unwrap() as u32,
_ => 0,
}
} else {
c as u32
};
Some(char::from_u32(d).unwrap())
})
.collect()
}
}
impl<'a, const L: usize> PetsciiString<'a, L> {
pub fn new(len: u32, data: [u8; L]) -> Self {
PetsciiString {
len,
data,
character_map: None,
strip_shifted_space: false,
}
}
pub fn new_with_config(len: u32, data: [u8; L], character_map: &'a SystemConfig) -> Self {
PetsciiString {
len,
data,
character_map: Some(character_map),
strip_shifted_space: false,
}
}
pub fn len(&self) -> usize {
self.len as usize
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn from_byte_slice_strip_shifted_space(s: &'a [u8]) -> PetsciiString<'a, L> {
let mut bytes: [u8; L] = [0; L];
if s.len() > L {
panic!("u8 slice is too large");
}
bytes[..s.len()].copy_from_slice(s);
PetsciiString {
len: L as u32,
data: bytes,
character_map: None,
strip_shifted_space: true,
}
}
pub fn from_str_with_config(s: &str, character_map: &'a SystemConfig) -> PetsciiString<'a, L> {
let mut final_bytes: [u8; L] = [0; L];
let bytes = unicode_to_petscii_bytes(s);
if bytes.len() > L {
panic!("u8 vector is too large");
}
let b = bytes.as_slice();
final_bytes[..b.len()].copy_from_slice(&b[..b.len()]);
PetsciiString {
len: b.len() as u32,
data: final_bytes,
character_map: Some(character_map),
strip_shifted_space: false,
}
}
pub fn from_byte_slice_strip_shifted_space_with_config(
s: &'a [u8],
character_map: &'a SystemConfig,
) -> PetsciiString<'a, L> {
let mut bytes: [u8; L] = [0; L];
if s.len() > L {
panic!("u8 slice is too large");
}
bytes[..s.len()].copy_from_slice(s);
PetsciiString {
len: L as u32,
data: bytes,
character_map: Some(character_map),
strip_shifted_space: true,
}
}
}
#[cfg(test)]
mod tests {
use std::fmt::Write;
use crate::{
petscii::{PetsciiConfig, PetsciiString, CONFIG},
Config, Configuration,
};
#[test]
fn petscii_load_config_works() {
let mut saved_config: Option<PetsciiConfig> = None;
assert!(saved_config.is_none());
{
let mut lock_res = CONFIG
.write()
.expect("Should be able to acquire config lock");
saved_config = lock_res.take();
}
{
let binding = CONFIG.read().expect("Should be able to get reader lock");
assert!(binding.as_ref().is_none());
}
let config_result = PetsciiConfig::load();
assert!(config_result.is_ok());
{
let binding = CONFIG.read().expect("Should be able to get reader lock");
assert!(binding.as_ref().is_some());
}
let mut lock_res = CONFIG
.write()
.expect("Should be able to acquire config lock");
*lock_res = saved_config.take();
}
#[test]
fn petscii_struct_works() {
let ps = PetsciiString::new(3, [0x41, 0x42, 0x43]);
assert_eq!(ps.len, 3);
assert_eq!(ps.data, [0x41, 0x42, 0x43]);
}
#[test]
fn petscii_with_config_works() {
let config = PetsciiConfig::load().expect("Error loading config file");
let ps = PetsciiString::new_with_config(
6,
[0x41, 0x42, 0x43, 0x5c, 0x5e, 0x5f],
&config.petscii,
);
let mut s: String = String::from(ps);
assert_eq!(s.pop().unwrap(), '←');
assert_eq!(s.pop().unwrap(), '↑');
assert_eq!(s.pop().unwrap(), '£');
assert_eq!(s.pop().unwrap(), 'C');
assert_eq!(s.pop().unwrap(), 'B');
assert_eq!(s.pop().unwrap(), 'A');
}
#[test]
fn petscii_with_config_unmapped_character_works() {
let config_result = PetsciiConfig::load();
let config: Config = match config_result {
Ok(c) => c,
Err(e) => {
panic!("Error loading config file: {e}");
}
};
let ps = PetsciiString::new_with_config(2, [0x41, 0xb2], &config.petscii);
let mut s: String = String::from(ps);
assert_eq!(s.pop().unwrap(), '┬');
assert_eq!(s.pop().unwrap(), 'A');
}
#[test]
fn petscii_without_config_works() {
let ps = PetsciiString::new(6, [0x41, 0x42, 0x43, 0x5c, 0x5e, 0x5f]);
let mut s: String = String::from(ps);
assert_eq!(s.pop().unwrap(), '_');
assert_eq!(s.pop().unwrap(), '^');
assert_eq!(s.pop().unwrap(), '\\');
assert_eq!(s.pop().unwrap(), 'C');
assert_eq!(s.pop().unwrap(), 'B');
assert_eq!(s.pop().unwrap(), 'A');
}
#[test]
fn petscii_len_unfilled_works() {
let ps = PetsciiString::new(6, [0x41, 0x42, 0x43]);
assert_eq!(ps.len(), 6);
}
#[test]
fn petscii_len_7bit_characters_works() {
let ps = PetsciiString::new(6, [0x41, 0x42, 0x43, 0x41, 0x42, 0x43]);
assert_eq!(ps.len(), 6);
}
#[test]
fn petscii_len_8bit_characters_works() {
let ps = PetsciiString::new(7, [0xa5, 0x74, 0x67, 0x7d, 0x68, 0x79, 0xa7]);
assert_eq!(ps.len(), 7);
}
#[test]
fn petscii_len_from_8bit_character_slice_works() {
let ps = PetsciiString::new(7, [0xa5, 0x74, 0x67, 0x7d, 0x68, 0x79, 0xa7]);
let s: String = String::from(ps);
assert_eq!(s.len(), 9);
assert_eq!(s.chars().count(), 7);
}
#[test]
fn petscii_len_from_8bit_character_slice_with_config_works() {
let config = {
let config_fn = String::from("data/config.json");
PetsciiConfig::load_from_file(&config_fn).expect("Error loading config file")
};
{
let mut lock_res = crate::CONFIG
.write()
.expect("Should be able to acquire config lock");
*lock_res = Some(config);
}
let binding = crate::CONFIG
.read()
.expect("Should be able to get reader lock");
let config = binding.as_ref().unwrap();
let ps = PetsciiString::new_with_config(
6,
[0x74, 0x67, 0x62, 0x7d, 0x68, 0x79],
&config.petscii,
);
let s: String = String::from(ps);
assert_eq!(s.len(), 24);
assert_eq!(s.chars().count(), 6);
}
#[test]
fn petscii_display_works() {
let config_fn = String::from("data/config.json");
let config = Config::load_from_file(&config_fn).expect("Error loading config file");
let hello_world_data: [u8; 61] = [
0x0d, 0x0a, 0xb0, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
0x60, 0x60, 0x60, 0x60, 0xae, 0x0d, 0x0a, 0x7d, 0x20, 0x48, 0x0e, 0x45, 0x4c, 0x4c,
0x4f, 0x2c, 0x20, 0x57, 0x4f, 0x52, 0x4c, 0x44, 0x21, 0x20, 0x8e, 0x7d, 0x0d, 0x0a,
0xad, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
0x60, 0x60, 0xbd, 0x0d, 0x0a,
];
let expected_unicode: [u32; 59] = [
13, 10, 9484, 129913, 129913, 129913, 129913, 129913, 129913, 129913, 129913, 129913,
129913, 129913, 129913, 129913, 129913, 129913, 9488, 13, 10, 129907, 32, 72, 101, 108,
108, 111, 44, 32, 119, 111, 114, 108, 100, 33, 32, 129907, 13, 10, 9492, 129913,
129913, 129913, 129913, 129913, 129913, 129913, 129913, 129913, 129913, 129913, 129913,
129913, 129913, 129913, 9496, 13, 10,
];
let ps = PetsciiString::new_with_config(61, hello_world_data, &config.petscii);
let mut string_buf = String::new();
write!(string_buf, "{}", ps).unwrap();
let bytes: Vec<u32> = string_buf.chars().map(|c| u32::from(c)).collect();
assert_eq!(Vec::from(expected_unicode), bytes);
}
#[test]
fn petscii_test_shifted_lowercase_characters_works() {
let data: [u8; 28] = [
0x0e, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d,
0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x8e,
];
let config = {
let config_fn = String::from("data/config.json");
PetsciiConfig::load_from_file(&config_fn).expect("Error loading config file")
};
let ps = PetsciiString::new_with_config(28, data, &config.petscii);
assert_eq!(ps.len(), 28);
let s: String = String::from(ps);
let expected = "abcdefghijklmnopqrstuvwxyz";
assert_eq!(s, expected);
}
#[test]
fn from_petscii_with_block_elements_graphic_character() {
let data: [u8; 0x01] = [0xB9];
let config = {
let config_fn = String::from("data/config.json");
PetsciiConfig::load_from_file(&config_fn).expect("Error loading config file")
};
let ps = PetsciiString::new_with_config(1, data, &config.petscii);
let s: String = String::from(ps);
let c = s.chars().nth(0).unwrap();
let expected: char = char::from_u32(0x2583).unwrap();
assert_eq!(c, expected);
}
#[test]
fn test_petscii_7bit_playing_cards_to_unicode() {
let data: [u8; 4] = [0x61, 0x73, 0x78, 0x7a];
let config = PetsciiConfig::load().expect("Error loading config file");
let ps = PetsciiString::new_with_config(4, data, &config.petscii);
let s: String = String::from(ps);
let expected = "♠♥♣♦";
assert_eq!(s, expected);
}
#[test]
fn test_petscii_8bit_playing_cards_to_unicode() {
let data: [u8; 4] = [0xc1, 0xd3, 0xd8, 0xda];
let config = PetsciiConfig::load().expect("Error loading config");
let ps = PetsciiString::new_with_config(4, data, &config.petscii);
let s: String = String::from(ps);
let expected = "♠♥♣♦";
assert_eq!(s, expected);
}
#[test]
fn test_petscii_7bit_reversed_video_playing_cards_to_unicode() {
let data: [u8; 6] = [0x12, 0x61, 0x73, 0x78, 0x7a, 0x92];
let config = PetsciiConfig::load().expect("Error loading config");
let ps = PetsciiString::new_with_config(6, data, &config.petscii);
let s: String = String::from(ps);
let expected = "♤♡♧♢";
assert_eq!(s, expected);
}
#[test]
fn test_petscii_8bit_reversed_video_playing_cards_to_unicode() {
let data: [u8; 6] = [0x12, 0xc1, 0xd3, 0xd8, 0xda, 0x92];
let config = PetsciiConfig::load().expect("Error loading config");
let ps = PetsciiString::new_with_config(6, data, &config.petscii);
let s: String = String::from(ps);
let expected = "♤♡♧♢";
assert_eq!(s, expected);
}
#[test]
fn test_petscii_misc_graphics_to_unicode() {
let data: [u8; 6] = [0x76, 0x77, 0x7e, 0x0e, 0xba, 0x8e];
let config = PetsciiConfig::load().expect("Error loading config");
let ps = PetsciiString::new_with_config(6, data, &config.petscii);
let s: String = String::from(ps);
let expected = "╳○π✓";
assert_eq!(s, expected);
#[cfg(feature = "external-json")]
{
let config_fn = String::from("data/config.json");
let config =
PetsciiConfig::load_from_file(&config_fn).expect("Error loading config file");
let ps = PetsciiString::new_with_config(6, data, &config.petscii);
let s: String = String::from(ps);
assert_eq!(s, expected);
}
let data: [u8; 6] = [0x76, 0xd7, 0xde, 0x0e, 0xfa, 0x8e];
let ps = PetsciiString::new_with_config(6, data, &config.petscii);
let s: String = String::from(ps);
let expected = "╳○π✓";
assert_eq!(s, expected);
#[cfg(feature = "external-json")]
{
let config_fn = String::from("data/config.json");
let config =
PetsciiConfig::load_from_file(&config_fn).expect("Error loading config file");
let ps = PetsciiString::new_with_config(6, data, &config.petscii);
let s: String = String::from(ps);
assert_eq!(s, expected);
}
}
#[test]
fn into_iter_works() {
#[cfg(not(feature = "external-json"))]
let config = PetsciiConfig::load().expect("Error loading config");
#[cfg(feature = "external-json")]
let config = {
let config_fn = String::from("data/config.json");
PetsciiConfig::load_from_file(&config_fn).expect("Error loading config file")
};
let ps = PetsciiString::new_with_config(3, [0x41, 0x42, 0x43], &config.petscii);
let mut iter = ps.into_iter();
assert_eq!(iter.next(), Some(0x41));
assert_eq!(iter.next(), Some(0x42));
assert_eq!(iter.next(), Some(0x43));
assert_eq!(iter.next(), None);
}
#[test]
fn petscii_test_from_unicode_uppercase_characters_works() {
let uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
let s: String = String::from(uppercase);
let expected: [u8; 26] = [
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e,
0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a,
];
let config = PetsciiConfig::load().expect("Error loading config");
let ps = PetsciiString::<26>::from_str_with_config(&s, &config.petscii);
assert_eq!(ps.len(), 26);
assert_eq!(ps.data, expected);
let s: String = String::from(ps);
assert_eq!(s, uppercase);
}
#[test]
fn petscii_test_from_unicode_lowercase_characters_works() {
let lowercase = "abcdefghijklmnopqrstuvwxyz";
let s: String = String::from(lowercase);
let expected: [u8; 28] = [
0x0e, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d,
0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x8e,
];
let config = PetsciiConfig::load().expect("Error loading config");
let ps = PetsciiString::<28>::from_str_with_config(&s, &config.petscii);
assert_eq!(ps.len(), 28);
assert_eq!(ps.data, expected);
let s: String = String::from(ps);
assert_eq!(s, lowercase);
}
#[test]
fn test_unicode_misc_graphics_to_petscii() {
let graphics_characters = "╳○π✓";
let s: String = String::from(graphics_characters);
let expected: [u8; 6] = [0x76, 0x77, 0x7e, 0x0e, 0xba, 0x8e];
let config = PetsciiConfig::load().expect("Error loading config");
let ps = PetsciiString::<6>::from_str_with_config(&s, &config.petscii);
assert_eq!(ps.len(), 6);
assert_eq!(ps.data, expected);
let s: String = String::from(ps);
assert_eq!(s, graphics_characters);
#[cfg(feature = "external-json")]
{
let config_fn = String::from("data/config.json");
let config =
PetsciiConfig::load_from_file(&config_fn).expect("Error loading config file");
let ps = PetsciiString::<6>::from_str_with_config(&s, &config.petscii);
assert_eq!(ps.len(), 6);
assert_eq!(ps.data, expected);
let s: String = String::from(ps);
assert_eq!(s, graphics_characters);
}
}
}