forbidden_bands/petscii.rs
1//!
2//! PETSCII string library
3//!
4//! PETSCII is a character set used in Commodore Business Machines'
5//! 8-bit computers. It's based on the 1963 version of ASCII, not the
6//! 1967 version. In addition, it has some custom block graphics
7//! characters, geometric shapes and playing card suits.
8//!
9//! There are actually two PETSCII character sets, an "unshifted" one
10//! and a "shifted" one. Confusingly, the unshifted one has uppercase
11//! characters and most of the graphics characters while the shifted
12//! one has lowercase characters and uppercase characters.
13//!
14//! In addition to the PETSCII character sets, the Commodore has an
15//! in-memory screen display code format that is different than their
16//! character values. There are also two sets of screen display
17//! codes, one containing mostly uppercase characters and graphics
18//! symbols (Set 1) and one containing lowercase characters and
19//! uppercase characters (Set 2).
20//!
21//! These tables are partially outlined in the Commodore 64
22//! Programmer's Reference Guide under Appendix B (Screen Display
23//! Codes) and Appendix C (ASCII and CHR$ Codes)
24//!
25//! Unicode mappings
26//!
27//! A couple standards provide mappings between Commodore graphics
28//! characters and Unicode:
29//!
30//! Symbols for Legacy Computing: Unicode Standard 16.0 Section 22.7.4
31//!
32//! Legacy Computing Sources: 18235-aux-LegacyComputingSources.pdf
33//! Provides actual code maps for Unicode symbols to Commodore and
34//! other legacy computers graphic characters.
35//!
36//! The Legacy Computing Standards auxillary supplement to Unicode
37//! uses Commodore screen codes to specify the PET/VIC20 and C64/C128
38//! characters. Set 1 is specified with G0 in parentheses after the
39//! screen code and Set 2 is specified with G1 in parentheses.
40//!
41//! Because there are two sets of screen codes and two sets of PETSCII
42//! codes, converting between PETSCII characters and Unicode
43//! characters isn't a simple single table lookup.
44#![warn(missing_docs)]
45#![warn(unsafe_code)]
46
47use enumset::{EnumSet, EnumSetType};
48use std::{
49 fmt::{Debug, Display, Formatter, Result},
50 sync::RwLock,
51};
52
53// See the notes about optional JSON support in the Cargo.toml file
54// #[cfg(feature = "json")]
55use serde::{Deserialize, Serialize};
56// #[cfg(feature = "json")]
57use serde_json::{Map, Value};
58
59use crate::{config_data, Configuration, SystemConfig};
60
61/// A Commodore screen code value and the screen set it is in
62///
63/// The configuration file uses a two-element tuple or list to store
64/// the set and value fields. The Serde and Serde JSON serializer
65/// automatically support deserializing from a tuple into a struct.
66/// This may be confusing so this note is here to let people know.
67#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
68pub struct ScreenCodeValue {
69 /// The screen set this code is in
70 pub set: u8,
71 /// The screen code value
72 pub value: u8,
73}
74
75/// Commodore 64 character attributes
76/// A character with no attributes is just a normal unshifted
77/// character
78#[derive(Debug, EnumSetType, Serialize, Deserialize)]
79#[enumset(serialize_repr = "u8", repr = "u8")]
80pub enum PetsciiCharacterAttributes {
81 /// A shifted character
82 Shifted,
83}
84
85/// The Petscii Code along with whether it's the "shifted" table
86/// The unshifted table contains uppercase and graphics characters
87/// The shifted table contains lowercase and uppercase characters.
88#[derive(Clone, Debug, Serialize, Deserialize)]
89pub struct PetsciiCodeValue {
90 /// Whether the value is shifted and other attributes
91 pub attributes: u8,
92 /// The PETSCII code value
93 pub value: u8,
94}
95
96/// Configuration data including character maps for the PETSCII crate
97// #[cfg(feature = "json")]
98#[derive(Clone, Serialize, Deserialize)]
99pub struct PetsciiConfig {
100 /// Version of the PETSCII config
101 pub version: String,
102
103 /// shifted PETSCII codes to screen codes
104 pub c64_petscii_shifted_codes_to_screen_codes: Map<String, Value>,
105
106 /// unshifted PETSCII codes to screen codes
107 pub c64_petscii_unshifted_codes_to_screen_codes: Map<String, Value>,
108
109 /// C64 screen codes set 1 to Unicode codes
110 pub c64_screen_codes_set_1_to_unicode_codes: Map<String, Value>,
111 /// C64 screen codes set 2 to Unicode codes
112 pub c64_screen_codes_set_2_to_unicode_codes: Map<String, Value>,
113
114 /// C64 screen codes set 3 (virtual table) to Unicode codes
115 pub c64_screen_codes_set_3_to_unicode_codes: Map<String, Value>,
116
117 // Maps from Unicode to PETSCII
118 /// Map from Unicode codes to C64 screen codes
119 pub unicode_codes_to_c64_screen_codes: Map<String, Value>,
120
121 /// Maps from C64 screen codes set 1 to to PETSCII codes
122 pub c64_screen_codes_set_1_to_petscii_codes: Map<String, Value>,
123 /// Maps from C64 screen codes set 2 to to PETSCII codes
124 pub c64_screen_codes_set_2_to_petscii_codes: Map<String, Value>,
125
126 /// Maps from C64 screen codes set 3 to to PETSCII codes Screen
127 /// Code Set 3 is a "virtual" screen code set that doesn't exist
128 /// on the actual C64. It exists here to represent intermediate
129 /// control values line line feed and carriage return.
130 ///
131 /// Trains are hats
132 pub c64_screen_codes_set_3_to_petscii_codes: Map<String, Value>,
133}
134
135/// Configuration data for the PETSCII crate
136///
137/// We try to load this once on first use and then only read from it
138/// There is an overhead creating each PetsciiString getting a read
139/// lock on the config variable.
140pub static CONFIG: RwLock<Option<PetsciiConfig>> = RwLock::new(None);
141
142/// Load the configuration data from the PETSCII configuration string
143impl Configuration for PetsciiConfig {
144 fn load() -> std::result::Result<crate::Config, crate::error::Error> {
145 let crate_config = crate::Config::load()?;
146
147 // First see if the configuration is already loaded
148 {
149 let binding = CONFIG.read().expect("Should be able to get reader lock");
150
151 let test = binding.as_ref();
152
153 if let Some(petscii_config) = test {
154 return Ok(crate::Config {
155 version: crate_config.version,
156 petscii: crate::SystemConfig {
157 version: crate_config.petscii.version,
158 character_set_map: petscii_config.clone(),
159 },
160 });
161 }
162 }
163
164 // If the configuration is not loaded, load it and save it
165 let json_str = config_data::C64_PETSCII_MAP;
166 let petscii_config: PetsciiConfig =
167 serde_json::from_str(json_str).expect("Couldn't load embedded config");
168
169 {
170 let mut lock_res = CONFIG
171 .write()
172 .expect("Should be able to acquire config lock");
173 *lock_res = Some(petscii_config.clone());
174 }
175
176 Ok(crate::Config {
177 version: crate_config.version,
178 petscii: crate::SystemConfig {
179 version: crate_config.petscii.version,
180 character_set_map: petscii_config.clone(),
181 },
182 })
183 }
184
185 fn load_from_file(filename: &str) -> std::result::Result<crate::Config, crate::error::Error> {
186 // let path = Path::new(filename);
187 // let file = File::open(path)?;
188 // let reader = BufReader::new(file);
189
190 // This assumes the root crate knows about this crates config
191 // This is a bad design, and should be fixed in future versions
192 let crate_config = crate::Config::load_from_file(filename)?;
193
194 // let json: Config = serde_json::from_reader(reader)?;
195
196 Ok(crate_config)
197 }
198}
199
200/// Commodore 64 character attributes
201#[derive(Debug, EnumSetType)]
202pub enum CharacterAttributes {
203 /// A normal character
204 Normal = 0,
205 /// A reversed-video character
206 Reversed = 1,
207}
208
209/// A PETSCII character has a set of associated attributes (normal, reversed, etc.)
210/// and PETSCII code
211pub struct PetsciiCharacter {
212 /// The attributes of this character
213 pub attributes: CharacterAttributes,
214 /// The character PETSCII code
215 pub character: u8,
216}
217
218/// A PETSCII string
219///
220/// A fixed-length PETSCII string
221///
222/// Later versions may support variable-length strings. This library
223/// was created to help debug C64 file systems, which use fixed-length
224/// strings for some of the data structures.
225#[derive(Clone, Copy)]
226pub struct PetsciiString<'a, const L: usize> {
227 /// The length of the string
228 pub len: u32,
229 /// The string data
230 pub data: [u8; L],
231
232 /// The character map for this string
233 pub character_map: Option<&'a SystemConfig>,
234
235 /// strip "shifted space" (0xA0) characters in the display of this
236 /// PetsciiString.
237 /// CBM DOS uses shifted space characters to pad file names and
238 /// disk names.
239 pub strip_shifted_space: bool,
240}
241
242impl<const L: usize> Debug for PetsciiString<'_, L> {
243 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
244 write!(f, "length: {:?}, ", self.len)?;
245 write!(f, "data: {:?}, ", self.data)?;
246 write!(f, "display: {}", self)
247 }
248}
249
250impl<const L: usize> Display for PetsciiString<'_, L> {
251 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
252 write!(f, "{}", String::from(self))
253 }
254}
255
256/// An IntoIter structure for PetsciiStrings
257/// We need to keep track of the index of the current element, along
258/// with the data.
259pub struct IntoIter<'a, const L: usize> {
260 index: usize,
261 data: PetsciiString<'a, L>,
262}
263
264impl<'a, const L: usize> IntoIterator for PetsciiString<'a, L> {
265 type Item = u8;
266 type IntoIter = IntoIter<'a, L>;
267 fn into_iter(self) -> IntoIter<'a, L> {
268 IntoIter {
269 index: 0,
270 data: self,
271 }
272 }
273}
274
275impl<const L: usize> Iterator for IntoIter<'_, L> {
276 type Item = u8;
277 fn next(&mut self) -> Option<Self::Item> {
278 if self.index < self.data.len.try_into().unwrap() {
279 self.index += 1;
280 Some(self.data.data[self.index - 1])
281 } else {
282 None
283 }
284 }
285}
286
287impl<'a, const L: usize> From<&'a [u8]> for PetsciiString<'a, L> {
288 fn from(s: &'a [u8]) -> PetsciiString<'a, L> {
289 let mut bytes: [u8; L] = [0; L];
290 if s.len() > L {
291 panic!("u8 slice is too large");
292 }
293
294 // Replacing the below manual copy loop between slices with
295 // the following recomendation from clippy
296 // for i in 0..L {
297 // bytes[i] = s[i];
298 // }
299 bytes[..L].copy_from_slice(&s[..L]);
300
301 PetsciiString {
302 len: L as u32,
303 data: bytes,
304 character_map: None,
305 strip_shifted_space: false,
306 }
307 }
308}
309
310/// Convert a Unicode string slice to a vector of PETSCII bytes
311///
312/// This current code handles shifted and unshifted PETSCII characters.
313/// It assumes the default character set is unshifted and will return
314/// to that state at the end of every string.
315///
316/// So for example, if a string consists of uppercase characters followed
317/// by lowercase: ABCabc, it will output:
318/// 0x41, 0x42, 0x43, 0x0e, 0x41, 0x42, 0x43, 0x8e
319///
320/// NOT the following leaving the next possible concatenated string in
321/// a shifted state
322///
323/// 0x41, 0x42, 0x43, 0x0e, 0x41, 0x42, 0x43
324///
325/// If there are other common uses cases, this could be made a
326/// parameter or the default changed.
327fn unicode_to_petscii_bytes(s: &str) -> Vec<u8> {
328 let mut attributes = EnumSet::new();
329 let mut shifted = false;
330
331 let config = PetsciiConfig::load().expect("Error loading config");
332
333 let uc_map = config
334 .petscii
335 .character_set_map
336 .unicode_codes_to_c64_screen_codes;
337 let sc1_map = config
338 .petscii
339 .character_set_map
340 .c64_screen_codes_set_1_to_petscii_codes;
341 let sc2_map = config
342 .petscii
343 .character_set_map
344 .c64_screen_codes_set_2_to_petscii_codes;
345 let sc3_map = config
346 .petscii
347 .character_set_map
348 .c64_screen_codes_set_3_to_petscii_codes;
349
350 attributes.insert(CharacterAttributes::Normal);
351
352 let mut bytes: Vec<u8> = s
353 .chars()
354 .filter_map(|c| {
355 let key = u32::from(c).to_string();
356
357 let screen_code_opt = uc_map.get(&key);
358
359 let screen_code_value = match screen_code_opt {
360 Some(s) => s,
361 None => {
362 return None;
363 }
364 };
365
366 let screen_code_res = ScreenCodeValue::deserialize(screen_code_value);
367 let screen_code = match screen_code_res {
368 Ok(s) => s,
369 Err(_) => {
370 return None;
371 }
372 };
373
374 let key = screen_code.value.to_string();
375 let petscii_code_opt = if screen_code.set == 1 {
376 sc1_map.get(&key)
377 } else if screen_code.set == 2 {
378 sc2_map.get(&key)
379 } else if screen_code.set == 3 {
380 // Screen code set 3 is a "virtual" screen code set
381 // It's used to transform control characters like line feed
382 // and carriage return
383 sc3_map.get(&key)
384 } else {
385 return None;
386 };
387 let petscii_code_value = match petscii_code_opt {
388 Some(s) => s,
389 None => {
390 return None;
391 }
392 };
393
394 let petscii_code_res = PetsciiCodeValue::deserialize(petscii_code_value);
395 let petscii_code = match petscii_code_res {
396 Ok(s) => s,
397 Err(_) => {
398 return None;
399 }
400 };
401
402 Some(petscii_code)
403 })
404 .flat_map(|petscii_code| {
405 let mut codes: Vec<u8> = Vec::new();
406 let eset: EnumSet<PetsciiCharacterAttributes> =
407 EnumSet::from_repr(petscii_code.attributes);
408
409 if eset.contains(PetsciiCharacterAttributes::Shifted) {
410 if !shifted {
411 // Output a new shift in character
412 codes.push(0x0E);
413 shifted = true;
414 }
415 } else if shifted {
416 // Output a new shift out character
417 codes.push(0x8E);
418 shifted = false;
419 }
420 codes.push(petscii_code.value);
421 codes
422 })
423 .collect();
424
425 // Shift out if we're still shifted at the end of a string
426 if shifted {
427 bytes.push(0x8E);
428 }
429
430 bytes
431}
432
433impl<'a, const L: usize> From<&str> for PetsciiString<'a, L> {
434 fn from(s: &str) -> PetsciiString<'a, L> {
435 let mut final_bytes: [u8; L] = [0; L];
436
437 let bytes = unicode_to_petscii_bytes(s);
438
439 if bytes.len() > L {
440 panic!("u8 slice is too large");
441 }
442 let b = bytes.as_slice();
443
444 final_bytes[..b.len()].copy_from_slice(&b[..b.len()]);
445
446 PetsciiString {
447 len: b.len() as u32,
448 data: final_bytes,
449 character_map: None,
450 strip_shifted_space: false,
451 }
452 }
453}
454
455impl<const L: usize> From<PetsciiString<'_, L>> for String {
456 /// Create a String from a PetsciiString
457 ///
458 /// # Examples
459 ///
460 /// ```
461 /// use forbidden_bands::{
462 /// petscii::{PetsciiConfig, PetsciiString},
463 /// Config,
464 /// Configuration,
465 /// };
466 ///
467 /// let config = PetsciiConfig::load().expect("Error loading config file");
468 ///
469 /// let ps = PetsciiString::new_with_config(6, [0x41, 0x42, 0x43, 0x5c, 0x5e, 0x5f], &config.petscii);
470 /// let mut s: String = String::from(ps);
471 ///
472 /// assert_eq!(s.pop().unwrap(), '←');
473 /// assert_eq!(s.pop().unwrap(), '↑');
474 /// assert_eq!(s.pop().unwrap(), '£');
475 /// assert_eq!(s.pop().unwrap(), 'C');
476 /// assert_eq!(s.pop().unwrap(), 'B');
477 /// assert_eq!(s.pop().unwrap(), 'A');
478 /// ```
479 fn from(s: PetsciiString<L>) -> String {
480 String::from(&s)
481 }
482}
483
484impl<const L: usize> From<&PetsciiString<'_, L>> for String {
485 /// Create a String from a reference to a PetsciiString
486 ///
487 /// # Examples
488 ///
489 /// ```
490 /// use forbidden_bands::{
491 /// petscii::{PetsciiConfig, PetsciiString},
492 /// Config,
493 /// Configuration,
494 /// };
495 ///
496 /// let config = PetsciiConfig::load().expect("Error loading config file");
497 ///
498 /// let ps = PetsciiString::new_with_config(6, [0x41, 0x42, 0x43, 0x5c, 0x5e, 0x5f], &config.petscii);
499 /// let mut s: String = String::from(&ps);
500 ///
501 /// assert_eq!(s.pop().unwrap(), '←');
502 /// assert_eq!(s.pop().unwrap(), '↑');
503 /// assert_eq!(s.pop().unwrap(), '£');
504 /// assert_eq!(s.pop().unwrap(), 'C');
505 /// assert_eq!(s.pop().unwrap(), 'B');
506 /// assert_eq!(s.pop().unwrap(), 'A');
507 /// ```
508 // TODO: Unicode 13 now has "Legacy Computing Sources"
509 // (Unicode 13 was released around March 10, 2020).
510 fn from(s: &PetsciiString<L>) -> String {
511 let mut attributes = EnumSet::new();
512 let mut shifted = false;
513
514 attributes.insert(CharacterAttributes::Normal);
515 s.into_iter()
516 .filter(|c| !s.strip_shifted_space || (*c != 0xA0))
517 .filter_map(|c| {
518 // TODO: refactor this into another function.
519 //
520 // It's a good opportunity to learn State patterns and
521 // integrate that into this code.
522 match c {
523 0x0E => {
524 // Switch to lowercase / shifted
525 // This is the "shifted" state on the C64
526 // Unshifted is an uppercase and graphic
527 // character set
528 shifted = true;
529 return None;
530 },
531 0x12 => {
532 attributes.remove(CharacterAttributes::Normal);
533 attributes.insert(CharacterAttributes::Reversed);
534 return None;
535 },
536 0x8E => {
537 // Switch to uppercase / unshifted
538 // This is the "unshifted" state on the C64
539 // shifted is a lowercase and uppercase
540 // character set (business mode)
541 shifted = false;
542 return None;
543 },
544 0x92 => {
545 attributes.remove(CharacterAttributes::Reversed);
546 attributes.insert(CharacterAttributes::Normal);
547 return None;
548 },
549 _ => {}
550 }
551
552 let cm = match &s.character_map {
553 Some(s) => s,
554 None => { return Some(char::from_u32(c as u32).unwrap()); },
555 };
556
557 // There are three sets of code that are duplicated in
558 // PETSCII
559 // They're duplicated in both the PETSCII unshifted
560 // and shifted character sets.
561 //
562 // 192-223 are duplicates of 96-127
563 // 224-254 are duplicates of 160-190
564 // 255 is a duplicate of 126
565 //
566 // These should probably be explicity added to the
567 // configuration data instead of transformed here.
568 let c = match c {
569 0..=191 => c,
570 192..=223 => c - 96,
571 224..=254 => c - 64,
572 255 => 126,
573 };
574
575 // Map from PETSCII to screen codes
576 let petscii_to_screen_codes = if !shifted {
577 &cm.character_set_map.c64_petscii_unshifted_codes_to_screen_codes
578 } else {
579 &cm.character_set_map.c64_petscii_shifted_codes_to_screen_codes
580 };
581 let key = c.to_string();
582
583 let screen_code_opt: Option<ScreenCodeValue> =
584 petscii_to_screen_codes
585 .get(&key)
586 .and_then(|screen_code_value| {
587 ScreenCodeValue::deserialize(screen_code_value).ok()
588 });
589
590 // This chaining of None options is tricky. return
591 // None doesn't always return to the filter_map
592 // context in an closure context, but it does in a
593 // match context
594 let screen_code = match screen_code_opt {
595 Some(s) => s,
596 None => return None,
597 };
598
599 // TODO This test may be removed as we implement the full
600 // block character graphics set
601 if screen_code.value > 127 {
602 panic!("Should not have a screen code greater than 127 before applying reverse video transform");
603 }
604
605 let screen_code_value: u32 =
606 if attributes.contains(CharacterAttributes::Reversed) {
607 (screen_code.value as u32) + 128
608 } else {
609 screen_code.value.into()
610 };
611
612 // Now map from screen codes to Unicode
613 let screen_codes_to_unicode = match screen_code.set {
614 1 =>
615 &cm.character_set_map.c64_screen_codes_set_1_to_unicode_codes,
616 2 =>
617 &cm.character_set_map.c64_screen_codes_set_2_to_unicode_codes,
618 3 =>
619 &cm.character_set_map.c64_screen_codes_set_3_to_unicode_codes,
620 _ => {
621 panic!("Invalid screen code set");
622 }
623 };
624
625 let key = screen_code_value.to_string();
626 let d = if screen_codes_to_unicode.contains_key(&key) {
627 match screen_codes_to_unicode.get(&key).unwrap() {
628 serde_json::Value::Number(v) => v.as_u64().unwrap() as u32,
629 _ => 0,
630 }
631 } else {
632 c as u32
633 };
634
635 Some(char::from_u32(d).unwrap())
636 })
637 .collect()
638 }
639}
640
641impl<'a, const L: usize> PetsciiString<'a, L> {
642 /// Create a new Petscii string
643 ///
644 /// # Examples
645 ///
646 /// ```
647 /// use forbidden_bands::petscii::PetsciiString;
648 ///
649 /// let ps = PetsciiString::new(6, [0x41, 0x42, 0x43]);
650 ///
651 /// assert_eq!(ps.data[0], 0x41);
652 /// assert_eq!(ps.data[1], 0x42);
653 /// assert_eq!(ps.data[2], 0x43);
654 /// ```
655 pub fn new(len: u32, data: [u8; L]) -> Self {
656 PetsciiString {
657 len,
658 data,
659 character_map: None,
660 strip_shifted_space: false,
661 }
662 }
663
664 /// Create a new PETSCII string with a given character map
665 ///
666 /// # Examples
667 ///
668 /// ```
669 /// use forbidden_bands::{
670 /// petscii::{PetsciiConfig, PetsciiString},
671 /// Config,
672 /// Configuration,
673 /// };
674 ///
675 /// let config = PetsciiConfig::load().expect("Error loading config");
676 /// let ps = PetsciiString::new(6, [0x41, 0x42, 0x43]);
677 ///
678 /// assert_eq!(ps.data[0], 0x41);
679 /// assert_eq!(ps.data[1], 0x42);
680 /// assert_eq!(ps.data[2], 0x43);
681 /// ```
682 pub fn new_with_config(len: u32, data: [u8; L], character_map: &'a SystemConfig) -> Self {
683 PetsciiString {
684 len,
685 data,
686 character_map: Some(character_map),
687 strip_shifted_space: false,
688 }
689 }
690
691 /// Get the length of the Petscii string
692 ///
693 /// TODO: More details on what length means
694 /// TODO: Add example about getting number of characters
695 ///
696 /// # Examples
697 ///
698 /// ```
699 /// use forbidden_bands::petscii::PetsciiString;
700 ///
701 /// let ps = PetsciiString::new(3, [0x41, 0x42, 0x43]);
702 ///
703 /// assert_eq!(ps.len(), 3);
704 /// ```
705 pub fn len(&self) -> usize {
706 self.len as usize
707 }
708
709 /// Return true if the string is empty
710 /// # Examples
711 ///
712 /// ```
713 /// use forbidden_bands::petscii::PetsciiString;
714 ///
715 /// let ps = PetsciiString::new(0, []);
716 ///
717 /// assert!(ps.is_empty());
718 /// ```
719 pub fn is_empty(&self) -> bool {
720 self.len == 0
721 }
722
723 /// This function is the same as the From implementation for byte
724 /// slices but it strips any shifted spaces (0xA0) from the end.
725 ///
726 /// Shifted spaces are used to pad out filenames and disk namss in
727 /// CBM DOS
728 pub fn from_byte_slice_strip_shifted_space(s: &'a [u8]) -> PetsciiString<'a, L> {
729 let mut bytes: [u8; L] = [0; L];
730 if s.len() > L {
731 panic!("u8 slice is too large");
732 }
733
734 // Replacing the below manual copy loop between slices with
735 // the following recomendation from clippy
736 // for i in 0..s.len() {
737 // bytes[i] = s[i];
738 // }
739 bytes[..s.len()].copy_from_slice(s);
740
741 PetsciiString {
742 len: L as u32,
743 data: bytes,
744 character_map: None,
745 strip_shifted_space: true,
746 }
747 }
748
749 /// Create a PetsciiString from a string slice
750 ///
751 /// I think I'm going to have to decide on what to do about
752 /// configs.. boxes or arcs or passing around the RwLock or
753 /// whatever
754 ///
755 /// TODO: Figure this out and remove this function and the
756 /// with_config functions
757 pub fn from_str_with_config(s: &str, character_map: &'a SystemConfig) -> PetsciiString<'a, L> {
758 let mut final_bytes: [u8; L] = [0; L];
759
760 let bytes = unicode_to_petscii_bytes(s);
761
762 if bytes.len() > L {
763 panic!("u8 vector is too large");
764 }
765 let b = bytes.as_slice();
766
767 final_bytes[..b.len()].copy_from_slice(&b[..b.len()]);
768
769 PetsciiString {
770 len: b.len() as u32,
771 data: final_bytes,
772 character_map: Some(character_map),
773 strip_shifted_space: false,
774 }
775 }
776
777 /// Create a PetsciiString from a byte slice
778 /// strip shifted spaces
779 /// with a config
780 pub fn from_byte_slice_strip_shifted_space_with_config(
781 s: &'a [u8],
782 character_map: &'a SystemConfig,
783 ) -> PetsciiString<'a, L> {
784 let mut bytes: [u8; L] = [0; L];
785 if s.len() > L {
786 panic!("u8 slice is too large");
787 }
788
789 // Replacing the below manual copy loop between slices with
790 // the following recomendation from clippy
791 // for i in 0..s.len() {
792 // bytes[i] = s[i];
793 // }
794 bytes[..s.len()].copy_from_slice(s);
795
796 PetsciiString {
797 len: L as u32,
798 data: bytes,
799 character_map: Some(character_map),
800 strip_shifted_space: true,
801 }
802 }
803}
804
805#[cfg(test)]
806mod tests {
807 use std::fmt::Write;
808
809 use crate::{
810 petscii::{PetsciiConfig, PetsciiString, CONFIG},
811 Config, Configuration,
812 };
813
814 // #[cfg(feature = "external-json")]
815 // use crate::load_config_from_file;
816
817 /// Test loading the configuration works
818 ///
819 /// There are two tricky problems with testing the config and
820 /// static RWLock. First, tests may be run out-of-order.
821 /// Normally they are run alphabetically, but I don't want to play
822 /// Yellow Pages games with test names. I'm sure there are test
823 /// crates out there that augment the test framework with
824 /// deterministic ordering, but it should be in the core Rust
825 /// library.
826 ///
827 /// Clearing the config in this test and resetting it works
828 /// around that issue.
829 ///
830 /// The other issue is that tests may be run concurrently in
831 /// future versions of Rust or custom configurations.
832 ///
833 /// In this case, there is a small window where a newer config may
834 /// be replaced by an older config.
835 ///
836 /// We have version strings in configurations. We could add a new
837 /// runtime version counter and only accept configurations that
838 /// match the expected version or a greater version.
839 ///
840 /// But that's not implemented yet. If you are hit by a
841 /// concurrency bug in tests in the future, I'm sorry.
842 #[test]
843 fn petscii_load_config_works() {
844 // This test creates a race condition, where another test
845 // may try loading config while this test is running.
846 //
847 // Since tests can be run out-of-order we can't assume that
848 // the configuration is uninitialized.
849 //
850 // This test function acquires a write-lock for the duration of the test
851 // Then it saves the old config, replacing it with None.
852 // It tests this default value
853 // Then it calls load_config normally, tests that it was
854 // successful, and then swaps in the original value.
855 let mut saved_config: Option<PetsciiConfig> = None;
856
857 // Just read from saved_config so we don't get unused
858 // assignment warnings
859 // I had an ignore(unused_assignments) which will be a hard
860 // error soon! For now just test it to prevent an unused
861 // assignments clippy warning.
862 assert!(saved_config.is_none());
863
864 {
865 let mut lock_res = CONFIG
866 .write()
867 .expect("Should be able to acquire config lock");
868 // *lock_res = Some(config);
869 saved_config = lock_res.take();
870 }
871
872 {
873 // Now test that a "first" read of the config fails.
874 let binding = CONFIG.read().expect("Should be able to get reader lock");
875 // Reading an unloaded config should fail
876 assert!(binding.as_ref().is_none());
877 }
878
879 // Now call load_config and test for a good result
880 let config_result = PetsciiConfig::load();
881 assert!(config_result.is_ok());
882
883 // Now we should have a Some value in the Option
884 {
885 let binding = CONFIG.read().expect("Should be able to get reader lock");
886 // Reading an loaded config should work
887 assert!(binding.as_ref().is_some());
888 }
889
890 // Now swap back in the original value
891 let mut lock_res = CONFIG
892 .write()
893 .expect("Should be able to acquire config lock");
894 *lock_res = saved_config.take();
895 }
896
897 #[test]
898 fn petscii_struct_works() {
899 let ps = PetsciiString::new(3, [0x41, 0x42, 0x43]);
900 assert_eq!(ps.len, 3);
901 assert_eq!(ps.data, [0x41, 0x42, 0x43]);
902 }
903
904 #[test]
905 fn petscii_with_config_works() {
906 let config = PetsciiConfig::load().expect("Error loading config file");
907 let ps = PetsciiString::new_with_config(
908 6,
909 [0x41, 0x42, 0x43, 0x5c, 0x5e, 0x5f],
910 &config.petscii,
911 );
912 let mut s: String = String::from(ps);
913 assert_eq!(s.pop().unwrap(), '←');
914 assert_eq!(s.pop().unwrap(), '↑');
915 assert_eq!(s.pop().unwrap(), '£');
916 assert_eq!(s.pop().unwrap(), 'C');
917 assert_eq!(s.pop().unwrap(), 'B');
918 assert_eq!(s.pop().unwrap(), 'A');
919 }
920
921 #[test]
922 fn petscii_with_config_unmapped_character_works() {
923 let config_result = PetsciiConfig::load();
924 let config: Config = match config_result {
925 Ok(c) => c,
926 Err(e) => {
927 panic!("Error loading config file: {e}");
928 }
929 };
930 let ps = PetsciiString::new_with_config(2, [0x41, 0xb2], &config.petscii);
931 let mut s: String = String::from(ps);
932 assert_eq!(s.pop().unwrap(), '┬');
933 assert_eq!(s.pop().unwrap(), 'A');
934 }
935
936 #[test]
937 fn petscii_without_config_works() {
938 let ps = PetsciiString::new(6, [0x41, 0x42, 0x43, 0x5c, 0x5e, 0x5f]);
939 let mut s: String = String::from(ps);
940 assert_eq!(s.pop().unwrap(), '_');
941 assert_eq!(s.pop().unwrap(), '^');
942 assert_eq!(s.pop().unwrap(), '\\');
943 assert_eq!(s.pop().unwrap(), 'C');
944 assert_eq!(s.pop().unwrap(), 'B');
945 assert_eq!(s.pop().unwrap(), 'A');
946 }
947
948 #[test]
949 fn petscii_len_unfilled_works() {
950 let ps = PetsciiString::new(6, [0x41, 0x42, 0x43]);
951
952 assert_eq!(ps.len(), 6);
953 }
954
955 #[test]
956 fn petscii_len_7bit_characters_works() {
957 let ps = PetsciiString::new(6, [0x41, 0x42, 0x43, 0x41, 0x42, 0x43]);
958
959 assert_eq!(ps.len(), 6);
960 }
961
962 #[test]
963 fn petscii_len_8bit_characters_works() {
964 let ps = PetsciiString::new(7, [0xa5, 0x74, 0x67, 0x7d, 0x68, 0x79, 0xa7]);
965
966 assert_eq!(ps.len(), 7);
967 }
968
969 #[test]
970 fn petscii_len_from_8bit_character_slice_works() {
971 let ps = PetsciiString::new(7, [0xa5, 0x74, 0x67, 0x7d, 0x68, 0x79, 0xa7]);
972 let s: String = String::from(ps);
973
974 assert_eq!(s.len(), 9);
975 assert_eq!(s.chars().count(), 7);
976 }
977
978 #[test]
979 fn petscii_len_from_8bit_character_slice_with_config_works() {
980 let config = {
981 let config_fn = String::from("data/config.json");
982 PetsciiConfig::load_from_file(&config_fn).expect("Error loading config file")
983 };
984
985 // This should be called at a higher level than when creating
986 // strings usually. Possibly only once at library
987 // initialization.
988 {
989 let mut lock_res = crate::CONFIG
990 .write()
991 .expect("Should be able to acquire config lock");
992 *lock_res = Some(config);
993 }
994
995 let binding = crate::CONFIG
996 .read()
997 .expect("Should be able to get reader lock");
998 let config = binding.as_ref().unwrap();
999
1000 let ps = PetsciiString::new_with_config(
1001 6,
1002 [0x74, 0x67, 0x62, 0x7d, 0x68, 0x79],
1003 &config.petscii,
1004 );
1005 let s: String = String::from(ps);
1006
1007 // All six charactes are mapped to 32-bit unicode characters
1008 assert_eq!(s.len(), 24);
1009 assert_eq!(s.chars().count(), 6);
1010 }
1011
1012 /// Test that the Display trait implementation works for
1013 /// PetsciiString
1014 ///
1015 /// This also tests other stuff like the virtual screen code map
1016 /// and PETSCII to Unicode conversion.
1017 #[test]
1018 fn petscii_display_works() {
1019 let config_fn = String::from("data/config.json");
1020 let config = Config::load_from_file(&config_fn).expect("Error loading config file");
1021
1022 let hello_world_data: [u8; 61] = [
1023 0x0d, 0x0a, 0xb0, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
1024 0x60, 0x60, 0x60, 0x60, 0xae, 0x0d, 0x0a, 0x7d, 0x20, 0x48, 0x0e, 0x45, 0x4c, 0x4c,
1025 0x4f, 0x2c, 0x20, 0x57, 0x4f, 0x52, 0x4c, 0x44, 0x21, 0x20, 0x8e, 0x7d, 0x0d, 0x0a,
1026 0xad, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
1027 0x60, 0x60, 0xbd, 0x0d, 0x0a,
1028 ];
1029
1030 let expected_unicode: [u32; 59] = [
1031 13, 10, 9484, 129913, 129913, 129913, 129913, 129913, 129913, 129913, 129913, 129913,
1032 129913, 129913, 129913, 129913, 129913, 129913, 9488, 13, 10, 129907, 32, 72, 101, 108,
1033 108, 111, 44, 32, 119, 111, 114, 108, 100, 33, 32, 129907, 13, 10, 9492, 129913,
1034 129913, 129913, 129913, 129913, 129913, 129913, 129913, 129913, 129913, 129913, 129913,
1035 129913, 129913, 129913, 9496, 13, 10,
1036 ];
1037
1038 let ps = PetsciiString::new_with_config(61, hello_world_data, &config.petscii);
1039
1040 let mut string_buf = String::new();
1041
1042 write!(string_buf, "{}", ps).unwrap();
1043
1044 let bytes: Vec<u32> = string_buf.chars().map(|c| u32::from(c)).collect();
1045
1046 assert_eq!(Vec::from(expected_unicode), bytes);
1047 }
1048
1049 /// Test "shifted" PETSCII lowercase characters
1050 ///
1051 /// The default PETSCII character set has uppercase and graphics
1052 /// characters. When the character set is shifted, it has
1053 /// lowercase and uppercase characters.
1054 #[test]
1055 fn petscii_test_shifted_lowercase_characters_works() {
1056 // This data contains a "switch to lower case" PETSCII control
1057 // character, followed by lowercase characters a through z,
1058 // followed by a "switch to upper case" character.
1059 //
1060 // The behavior of 0x08 "disables shift C= / lock case" and
1061 // 0x09 "enables shift C= / unlock case" needs to be tested in
1062 // an emulator to see what needs to be implemented. It may
1063 // lead to "switch to lower case" and "switch to upper case"
1064 // being disabled in the PETSCII data stream.
1065 let data: [u8; 28] = [
1066 0x0e, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d,
1067 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x8e,
1068 ];
1069
1070 let config = {
1071 let config_fn = String::from("data/config.json");
1072 PetsciiConfig::load_from_file(&config_fn).expect("Error loading config file")
1073 };
1074
1075 let ps = PetsciiString::new_with_config(28, data, &config.petscii);
1076
1077 assert_eq!(ps.len(), 28);
1078
1079 let s: String = String::from(ps);
1080 let expected = "abcdefghijklmnopqrstuvwxyz";
1081
1082 assert_eq!(s, expected);
1083 }
1084
1085 /// Test converting a PETSCII string with a Block Elements Unicode
1086 /// character.
1087 ///
1088 /// PETSCII 0xB9 (decimal 185), is a lower three eighths block.
1089 /// This maps to screen code 0x69 (decimal 121) which is not in
1090 /// the Unicode Legacy Computing Sources table.
1091 ///
1092 /// TODO: Test all of the block element characters
1093 #[test]
1094 fn from_petscii_with_block_elements_graphic_character() {
1095 // This is a PETSCII sequence that contains:
1096 // lower 3/8 block
1097 //
1098 // PETSCII 0xB9 (decimal 185), is a lower three eighths block.
1099 // This maps to screen code 0x69 (decimal 121) which is not in
1100 // the Unicode Legacy Computing Sources table. But it is in the
1101 // normal Unicode Block Elements code tables as 0x2583
1102 let data: [u8; 0x01] = [0xB9];
1103
1104 let config = {
1105 let config_fn = String::from("data/config.json");
1106 PetsciiConfig::load_from_file(&config_fn).expect("Error loading config file")
1107 };
1108
1109 let ps = PetsciiString::new_with_config(1, data, &config.petscii);
1110
1111 let s: String = String::from(ps);
1112 let c = s.chars().nth(0).unwrap();
1113 let expected: char = char::from_u32(0x2583).unwrap();
1114
1115 assert_eq!(c, expected);
1116 }
1117
1118 // The following test is disabled for now
1119 //
1120 // There are quite a few block element characters and they're not
1121 // all in one place in the Unicode standard. Some are under Block
1122 // Elements, some are under Symbols for Legacy Computing or Legacy
1123 // Computing Sources.
1124 //
1125 // In addition, some we have to "infer" or "interpolate" ,
1126 // e.g. converting a three eighths top block element to five
1127 // eighths bottom by using reverse characters.
1128 //
1129 // Version 0.2.0 of this crate gets Unicode to PETSCII and PETSCII
1130 // to Unicode working with basic characters including lowercase.
1131 // Full block elements are a future version.
1132 //
1133 // /// Test converting a PETSCII string with reversed-video
1134 // /// characters
1135 // #[test]
1136 // fn from_petscii_with_reversed_works() {
1137 // // This is a PETSCII sequence that contains:
1138 // // REVERSE (RVS) ON, lower three eighths block, REVERSE (RVS) OFF
1139 // //
1140 // // So it should generate a string with an upper five eighths
1141 // // block (0x1FB83 in Symbols for Legacy Computing)
1142 // let data: [u8; 0x03] = [0x12, 0xB9, 0x92];
1143
1144 // let config = {
1145 // let config_fn = String::from("data/config.json");
1146 // PetsciiConfig::load_from_file(&config_fn)
1147 // .expect("Error loading config file")
1148 // };
1149
1150 // let ps = PetsciiString::new_with_config(3, data, &config.petscii);
1151
1152 // let s: String = String::from(ps);
1153 // let c = s.chars().nth(0).unwrap();
1154 // let expected: char = char::from_u32(0x1FB84).unwrap();
1155
1156 // assert_eq!(c, expected);
1157 // }
1158
1159 // // Test some non "Legacy Computing Sources" Unicode characters
1160 // // These tests also exercise the reverse video control
1161 // // characters and high-bit PETSCII maps
1162
1163 #[test]
1164 fn test_petscii_7bit_playing_cards_to_unicode() {
1165 // Test low-bits PETSCII playing card characters
1166 // 0x61 is a black spade
1167 // 0x73 is a black heart
1168 // 0x78 is a black club
1169 // 0x7a is a black diamond
1170 let data: [u8; 4] = [0x61, 0x73, 0x78, 0x7a];
1171
1172 let config = PetsciiConfig::load().expect("Error loading config file");
1173
1174 let ps = PetsciiString::new_with_config(4, data, &config.petscii);
1175 let s: String = String::from(ps);
1176 let expected = "♠♥♣♦";
1177
1178 assert_eq!(s, expected);
1179 }
1180
1181 #[test]
1182 fn test_petscii_8bit_playing_cards_to_unicode() {
1183 // Test high-bit PETSCII playing card characters
1184 // 0xc1 is a black spade
1185 // 0xd3 is a black heart
1186 // 0xd8 is a black club
1187 // 0xda is a black diamond
1188 let data: [u8; 4] = [0xc1, 0xd3, 0xd8, 0xda];
1189
1190 let config = PetsciiConfig::load().expect("Error loading config");
1191 let ps = PetsciiString::new_with_config(4, data, &config.petscii);
1192 let s: String = String::from(ps);
1193 let expected = "♠♥♣♦";
1194
1195 assert_eq!(s, expected);
1196 }
1197
1198 #[test]
1199 fn test_petscii_7bit_reversed_video_playing_cards_to_unicode() {
1200 // Test low-bits PETSCII reversed-video playing card characters
1201 // 0x61 in reversed video is a white spade
1202 // 0x73 in reversed video is a white heart
1203 // 0x78 in reversed video is a white club
1204 // 0x7a in reversed video is a white diamond
1205 let data: [u8; 6] = [0x12, 0x61, 0x73, 0x78, 0x7a, 0x92];
1206
1207 let config = PetsciiConfig::load().expect("Error loading config");
1208 let ps = PetsciiString::new_with_config(6, data, &config.petscii);
1209 let s: String = String::from(ps);
1210 let expected = "♤♡♧♢";
1211
1212 assert_eq!(s, expected);
1213 }
1214
1215 #[test]
1216 fn test_petscii_8bit_reversed_video_playing_cards_to_unicode() {
1217 // Test high-bit PETSCII reversed-video playing card characters
1218 // 0xc1 in reversed video is a white spade
1219 // 0xd3 in reversed video is a white heart
1220 // 0xd8 in reversed video is a white club
1221 // 0xda in reversed video is a white diamond
1222 let data: [u8; 6] = [0x12, 0xc1, 0xd3, 0xd8, 0xda, 0x92];
1223
1224 let config = PetsciiConfig::load().expect("Error loading config");
1225 let ps = PetsciiString::new_with_config(6, data, &config.petscii);
1226 let s: String = String::from(ps);
1227 let expected = "♤♡♧♢";
1228
1229 assert_eq!(s, expected);
1230 }
1231
1232 /// Test various miscellaneous graphics characters from PETSCII to
1233 /// Unicode
1234 #[test]
1235 fn test_petscii_misc_graphics_to_unicode() {
1236 // Test box drawings light diagonal cross
1237 // 0x76 PETSCII is 0x2573 UnicodeX
1238 // 0x77 PETSCII is 0x25cb Unicode white circle
1239 // 0x7e PETSCII is 0x03c0 Unicode Greek small letter pi
1240 // 0xba shifted PETSCII is 0x2713 Unicode checkmark
1241 let data: [u8; 6] = [0x76, 0x77, 0x7e, 0x0e, 0xba, 0x8e];
1242 let config = PetsciiConfig::load().expect("Error loading config");
1243 let ps = PetsciiString::new_with_config(6, data, &config.petscii);
1244 let s: String = String::from(ps);
1245 let expected = "╳○π✓";
1246
1247 assert_eq!(s, expected);
1248
1249 #[cfg(feature = "external-json")]
1250 {
1251 let config_fn = String::from("data/config.json");
1252 let config =
1253 PetsciiConfig::load_from_file(&config_fn).expect("Error loading config file");
1254
1255 let ps = PetsciiString::new_with_config(6, data, &config.petscii);
1256 let s: String = String::from(ps);
1257
1258 assert_eq!(s, expected);
1259 }
1260
1261 // Test the mapped characters.
1262 // These should be handled even if they aren't explicitly in
1263 // the configuration data.
1264 // 192-223 is the same as 96-127
1265 // 224-254 is the same as 160-190
1266 // 255 is the same as 126
1267 let data: [u8; 6] = [0x76, 0xd7, 0xde, 0x0e, 0xfa, 0x8e];
1268
1269 let ps = PetsciiString::new_with_config(6, data, &config.petscii);
1270 let s: String = String::from(ps);
1271 let expected = "╳○π✓";
1272
1273 assert_eq!(s, expected);
1274
1275 #[cfg(feature = "external-json")]
1276 {
1277 let config_fn = String::from("data/config.json");
1278 let config =
1279 PetsciiConfig::load_from_file(&config_fn).expect("Error loading config file");
1280
1281 let ps = PetsciiString::new_with_config(6, data, &config.petscii);
1282 let s: String = String::from(ps);
1283
1284 assert_eq!(s, expected);
1285 }
1286 }
1287
1288 #[test]
1289 fn into_iter_works() {
1290 #[cfg(not(feature = "external-json"))]
1291 let config = PetsciiConfig::load().expect("Error loading config");
1292 #[cfg(feature = "external-json")]
1293 let config = {
1294 let config_fn = String::from("data/config.json");
1295 PetsciiConfig::load_from_file(&config_fn).expect("Error loading config file")
1296 };
1297
1298 let ps = PetsciiString::new_with_config(3, [0x41, 0x42, 0x43], &config.petscii);
1299
1300 let mut iter = ps.into_iter();
1301
1302 assert_eq!(iter.next(), Some(0x41));
1303 assert_eq!(iter.next(), Some(0x42));
1304 assert_eq!(iter.next(), Some(0x43));
1305 assert_eq!(iter.next(), None);
1306 }
1307
1308 // Tests from Unicode to PETSCII
1309
1310 /// Test basic uppercase Unicode to PETSCII works
1311 #[test]
1312 fn petscii_test_from_unicode_uppercase_characters_works() {
1313 let uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1314 let s: String = String::from(uppercase);
1315
1316 let expected: [u8; 26] = [
1317 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e,
1318 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a,
1319 ];
1320
1321 let config = PetsciiConfig::load().expect("Error loading config");
1322
1323 let ps = PetsciiString::<26>::from_str_with_config(&s, &config.petscii);
1324
1325 assert_eq!(ps.len(), 26);
1326 assert_eq!(ps.data, expected);
1327
1328 let s: String = String::from(ps);
1329
1330 assert_eq!(s, uppercase);
1331 }
1332
1333 /// Test "shifted" PETSCII lowercase characters
1334 ///
1335 /// The default PETSCII character set has uppercase and graphics
1336 /// characters. When the character set is shifted, it has
1337 /// lowercase and uppercase characters.
1338 ///
1339 /// I love that this test found a possible bug where I wasn't
1340 /// shifting out when there were no uppercase characters at the
1341 /// end of a string. We'll assume the user wants the state of the
1342 /// character set to return to the default, but that should be
1343 /// specified.
1344 #[test]
1345 fn petscii_test_from_unicode_lowercase_characters_works() {
1346 let lowercase = "abcdefghijklmnopqrstuvwxyz";
1347 let s: String = String::from(lowercase);
1348
1349 // This data contains a "switch to lower case" PETSCII control
1350 // character, followed by lowercase characters a through z,
1351 // followed by a "switch to upper case" character.
1352 //
1353 // The behavior of 0x08 "disables shift C= / lock case" and
1354 // 0x09 "enables shift C= / unlock case" needs to be tested in
1355 // an emulator to see what needs to be implemented. It may
1356 // lead to "switch to lower case" and "switch to upper case"
1357 // being disabled in the PETSCII data stream.
1358 let expected: [u8; 28] = [
1359 0x0e, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d,
1360 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x8e,
1361 ];
1362
1363 let config = PetsciiConfig::load().expect("Error loading config");
1364
1365 let ps = PetsciiString::<28>::from_str_with_config(&s, &config.petscii);
1366
1367 assert_eq!(ps.len(), 28);
1368 assert_eq!(ps.data, expected);
1369
1370 let s: String = String::from(ps);
1371
1372 assert_eq!(s, lowercase);
1373 }
1374
1375 /// Test various miscellaneous graphics characters from Unicode to
1376 /// PETSCII
1377 #[test]
1378 fn test_unicode_misc_graphics_to_petscii() {
1379 // Test box drawings light diagonal cross
1380 // 0x2573 Unicode is 0x76 PETSCII
1381 // 0x25cb Unicode is white circle, 0x77 PETSCII
1382 // 0x03c0 Unicode is Greek small letter pi, 0x7e PETSCII
1383 //
1384 // The petscii.pdf document by Aivosto Oy at www.aivosto.com
1385 // incorrectly swaps the Unicode block character for the
1386 // unshifted pi character at 0x7e PETSCII on page 3.
1387 //
1388 // Otherwise it is another great resource for understanding
1389 // Commodore character sets and has an intuitive informtaion
1390 // design that nicely complements the Commodore Reference
1391 // guides.
1392 let graphics_characters = "╳○π✓";
1393 let s: String = String::from(graphics_characters);
1394
1395 let expected: [u8; 6] = [0x76, 0x77, 0x7e, 0x0e, 0xba, 0x8e];
1396
1397 let config = PetsciiConfig::load().expect("Error loading config");
1398
1399 let ps = PetsciiString::<6>::from_str_with_config(&s, &config.petscii);
1400
1401 assert_eq!(ps.len(), 6);
1402 assert_eq!(ps.data, expected);
1403
1404 let s: String = String::from(ps);
1405
1406 assert_eq!(s, graphics_characters);
1407
1408 #[cfg(feature = "external-json")]
1409 {
1410 let config_fn = String::from("data/config.json");
1411 let config =
1412 PetsciiConfig::load_from_file(&config_fn).expect("Error loading config file");
1413
1414 let ps = PetsciiString::<6>::from_str_with_config(&s, &config.petscii);
1415
1416 assert_eq!(ps.len(), 6);
1417 assert_eq!(ps.data, expected);
1418
1419 let s: String = String::from(ps);
1420
1421 assert_eq!(s, graphics_characters);
1422 }
1423 }
1424}