mos_hardware/
petscii.rs

1//
2// Copyright 2022 Mikael Lund aka Wombat
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// https://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15//
16
17//! Utility functions for working with single PETSCII characters
18//!
19//! This is based on the following resources:
20//! - PETSCII to unicode look-up table from <https://github.com/simmons/cbm>
21//!   which in turn is from <https://sta.c64.org/cbm64pettoscr.html>.
22//! - PETSCII to screen code conversion based on <https://sta.c64.org/cbm64pettoscr.html>.
23
24use core::fmt;
25
26/// The Unicode code point we use for untranslatable PETSCII characters.
27pub const NONE: char = char::REPLACEMENT_CHARACTER;
28
29/// From: http://style64.org/petscii/
30#[rustfmt::skip]
31const PETSCII_TO_CHAR_MAP: [char; 256] = [
32    // control codes
33    NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE,
34    NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE,
35    // punctuation, numbers, a-z
36    ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/',
37    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
38    '@', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
39    'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
40    // [, british pound, ], up arrow, left arrow, horizontal line
41    '[', '\u{00A3}', ']', '\u{2191}', '\u{2190}', '\u{2501}',
42    // A-Z
43    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
44    'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
45    // box vert/horiz, left checkerboard, box vert, checkerboard-0, \-diag lines,
46    '\u{254b}', NONE, '\u{2503}', '\u{2592}', NONE,
47    // control codes
48    NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE,
49    NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE,
50    // non-breaking space, left half block, lower half block, upper 1/8 block
51    '\u{00a0}', '\u{258c}', '\u{2584}', '\u{2594}',
52    // lower 1/8 block, left 1/4 block, checkerboard-1, right 1/4 block -> 1/8
53    '\u{2581}', '\u{258e}', '\u{2592}', '\u{2595}',
54    // lower half checkerboard, /-diag lines, right 1/4 block -> 1/8, box vert+right
55    NONE, NONE, '\u{2595}', '\u{2523}',
56    // quadrant lower right, box up+right, box down+left, lower 1/4 block
57    '\u{2597}', '\u{2517}', '\u{2513}', '\u{2582}',
58    // box down+right, box up+horiz, box down+horiz, box vertical+left
59    '\u{250f}', '\u{253b}', '\u{2533}', '\u{252b}',
60    // left 1/4 block, left 3/8 block, right 3/8 block -> 1/8, upper 1/4 block -> 1/8
61    '\u{258e}', '\u{258d}', '\u{2595}', '\u{2594}',
62    // upper 3/8 block -> 1/8, lower 3/8 block, check mark, quadrant lower left
63    '\u{2594}', '\u{2583}', '\u{2713}', '\u{2596}',
64    // quadrant upper right, box up+left, quadrant upper left, quadrant upper left and lower right
65    '\u{259d}', '\u{2518}', '\u{2598}', '\u{259a}',
66    // box horiz
67    '\u{2501}',
68    // A-Z
69    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
70    'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
71    // box vert/horiz, left checkerboard, box vert, checkerboard-0, \-diag lines,
72    '\u{254b}', NONE, '\u{2503}', '\u{2592}', NONE,
73    // non-breaking space, left half block, lower half block, upper 1/8 block
74    '\u{00a0}', '\u{258c}', '\u{2584}', '\u{2594}',
75    // lower 1/8 block, left 1/4 block, checkerboard-1, right 1/4 block -> 1/8
76    '\u{2581}', '\u{258e}', '\u{2592}', '\u{2595}',
77    // lower half checkerboard, /-diag lines, right 1/4 block -> 1/8, box vert+right
78    NONE, NONE, '\u{2595}', '\u{2523}',
79    // quadrant lower right, box up+right, box down+left, lower 1/4 block
80    '\u{2597}', '\u{2517}', '\u{2513}', '\u{2582}',
81    // box down+right, box up+horiz, box down+horiz, box vertical+left
82    '\u{250f}', '\u{253b}', '\u{2533}', '\u{252b}',
83    // left 1/4 block, left 3/8 block, right 3/8 block -> 1/8, upper 1/4 block -> 1/8
84    '\u{258e}', '\u{258d}', '\u{2595}', '\u{2594}',
85    // upper 3/8 block -> 1/8, lower 3/8 block, check mark, quadrant lower left
86    '\u{2594}', '\u{2583}', '\u{2713}', '\u{2596}',
87    // quadrant upper right, box up+left, quadrant upper left, checkerboard-0
88    '\u{259d}', '\u{2518}', '\u{2598}', '\u{2592}',
89];
90
91/// Structure for working with single PETSCII characters
92///
93/// # Examples
94/// ~~~
95/// use mos_hardware::petscii::Petscii;
96///
97/// let a = Petscii::default();
98/// assert_eq!(u8::from(a), 0);
99///
100/// let byte: u8 = Petscii::from(1).into(); // u8 -> petscii -> u8
101/// assert_eq!(byte, 1);
102///
103/// let c: Petscii = 'c'.into();
104/// assert_eq!(u8::from(c), 67);
105/// assert_eq!(c.to_char(), 'c');
106/// assert_eq!(char::from(c), 'c');
107///
108/// let unicode: char = c.into();
109/// assert_eq!(unicode, 'c');
110///
111#[derive(Default, Copy, Clone, PartialEq, Eq, Debug)]
112pub struct Petscii(u8);
113
114impl Petscii {
115    /// Create from byte
116    pub const fn from_byte(byte: u8) -> Petscii {
117        Petscii(byte)
118    }
119
120    /// Create from unicode character
121    pub const fn from_char(letter: char) -> Petscii {
122        let mut petscii = 0;
123        while petscii < PETSCII_TO_CHAR_MAP.len() {
124            if letter == PETSCII_TO_CHAR_MAP[petscii] {
125                return Petscii::from_byte(petscii as u8);
126            }
127            petscii += 1;
128        }
129        panic!("INVALID LETTER");
130    }
131
132    /// Convert PETSCII to screen code
133    ///
134    /// See <https://sta.c64.org/cbm64pettoscr.html>
135    ///
136    /// # Examples
137    /// ~~~
138    /// use mos_hardware::petscii::Petscii;
139    /// let value = Petscii::from('c');
140    /// assert_eq!(value.to_byte(), 67);
141    /// assert_eq!(value.to_screen_code(), 3);
142    /// ~~~
143    pub const fn to_screen_code(&self) -> u8 {
144        match self.0 {
145            0..=31 => self.0 + 128,
146            32..=63 => self.0,
147            64..=95 => self.0 - 64,
148            96..=127 => self.0 - 32,
149            128..=159 => self.0 + 64,
150            160..=191 => self.0 - 64,
151            192..=254 => self.0 - 128,
152            255 => 94,
153        }
154    }
155
156    /// Convert to unicode
157    pub const fn to_char(&self) -> char {
158        PETSCII_TO_CHAR_MAP[self.0 as usize]
159    }
160
161    /// Convert to byte
162    pub const fn to_byte(&self) -> u8 {
163        self.0
164    }
165}
166
167impl From<Petscii> for char {
168    fn from(petscii: Petscii) -> Self {
169        petscii.to_char()
170    }
171}
172
173impl From<Petscii> for u8 {
174    fn from(petscii: Petscii) -> Self {
175        petscii.0
176    }
177}
178
179impl From<u8> for Petscii {
180    fn from(value: u8) -> Self {
181        Petscii::from_byte(value)
182    }
183}
184
185impl From<char> for Petscii {
186    fn from(value: char) -> Self {
187        Petscii::from_char(value)
188    }
189}
190
191/// Display as char
192impl fmt::Display for Petscii {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        write!(f, "{}", self.to_char())
195    }
196}
197
198/// Convert string slice to array of screen codes at _compile time_
199///
200/// Examples
201/// ~~~
202/// use mos_hardware::screen_codes;
203/// const SCREEN_CODES: [u8; 4] = screen_codes!("way!");
204/// assert_eq!(SCREEN_CODES, [23, 1, 25, 33]);
205/// ~~~
206#[macro_export]
207macro_rules! screen_codes {
208    ($A:expr) => {{
209        use $crate::petscii::*;
210        const N: usize = const_str::to_char_array!($A).len();
211        const CHARS: [char; N] = const_str::to_char_array!($A);
212        let mut screen_codes = [0u8; N];
213        let mut i = 0;
214        while i < N {
215            let petscii = Petscii::from_char(CHARS[i]);
216            screen_codes[i] = petscii.to_screen_code();
217            i += 1;
218        }
219        screen_codes
220    }};
221}
222
223/// As `screen_codes!` but null-terminated
224///
225/// # Examples
226/// ~~~
227/// use mos_hardware::screen_codes_null;
228/// const SCREEN_CODES: [u8; 5] = screen_codes_null!("way!");
229/// assert_eq!(SCREEN_CODES, [23, 1, 25, 33, 0]);
230/// ~~~
231#[macro_export]
232macro_rules! screen_codes_null {
233    ($A:expr) => {{
234        use $crate::screen_codes;
235        *const_str::concat_bytes!(screen_codes!($A), 0u8)
236    }};
237}
238
239/// Convert string slice to array of petscii bytes at _compile time_
240///
241/// Examples
242/// ~~~
243/// use mos_hardware::petscii_codes;
244/// const PETSCII_BYTES: [u8; 4] = petscii_codes!("way!");
245/// ~~~
246#[macro_export]
247macro_rules! petscii_codes {
248    ($A:expr) => {{
249        use $crate::petscii::*;
250        const N: usize = const_str::to_char_array!($A).len();
251        const CHARS: [char; N] = const_str::to_char_array!($A);
252        let mut petscii_bytes = [0u8; N];
253        let mut i = 0;
254        while i < N {
255            let petscii = Petscii::from_char(CHARS[i]);
256            screen_codes[i] = petscii.to_byte();
257            i += 1;
258        }
259        petscii_bytes
260    }};
261}
262
263/// As `petscii_codes!` but null-terminated
264#[macro_export]
265macro_rules! petscii_codes_null {
266    ($A:expr) => {{
267        use $crate::petscii_codes;
268        *const_str::concat_bytes!(petscii_codes!($A), 0u8)
269    }};
270}