1#![no_std]
8#![allow(clippy::uninlined_format_args)]
9#![forbid(unsafe_code, future_incompatible)]
10
11extern crate alloc;
12
13#[cfg(feature = "std")]
14extern crate std;
15
16use alloc::string::String;
17use alloc::vec::Vec;
18use core::fmt;
19
20#[cfg(feature = "std")]
21use std::io::{self, Write};
22
23const UTF8_START: &[u8] = &[0x1B, 0x25, 0x47];
24const UTF8_END: &[u8] = &[0x1B, 0x25, 0x40];
25
26#[derive(Clone, Copy)]
28#[repr(transparent)]
29pub struct CText<'s> {
30 utf8: &'s str,
31}
32
33impl<'s> fmt::Debug for CText<'s> {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 f.write_str(self.utf8)
36 }
37}
38
39impl<'s> fmt::Display for CText<'s> {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 f.write_str(self.utf8)
42 }
43}
44
45impl<'s> CText<'s> {
46 pub const fn new(utf8: &'s str) -> Self {
47 Self { utf8 }
48 }
49
50 pub const fn len(self) -> usize {
51 self.utf8.len() + UTF8_START.len() + UTF8_END.len()
52 }
53
54 pub const fn is_empty(self) -> bool {
55 self.utf8.is_empty()
56 }
57
58 #[cfg(feature = "std")]
59 pub fn write(self, mut out: impl Write) -> io::Result<usize> {
60 let mut writed = 0;
61 writed += out.write(UTF8_START)?;
62 writed += out.write(self.utf8.as_bytes())?;
63 writed += out.write(UTF8_END)?;
64 Ok(writed)
65 }
66}
67
68pub fn utf8_to_compound_text(text: &str) -> Vec<u8> {
70 let mut ret = Vec::with_capacity(text.len() + 6);
71 ret.extend_from_slice(UTF8_START);
72 ret.extend_from_slice(text.as_bytes());
73 ret.extend_from_slice(UTF8_END);
74 ret
75}
76
77#[derive(Debug, Clone)]
78pub enum DecodeError {
79 InvalidEncoding,
80 UnsupportedEncoding,
81 Utf8Error(alloc::string::FromUtf8Error),
82}
83
84impl From<alloc::string::FromUtf8Error> for DecodeError {
85 fn from(err: alloc::string::FromUtf8Error) -> Self {
86 DecodeError::Utf8Error(err)
87 }
88}
89
90impl fmt::Display for DecodeError {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 match self {
93 Self::InvalidEncoding => write!(f, "Invalid compound text"),
94 Self::UnsupportedEncoding => write!(f, "This encoding is not supported yet"),
95 Self::Utf8Error(e) => write!(f, "Not a valid utf8 {}", e),
96 }
97 }
98}
99
100macro_rules! decode {
101 ($decoder:expr, $out:expr, $bytes:expr, $last:expr) => {
102 let mut _current_bytes: &[u8] = $bytes;
103 loop {
104 let (ret, nread, _) = $decoder.decode_to_string(_current_bytes, $out, $last);
105
106 match ret {
107 encoding_rs::CoderResult::InputEmpty => break,
108 encoding_rs::CoderResult::OutputFull => {
109 $out.reserve(
110 $decoder
111 .max_utf8_buffer_length($bytes.len())
112 .unwrap_or_default(),
113 );
114 _current_bytes = &_current_bytes[nread..];
115 }
116 }
117 }
118 };
119}
120
121pub fn compound_text_to_utf8(bytes: &[u8]) -> Result<String, DecodeError> {
122 let split = bytes.split(|&b| b == 0x1b);
123
124 let mut result = String::new();
125
126 for chunk in split {
127 let mut iter = chunk.iter();
128 match (iter.next(), iter.next()) {
129 (Some(0x25), Some(0x47)) => {
131 let left = iter.as_slice().to_vec();
132 match String::from_utf8(left) {
133 Ok(out) => result.push_str(&out),
134 Err(e) => return Err(DecodeError::from(e)),
135 };
136 }
137 (Some(0x25), Some(0x40)) => {}
139 (Some(0x28), Some(0x42)) | (Some(0x28), Some(0x4a)) => {
143 let left = iter.as_slice();
144 let out = encoding_rs::mem::decode_latin1(left);
145 result.push_str(&out);
146 }
147 (Some(0x24), Some(0x28)) => match iter.next() {
149 Some(0x42) => {
151 let left = iter.as_slice();
152 let mut decoder = encoding_rs::ISO_2022_JP.new_decoder_without_bom_handling();
153 let mut out = String::new();
154 decode!(decoder, &mut out, &[0x1B, 0x24, 0x42], false);
155 decode!(decoder, &mut out, &left, true);
156
157 result.push_str(&out);
158 }
159
160 Some(0x41) => {
162 let left: Vec<u8> = iter.map(|&b| b + 0x80).collect();
163 let (out, _) = encoding_rs::GBK.decode_without_bom_handling(&left);
164 result.push_str(&out);
165 }
166
167 Some(0x43) => {
169 let left: Vec<u8> = iter.map(|&b| b + 0x80).collect();
170 let (out, _) = encoding_rs::EUC_KR.decode_with_bom_removal(&left);
171 result.push_str(&out);
172 }
173 _ => return Err(DecodeError::InvalidEncoding),
175 },
176 (Some(0x2d), Some(0x41)) => {
178 let left = iter.as_slice();
179 let out = encoding_rs::mem::decode_latin1(left);
180 result.push_str(&out);
181 }
182 (Some(0x2d), Some(0x42)) => {
184 let left = iter.as_slice();
185 let (out, _) = encoding_rs::ISO_8859_2.decode_without_bom_handling(left);
186 result.push_str(&out);
187 }
188 (Some(0x2d), Some(0x43)) => {
190 let left = iter.as_slice();
191 let (out, _) = encoding_rs::ISO_8859_3.decode_without_bom_handling(left);
192 result.push_str(&out);
193 }
194 (Some(0x2d), Some(0x44)) => {
196 let left = iter.as_slice();
197 let (out, _) = encoding_rs::ISO_8859_4.decode_without_bom_handling(left);
198 result.push_str(&out);
199 }
200 (Some(0x2d), Some(0x46)) => {
202 let left = iter.as_slice();
203 let (out, _) = encoding_rs::ISO_8859_7.decode_without_bom_handling(left);
204 result.push_str(&out);
205 }
206 (Some(0x2d), Some(0x47)) => {
208 let left = iter.as_slice();
209 let (out, _) = encoding_rs::ISO_8859_6.decode_without_bom_handling(left);
210 result.push_str(&out);
211 }
212 (Some(0x2d), Some(0x48)) => {
214 let left = iter.as_slice();
215 let (out, _) = encoding_rs::ISO_8859_8.decode_without_bom_handling(left);
216 result.push_str(&out);
217 }
218 (Some(0x2d), Some(0x4c)) => {
220 let left = iter.as_slice();
221 let (out, _) = encoding_rs::ISO_8859_5.decode_without_bom_handling(left);
222 result.push_str(&out);
223 }
224 (Some(0x2d), Some(0x4d)) => {
226 let left = iter.as_slice();
227 let (out, _) = encoding_rs::WINDOWS_1254.decode_without_bom_handling(left);
228 result.push_str(&out);
229 }
230 (Some(0x2d), Some(0x56)) => {
232 let left = iter.as_slice();
233 let (out, _) = encoding_rs::ISO_8859_10.decode_without_bom_handling(left);
234 result.push_str(&out);
235 }
236 (Some(0x2d), Some(0x59)) => {
238 let left = iter.as_slice();
239 let (out, _) = encoding_rs::ISO_8859_13.decode_without_bom_handling(left);
240 result.push_str(&out);
241 }
242 (Some(0x2d), Some(0x5f)) => {
244 let left = iter.as_slice();
245 let (out, _) = encoding_rs::ISO_8859_14.decode_without_bom_handling(left);
246 result.push_str(&out);
247 }
248 (Some(0x2d), Some(0x62)) => {
250 let left = iter.as_slice();
251 let (out, _) = encoding_rs::ISO_8859_15.decode_without_bom_handling(left);
252 result.push_str(&out);
253 }
254 (Some(0x2d), Some(0x66)) => {
256 let left = iter.as_slice();
257 let (out, _) = encoding_rs::ISO_8859_16.decode_without_bom_handling(left);
258 result.push_str(&out);
259 }
260 _ => {
262 let out = encoding_rs::mem::decode_latin1(chunk);
263 result.push_str(&out);
264 }
265 };
266 }
267 Ok(result)
268}
269
270#[cfg(test)]
271mod tests {
272 #[test]
273 fn korean() {
274 const UTF8: &str = "가나다";
275 const COMP: &[u8] = &[
276 27, 37, 71, 234, 176, 128, 235, 130, 152, 235, 139, 164, 27, 37, 64,
277 ];
278 assert_eq!(crate::utf8_to_compound_text(UTF8), COMP);
279 assert_eq!(crate::compound_text_to_utf8(COMP).unwrap(), UTF8);
280 }
281
282 #[test]
283 fn iso_2022_jp() {
284 const UTF8: &str = "東京";
285 const COMP: &[u8] = &[27, 36, 40, 66, 69, 108, 53, 126];
286 assert_eq!(crate::compound_text_to_utf8(COMP).unwrap(), UTF8);
287 }
288
289 #[test]
290 fn iso_2022_jp_long() {
291 const UTF8: &str = "知ってるつもり";
292 const COMP: &[u8] = &[
293 27, 36, 40, 66, 67, 78, 36, 67, 36, 70, 36, 107, 36, 68, 36, 98, 36, 106, 27, 40, 66,
294 ];
295 assert_eq!(crate::compound_text_to_utf8(COMP).unwrap(), UTF8);
296 }
297
298 #[test]
299 fn gb2312_cn_mixed_ascii_digits() {
300 const UTF8: &str = "2026年07月16日";
302 #[rustfmt::skip]
303 const COMP: &[u8] = &[
304 b'2', b'0', b'2', b'6',
305 0x1b, 0x24, 0x28, 0x41, 0x44, 0x6a, 0x1b, 0x28, 0x42, b'0', b'7',
307 0x1b, 0x24, 0x28, 0x41, 0x54, 0x42, 0x1b, 0x28, 0x42, b'1', b'6',
309 0x1b, 0x24, 0x28, 0x41, 0x48, 0x55, ];
311 assert_eq!(crate::compound_text_to_utf8(COMP).unwrap(), UTF8);
312 }
313
314 #[test]
315 fn gb2312_cn() {
316 const UTF8: &str = "很高兴认识你";
317 const COMP: &[u8] = &[
318 0x1b, 0x24, 0x28, 0x41, 0x3a, 0x5c, 0x38, 0x5f, 0x50, 0x4b, 0x48, 0x4f, 0x4a, 0x36,
319 0x44, 0x63,
320 ];
321 assert_eq!(crate::compound_text_to_utf8(COMP).unwrap(), UTF8);
322 }
323
324 #[test]
325 fn gb2312_cn_mixed() {
326 const UTF8: &str = "炸哦你";
327 const COMP: &[u8] = &[
328 0x1b, 0x24, 0x28, 0x42, 0x5f, 0x5a, 0x53, 0x28, 0x1b, 0x24, 0x28, 0x41, 0x44, 0x63,
329 ];
330 assert_eq!(crate::compound_text_to_utf8(COMP).unwrap(), UTF8);
331 }
332
333 #[test]
334 fn ks_c_5601() {
335 const UTF8: &str = "넌최고야";
336 const COMP: &[u8] = &[
337 0x1b, 0x24, 0x28, 0x43, 0x33, 0x4d, 0x43, 0x56, 0x30, 0x6d, 0x3e, 0x5f,
338 ];
339 assert_eq!(crate::compound_text_to_utf8(COMP).unwrap(), UTF8);
340 }
341
342 #[test]
343 fn iso_8859_1() {
344 const UTF8: &str = "¡¸ÀÑâó";
345 const COMP: &[u8] = &[0x1b, 0x2d, 0x41, 0xa1, 0xb8, 0xc0, 0xd1, 0xe2, 0xf3];
346 assert_eq!(crate::compound_text_to_utf8(COMP).unwrap(), UTF8);
347 }
348
349 #[test]
350 fn iso_8859_2() {
351 const UTF8: &str = "ĄŁĽŚŠŤ";
352 const COMP: &[u8] = &[0x1b, 0x2d, 0x42, 0xa1, 0xa3, 0xa5, 0xa6, 0xa9, 0xab];
353 assert_eq!(crate::compound_text_to_utf8(COMP).unwrap(), UTF8);
354 }
355}