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}