1#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
11pub enum CodeError {
12 #[error("Invalid parity")]
14 InvalidParity,
15 #[error("Length of the data ({actual}) does not match the expected length ({expected})")]
17 LengthMismatch {
18 expected: usize,
20 actual: usize,
22 },
23}
24
25#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
27pub struct Channel(pub(crate) bool);
28
29impl Channel {
30 pub const ONE: Channel = Channel(true);
32 pub const TWO: Channel = Channel(false);
34
35 pub fn id(&self) -> u8 {
37 if self.0 {
38 1
39 } else {
40 2
41 }
42 }
43}
44
45#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
47pub struct Field(pub(crate) bool);
48
49impl Field {
50 pub const ONE: Field = Field(true);
52 pub const TWO: Field = Field(false);
54
55 pub fn id(&self) -> u8 {
57 if self.0 {
58 1
59 } else {
60 2
61 }
62 }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
67pub struct ControlCode {
70 pub field: Option<Field>,
72 pub channel: Channel,
74 pub control: Control,
76}
77
78impl ControlCode {
79 pub fn new(field: Field, channel: Channel, control: Control) -> Self {
81 Self {
82 field: Some(field),
83 channel,
84 control,
85 }
86 }
87
88 pub fn channel(&self) -> Channel {
90 self.channel
91 }
92
93 pub fn field(&self) -> Option<Field> {
95 self.field
96 }
97
98 pub fn code(&self) -> Control {
100 self.control
101 }
102
103 fn write(&self) -> [u8; 2] {
104 let mut data;
105 match self.control {
106 Control::Unknown(unk) => {
107 data = [unk[0], unk[1]];
108 }
109 Control::MidRow(midrow) => {
110 data = midrow.to_bytes();
111 }
112 Control::PreambleAddress(preamble) => {
113 data = preamble.to_bytes();
114 }
115 _ => {
116 if let Ok(idx) = CONTROL_MAP_TABLE
117 .binary_search_by_key(&self.control, |control_map| control_map.control)
118 {
119 data = CONTROL_MAP_TABLE[idx].cea608_bytes;
120 } else {
121 unreachable!();
122 }
123 }
124 }
125 if (0x20..=0x2f).contains(&data[1]) && data[0] == 0x14 && self.field == Some(Field::TWO) {
126 data[0] |= 0x01;
127 }
128 if self.channel == Channel::TWO {
129 data[0] |= 0x08;
130 }
131 for data in data.iter_mut() {
132 *data = add_parity(*data);
133 }
134 data
135 }
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
139enum MidRowColor {
140 Color(Color),
141 Italics,
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
146pub struct MidRow {
147 color: MidRowColor,
148 underline: bool,
149}
150
151impl MidRow {
152 pub fn new_color(color: Color, underline: bool) -> Self {
154 Self {
155 color: MidRowColor::Color(color),
156 underline,
157 }
158 }
159
160 pub fn new_italics(underline: bool) -> Self {
162 Self {
163 color: MidRowColor::Italics,
164 underline,
165 }
166 }
167
168 pub fn color(&self) -> Option<Color> {
170 if let MidRowColor::Color(color) = self.color {
171 Some(color)
172 } else {
173 None
174 }
175 }
176
177 pub fn underline(&self) -> bool {
179 self.underline
180 }
181
182 pub fn italics(&self) -> bool {
184 matches!(self.color, MidRowColor::Italics)
185 }
186
187 fn to_bytes(self) -> [u8; 2] {
188 let underline = if self.underline { 0x01 } else { 0x0 };
189 let color = match self.color {
190 MidRowColor::Color(Color::White) => 0x20,
191 MidRowColor::Color(Color::Green) => 0x22,
192 MidRowColor::Color(Color::Blue) => 0x24,
193 MidRowColor::Color(Color::Cyan) => 0x26,
194 MidRowColor::Color(Color::Red) => 0x28,
195 MidRowColor::Color(Color::Yellow) => 0x2a,
196 MidRowColor::Color(Color::Magenta) => 0x2c,
197 MidRowColor::Italics => 0x2e,
198 };
199 [0x11, color + underline]
200 }
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
205pub enum Color {
206 White,
208 Green,
210 Blue,
212 Cyan,
214 Red,
216 Yellow,
218 Magenta,
220}
221
222#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
224pub enum Control {
227 MidRow(MidRow),
229 RegisteredTrademarkSign,
231 DegreeSign,
233 Fraction12,
235 InvertedQuestionMark,
237 TradeMarkSign,
239 CentSign,
241 PoundSign,
243 MusicalNote,
245 LatinLowerAWithGrave,
247 TransparentSpace,
249 LatinLowerEWithGrave,
251 LatinLowerAWithCircumflex,
253 LatinLowerEWithCircumflex,
255 LatinLowerIWithCircumflex,
257 LatinLowerOWithCircumflex,
259 LatinLowerUWithCircumflex,
261
262 LatinCapitalAWithAcute,
264 LatinCapitalEWithAcute,
266 LatinCapitalOWithAcute,
268 LatinCapitalUWithAcute,
270 LatinCapitalUWithDiaeseresis,
272 LatinLowerUWithDiaeseresis,
274 OpeningSingleQuote,
276 InvertedExclamationMark,
278 Asterisk,
280 SingleOpenQuote,
282 EmDash,
284 CopyrightSign,
286 ServiceMarkSign,
288 RoundBullet,
290 DoubleOpenQuote,
292 DoubleCloseQuote,
294 LatinCapitalAWithGrave,
296 LatinCapitalAWithCircumflex,
298 LatinCapitalCWithCedilla,
300 LatinCapitalEWithGrave,
302 LatinCapitalEWithCircumflex,
304 LatinCapitalEWithDiaeresis,
306 LatinLowerEWithDiaeresis,
308 LatinCapitalIWithCircumflex,
310 LatinCapitalIWithDiaeresis,
312 LatinLowerIWithDiaeresis,
314 LatinCapitalOWithCircumflex,
316 LatinCapitalUWithGrave,
318 LatinLowerUWithGrave,
320 LatinCapitalUWithCircumflex,
322 OpeningGuillemets,
324 ClosingGuillemets,
326
327 LatinCapitalAWithTilde,
329 LatinLowerAWithTilde,
331 LatinCapitalIWithAcute,
333 LatinCapitalIWithGrave,
335 LatinLowerIWithGrave,
337 LatinCapitalOWithGrave,
339 LatinLowerOWithGrave,
341 LatinCapitalOWithTilde,
343 LatinLowerOWithTilde,
345 OpeningBrace,
347 ClosingBrace,
349 ReverseSolidus,
351 Caret,
353 Underbar,
355 Pipe,
357 Tilde,
359 LatinCapitalAWithDiaeresis,
361 LatinLowerAWithDiaeresis,
363 LatinCapitalOWithDiaeresis,
365 LatinLowerOWithDiaeresis,
367 LatinLowerSharpS,
369 YenSign,
371 GeneralCurrencySign,
373 VerticalBar,
375 LatinCapitalAWithRingAbove,
377 LatinLowerAWithRingAbove,
379 LatinCapitalOWithStroke,
381 LatinLowerOWithStroke,
383 UpperLeftBorder,
385 UpperRightBorder,
387 LowerLeftBorder,
389 LowerRightBorder,
391
392 ResumeCaptionLoading,
394 Backspace,
396 AlarmOff,
398 AlarmOn,
400 DeleteToEndOfRow,
402 RollUp2,
404 RollUp3,
406 RollUp4,
408 FlashOn,
410 ResumeDirectionCaptioning,
412 TextRestart,
414 ResumeTextDisplay,
416 EraseDisplayedMemory,
418 CarriageReturn,
421 EraseNonDisplayedMemory,
423 EndOfCaption,
425
426 TabOffset1,
428 TabOffset2,
430 TabOffset3,
432
433 PreambleAddress(PreambleAddressCode),
436 Unknown([u8; 2]),
438}
439
440impl Control {
441 pub fn tab_offset(offset: u8) -> Option<Control> {
443 match offset {
444 1 => Some(Control::TabOffset1),
445 2 => Some(Control::TabOffset2),
446 3 => Some(Control::TabOffset3),
447 _ => None,
448 }
449 }
450}
451
452#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
454pub struct PreambleAddressCode {
455 row: u8,
456 underline: bool,
457 ty: PreambleType,
458}
459
460impl PreambleAddressCode {
461 pub fn new(base_row: u8, underline: bool, code: PreambleType) -> Self {
463 Self {
464 row: base_row,
465 underline,
466 ty: code,
467 }
468 }
469
470 pub fn row(&self) -> u8 {
472 self.row
473 }
474
475 pub fn column(&self) -> u8 {
477 match self.ty {
478 PreambleType::Indent0 => 0,
479 PreambleType::Indent4 => 4,
480 PreambleType::Indent8 => 8,
481 PreambleType::Indent12 => 12,
482 PreambleType::Indent16 => 16,
483 PreambleType::Indent20 => 20,
484 PreambleType::Indent24 => 24,
485 PreambleType::Indent28 => 28,
486 _ => 0,
487 }
488 }
489
490 pub fn underline(&self) -> bool {
492 self.underline
493 }
494
495 pub fn code(&self) -> PreambleType {
497 self.ty
498 }
499
500 pub fn italics(&self) -> bool {
502 matches!(self.ty, PreambleType::WhiteItalics)
503 }
504
505 pub fn color(&self) -> Color {
507 self.ty.color()
508 }
509
510 fn to_bytes(self) -> [u8; 2] {
511 let underline = if self.underline { 0x1 } else { 0x0 };
512 let (row0, row1) = match self.row {
513 0 => (0x11, 0x40),
514 1 => (0x11, 0x60),
515 2 => (0x12, 0x40),
516 3 => (0x12, 0x60),
517 4 => (0x15, 0x40),
518 5 => (0x15, 0x60),
519 6 => (0x16, 0x40),
520 7 => (0x16, 0x60),
521 8 => (0x17, 0x40),
522 9 => (0x17, 0x60),
523 10 => (0x10, 0x40),
524 11 => (0x13, 0x40),
525 12 => (0x13, 0x60),
526 13 => (0x14, 0x40),
527 14 => (0x14, 0x60),
528 _ => unreachable!(),
529 };
530 let ty = match self.ty {
531 PreambleType::Color(Color::White) => 0x00,
532 PreambleType::Color(Color::Green) => 0x02,
533 PreambleType::Color(Color::Blue) => 0x04,
534 PreambleType::Color(Color::Cyan) => 0x06,
535 PreambleType::Color(Color::Red) => 0x08,
536 PreambleType::Color(Color::Yellow) => 0x0a,
537 PreambleType::Color(Color::Magenta) => 0x0c,
538 PreambleType::WhiteItalics => 0x0e,
539 PreambleType::Indent0 => 0x10,
540 PreambleType::Indent4 => 0x12,
541 PreambleType::Indent8 => 0x14,
542 PreambleType::Indent12 => 0x16,
543 PreambleType::Indent16 => 0x18,
544 PreambleType::Indent20 => 0x1a,
545 PreambleType::Indent24 => 0x1c,
546 PreambleType::Indent28 => 0x1e,
547 };
548 [row0, row1 | ty | underline]
549 }
550}
551
552#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
554pub enum PreambleType {
555 Color(Color),
557 WhiteItalics,
559 Indent0,
561 Indent4,
563 Indent8,
565 Indent12,
567 Indent16,
569 Indent20,
571 Indent24,
573 Indent28,
575}
576
577impl PreambleType {
578 pub fn from_indent(indent: u8) -> Option<Self> {
580 match indent {
581 0 => Some(Self::Indent0),
582 4 => Some(Self::Indent4),
583 8 => Some(Self::Indent8),
584 12 => Some(Self::Indent12),
585 16 => Some(Self::Indent16),
586 20 => Some(Self::Indent20),
587 24 => Some(Self::Indent24),
588 28 => Some(Self::Indent28),
589 _ => None,
590 }
591 }
592
593 pub fn from_color(color: Color) -> Self {
595 Self::Color(color)
596 }
597
598 pub fn color(&self) -> Color {
600 if let PreambleType::Color(color) = self {
601 *color
602 } else {
603 Color::White
605 }
606 }
607
608 pub fn indent(&self) -> Option<u8> {
610 match self {
611 Self::Indent0 => Some(0),
612 Self::Indent4 => Some(4),
613 Self::Indent8 => Some(8),
614 Self::Indent12 => Some(12),
615 Self::Indent16 => Some(16),
616 Self::Indent20 => Some(20),
617 Self::Indent24 => Some(24),
618 Self::Indent28 => Some(28),
619 _ => None,
620 }
621 }
622}
623
624#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
626pub enum Code {
628 NUL,
630 Control(ControlCode),
632 Space, ExclamationMark,
636 QuotationMark,
638 NumberSign,
640 DollarSign,
642 PercentSign,
644 Ampersand,
646 Apostrophe,
648 LeftParenthesis,
650 RightParenthesis,
652 LatinLowerAWithAcute,
654 PlusSign,
656 Comma,
658 HyphenMinus,
660 FullStop,
662 Solidus,
664 Zero,
666 One,
668 Two,
670 Three,
672 Four,
674 Five,
676 Six,
678 Seven,
680 Eight,
682 Nine,
684 Colon,
686 SemiColon,
688 LessThan,
690 Equals,
692 GreaterThan,
694 QuestionMark,
696 CommercialAt,
698 LatinCapitalA,
700 LatinCapitalB,
702 LatinCapitalC,
704 LatinCapitalD,
706 LatinCapitalE,
708 LatinCapitalF,
710 LatinCapitalG,
712 LatinCapitalH,
714 LatinCapitalI,
716 LatinCapitalJ,
718 LatinCapitalK,
720 LatinCapitalL,
722 LatinCapitalM,
724 LatinCapitalN,
726 LatinCapitalO,
728 LatinCapitalP,
730 LatinCapitalQ,
732 LatinCapitalR,
734 LatinCapitalS,
736 LatinCapitalT,
738 LatinCapitalU,
740 LatinCapitalV,
742 LatinCapitalW,
744 LatinCapitalX,
746 LatinCapitalY,
748 LatinCapitalZ,
750 LeftSquareBracket,
752 LatinLowerEWithAcute,
754 RightSquareBracket,
756 LatinLowerIWithAcute,
758 LatinLowerOWithAcute,
760 LatinLowerUWithAcute,
762 LatinLowerA,
764 LatinLowerB,
766 LatinLowerC,
768 LatinLowerD,
770 LatinLowerE,
772 LatinLowerF,
774 LatinLowerG,
776 LatinLowerH,
778 LatinLowerI,
780 LatinLowerJ,
782 LatinLowerK,
784 LatinLowerL,
786 LatinLowerM,
788 LatinLowerN,
790 LatinLowerO,
792 LatinLowerP,
794 LatinLowerQ,
796 LatinLowerR,
798 LatinLowerS,
800 LatinLowerT,
802 LatinLowerU,
804 LatinLowerV,
806 LatinLowerW,
808 LatinLowerX,
810 LatinLowerY,
812 LatinLowerZ,
814 LatinLowerCWithCedilla,
816 DivisionSign,
818 LatinCapitalNWithTilde,
820 LatinLowerNWithTilde,
822 SolidBlock, Unknown(u8),
827}
828
829#[derive(Debug, Clone)]
830struct CodeMap<'a> {
831 pub cea608_bytes: &'a [u8],
832 pub code: Code,
833 pub utf8: Option<char>,
834}
835
836macro_rules! code_map_bytes {
837 ($bytes:expr, $code:expr, $utf8:expr) => {
838 CodeMap {
839 cea608_bytes: &$bytes,
840 code: $code,
841 utf8: $utf8,
842 }
843 };
844}
845
846macro_rules! code_map_single_byte {
847 ($byte:expr, $code:expr, $utf8:expr) => {
848 code_map_bytes!([$byte], $code, $utf8)
849 };
850}
851
852static CODE_MAP_TABLE: [CodeMap; 97] = [
854 code_map_single_byte!(0x00, Code::NUL, None),
855 code_map_single_byte!(0x20, Code::Space, Some(' ')),
856 code_map_single_byte!(0x21, Code::ExclamationMark, Some('!')),
857 code_map_single_byte!(0x22, Code::QuotationMark, Some('\"')),
858 code_map_single_byte!(0x23, Code::NumberSign, Some('#')),
859 code_map_single_byte!(0x24, Code::DollarSign, Some('$')),
860 code_map_single_byte!(0x25, Code::PercentSign, Some('%')),
861 code_map_single_byte!(0x26, Code::Ampersand, Some('&')),
862 code_map_single_byte!(0x27, Code::Apostrophe, Some('’')),
863 code_map_single_byte!(0x28, Code::LeftParenthesis, Some('(')),
864 code_map_single_byte!(0x29, Code::RightParenthesis, Some(')')),
865 code_map_single_byte!(0x2A, Code::LatinLowerAWithAcute, Some('á')),
866 code_map_single_byte!(0x2B, Code::PlusSign, Some('+')),
868 code_map_single_byte!(0x2C, Code::Comma, Some(',')),
869 code_map_single_byte!(0x2D, Code::HyphenMinus, Some('-')),
870 code_map_single_byte!(0x2E, Code::FullStop, Some('.')),
871 code_map_single_byte!(0x2F, Code::Solidus, Some('/')),
872 code_map_single_byte!(0x30, Code::Zero, Some('0')),
873 code_map_single_byte!(0x31, Code::One, Some('1')),
874 code_map_single_byte!(0x32, Code::Two, Some('2')),
875 code_map_single_byte!(0x33, Code::Three, Some('3')),
876 code_map_single_byte!(0x34, Code::Four, Some('4')),
877 code_map_single_byte!(0x35, Code::Five, Some('5')),
878 code_map_single_byte!(0x36, Code::Six, Some('6')),
879 code_map_single_byte!(0x37, Code::Seven, Some('7')),
880 code_map_single_byte!(0x38, Code::Eight, Some('8')),
881 code_map_single_byte!(0x39, Code::Nine, Some('9')),
882 code_map_single_byte!(0x3A, Code::Colon, Some(':')),
883 code_map_single_byte!(0x3B, Code::SemiColon, Some(';')),
884 code_map_single_byte!(0x3C, Code::LessThan, Some('<')),
885 code_map_single_byte!(0x3D, Code::Equals, Some('=')),
886 code_map_single_byte!(0x3E, Code::GreaterThan, Some('>')),
887 code_map_single_byte!(0x3F, Code::QuestionMark, Some('?')),
888 code_map_single_byte!(0x40, Code::CommercialAt, Some('@')),
889 code_map_single_byte!(0x41, Code::LatinCapitalA, Some('A')),
890 code_map_single_byte!(0x42, Code::LatinCapitalB, Some('B')),
891 code_map_single_byte!(0x43, Code::LatinCapitalC, Some('C')),
892 code_map_single_byte!(0x44, Code::LatinCapitalD, Some('D')),
893 code_map_single_byte!(0x45, Code::LatinCapitalE, Some('E')),
894 code_map_single_byte!(0x46, Code::LatinCapitalF, Some('F')),
895 code_map_single_byte!(0x47, Code::LatinCapitalG, Some('G')),
896 code_map_single_byte!(0x48, Code::LatinCapitalH, Some('H')),
897 code_map_single_byte!(0x49, Code::LatinCapitalI, Some('I')),
898 code_map_single_byte!(0x4A, Code::LatinCapitalJ, Some('J')),
899 code_map_single_byte!(0x4B, Code::LatinCapitalK, Some('K')),
900 code_map_single_byte!(0x4C, Code::LatinCapitalL, Some('L')),
901 code_map_single_byte!(0x4D, Code::LatinCapitalM, Some('M')),
902 code_map_single_byte!(0x4E, Code::LatinCapitalN, Some('N')),
903 code_map_single_byte!(0x4F, Code::LatinCapitalO, Some('O')),
904 code_map_single_byte!(0x50, Code::LatinCapitalP, Some('P')),
905 code_map_single_byte!(0x51, Code::LatinCapitalQ, Some('Q')),
906 code_map_single_byte!(0x52, Code::LatinCapitalR, Some('R')),
907 code_map_single_byte!(0x53, Code::LatinCapitalS, Some('S')),
908 code_map_single_byte!(0x54, Code::LatinCapitalT, Some('T')),
909 code_map_single_byte!(0x55, Code::LatinCapitalU, Some('U')),
910 code_map_single_byte!(0x56, Code::LatinCapitalV, Some('V')),
911 code_map_single_byte!(0x57, Code::LatinCapitalW, Some('W')),
912 code_map_single_byte!(0x58, Code::LatinCapitalX, Some('X')),
913 code_map_single_byte!(0x59, Code::LatinCapitalY, Some('Y')),
914 code_map_single_byte!(0x5A, Code::LatinCapitalZ, Some('Z')),
915 code_map_single_byte!(0x5B, Code::LeftSquareBracket, Some('[')),
916 code_map_single_byte!(0x5C, Code::LatinLowerEWithAcute, Some('é')),
917 code_map_single_byte!(0x5D, Code::RightSquareBracket, Some(']')),
918 code_map_single_byte!(0x5E, Code::LatinLowerIWithAcute, Some('í')),
919 code_map_single_byte!(0x5F, Code::LatinLowerOWithAcute, Some('ó')),
920 code_map_single_byte!(0x60, Code::LatinLowerUWithAcute, Some('ú')),
921 code_map_single_byte!(0x61, Code::LatinLowerA, Some('a')),
922 code_map_single_byte!(0x62, Code::LatinLowerB, Some('b')),
923 code_map_single_byte!(0x63, Code::LatinLowerC, Some('c')),
924 code_map_single_byte!(0x64, Code::LatinLowerD, Some('d')),
925 code_map_single_byte!(0x65, Code::LatinLowerE, Some('e')),
926 code_map_single_byte!(0x66, Code::LatinLowerF, Some('f')),
927 code_map_single_byte!(0x67, Code::LatinLowerG, Some('g')),
928 code_map_single_byte!(0x68, Code::LatinLowerH, Some('h')),
929 code_map_single_byte!(0x69, Code::LatinLowerI, Some('i')),
930 code_map_single_byte!(0x6A, Code::LatinLowerJ, Some('j')),
931 code_map_single_byte!(0x6B, Code::LatinLowerK, Some('k')),
932 code_map_single_byte!(0x6C, Code::LatinLowerL, Some('l')),
933 code_map_single_byte!(0x6D, Code::LatinLowerM, Some('m')),
934 code_map_single_byte!(0x6E, Code::LatinLowerN, Some('n')),
935 code_map_single_byte!(0x6F, Code::LatinLowerO, Some('o')),
936 code_map_single_byte!(0x70, Code::LatinLowerP, Some('p')),
937 code_map_single_byte!(0x71, Code::LatinLowerQ, Some('q')),
938 code_map_single_byte!(0x72, Code::LatinLowerR, Some('r')),
939 code_map_single_byte!(0x73, Code::LatinLowerS, Some('s')),
940 code_map_single_byte!(0x74, Code::LatinLowerT, Some('t')),
941 code_map_single_byte!(0x75, Code::LatinLowerU, Some('u')),
942 code_map_single_byte!(0x76, Code::LatinLowerV, Some('v')),
943 code_map_single_byte!(0x77, Code::LatinLowerW, Some('w')),
944 code_map_single_byte!(0x78, Code::LatinLowerX, Some('x')),
945 code_map_single_byte!(0x79, Code::LatinLowerY, Some('y')),
946 code_map_single_byte!(0x7A, Code::LatinLowerZ, Some('z')),
947 code_map_single_byte!(0x7B, Code::LatinLowerCWithCedilla, Some('ç')),
948 code_map_single_byte!(0x7C, Code::DivisionSign, Some('÷')),
949 code_map_single_byte!(0x7D, Code::LatinCapitalNWithTilde, Some('Ñ')),
950 code_map_single_byte!(0x7E, Code::LatinLowerNWithTilde, Some('ñ')),
951 code_map_single_byte!(0x7F, Code::SolidBlock, Some('█')),
952];
953
954#[derive(Debug, Clone)]
955struct ControlMap {
956 cea608_bytes: [u8; 2],
957 control: Control,
958 utf8: Option<char>,
959}
960
961macro_rules! control_map_bytes {
962 ($bytes:expr, $control:expr, $utf8:expr) => {
963 ControlMap {
964 cea608_bytes: $bytes,
965 control: $control,
966 utf8: $utf8,
967 }
968 };
969}
970
971static CONTROL_MAP_TABLE: [ControlMap; 99] = [
972 control_map_bytes!([0x11, 0x30], Control::RegisteredTrademarkSign, Some('Ⓡ')),
973 control_map_bytes!([0x11, 0x31], Control::DegreeSign, Some('°')),
974 control_map_bytes!([0x11, 0x32], Control::Fraction12, Some('½')),
975 control_map_bytes!([0x11, 0x33], Control::InvertedQuestionMark, Some('¿')),
976 control_map_bytes!([0x11, 0x34], Control::TradeMarkSign, Some('™')),
977 control_map_bytes!([0x11, 0x35], Control::CentSign, Some('¢')),
978 control_map_bytes!([0x11, 0x36], Control::PoundSign, Some('£')),
979 control_map_bytes!([0x11, 0x37], Control::MusicalNote, Some('♪')),
980 control_map_bytes!([0x11, 0x38], Control::LatinLowerAWithGrave, Some('à')),
981 control_map_bytes!([0x11, 0x39], Control::TransparentSpace, None),
982 control_map_bytes!([0x11, 0x3a], Control::LatinLowerEWithGrave, Some('è')),
983 control_map_bytes!([0x11, 0x3b], Control::LatinLowerAWithCircumflex, Some('â')),
984 control_map_bytes!([0x11, 0x3c], Control::LatinLowerEWithCircumflex, Some('ê')),
985 control_map_bytes!([0x11, 0x3d], Control::LatinLowerIWithCircumflex, Some('î')),
986 control_map_bytes!([0x11, 0x3e], Control::LatinLowerOWithCircumflex, Some('ô')),
987 control_map_bytes!([0x11, 0x3f], Control::LatinLowerUWithCircumflex, Some('û')),
988 control_map_bytes!([0x12, 0x20], Control::LatinCapitalAWithAcute, Some('Á')),
989 control_map_bytes!([0x12, 0x21], Control::LatinCapitalEWithAcute, Some('É')),
990 control_map_bytes!([0x12, 0x22], Control::LatinCapitalOWithAcute, Some('Ó')),
991 control_map_bytes!([0x12, 0x23], Control::LatinCapitalUWithAcute, Some('Ú')),
992 control_map_bytes!(
993 [0x12, 0x24],
994 Control::LatinCapitalUWithDiaeseresis,
995 Some('Ü')
996 ),
997 control_map_bytes!([0x12, 0x25], Control::LatinLowerUWithDiaeseresis, Some('ü')),
998 control_map_bytes!([0x12, 0x26], Control::OpeningSingleQuote, Some('‘')),
999 control_map_bytes!([0x12, 0x27], Control::InvertedExclamationMark, Some('¡')),
1000 control_map_bytes!([0x12, 0x28], Control::Asterisk, Some('*')),
1001 control_map_bytes!([0x12, 0x29], Control::SingleOpenQuote, Some('\'')),
1002 control_map_bytes!([0x12, 0x2a], Control::EmDash, Some('—')),
1003 control_map_bytes!([0x12, 0x2b], Control::CopyrightSign, Some('Ⓒ')),
1004 control_map_bytes!([0x12, 0x2c], Control::ServiceMarkSign, Some('℠')),
1005 control_map_bytes!([0x12, 0x2d], Control::RoundBullet, None),
1006 control_map_bytes!([0x12, 0x2e], Control::DoubleOpenQuote, Some('“')),
1007 control_map_bytes!([0x12, 0x2f], Control::DoubleCloseQuote, Some('”')),
1008 control_map_bytes!([0x12, 0x30], Control::LatinCapitalAWithGrave, Some('À')),
1009 control_map_bytes!(
1010 [0x12, 0x31],
1011 Control::LatinCapitalAWithCircumflex,
1012 Some('Â')
1013 ),
1014 control_map_bytes!([0x12, 0x32], Control::LatinCapitalCWithCedilla, Some('Ç')),
1015 control_map_bytes!([0x12, 0x33], Control::LatinCapitalEWithGrave, Some('È')),
1016 control_map_bytes!(
1017 [0x12, 0x34],
1018 Control::LatinCapitalEWithCircumflex,
1019 Some('Ê')
1020 ),
1021 control_map_bytes!([0x12, 0x35], Control::LatinCapitalEWithDiaeresis, Some('Ë')),
1022 control_map_bytes!([0x12, 0x36], Control::LatinLowerEWithDiaeresis, Some('ë')),
1023 control_map_bytes!(
1024 [0x12, 0x37],
1025 Control::LatinCapitalIWithCircumflex,
1026 Some('Î')
1027 ),
1028 control_map_bytes!([0x12, 0x38], Control::LatinCapitalIWithDiaeresis, Some('Ï')),
1029 control_map_bytes!([0x12, 0x39], Control::LatinLowerIWithDiaeresis, Some('ï')),
1030 control_map_bytes!(
1031 [0x12, 0x3a],
1032 Control::LatinCapitalOWithCircumflex,
1033 Some('Ô')
1034 ),
1035 control_map_bytes!([0x12, 0x3b], Control::LatinCapitalUWithGrave, Some('Ù')),
1036 control_map_bytes!([0x12, 0x3c], Control::LatinLowerUWithGrave, Some('ù')),
1037 control_map_bytes!(
1038 [0x12, 0x3d],
1039 Control::LatinCapitalUWithCircumflex,
1040 Some('Û')
1041 ),
1042 control_map_bytes!([0x12, 0x3e], Control::OpeningGuillemets, Some('«')),
1043 control_map_bytes!([0x12, 0x3f], Control::ClosingGuillemets, Some('»')),
1044 control_map_bytes!([0x13, 0x20], Control::LatinCapitalAWithTilde, Some('Ã')),
1045 control_map_bytes!([0x13, 0x21], Control::LatinLowerAWithTilde, Some('ã')),
1046 control_map_bytes!([0x13, 0x22], Control::LatinCapitalIWithAcute, Some('Í')),
1047 control_map_bytes!([0x13, 0x23], Control::LatinCapitalIWithGrave, Some('Ì')),
1048 control_map_bytes!([0x13, 0x24], Control::LatinLowerIWithGrave, Some('ì')),
1049 control_map_bytes!([0x13, 0x25], Control::LatinCapitalOWithGrave, Some('Ò')),
1050 control_map_bytes!([0x13, 0x26], Control::LatinLowerOWithGrave, Some('ò')),
1051 control_map_bytes!([0x13, 0x27], Control::LatinCapitalOWithTilde, Some('Õ')),
1052 control_map_bytes!([0x13, 0x28], Control::LatinLowerOWithTilde, Some('õ')),
1053 control_map_bytes!([0x13, 0x29], Control::OpeningBrace, Some('{')),
1054 control_map_bytes!([0x13, 0x2a], Control::ClosingBrace, Some('}')),
1055 control_map_bytes!([0x13, 0x2b], Control::ReverseSolidus, Some('\\')),
1056 control_map_bytes!([0x13, 0x2c], Control::Caret, Some('^')),
1057 control_map_bytes!([0x13, 0x2d], Control::Underbar, Some('_')),
1058 control_map_bytes!([0x13, 0x2e], Control::Pipe, Some('|')),
1059 control_map_bytes!([0x13, 0x2f], Control::Tilde, Some('~')),
1060 control_map_bytes!([0x13, 0x30], Control::LatinCapitalAWithDiaeresis, Some('Ä')),
1061 control_map_bytes!([0x13, 0x31], Control::LatinLowerAWithDiaeresis, Some('ä')),
1062 control_map_bytes!([0x13, 0x32], Control::LatinCapitalOWithDiaeresis, Some('Ö')),
1063 control_map_bytes!([0x13, 0x33], Control::LatinLowerOWithDiaeresis, Some('ö')),
1064 control_map_bytes!([0x13, 0x34], Control::LatinLowerSharpS, Some('ß')),
1065 control_map_bytes!([0x13, 0x35], Control::YenSign, Some('¥')),
1066 control_map_bytes!([0x13, 0x36], Control::GeneralCurrencySign, Some('¤')),
1067 control_map_bytes!([0x13, 0x37], Control::VerticalBar, Some('¦')),
1068 control_map_bytes!([0x13, 0x38], Control::LatinCapitalAWithRingAbove, Some('Å')),
1069 control_map_bytes!([0x13, 0x39], Control::LatinLowerAWithRingAbove, Some('å')),
1070 control_map_bytes!([0x13, 0x3a], Control::LatinCapitalOWithStroke, Some('Ø')),
1071 control_map_bytes!([0x13, 0x3b], Control::LatinLowerOWithStroke, Some('ø')),
1072 control_map_bytes!([0x13, 0x3c], Control::UpperLeftBorder, None),
1073 control_map_bytes!([0x13, 0x3d], Control::UpperRightBorder, None),
1074 control_map_bytes!([0x13, 0x3e], Control::LowerLeftBorder, None),
1075 control_map_bytes!([0x13, 0x3f], Control::LowerRightBorder, None),
1076 control_map_bytes!([0x14, 0x20], Control::ResumeCaptionLoading, None),
1077 control_map_bytes!([0x14, 0x21], Control::Backspace, None),
1078 control_map_bytes!([0x14, 0x22], Control::AlarmOff, None),
1079 control_map_bytes!([0x14, 0x23], Control::AlarmOn, None),
1080 control_map_bytes!([0x14, 0x24], Control::DeleteToEndOfRow, None),
1081 control_map_bytes!([0x14, 0x25], Control::RollUp2, None),
1082 control_map_bytes!([0x14, 0x26], Control::RollUp3, None),
1083 control_map_bytes!([0x14, 0x27], Control::RollUp4, None),
1084 control_map_bytes!([0x14, 0x28], Control::FlashOn, None),
1085 control_map_bytes!([0x14, 0x29], Control::ResumeDirectionCaptioning, None),
1086 control_map_bytes!([0x14, 0x2a], Control::TextRestart, None),
1087 control_map_bytes!([0x14, 0x2b], Control::ResumeTextDisplay, None),
1088 control_map_bytes!([0x14, 0x2c], Control::EraseDisplayedMemory, None),
1089 control_map_bytes!([0x14, 0x2d], Control::CarriageReturn, None),
1090 control_map_bytes!([0x14, 0x2e], Control::EraseNonDisplayedMemory, None),
1091 control_map_bytes!([0x14, 0x2f], Control::EndOfCaption, None),
1092 control_map_bytes!([0x17, 0x21], Control::TabOffset1, None),
1093 control_map_bytes!([0x17, 0x22], Control::TabOffset2, None),
1094 control_map_bytes!([0x17, 0x23], Control::TabOffset3, None),
1095];
1096
1097fn strip_parity(byte: u8) -> u8 {
1098 byte & 0x7F
1099}
1100
1101fn add_parity(byte: u8) -> u8 {
1102 debug_assert!((byte & 0x80) == 0);
1103 if check_odd_parity(byte) {
1104 byte
1105 } else {
1106 byte | 0x80
1107 }
1108}
1109
1110fn check_odd_parity(byte: u8) -> bool {
1111 byte.count_ones() % 2 == 1
1112}
1113
1114fn parse_control_code(data: [u8; 2]) -> ControlCode {
1115 let channel = data[0] & 0x08;
1116 let underline = data[1] & 0x1 != 0;
1117 let mut byte0 = data[0] & !0x08;
1118 let field = if (0x20..=0x2f).contains(&data[1]) {
1119 match data[0] & !0x08 {
1120 0x14 => Some(Field::ONE),
1121 0x15 => {
1122 byte0 &= !0x01;
1123 Some(Field::TWO)
1124 }
1125 _ => None,
1126 }
1127 } else {
1128 None
1129 };
1130
1131 ControlCode {
1132 field,
1133 channel: Channel(channel == 0),
1134 control: match (byte0, data[1]) {
1135 (0x11, 0x20 | 0x21) => Control::MidRow(MidRow {
1136 color: MidRowColor::Color(Color::White),
1137 underline,
1138 }),
1139 (0x11, 0x22 | 0x23) => Control::MidRow(MidRow {
1140 color: MidRowColor::Color(Color::Green),
1141 underline,
1142 }),
1143 (0x11, 0x24 | 0x25) => Control::MidRow(MidRow {
1144 color: MidRowColor::Color(Color::Blue),
1145 underline,
1146 }),
1147 (0x11, 0x26 | 0x27) => Control::MidRow(MidRow {
1148 color: MidRowColor::Color(Color::Cyan),
1149 underline,
1150 }),
1151 (0x11, 0x28 | 0x29) => Control::MidRow(MidRow {
1152 color: MidRowColor::Color(Color::Red),
1153 underline,
1154 }),
1155 (0x11, 0x2a | 0x2b) => Control::MidRow(MidRow {
1156 color: MidRowColor::Color(Color::Yellow),
1157 underline,
1158 }),
1159 (0x11, 0x2c | 0x2d) => Control::MidRow(MidRow {
1160 color: MidRowColor::Color(Color::Magenta),
1161 underline,
1162 }),
1163 (0x11, 0x2e | 0x2f) => Control::MidRow(MidRow {
1164 color: MidRowColor::Italics,
1165 underline,
1166 }),
1167 (0x10..=0x19, 0x20..=0x3f) => {
1168 let idx = CONTROL_MAP_TABLE
1169 .binary_search_by_key(&[byte0, data[1]], |control_map| {
1170 control_map.cea608_bytes
1171 });
1172 idx.map(|idx| CONTROL_MAP_TABLE[idx].control)
1173 .unwrap_or_else(|_| Control::Unknown(data))
1174 }
1175 (byte0, 0x40..=0x7f) => {
1176 if let Some(preamble) = parse_preamble(byte0, data[1]) {
1177 Control::PreambleAddress(preamble)
1178 } else {
1179 Control::Unknown(data)
1180 }
1181 }
1182 _ => Control::Unknown(data),
1183 },
1184 }
1185}
1186
1187fn parse_preamble(byte0: u8, byte1: u8) -> Option<PreambleAddressCode> {
1188 let underline = byte1 & 0x1 != 0;
1189 let row = match (byte0, byte1) {
1190 (0x11, 0x40..=0x5f) => 0,
1191 (0x11, 0x60..=0x7f) => 1,
1192 (0x12, 0x40..=0x5f) => 2,
1193 (0x12, 0x60..=0x7f) => 3,
1194 (0x15, 0x40..=0x5f) => 4,
1195 (0x15, 0x60..=0x7f) => 5,
1196 (0x16, 0x40..=0x5f) => 6,
1197 (0x16, 0x60..=0x7f) => 7,
1198 (0x17, 0x40..=0x5f) => 8,
1199 (0x17, 0x60..=0x7f) => 9,
1200 (0x10, 0x40..=0x5f) => 10,
1201 (0x13, 0x40..=0x5f) => 11,
1202 (0x13, 0x60..=0x7f) => 12,
1203 (0x14, 0x40..=0x5f) => 13,
1204 (0x14, 0x60..=0x7f) => 14,
1205 _ => return None,
1206 };
1207 let ty = match byte1 & 0x1e {
1208 0x00 => PreambleType::Color(Color::White),
1209 0x02 => PreambleType::Color(Color::Green),
1210 0x04 => PreambleType::Color(Color::Blue),
1211 0x06 => PreambleType::Color(Color::Cyan),
1212 0x08 => PreambleType::Color(Color::Red),
1213 0x0a => PreambleType::Color(Color::Yellow),
1214 0x0c => PreambleType::Color(Color::Magenta),
1215 0x0e => PreambleType::WhiteItalics,
1216 0x10 => PreambleType::Indent0,
1217 0x12 => PreambleType::Indent4,
1218 0x14 => PreambleType::Indent8,
1219 0x16 => PreambleType::Indent12,
1220 0x18 => PreambleType::Indent16,
1221 0x1a => PreambleType::Indent20,
1222 0x1c => PreambleType::Indent24,
1223 0x1e => PreambleType::Indent28,
1224 _ => return None,
1225 };
1226 Some(PreambleAddressCode { row, underline, ty })
1227}
1228
1229impl Code {
1230 pub fn byte_len(&self) -> usize {
1238 match self {
1239 Code::Control(_) => 2,
1240 _ => 1,
1241 }
1242 }
1243
1244 pub fn from_data(data: [u8; 2]) -> Result<[Code; 2], CodeError> {
1252 if !check_odd_parity(data[0]) {
1253 return Err(CodeError::InvalidParity);
1254 }
1255 if !check_odd_parity(data[1]) {
1256 return Err(CodeError::InvalidParity);
1257 }
1258 let data = [strip_parity(data[0]), strip_parity(data[1])];
1259
1260 if (0x10..=0x1F).contains(&data[0]) {
1261 Ok([Code::Control(parse_control_code(data)), Code::NUL])
1262 } else {
1263 let code0 = CODE_MAP_TABLE
1264 .binary_search_by_key(&[data[0]].as_slice(), |code_map| code_map.cea608_bytes);
1265 let code1 = CODE_MAP_TABLE
1266 .binary_search_by_key(&[data[1]].as_slice(), |code_map| code_map.cea608_bytes);
1267 Ok([
1268 code0
1269 .map(|idx| CODE_MAP_TABLE[idx].code)
1270 .unwrap_or_else(|_| Code::Unknown(data[0])),
1271 code1
1272 .map(|idx| CODE_MAP_TABLE[idx].code)
1273 .unwrap_or_else(|_| Code::Unknown(data[1])),
1274 ])
1275 }
1276 }
1277
1278 pub fn write<W: std::io::Write>(&self, w: &mut W) -> Result<(), std::io::Error> {
1288 match self {
1289 Code::Unknown(data) => {
1290 return w.write_all(&[add_parity(*data)]);
1291 }
1292 Code::Control(control) => return w.write_all(&control.write()),
1293 _ => {
1294 if let Ok(idx) =
1295 CODE_MAP_TABLE.binary_search_by_key(&self, |code_map| &code_map.code)
1296 {
1297 let data = CODE_MAP_TABLE[idx]
1298 .cea608_bytes
1299 .iter()
1300 .map(|b| add_parity(*b))
1301 .collect::<Vec<_>>();
1302 return w.write_all(&data);
1303 }
1304 }
1305 }
1306 unreachable!()
1307 }
1308
1309 pub fn write_into(&self, bytes: &mut [u8; 2]) -> usize {
1319 match self {
1320 Code::Unknown(data) => {
1321 bytes[0] = add_parity(*data);
1322 bytes[1] = 0x80;
1323 return 1;
1324 }
1325 Code::Control(control) => {
1326 bytes.copy_from_slice(&control.write());
1327 return 2;
1328 }
1329 _ => {
1330 if let Ok(idx) =
1331 CODE_MAP_TABLE.binary_search_by_key(&self, |code_map| &code_map.code)
1332 {
1333 let len = CODE_MAP_TABLE[idx].cea608_bytes.len();
1334 for (i, b) in CODE_MAP_TABLE[idx]
1335 .cea608_bytes
1336 .iter()
1337 .map(|b| add_parity(*b))
1338 .chain([0x80, 0x80].into_iter())
1339 .enumerate()
1340 .take(2)
1341 {
1342 bytes[i] = b;
1343 }
1344 return len;
1345 }
1346 }
1347 }
1348 unreachable!()
1349 }
1350
1351 pub fn char(&self) -> Option<char> {
1361 if let Code::Control(ControlCode { control, .. }) = self {
1364 return CONTROL_MAP_TABLE.iter().find_map(|control_map| {
1365 if control_map.control == *control {
1366 control_map.utf8
1367 } else {
1368 None
1369 }
1370 });
1371 }
1372
1373 CODE_MAP_TABLE.iter().find_map(|code_map| {
1374 if code_map.code == *self {
1375 code_map.utf8
1376 } else {
1377 None
1378 }
1379 })
1380 }
1381
1382 pub fn from_char(c: char, channel: Channel) -> Option<Code> {
1392 CODE_MAP_TABLE
1395 .iter()
1396 .find_map(|code_map| {
1397 if code_map.utf8 == Some(c) {
1398 Some(code_map.code)
1399 } else {
1400 None
1401 }
1402 })
1403 .or_else(|| {
1404 CONTROL_MAP_TABLE.iter().find_map(|control_map| {
1405 if control_map.utf8 == Some(c) {
1406 Some(Code::Control(ControlCode {
1407 field: None,
1408 channel,
1409 control: control_map.control,
1410 }))
1411 } else {
1412 None
1413 }
1414 })
1415 })
1416 }
1417
1418 pub fn needs_backspace(&self) -> bool {
1420 let Code::Control(ControlCode {
1421 field: _,
1422 channel: _,
1423 control,
1424 }) = self
1425 else {
1426 return false;
1427 };
1428 matches!(
1429 control,
1430 Control::MidRow(_)
1431 | Control::LatinCapitalAWithAcute
1432 | Control::LatinCapitalEWithAcute
1433 | Control::LatinCapitalOWithAcute
1434 | Control::LatinCapitalUWithAcute
1435 | Control::LatinCapitalUWithDiaeseresis
1436 | Control::LatinLowerUWithDiaeseresis
1437 | Control::OpeningSingleQuote
1438 | Control::InvertedExclamationMark
1439 | Control::Asterisk
1441 | Control::SingleOpenQuote
1442 | Control::EmDash
1443 | Control::CopyrightSign
1444 | Control::ServiceMarkSign
1445 | Control::RoundBullet
1446 | Control::DoubleOpenQuote
1447 | Control::DoubleCloseQuote
1448 | Control::LatinCapitalAWithGrave
1450 | Control::LatinCapitalAWithCircumflex
1451 | Control::LatinCapitalCWithCedilla
1452 | Control::LatinCapitalEWithGrave
1453 | Control::LatinCapitalEWithCircumflex
1454 | Control::LatinCapitalEWithDiaeresis
1455 | Control::LatinLowerEWithDiaeresis
1456 | Control::LatinCapitalIWithCircumflex
1457 | Control::LatinCapitalIWithDiaeresis
1458 | Control::LatinLowerIWithDiaeresis
1459 | Control::LatinCapitalOWithCircumflex
1460 | Control::LatinCapitalUWithGrave
1461 | Control::LatinLowerUWithGrave
1462 | Control::LatinCapitalUWithCircumflex
1463 | Control::OpeningGuillemets
1464 | Control::ClosingGuillemets
1465 | Control::LatinCapitalAWithTilde
1467 | Control::LatinLowerAWithTilde
1468 | Control::LatinCapitalIWithAcute
1469 | Control::LatinCapitalIWithGrave
1470 | Control::LatinLowerIWithGrave
1471 | Control::LatinCapitalOWithGrave
1472 | Control::LatinLowerOWithGrave
1473 | Control::LatinCapitalOWithTilde
1474 | Control::LatinLowerOWithTilde
1475 | Control::OpeningBrace
1476 | Control::ClosingBrace
1477 | Control::ReverseSolidus
1478 | Control::Caret
1479 | Control::Underbar
1480 | Control::Pipe
1481 | Control::Tilde
1482 | Control::LatinCapitalAWithDiaeresis
1484 | Control::LatinLowerAWithDiaeresis
1485 | Control::LatinCapitalOWithDiaeresis
1486 | Control::LatinLowerOWithDiaeresis
1487 | Control::LatinLowerSharpS
1488 | Control::YenSign
1489 | Control::GeneralCurrencySign
1490 | Control::VerticalBar
1491 | Control::LatinCapitalAWithRingAbove
1493 | Control::LatinLowerAWithRingAbove
1494 | Control::LatinCapitalOWithStroke
1495 | Control::LatinLowerOWithStroke
1496 | Control::UpperLeftBorder
1497 | Control::UpperRightBorder
1498 | Control::LowerLeftBorder
1499 | Control::LowerRightBorder
1500 )
1501 }
1502}
1503
1504#[cfg(test)]
1505mod test {
1506 use super::*;
1507 use crate::tests::*;
1508
1509 #[test]
1510 fn codes_table_ordered() {
1511 test_init_log();
1512 let mut iter = CODE_MAP_TABLE.iter().peekable();
1513 while let Some(code_map) = iter.next() {
1514 if let Some(peek) = iter.peek() {
1515 trace!("checking ordinality for {code_map:?} and {peek:?}");
1516 assert!(peek.code > code_map.code);
1517 assert!(peek.cea608_bytes > code_map.cea608_bytes);
1518 }
1519 }
1520 }
1521
1522 #[test]
1523 fn control_table_ordered() {
1524 test_init_log();
1525 let mut iter = CONTROL_MAP_TABLE.iter().peekable();
1526 while let Some(control_map) = iter.next() {
1527 if let Some(peek) = iter.peek() {
1528 trace!("checking ordinality for {control_map:?} and {peek:?}");
1529 assert!(peek.control > control_map.control);
1530 assert!(peek.cea608_bytes > control_map.cea608_bytes);
1531 }
1532 }
1533 }
1534
1535 #[test]
1536 fn codes_to_from_bytes() {
1537 test_init_log();
1538 for code_map in CODE_MAP_TABLE.iter() {
1539 trace!("parsing {code_map:?}");
1540 let mut data = Vec::from_iter(code_map.cea608_bytes.iter().map(|b| add_parity(*b)));
1541 data.resize(2, 0x80);
1542 let parsed_code = Code::from_data(data.try_into().unwrap()).unwrap();
1543 assert_eq!(parsed_code[0], code_map.code);
1544 let mut written = vec![];
1545 parsed_code[0].write(&mut written).unwrap();
1546 assert_eq!(written.len(), code_map.code.byte_len());
1547 let written = written.iter().map(|b| strip_parity(*b)).collect::<Vec<_>>();
1548 assert_eq!(written, code_map.cea608_bytes);
1549 }
1550 }
1551
1552 #[test]
1553 fn codes_to_from_char() {
1554 test_init_log();
1555 for code_map in CODE_MAP_TABLE.iter() {
1556 trace!("parsing {code_map:?}");
1557 if let Some(c) = code_map.utf8 {
1558 let parsed_code = Code::from_char(c, Channel(true)).unwrap();
1559 assert_eq!(parsed_code.char(), code_map.utf8);
1560 assert_eq!(parsed_code, code_map.code);
1561 let mut written = vec![];
1562 parsed_code.write(&mut written).unwrap();
1563 let written = written.iter().map(|b| strip_parity(*b)).collect::<Vec<_>>();
1564 assert_eq!(written, code_map.cea608_bytes);
1565 }
1566 }
1567 }
1568
1569 #[test]
1570 fn preamble_to_from_bytes() {
1571 test_init_log();
1572 let tys = [
1573 PreambleType::Color(Color::White),
1574 PreambleType::Color(Color::Green),
1575 PreambleType::Color(Color::Blue),
1576 PreambleType::Color(Color::Cyan),
1577 PreambleType::Color(Color::Red),
1578 PreambleType::Color(Color::Yellow),
1579 PreambleType::Color(Color::Magenta),
1580 PreambleType::WhiteItalics,
1581 PreambleType::Indent0,
1582 PreambleType::Indent4,
1583 PreambleType::Indent8,
1584 PreambleType::Indent12,
1585 PreambleType::Indent16,
1586 PreambleType::Indent20,
1587 PreambleType::Indent24,
1588 PreambleType::Indent28,
1589 ];
1590 for row in 0..=14 {
1591 for underline in [true, false] {
1592 for ty in tys {
1593 for channel in [Channel::ONE, Channel::TWO] {
1594 let preamble = Code::Control(ControlCode {
1595 field: None,
1596 channel,
1597 control: Control::PreambleAddress(PreambleAddressCode {
1598 row,
1599 underline,
1600 ty,
1601 }),
1602 });
1603 debug!("{preamble:?}");
1604 let mut data = vec![];
1605 preamble.write(&mut data).unwrap();
1606 debug!("{data:x?}");
1607 let parsed = Code::from_data([data[0], data[1]]).unwrap();
1608 assert_eq!(preamble, parsed[0]);
1609 }
1610 }
1611 }
1612 }
1613 }
1614
1615 #[test]
1616 fn midrow_to_from_bytes() {
1617 test_init_log();
1618 let colors = [
1619 MidRowColor::Color(Color::White),
1620 MidRowColor::Color(Color::Green),
1621 MidRowColor::Color(Color::Blue),
1622 MidRowColor::Color(Color::Cyan),
1623 MidRowColor::Color(Color::Red),
1624 MidRowColor::Color(Color::Yellow),
1625 MidRowColor::Color(Color::Magenta),
1626 MidRowColor::Italics,
1627 ];
1628 for underline in [true, false] {
1629 for color in colors {
1630 for channel in [Channel::ONE, Channel::TWO] {
1631 let midrow = Code::Control(ControlCode {
1632 field: None,
1633 channel,
1634 control: Control::MidRow(MidRow { underline, color }),
1635 });
1636 debug!("{midrow:?}");
1637 let mut data = vec![];
1638 midrow.write(&mut data).unwrap();
1639 debug!("{data:x?}");
1640 let parsed = Code::from_data([data[0], data[1]]).unwrap();
1641 assert_eq!(midrow, parsed[0]);
1642 }
1643 }
1644 }
1645 }
1646
1647 #[test]
1648 fn field2_control_to_from_bytes() {
1649 test_init_log();
1650 let codes = [
1651 Control::ResumeCaptionLoading,
1652 Control::Backspace,
1653 Control::AlarmOff,
1654 Control::AlarmOn,
1655 Control::DeleteToEndOfRow,
1656 Control::RollUp2,
1657 Control::RollUp3,
1658 Control::RollUp4,
1659 Control::FlashOn,
1660 Control::ResumeDirectionCaptioning,
1661 Control::TextRestart,
1662 Control::ResumeTextDisplay,
1663 Control::EraseDisplayedMemory,
1664 Control::CarriageReturn,
1665 Control::EraseNonDisplayedMemory,
1666 Control::EndOfCaption,
1667 ];
1668 for control in codes {
1669 for field in [Field::ONE, Field::TWO] {
1670 for channel in [Channel::ONE, Channel::TWO] {
1671 let control = Code::Control(ControlCode {
1672 field: Some(field),
1673 channel,
1674 control,
1675 });
1676 debug!("{control:?}");
1677 let mut data = vec![];
1678 control.write(&mut data).unwrap();
1679 debug!("{data:x?}");
1680 let parsed = Code::from_data([data[0], data[1]]).unwrap();
1681 assert_eq!(control, parsed[0]);
1682 }
1683 }
1684 }
1685 }
1686
1687 #[test]
1688 fn control_code_to_from_char() {
1689 test_init_log();
1690
1691 for control in CONTROL_MAP_TABLE.iter() {
1692 let Some(utf8) = control.utf8 else {
1693 continue;
1694 };
1695
1696 debug!("{control:?}");
1697 let orig = Code::Control(ControlCode {
1698 field: None,
1699 channel: Channel::ONE,
1700 control: control.control,
1701 });
1702 assert_eq!(Some(utf8), orig.char());
1703 let code = Code::from_char(utf8, Channel::ONE).unwrap();
1704 assert_eq!(orig, code);
1705 assert_eq!(Some(utf8), code.char());
1706 }
1707 }
1708}