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}