1use alloc::borrow::Cow;
43use alloc::string::String;
44
45#[must_use]
49pub fn decode_dvb_string(bytes: &[u8]) -> String {
50 if bytes.is_empty() {
51 return String::new();
52 }
53
54 let (charset, body) = split_charset(bytes);
55 let decoded = match charset {
56 Charset::Iso6937 => decode_iso_6937(body),
57 Charset::Iso8859(n) => decode_iso_8859(n, body),
58 Charset::Utf8 => String::from_utf8_lossy(body).into_owned(),
59 Charset::Ucs2Be => decode_ucs2_be(body),
60 #[cfg(feature = "std")]
61 Charset::Ksx1001 => decode_with(encoding_rs::EUC_KR, body),
62 #[cfg(feature = "std")]
63 Charset::Gb2312 => decode_with(encoding_rs::GBK, body),
64 #[cfg(feature = "std")]
65 Charset::Big5 => decode_with(encoding_rs::BIG5, body),
66 #[cfg(not(feature = "std"))]
70 Charset::Ksx1001 | Charset::Gb2312 | Charset::Big5 => {
71 body.iter().map(|_| '\u{FFFD}').collect()
72 }
73 Charset::Unsupported(_indicator) => body.iter().map(|_| '\u{FFFD}').collect(),
74 };
75
76 strip_control_codes(decoded)
83}
84
85fn is_control_char(c: char) -> bool {
88 matches!(c as u32, 0x00..0x20 | 0x80..0xA0 | 0xE080..0xE0A0)
89}
90
91fn strip_control_codes(decoded: String) -> String {
97 if !decoded.chars().any(is_control_char) {
98 return decoded;
99 }
100 let mut out = String::with_capacity(decoded.len());
101 out.extend(decoded.chars().filter_map(|c| match c as u32 {
102 0x86 | 0x87 | 0xE086 | 0xE087 => None,
103 0x8A | 0xE08A => Some(' '),
104 0x0A => Some(' '),
105 0x00..0x20 => None,
106 0x80..0xA0 => None,
107 0xE080..0xE0A0 => None,
108 _ => Some(c),
109 }));
110 out
111}
112
113#[must_use]
116pub fn decode(bytes: &[u8]) -> Cow<'_, str> {
117 if bytes.iter().all(|&b| b.is_ascii() && b >= 0x20) {
118 return Cow::Borrowed(core::str::from_utf8(bytes).unwrap_or(""));
119 }
120 Cow::Owned(decode_dvb_string(bytes))
121}
122
123#[derive(Clone, Copy, PartialEq, Eq, Hash)]
127#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
128pub struct DvbText<'a>(&'a [u8]);
129
130impl<'a> DvbText<'a> {
131 #[must_use]
133 pub const fn new(raw: &'a [u8]) -> Self {
134 Self(raw)
135 }
136 #[must_use]
138 pub const fn raw(&self) -> &'a [u8] {
139 self.0
140 }
141 #[must_use]
145 pub fn decode(&self) -> Cow<'a, str> {
146 decode(self.0)
147 }
148}
149
150impl core::ops::Deref for DvbText<'_> {
151 type Target = [u8];
154 fn deref(&self) -> &[u8] {
155 self.0
156 }
157}
158
159impl core::fmt::Display for DvbText<'_> {
160 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
161 f.write_str(&self.decode())
162 }
163}
164
165impl core::fmt::Debug for DvbText<'_> {
166 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
167 write!(f, "DvbText({:?})", self.decode())
168 }
169}
170
171impl<'a> From<&'a [u8]> for DvbText<'a> {
172 fn from(raw: &'a [u8]) -> Self {
173 Self(raw)
174 }
175}
176
177#[cfg(feature = "serde")]
178impl serde::Serialize for DvbText<'_> {
179 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
180 s.serialize_str(&self.decode())
181 }
182}
183#[derive(Clone, Copy, PartialEq, Eq, Hash)]
188pub struct LangCode(pub [u8; 3]);
189
190impl LangCode {
191 #[must_use]
193 pub fn as_str(&self) -> Cow<'_, str> {
194 String::from_utf8_lossy(&self.0)
195 }
196}
197
198impl core::ops::Deref for LangCode {
199 type Target = [u8; 3];
200 fn deref(&self) -> &[u8; 3] {
201 &self.0
202 }
203}
204
205impl core::fmt::Display for LangCode {
206 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
207 f.write_str(&self.as_str())
208 }
209}
210
211impl core::fmt::Debug for LangCode {
212 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
213 write!(f, "LangCode({})", self.as_str())
214 }
215}
216
217#[cfg(feature = "serde")]
218impl serde::Serialize for LangCode {
219 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
220 s.serialize_str(&self.as_str())
221 }
222}
223
224#[derive(Debug)]
225enum Charset {
226 Iso6937,
227 Iso8859(u8),
228 Utf8,
229 Ucs2Be,
230 Ksx1001,
232 Gb2312,
234 Big5,
236 Unsupported(u8),
237}
238
239fn split_charset(bytes: &[u8]) -> (Charset, &[u8]) {
240 match bytes[0] {
241 b if b >= 0x20 => (Charset::Iso6937, bytes),
242 0x00 => (Charset::Iso6937, &bytes[1..]),
243 0x08 => (Charset::Unsupported(0x08), &bytes[1..]),
246 0x01..=0x0B => (Charset::Iso8859(bytes[0] + 4), &bytes[1..]),
247 0x10 if bytes.len() >= 3 && bytes[1] == 0x00 => (Charset::Iso8859(bytes[2]), &bytes[3..]),
248 0x11 => (Charset::Ucs2Be, &bytes[1..]),
249 0x12 => (Charset::Ksx1001, &bytes[1..]),
250 0x13 => (Charset::Gb2312, &bytes[1..]),
251 0x14 => (Charset::Big5, &bytes[1..]),
252 0x15 => (Charset::Utf8, &bytes[1..]),
253 0x1F if bytes.len() >= 2 => (Charset::Unsupported(0x1F), &bytes[2..]),
256 other => (Charset::Unsupported(other), &bytes[1..]),
257 }
258}
259
260fn decode_iso_6937(bytes: &[u8]) -> String {
261 let mut out = String::with_capacity(bytes.len());
262 let mut i = 0;
263 while i < bytes.len() {
264 let b = bytes[i];
265 if (0xC0..=0xCF).contains(&b) {
267 match combining_mark(b) {
268 Some(mark) if i + 1 < bytes.len() => {
269 let base = bytes[i + 1];
270 if let Some(c) = combine(b, base) {
271 out.push(c);
272 } else {
273 out.push(iso_6937_single(base));
276 out.push(mark);
277 }
278 i += 2;
279 }
280 _ => {
282 out.push('\u{FFFD}');
283 i += 1;
284 }
285 }
286 continue;
287 }
288 out.push(iso_6937_single(b));
289 i += 1;
290 }
291 out
292}
293
294fn iso_6937_single(b: u8) -> char {
302 match b {
303 0x00..=0x7F => b as char,
304 0x86 | 0x87 | 0x8A => b as char,
306 0x80..=0x9F => '\u{FFFD}',
307 0xA0 => '\u{00A0}', 0xA1 => '¡',
309 0xA2 => '¢',
310 0xA3 => '£',
311 0xA4 => '\u{20AC}', 0xA5 => '¥',
313 0xA6 => '\u{FFFD}', 0xA7 => '§',
315 0xA8 => '\u{00A4}', 0xA9 => '\u{2018}', 0xAA => '\u{201C}', 0xAB => '«',
319 0xAC => '\u{2190}', 0xAD => '\u{2191}', 0xAE => '\u{2192}', 0xAF => '\u{2193}', 0xB0 => '°',
324 0xB1 => '±',
325 0xB2 => '²',
326 0xB3 => '³',
327 0xB4 => '\u{00D7}', 0xB5 => 'µ',
329 0xB6 => '¶',
330 0xB7 => '·',
331 0xB8 => '\u{00F7}', 0xB9 => '\u{2019}', 0xBA => '\u{201D}', 0xBB => '»',
335 0xBC => '¼',
336 0xBD => '½',
337 0xBE => '¾',
338 0xBF => '¿',
339 0xC0..=0xCF => '\u{FFFD}',
341 0xD0 => '\u{2015}', 0xD1 => '¹',
343 0xD2 => '®',
344 0xD3 => '©',
345 0xD4 => '\u{2122}', 0xD5 => '\u{266A}', 0xD6 => '¬',
348 0xD7 => '\u{00A6}', 0xD8..=0xDB => '\u{FFFD}', 0xDC => '\u{215B}', 0xDD => '\u{215C}', 0xDE => '\u{215D}', 0xDF => '\u{215E}', 0xE0 => '\u{2126}', 0xE1 => 'Æ',
356 0xE2 => '\u{0110}', 0xE3 => 'ª',
358 0xE4 => '\u{0126}', 0xE5 => '\u{FFFD}', 0xE6 => '\u{0132}', 0xE7 => '\u{013F}', 0xE8 => '\u{0141}', 0xE9 => 'Ø',
364 0xEA => '\u{0152}', 0xEB => 'º',
366 0xEC => 'Þ',
367 0xED => '\u{0166}', 0xEE => '\u{014A}', 0xEF => '\u{0149}', 0xF0 => '\u{0138}', 0xF1 => 'æ',
372 0xF2 => '\u{0111}', 0xF3 => 'ð',
374 0xF4 => '\u{0127}', 0xF5 => '\u{0131}', 0xF6 => '\u{0133}', 0xF7 => '\u{0140}', 0xF8 => '\u{0142}', 0xF9 => 'ø',
380 0xFA => '\u{0153}', 0xFB => 'ß',
382 0xFC => '\u{00FE}', 0xFD => '\u{0167}', 0xFE => '\u{014B}', 0xFF => '\u{00AD}', }
387}
388
389fn combining_mark(prefix: u8) -> Option<char> {
392 Some(match prefix {
393 0xC1 => '\u{0300}', 0xC2 => '\u{0301}', 0xC3 => '\u{0302}', 0xC4 => '\u{0303}', 0xC5 => '\u{0304}', 0xC6 => '\u{0306}', 0xC7 => '\u{0307}', 0xC8 => '\u{0308}', 0xCA => '\u{030A}', 0xCB => '\u{0327}', 0xCD => '\u{030B}', 0xCE => '\u{0328}', 0xCF => '\u{030C}', _ => return None,
407 })
408}
409
410fn combine(prefix: u8, base: u8) -> Option<char> {
411 Some(match (prefix, base) {
412 (0xC1, b'A') => 'À',
413 (0xC1, b'E') => 'È',
414 (0xC1, b'I') => 'Ì',
415 (0xC1, b'O') => 'Ò',
416 (0xC1, b'U') => 'Ù',
417 (0xC1, b'a') => 'à',
418 (0xC1, b'e') => 'è',
419 (0xC1, b'i') => 'ì',
420 (0xC1, b'o') => 'ò',
421 (0xC1, b'u') => 'ù',
422 (0xC2, b'A') => 'Á',
423 (0xC2, b'E') => 'É',
424 (0xC2, b'I') => 'Í',
425 (0xC2, b'O') => 'Ó',
426 (0xC2, b'U') => 'Ú',
427 (0xC2, b'Y') => 'Ý',
428 (0xC2, b'a') => 'á',
429 (0xC2, b'e') => 'é',
430 (0xC2, b'i') => 'í',
431 (0xC2, b'o') => 'ó',
432 (0xC2, b'u') => 'ú',
433 (0xC2, b'y') => 'ý',
434 (0xC2, b'C') => 'Ć',
435 (0xC2, b'c') => 'ć',
436 (0xC2, b'L') => 'Ĺ',
437 (0xC2, b'l') => 'ĺ',
438 (0xC2, b'N') => 'Ń',
439 (0xC2, b'n') => 'ń',
440 (0xC2, b'R') => 'Ŕ',
441 (0xC2, b'r') => 'ŕ',
442 (0xC2, b'S') => 'Ś',
443 (0xC2, b's') => 'ś',
444 (0xC2, b'Z') => 'Ź',
445 (0xC2, b'z') => 'ź',
446 (0xC3, b'A') => 'Â',
447 (0xC3, b'E') => 'Ê',
448 (0xC3, b'I') => 'Î',
449 (0xC3, b'O') => 'Ô',
450 (0xC3, b'U') => 'Û',
451 (0xC3, b'a') => 'â',
452 (0xC3, b'e') => 'ê',
453 (0xC3, b'i') => 'î',
454 (0xC3, b'o') => 'ô',
455 (0xC3, b'u') => 'û',
456 (0xC4, b'A') => 'Ã',
457 (0xC4, b'N') => 'Ñ',
458 (0xC4, b'O') => 'Õ',
459 (0xC4, b'a') => 'ã',
460 (0xC4, b'n') => 'ñ',
461 (0xC4, b'o') => 'õ',
462 (0xC4, b'I') => 'Ĩ',
463 (0xC4, b'i') => 'ĩ',
464 (0xC4, b'U') => 'Ũ',
465 (0xC4, b'u') => 'ũ',
466 (0xC5, b'A') => 'Ā',
468 (0xC5, b'a') => 'ā',
469 (0xC5, b'E') => 'Ē',
470 (0xC5, b'e') => 'ē',
471 (0xC5, b'I') => 'Ī',
472 (0xC5, b'i') => 'ī',
473 (0xC5, b'O') => 'Ō',
474 (0xC5, b'o') => 'ō',
475 (0xC5, b'U') => 'Ū',
476 (0xC5, b'u') => 'ū',
477 (0xC6, b'A') => 'Ă',
479 (0xC6, b'a') => 'ă',
480 (0xC6, b'G') => 'Ğ',
481 (0xC6, b'g') => 'ğ',
482 (0xC6, b'U') => 'Ŭ',
483 (0xC6, b'u') => 'ŭ',
484 (0xC7, b'C') => 'Ċ',
486 (0xC7, b'c') => 'ċ',
487 (0xC7, b'E') => 'Ė',
488 (0xC7, b'e') => 'ė',
489 (0xC7, b'G') => 'Ġ',
490 (0xC7, b'g') => 'ġ',
491 (0xC7, b'I') => 'İ',
492 (0xC7, b'Z') => 'Ż',
493 (0xC7, b'z') => 'ż',
494 (0xC8, b'A') => 'Ä',
495 (0xC8, b'E') => 'Ë',
496 (0xC8, b'I') => 'Ï',
497 (0xC8, b'O') => 'Ö',
498 (0xC8, b'U') => 'Ü',
499 (0xC8, b'Y') => 'Ÿ',
500 (0xC8, b'a') => 'ä',
501 (0xC8, b'e') => 'ë',
502 (0xC8, b'i') => 'ï',
503 (0xC8, b'o') => 'ö',
504 (0xC8, b'u') => 'ü',
505 (0xC8, b'y') => 'ÿ',
506 (0xCA, b'A') => 'Å',
508 (0xCA, b'a') => 'å',
509 (0xCA, b'U') => 'Ů',
510 (0xCA, b'u') => 'ů',
511 (0xCB, b'C') => 'Ç',
512 (0xCB, b'c') => 'ç',
513 (0xCB, b'G') => 'Ģ',
514 (0xCB, b'g') => 'ģ',
515 (0xCB, b'K') => 'Ķ',
516 (0xCB, b'k') => 'ķ',
517 (0xCB, b'L') => 'Ļ',
518 (0xCB, b'l') => 'ļ',
519 (0xCB, b'N') => 'Ņ',
520 (0xCB, b'n') => 'ņ',
521 (0xCB, b'R') => 'Ŗ',
522 (0xCB, b'r') => 'ŗ',
523 (0xCB, b'S') => 'Ş',
524 (0xCB, b's') => 'ş',
525 (0xCB, b'T') => 'Ţ',
526 (0xCB, b't') => 'ţ',
527 (0xCD, b'O') => 'Ő',
529 (0xCD, b'o') => 'ő',
530 (0xCD, b'U') => 'Ű',
531 (0xCD, b'u') => 'ű',
532 (0xCE, b'A') => 'Ą',
534 (0xCE, b'a') => 'ą',
535 (0xCE, b'E') => 'Ę',
536 (0xCE, b'e') => 'ę',
537 (0xCE, b'I') => 'Į',
538 (0xCE, b'i') => 'į',
539 (0xCE, b'U') => 'Ų',
540 (0xCE, b'u') => 'ų',
541 (0xCF, b'C') => 'Č',
543 (0xCF, b'c') => 'č',
544 (0xCF, b'D') => 'Ď',
545 (0xCF, b'd') => 'ď',
546 (0xCF, b'E') => 'Ě',
547 (0xCF, b'e') => 'ě',
548 (0xCF, b'L') => 'Ľ',
549 (0xCF, b'l') => 'ľ',
550 (0xCF, b'N') => 'Ň',
551 (0xCF, b'n') => 'ň',
552 (0xCF, b'R') => 'Ř',
553 (0xCF, b'r') => 'ř',
554 (0xCF, b'S') => 'Š',
555 (0xCF, b's') => 'š',
556 (0xCF, b'T') => 'Ť',
557 (0xCF, b't') => 'ť',
558 (0xCF, b'Z') => 'Ž',
559 (0xCF, b'z') => 'ž',
560 _ => return None,
561 })
562}
563
564fn decode_iso_8859(n: u8, bytes: &[u8]) -> String {
565 if n == 1 {
570 return bytes.iter().map(|&b| b as char).collect();
571 }
572 #[cfg(feature = "std")]
576 {
577 use encoding_rs::*;
578 if n == 9 || n == 11 {
593 let encoding: &'static Encoding = if n == 9 { WINDOWS_1254 } else { WINDOWS_874 };
594 let (cow, _) = encoding.decode_without_bom_handling(bytes);
595 debug_assert_eq!(cow.chars().count(), bytes.len());
596 return bytes
597 .iter()
598 .zip(cow.chars())
599 .map(|(&b, c)| {
600 if (0x80..=0x9F).contains(&b) {
601 b as char
602 } else {
603 c
604 }
605 })
606 .collect();
607 }
608 let encoding: &'static Encoding = match n {
609 2 => ISO_8859_2,
610 3 => ISO_8859_3,
611 4 => ISO_8859_4,
612 5 => ISO_8859_5,
613 6 => ISO_8859_6,
614 7 => ISO_8859_7,
615 8 => ISO_8859_8,
616 10 => ISO_8859_10,
617 13 => ISO_8859_13,
618 14 => ISO_8859_14,
619 15 => ISO_8859_15,
620 _ => return bytes.iter().map(|_| '\u{FFFD}').collect(),
621 };
622 let (cow, _, _) = encoding.decode(bytes);
623 cow.into_owned()
624 }
625 #[cfg(not(feature = "std"))]
626 {
627 let _ = n;
628 bytes.iter().map(|_| '\u{FFFD}').collect()
629 }
630}
631
632#[cfg(feature = "std")]
633fn decode_with(encoding: &'static encoding_rs::Encoding, bytes: &[u8]) -> String {
634 let (cow, _, _) = encoding.decode(bytes);
635 cow.into_owned()
636}
637
638fn decode_ucs2_be(bytes: &[u8]) -> String {
639 let mut out = String::with_capacity(bytes.len());
646 out.extend(
647 char::decode_utf16(
648 bytes
649 .chunks_exact(2)
650 .map(|pair| u16::from_be_bytes([pair[0], pair[1]])),
651 )
652 .map(|r| r.unwrap_or(char::REPLACEMENT_CHARACTER)),
653 );
654 out
655}
656
657#[cfg(test)]
658mod tests {
659 use super::*;
660
661 #[test]
662 fn decode_empty_input_returns_empty_string() {
663 assert_eq!(decode_dvb_string(&[]), "");
664 }
665
666 #[test]
667 fn decode_plain_ascii_is_borrowed() {
668 let cow = decode(b"HELLO");
669 assert!(matches!(cow, Cow::Borrowed(_)));
670 assert_eq!(cow, "HELLO");
671 }
672
673 #[test]
674 fn decode_iso6937_latin_accent_chars() {
675 assert_eq!(decode_dvb_string(&[0x00, 0xC2, b'A']), "Á");
676 assert_eq!(decode_dvb_string(&[0x00, 0xC1, b'e']), "è");
677 assert_eq!(decode_dvb_string(&[0x00, 0xC8, b'o']), "ö");
678 }
679
680 #[test]
681 fn decode_selector_0x01_yields_iso8859_5_cyrillic() {
682 let s = decode_dvb_string(&[0x01, 0xB0, 0xB1]);
683 assert!(s.chars().all(|c| c != '\u{FFFD}'), "got: {s:?}");
684 assert!(!s.is_empty());
685 }
686
687 #[test]
688 fn decode_selector_0x10_extended_yields_iso8859_nn() {
689 let s = decode_dvb_string(&[0x10, 0x00, 0x09, b'A', b'B']);
690 assert_eq!(s, "AB");
691 }
692
693 #[test]
694 fn decode_selector_0x11_ucs2_be() {
695 let s = decode_dvb_string(&[0x11, 0x00, 0x41, 0x00, 0x42]);
696 assert_eq!(s, "AB");
697 }
698
699 #[test]
700 fn decode_selector_0x15_utf8_passthrough() {
701 let s = decode_dvb_string(&[0x15, 0xC3, 0xA9, 0xC3, 0xA9]);
702 assert_eq!(s, "éé");
703 }
704
705 #[test]
706 fn decode_control_chars_stripped_linefeed_becomes_space() {
707 let s = decode_dvb_string(b"A\x01B\nC");
708 assert_eq!(s, "AB C");
709 }
710
711 #[test]
712 fn emphasis_on_off_markers_stripped_per_annex_a2() {
713 let s = decode_dvb_string(&[0x00, b'A', 0x86, b'B', 0x87, b'C']);
716 assert_eq!(s, "ABC");
717 }
718
719 #[test]
720 fn decode_annex_a2_crlf_0x8a_becomes_space() {
721 let s = decode_dvb_string(&[0x00, b'A', 0x8A, b'B']);
723 assert_eq!(s, "A B");
724 }
725
726 #[cfg(feature = "std")]
735 #[test]
736 fn decode_selector_9_and_11_treat_0x80_0x9f_as_c1_controls() {
737 let s = decode_dvb_string(&[0x10, 0x00, 0x09, b'A', 0x8A, b'B']);
741 assert_eq!(s, "A B");
742 let s = decode_dvb_string(&[0x10, 0x00, 0x09, b'A', 0x80, b'B']);
745 assert_eq!(s, "AB");
746
747 let s = decode_dvb_string(&[0x10, 0x00, 0x0B, b'A', 0x8A, b'B']);
749 assert_eq!(s, "A B");
750 let s = decode_dvb_string(&[0x10, 0x00, 0x0B, b'A', 0x80, b'B']);
751 assert_eq!(s, "AB");
752
753 let s = decode_dvb_string(&[0x10, 0x00, 0x09, 0xDD]);
756 assert_eq!(s, "İ");
757 }
758
759 #[test]
760 fn decode_selector_0x12_ksx1001_euc_kr() {
761 assert_eq!(decode_dvb_string(&[0x12, 0xB0, 0xA1]), "가");
763 }
764
765 #[test]
766 fn decode_selector_0x13_gb2312() {
767 assert_eq!(decode_dvb_string(&[0x13, 0xC4, 0xE3]), "你");
769 }
770
771 #[test]
772 fn decode_selector_0x14_big5() {
773 assert_eq!(decode_dvb_string(&[0x14, 0xA4, 0xA4]), "中");
775 }
776
777 #[test]
781 fn decode_selector_0x13_gbk_trail_byte_in_c1_range() {
782 assert_eq!(decode_dvb_string(&[0x13, 0x81, 0x80]), "亐");
783 }
784
785 #[test]
789 fn two_byte_control_codes_filtered() {
790 assert_eq!(decode_dvb_string(&[0x13, 0xAB, 0xCD]), " ");
791 assert_eq!(decode_dvb_string(&[0x13, 0xAB, 0xC3]), "");
792 }
793
794 #[test]
797 fn decode_selector_0x1f_encoding_type_id() {
798 let s = decode_dvb_string(&[0x1F, 0x01, 0x41, 0x42]);
799 assert_eq!(s.chars().count(), 2);
800 assert!(s.chars().all(|c| c == '\u{FFFD}'));
801 }
802
803 #[test]
805 fn reserved_selector_0x08_is_unsupported() {
806 let s = decode_dvb_string(&[0x08, 0x41, 0x42]);
807 assert!(s.chars().all(|c| c == '\u{FFFD}'));
808 assert_eq!(s.chars().count(), 2);
809 }
810
811 #[test]
812 fn unknown_selector_returns_replacement_characters() {
813 let s = decode_dvb_string(&[0x16, 0xAA, 0xBB, 0xCC]);
815 assert_eq!(s.chars().count(), 3);
816 assert!(s.chars().all(|c| c == '\u{FFFD}'));
817 }
818
819 #[test]
822 fn selector_0x10_iso_8859_1_decodes_latin1() {
823 let s = decode_dvb_string(&[0x10, 0x00, 0x01, 0x41, 0xE9]);
827 assert_eq!(s, "Aé");
828 }
829
830 #[test]
831 fn unsupported_iso_8859_12_yields_replacement() {
832 let s = decode_dvb_string(&[0x10, 0x00, 0x0C, 0x41, 0x42]);
835 assert!(s.chars().all(|c| c == '\u{FFFD}'), "got: {s:?}");
836 }
837
838 #[test]
843 fn figure_a1_gr_area_single_byte_mappings() {
844 let pins: &[(u8, char)] = &[
845 (0xA0, '\u{00A0}'), (0xA1, '¡'),
847 (0xA2, '¢'),
848 (0xA3, '£'),
849 (0xA4, '\u{20AC}'), (0xA5, '¥'),
851 (0xA7, '§'),
852 (0xA8, '\u{00A4}'), (0xA9, '\u{2018}'), (0xAA, '\u{201C}'), (0xAB, '«'),
856 (0xAC, '\u{2190}'), (0xAD, '\u{2191}'), (0xAE, '\u{2192}'), (0xAF, '\u{2193}'), (0xB0, '°'),
861 (0xB1, '±'),
862 (0xB2, '²'),
863 (0xB3, '³'),
864 (0xB4, '\u{00D7}'), (0xB5, 'µ'),
866 (0xB6, '¶'),
867 (0xB7, '·'),
868 (0xB8, '\u{00F7}'), (0xB9, '\u{2019}'), (0xBA, '\u{201D}'), (0xBB, '»'),
872 (0xBC, '¼'),
873 (0xBD, '½'),
874 (0xBE, '¾'),
875 (0xBF, '¿'),
876 (0xD0, '\u{2015}'), (0xD1, '¹'),
878 (0xD2, '®'),
879 (0xD3, '©'),
880 (0xD4, '\u{2122}'), (0xD5, '\u{266A}'), (0xD6, '¬'),
883 (0xD7, '\u{00A6}'), (0xDC, '\u{215B}'), (0xDD, '\u{215C}'), (0xDE, '\u{215D}'), (0xDF, '\u{215E}'), (0xE0, '\u{2126}'), (0xE1, 'Æ'),
890 (0xE2, '\u{0110}'), (0xE3, 'ª'),
892 (0xE4, '\u{0126}'), (0xE6, '\u{0132}'), (0xE7, '\u{013F}'), (0xE8, '\u{0141}'), (0xE9, 'Ø'),
897 (0xEA, '\u{0152}'), (0xEB, 'º'),
899 (0xEC, 'Þ'),
900 (0xED, '\u{0166}'), (0xEE, '\u{014A}'), (0xEF, '\u{0149}'), (0xF0, '\u{0138}'), (0xF1, 'æ'),
905 (0xF2, '\u{0111}'), (0xF3, 'ð'),
907 (0xF4, '\u{0127}'), (0xF5, '\u{0131}'), (0xF6, '\u{0133}'), (0xF7, '\u{0140}'), (0xF8, '\u{0142}'), (0xF9, 'ø'),
913 (0xFA, '\u{0153}'), (0xFB, 'ß'),
915 (0xFC, '\u{00FE}'), (0xFD, '\u{0167}'), (0xFE, '\u{014B}'), (0xFF, '\u{00AD}'), ];
920 for &(byte, want) in pins {
921 let got = decode_dvb_string(&[0x00, byte]);
922 assert_eq!(
923 got,
924 want.to_string(),
925 "byte {byte:#04x}: want {want:?} (U+{:04X}), got {got:?}",
926 want as u32
927 );
928 }
929 }
930
931 #[test]
933 fn figure_a1_undefined_positions_are_replacement() {
934 for byte in [0xA6u8, 0xD8, 0xD9, 0xDA, 0xDB, 0xE5] {
935 let got = decode_dvb_string(&[0x00, byte]);
936 assert_eq!(got, "\u{FFFD}", "byte {byte:#04x} should be U+FFFD");
937 }
938 }
939
940 #[test]
942 fn figure_a1_combining_precomposed() {
943 assert_eq!(decode_dvb_string(&[0x00, 0xCA, b'a']), "å"); assert_eq!(decode_dvb_string(&[0x00, 0xCA, b'A']), "Å");
945 assert_eq!(decode_dvb_string(&[0x00, 0xCF, b's']), "š"); assert_eq!(decode_dvb_string(&[0x00, 0xCF, b'Z']), "Ž");
947 assert_eq!(decode_dvb_string(&[0x00, 0xCE, b'e']), "ę"); assert_eq!(decode_dvb_string(&[0x00, 0xCD, b'o']), "ő"); assert_eq!(decode_dvb_string(&[0x00, 0xC7, b'z']), "ż"); assert_eq!(decode_dvb_string(&[0x00, 0xC5, b'a']), "ā"); assert_eq!(decode_dvb_string(&[0x00, 0xC6, b'g']), "ğ"); }
953
954 #[test]
957 fn figure_a1_combining_fallback_emits_base_plus_mark() {
958 assert_eq!(decode_dvb_string(&[0x00, 0xC5, b'x']), "x\u{0304}");
959 }
960
961 #[test]
964 fn figure_a1_combining_undefined_or_dangling_prefix() {
965 assert_eq!(decode_dvb_string(&[0x00, 0xC0, b'a']), "\u{FFFD}a");
966 assert_eq!(decode_dvb_string(&[0x00, 0xC9, b'a']), "\u{FFFD}a");
967 assert_eq!(decode_dvb_string(&[0x00, 0xCC, b'a']), "\u{FFFD}a");
968 assert_eq!(decode_dvb_string(&[0x00, 0xC2]), "\u{FFFD}");
969 }
970
971 #[test]
972 fn dvb_text_decodes_with_charset_selector() {
973 let t = DvbText::new(&[0x15, 0xC3, 0xA9]); assert_eq!(t.decode(), "é");
975 assert_eq!(t.raw(), &[0x15, 0xC3, 0xA9]);
976 assert_eq!(&t[..], &[0x15, 0xC3, 0xA9]); assert_eq!(format!("{t}"), "é");
978 }
979
980 #[test]
981 fn lang_code_as_str() {
982 assert_eq!(LangCode(*b"fre").as_str(), "fre");
983 assert_eq!(LangCode([0xFF, b'r', b'e']).as_str(), "\u{FFFD}re"); }
985
986 #[cfg(feature = "serde")]
987 #[test]
988 fn dvb_text_serializes_decoded() {
989 let t = DvbText::new(&[0x15, 0xC3, 0xA9]);
990 assert_eq!(serde_json::to_string(&t).unwrap(), "\"é\"");
991 }
992
993 #[cfg(feature = "serde")]
994 #[test]
995 fn lang_code_serializes_as_string() {
996 let lc = LangCode(*b"FRA");
999 assert_eq!(serde_json::to_string(&lc).unwrap(), "\"FRA\"");
1000 }
1001}