1use core::{
2 borrow::Borrow,
3 ffi::CStr,
4 fmt::{self, Display},
5 slice,
6};
7
8use crate::{
9 String_CalcLen,
10 bindings::{STRING_SIZE, cc_codepoint, cc_string, cc_uint16, cc_unichar},
11 std_types::{Box, CString, String, ToString, Vec, c_char, c_int},
12};
13
14impl cc_string {
15 #[must_use]
16 pub fn as_slice(&self) -> &[u8] {
17 let len = self.length as usize;
18 let data = self.buffer as *const u8;
19 if len == 0 || data.is_null() {
20 return &[];
21 }
22 unsafe { slice::from_raw_parts(data, len) }
23 }
24}
25
26impl Display for cc_string {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 String::from_utf16_lossy(
29 &self
30 .as_slice()
31 .iter()
32 .map(|c| Convert_CP437ToUnicode(*c))
33 .collect::<Vec<_>>(),
34 )
35 .fmt(f)
36 }
37}
38
39impl From<cc_string> for String {
40 fn from(cc_string: cc_string) -> Self {
41 cc_string.to_string()
42 }
43}
44
45pub struct OwnedString {
46 cc_string: cc_string,
47
48 #[allow(dead_code)]
49 c_str: Box<CStr>,
50}
51
52impl OwnedString {
53 pub fn new<S: Into<String>>(s: S) -> Self {
60 let bytes = s
61 .into()
62 .chars()
64 .map(|c| c as cc_codepoint)
65 .map(Convert_CodepointToCP437)
66 .collect::<Vec<_>>();
67 let length = bytes.len();
68 let capacity = bytes.len();
69
70 let c_str = CString::new(bytes).unwrap().into_boxed_c_str();
71 let buffer: *const c_char = c_str.as_ptr();
72
73 let length = cc_uint16::try_from(length).expect("string length exceeds cc_string capacity");
74 let capacity =
75 cc_uint16::try_from(capacity).expect("string length exceeds cc_string capacity");
76
77 Self {
78 c_str,
79 cc_string: cc_string {
80 buffer: buffer.cast_mut(),
81 length,
82 capacity,
83 },
84 }
85 }
86
87 #[must_use]
88 pub fn as_cc_string(&self) -> &cc_string {
89 &self.cc_string
90 }
91
92 #[must_use]
96 pub unsafe fn get_cc_string(&self) -> cc_string {
97 cc_string { ..self.cc_string }
98 }
99}
100
101impl Borrow<cc_string> for OwnedString {
102 fn borrow(&self) -> &cc_string {
103 self.as_cc_string()
104 }
105}
106
107#[test]
108fn test_owned_string() {
109 fn use_cc_string<T: Borrow<cc_string>>(s: T) {
110 #[cfg(not(feature = "no_std"))]
111 {
112 println!("{:?}", s.borrow());
113 }
114 }
115
116 let owned_string = OwnedString::new("hello");
117
118 use_cc_string(owned_string.as_cc_string());
119
120 use_cc_string(owned_string);
121
122 }
124
125pub unsafe fn String_Init(buffer: *mut c_char, length: c_int, capacity: c_int) -> cc_string {
135 cc_string {
136 buffer,
137 length: cc_uint16::try_from(length).expect("cc_string length out of range"),
138 capacity: cc_uint16::try_from(capacity).expect("cc_string capacity out of range"),
139 }
140}
141
142#[must_use]
148pub unsafe fn String_FromRaw(buffer: *mut c_char, capacity: c_int) -> cc_string {
149 unsafe { String_Init(buffer, String_CalcLen(buffer, capacity), capacity) }
150}
151
152#[must_use]
162pub unsafe fn String_FromRawArray(buffer: &mut [c_char]) -> cc_string {
163 unsafe { String_FromRaw(buffer.as_mut_ptr(), c_int::try_from(buffer.len()).unwrap()) }
164}
165
166#[must_use]
171#[expect(
172 clippy::cast_possible_wrap,
173 reason = "STRING_SIZE and resulting length fit in c_int"
174)]
175pub unsafe fn UNSAFE_GetString(data: &[u8]) -> cc_string {
176 let mut length = 0;
177 for i in (0..STRING_SIZE).rev() {
178 let code = data[i as usize];
179 if code == b'\0' || code == b' ' {
180 continue;
181 }
182 length = i + 1;
183 break;
184 }
185
186 unsafe {
187 String_Init(
188 data.as_ptr() as *mut c_char,
189 length as c_int,
190 STRING_SIZE as c_int,
191 )
192 }
193}
194
195#[test]
196fn test_get_string() {
197 unsafe {
198 let mut s =
199 b"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl0000000000000000"
200 .to_vec();
201 s.resize(STRING_SIZE as usize, 0);
202 assert_eq!(
203 UNSAFE_GetString(&s).to_string(),
204 "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl"
205 );
206 }
207}
208
209pub const controlChars: &[cc_unichar] = &[
210 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, 0x25D8, 0x25CB, 0x25D9, 0x2642,
211 0x2640, 0x266A, 0x266B, 0x263C, 0x25BA, 0x25C4, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
212 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
213];
214
215pub const extendedChars: &[cc_unichar] = &[
216 0x2302, 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8,
217 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB,
218 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192, 0x00E1, 0x00ED, 0x00F3,
219 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB,
220 0x00BB, 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551,
221 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E,
222 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, 0x2568, 0x2564, 0x2565,
223 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590,
224 0x2580, 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9,
225 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7,
226 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0,
227];
228
229#[must_use]
230pub fn Convert_CP437ToUnicode(raw: u8) -> cc_unichar {
231 if raw < 0x20 {
232 controlChars[raw as usize]
233 } else if raw < 0x7F {
234 cc_unichar::from(raw)
235 } else {
236 extendedChars[raw as usize - 0x7F]
237 }
238}
239
240#[must_use]
241pub fn Convert_CodepointToCP437(cp: cc_codepoint) -> u8 {
242 let mut c: u8 = 0;
243 Convert_TryCodepointToCP437(cp, &mut c);
244 c
245}
246
247fn ReduceEmoji(cp: cc_codepoint) -> cc_codepoint {
248 if cp == 0x1F31E {
249 return 0x263C;
250 }
251 if cp == 0x1F3B5 {
252 return 0x266B;
253 }
254 if cp == 0x1F642 {
255 return 0x263A;
256 }
257
258 if cp == 0x1F600 || cp == 0x1F601 || cp == 0x1F603 {
259 return 0x263A;
260 }
261 if cp == 0x1F604 || cp == 0x1F606 || cp == 0x1F60A {
262 return 0x263A;
263 }
264 cp
265}
266
267#[expect(
268 clippy::cast_possible_truncation,
269 reason = "codepoint and lookup indices have been range-checked to fit in u8"
270)]
271fn Convert_TryCodepointToCP437(mut cp: cc_codepoint, c: &mut u8) -> bool {
272 if (0x20..0x7F).contains(&cp) {
273 *c = cp as u8;
274 return true;
275 }
276 if cp >= 0x1F000 {
277 cp = ReduceEmoji(cp);
278 }
279
280 for (i, &chr) in controlChars.iter().enumerate() {
281 if cc_codepoint::from(chr) == cp {
282 *c = i as u8;
283 return true;
284 }
285 }
286
287 for (i, &chr) in extendedChars.iter().enumerate() {
288 if cc_codepoint::from(chr) == cp {
289 *c = (i + 0x7F) as u8;
290 return true;
291 }
292 }
293
294 *c = b'?';
295 false
296}
297
298#[test]
299fn test_cp_437_conversion() {
300 let bytes: &[u8] = &[97, 236, 236]; let c_str = CString::new(bytes).unwrap();
303 let len = cc_uint16::try_from(bytes.len()).unwrap();
304 let a = cc_string {
305 buffer: c_str.as_ptr().cast_mut(),
306 length: len,
307 capacity: len,
308 };
309 assert_eq!(a.to_string(), "a∞∞");
310
311 let str = "a∞∞";
312 let s = OwnedString::new(str);
313 unsafe {
314 assert_eq!(
315 slice::from_raw_parts(
316 s.as_cc_string().buffer as *const u8,
317 s.as_cc_string().length as usize
318 ),
319 bytes
320 );
321 }
322}