1#![allow(dead_code)]
17
18#[derive(Clone, Debug, PartialEq)]
20pub struct Dtvcc708Packet {
21 pub sequence: u8,
23 pub data: Vec<u8>,
25}
26
27impl Dtvcc708Packet {
28 #[must_use]
30 pub fn new(sequence: u8, data: Vec<u8>) -> Self {
31 Self { sequence, data }
32 }
33}
34
35#[derive(Clone, Debug, PartialEq)]
37pub struct CaptionWindow {
38 pub id: u8,
40 pub visible: bool,
42 pub row_count: u8,
44 pub col_count: u8,
46 pub text: String,
48 pub pen_row: u8,
50 pub pen_col: u8,
52}
53
54impl CaptionWindow {
55 #[must_use]
57 pub fn new(id: u8) -> Self {
58 Self {
59 id,
60 visible: false,
61 row_count: 15,
62 col_count: 32,
63 text: String::new(),
64 pen_row: 0,
65 pen_col: 0,
66 }
67 }
68}
69
70impl Default for CaptionWindow {
71 fn default() -> Self {
72 Self::new(0)
73 }
74}
75
76#[derive(Clone, Debug, PartialEq)]
78pub enum Dtvcc708Command {
79 SetCurrentWindow(u8),
81 DeleteWindows(u8),
83 DisplayWindows(u8),
85 ClearWindows(u8),
87 HideWindows(u8),
89 SetWindowAttributes,
91 SetPenAttributes,
93 SetPenColor,
95 SetPenLocation {
97 row: u8,
99 col: u8,
101 },
102 Text(String),
104 Backspace,
106 FormFeed,
108 CarriageReturn,
110 Unknown(u8),
112}
113
114const NUM_WINDOWS: usize = 8;
118
119pub struct Dtvcc708Decoder {
123 windows: [CaptionWindow; NUM_WINDOWS],
125 current_window: usize,
127}
128
129impl Dtvcc708Decoder {
130 #[must_use]
132 pub fn new() -> Self {
133 Self {
134 windows: [
135 CaptionWindow::new(0),
136 CaptionWindow::new(1),
137 CaptionWindow::new(2),
138 CaptionWindow::new(3),
139 CaptionWindow::new(4),
140 CaptionWindow::new(5),
141 CaptionWindow::new(6),
142 CaptionWindow::new(7),
143 ],
144 current_window: 0,
145 }
146 }
147
148 pub fn decode(&mut self, packet: &Dtvcc708Packet) -> Vec<CaptionWindow> {
152 let commands = Self::parse_commands(&packet.data);
153 for cmd in commands {
154 self.apply_command(&cmd);
155 }
156 self.windows.to_vec()
157 }
158
159 #[must_use]
161 pub fn current_window(&self) -> usize {
162 self.current_window
163 }
164
165 #[must_use]
167 pub fn windows(&self) -> &[CaptionWindow; NUM_WINDOWS] {
168 &self.windows
169 }
170
171 fn parse_commands(data: &[u8]) -> Vec<Dtvcc708Command> {
175 let mut commands = Vec::new();
176 let mut i = 0;
177 let mut text_buf = String::new();
178
179 while i < data.len() {
180 let byte = data[i];
181
182 match byte {
183 0x20..=0x7E => {
185 text_buf.push(byte as char);
186 i += 1;
187 }
188 0x7F => {
190 i += 1;
191 }
192 _ => {
194 if !text_buf.is_empty() {
195 commands.push(Dtvcc708Command::Text(text_buf.clone()));
196 text_buf.clear();
197 }
198
199 match byte {
200 0x08 => {
202 commands.push(Dtvcc708Command::Backspace);
203 i += 1;
204 }
205 0x0C => {
207 commands.push(Dtvcc708Command::FormFeed);
208 i += 1;
209 }
210 0x0D => {
212 commands.push(Dtvcc708Command::CarriageReturn);
213 i += 1;
214 }
215 0x80..=0x87 => {
217 let win_id = byte & 0x07;
218 commands.push(Dtvcc708Command::SetCurrentWindow(win_id));
219 i += 1;
220 }
221 0x88 => {
223 i += 1;
224 let mask = data.get(i).copied().unwrap_or(0);
225 commands.push(Dtvcc708Command::ClearWindows(mask));
226 i += 1;
227 }
228 0x89 => {
230 i += 1;
231 let mask = data.get(i).copied().unwrap_or(0);
232 commands.push(Dtvcc708Command::DisplayWindows(mask));
233 i += 1;
234 }
235 0x8A => {
237 i += 1;
238 let mask = data.get(i).copied().unwrap_or(0);
239 commands.push(Dtvcc708Command::HideWindows(mask));
240 i += 1;
241 }
242 0x8B => {
244 i += 2; }
246 0x8C => {
248 i += 1;
249 let mask = data.get(i).copied().unwrap_or(0);
250 commands.push(Dtvcc708Command::DeleteWindows(mask));
251 i += 1;
252 }
253 0x8D => {
255 i += 2;
256 }
257 0x8E => {
259 i += 1;
260 }
261 0x8F => {
263 i += 1;
264 }
265 0x91..=0x97 => {
267 let win_id = byte & 0x07;
268 commands.push(Dtvcc708Command::SetCurrentWindow(win_id));
269 i += 1;
270 }
271 0x98 => {
273 commands.push(Dtvcc708Command::SetWindowAttributes);
274 i += 1 + 4; }
276 0x99 => {
278 commands.push(Dtvcc708Command::SetPenAttributes);
279 i += 1 + 2;
280 }
281 0x9A => {
283 commands.push(Dtvcc708Command::SetPenColor);
284 i += 1 + 3;
285 }
286 0x9B => {
288 i += 1;
289 let row = data.get(i).copied().unwrap_or(0) & 0x0F;
290 i += 1;
291 let col = data.get(i).copied().unwrap_or(0) & 0x3F;
292 i += 1;
293 commands.push(Dtvcc708Command::SetPenLocation { row, col });
294 }
295 0x10..=0x17 => {
297 i += 2;
298 }
299 _ => {
301 commands.push(Dtvcc708Command::Unknown(byte));
302 i += 1;
303 }
304 }
305 }
306 }
307 }
308
309 if !text_buf.is_empty() {
311 commands.push(Dtvcc708Command::Text(text_buf));
312 }
313
314 commands
315 }
316
317 fn apply_command(&mut self, cmd: &Dtvcc708Command) {
320 match cmd {
321 Dtvcc708Command::SetCurrentWindow(id) => {
322 let win_id = (*id as usize).min(NUM_WINDOWS - 1);
323 self.current_window = win_id;
324 }
325 Dtvcc708Command::DeleteWindows(mask) => {
326 for bit in 0..NUM_WINDOWS {
327 if mask & (1 << bit) != 0 {
328 self.windows[bit].text.clear();
329 self.windows[bit].visible = false;
330 self.windows[bit].pen_row = 0;
331 self.windows[bit].pen_col = 0;
332 }
333 }
334 }
335 Dtvcc708Command::DisplayWindows(mask) => {
336 for bit in 0..NUM_WINDOWS {
337 if mask & (1 << bit) != 0 {
338 self.windows[bit].visible = true;
339 }
340 }
341 }
342 Dtvcc708Command::HideWindows(mask) => {
343 for bit in 0..NUM_WINDOWS {
344 if mask & (1 << bit) != 0 {
345 self.windows[bit].visible = false;
346 }
347 }
348 }
349 Dtvcc708Command::ClearWindows(mask) => {
350 for bit in 0..NUM_WINDOWS {
351 if mask & (1 << bit) != 0 {
352 self.windows[bit].text.clear();
353 self.windows[bit].pen_row = 0;
354 self.windows[bit].pen_col = 0;
355 }
356 }
357 }
358 Dtvcc708Command::SetWindowAttributes => {
359 }
361 Dtvcc708Command::SetPenAttributes => {
362 }
364 Dtvcc708Command::SetPenColor => {
365 }
367 Dtvcc708Command::SetPenLocation { row, col } => {
368 self.windows[self.current_window].pen_row = *row;
369 self.windows[self.current_window].pen_col = *col;
370 }
371 Dtvcc708Command::Text(text) => {
372 self.windows[self.current_window].text.push_str(text);
373 }
374 Dtvcc708Command::Backspace => {
375 let win = &mut self.windows[self.current_window];
376 if !win.text.is_empty() {
377 let mut chars: Vec<char> = win.text.chars().collect();
379 chars.pop();
380 win.text = chars.into_iter().collect();
381 }
382 }
383 Dtvcc708Command::FormFeed => {
384 let win = &mut self.windows[self.current_window];
385 win.text.clear();
386 win.pen_row = 0;
387 win.pen_col = 0;
388 }
389 Dtvcc708Command::CarriageReturn => {
390 let win = &mut self.windows[self.current_window];
391 win.text.push('\n');
392 win.pen_row = win.pen_row.saturating_add(1);
393 win.pen_col = 0;
394 }
395 Dtvcc708Command::Unknown(_) => {
396 }
398 }
399 }
400}
401
402impl Default for Dtvcc708Decoder {
403 fn default() -> Self {
404 Self::new()
405 }
406}
407
408#[cfg(test)]
413mod tests {
414 use super::*;
415
416 fn packet(seq: u8, data: &[u8]) -> Dtvcc708Packet {
417 Dtvcc708Packet::new(seq, data.to_vec())
418 }
419
420 #[test]
421 fn test_new_decoder_has_eight_windows() {
422 let decoder = Dtvcc708Decoder::new();
423 assert_eq!(decoder.windows().len(), 8);
424 }
425
426 #[test]
427 fn test_new_decoder_all_windows_invisible() {
428 let decoder = Dtvcc708Decoder::new();
429 for win in decoder.windows().iter() {
430 assert!(
431 !win.visible,
432 "window {} should be invisible by default",
433 win.id
434 );
435 }
436 }
437
438 #[test]
439 fn test_new_decoder_all_windows_empty_text() {
440 let decoder = Dtvcc708Decoder::new();
441 for win in decoder.windows().iter() {
442 assert!(win.text.is_empty());
443 }
444 }
445
446 #[test]
447 fn test_decode_empty_packet_returns_eight_windows() {
448 let mut decoder = Dtvcc708Decoder::new();
449 let windows = decoder.decode(&packet(0, &[]));
450 assert_eq!(windows.len(), 8);
451 }
452
453 #[test]
454 fn test_decode_printable_text_appends_to_current_window() {
455 let mut decoder = Dtvcc708Decoder::new();
456 let windows = decoder.decode(&packet(0, b"Hello"));
457 assert_eq!(windows[0].text, "Hello");
458 }
459
460 #[test]
461 fn test_set_current_window_switches_active() {
462 let mut decoder = Dtvcc708Decoder::new();
463 let windows = decoder.decode(&packet(0, &[0x82, b'X']));
465 assert_eq!(windows[2].text, "X");
466 assert!(windows[0].text.is_empty());
467 }
468
469 #[test]
470 fn test_clear_windows_by_bitmask() {
471 let mut decoder = Dtvcc708Decoder::new();
472 decoder.decode(&packet(0, b"Hello"));
474 let windows = decoder.decode(&packet(1, &[0x88, 0x01]));
476 assert!(windows[0].text.is_empty());
477 }
478
479 #[test]
480 fn test_display_windows_makes_window_visible() {
481 let mut decoder = Dtvcc708Decoder::new();
482 let windows = decoder.decode(&packet(0, &[0x89, 0x01]));
484 assert!(windows[0].visible);
485 assert!(!windows[1].visible);
486 }
487
488 #[test]
489 fn test_delete_windows_clears_text_and_visibility() {
490 let mut decoder = Dtvcc708Decoder::new();
491 decoder.decode(&packet(0, b"Test"));
492 decoder.decode(&packet(1, &[0x89, 0x01])); let windows = decoder.decode(&packet(2, &[0x8C, 0x01])); assert!(windows[0].text.is_empty());
495 assert!(!windows[0].visible);
496 }
497
498 #[test]
499 fn test_set_pen_location() {
500 let mut decoder = Dtvcc708Decoder::new();
501 let windows = decoder.decode(&packet(0, &[0x9B, 0x03, 0x05]));
503 assert_eq!(windows[0].pen_row, 3);
504 assert_eq!(windows[0].pen_col, 5);
505 }
506
507 #[test]
508 fn test_backspace_removes_last_char() {
509 let mut decoder = Dtvcc708Decoder::new();
510 decoder.decode(&packet(0, b"Hello"));
511 let windows = decoder.decode(&packet(1, &[0x08]));
512 assert_eq!(windows[0].text, "Hell");
513 }
514
515 #[test]
516 fn test_form_feed_clears_current_window() {
517 let mut decoder = Dtvcc708Decoder::new();
518 decoder.decode(&packet(0, b"Some text"));
519 let windows = decoder.decode(&packet(1, &[0x0C]));
520 assert!(windows[0].text.is_empty());
521 }
522
523 #[test]
524 fn test_sequence_number_stored() {
525 let pkt = Dtvcc708Packet::new(3, vec![0x20]);
526 assert_eq!(pkt.sequence, 3);
527 }
528
529 #[test]
530 fn test_window_id_bounds() {
531 let mut decoder = Dtvcc708Decoder::new();
532 decoder.decode(&packet(0, &[0x87, b'Z']));
534 assert_eq!(decoder.current_window(), 7);
535 assert_eq!(decoder.windows()[7].text, "Z");
536 }
537
538 #[test]
539 fn test_text_accumulates_across_multiple_decodes() {
540 let mut decoder = Dtvcc708Decoder::new();
541 decoder.decode(&packet(0, b"Foo"));
542 decoder.decode(&packet(1, b"Bar"));
543 let windows = decoder.decode(&packet(2, b"!"));
544 assert_eq!(windows[0].text, "FooBar!");
545 }
546
547 #[test]
548 fn test_mixed_control_and_text() {
549 let mut decoder = Dtvcc708Decoder::new();
550 let data = [0x81, b'H', b'i', 0x80, b'B', b'y', b'e'];
553 let windows = decoder.decode(&packet(0, &data));
554 assert_eq!(windows[1].text, "Hi");
555 assert_eq!(windows[0].text, "Bye");
556 }
557
558 #[test]
559 fn test_carriage_return_appends_newline() {
560 let mut decoder = Dtvcc708Decoder::new();
561 decoder.decode(&packet(0, b"Line1"));
562 let windows = decoder.decode(&packet(1, &[0x0D]));
563 assert_eq!(windows[0].text, "Line1\n");
564 }
565
566 #[test]
567 fn test_hide_windows_makes_invisible() {
568 let mut decoder = Dtvcc708Decoder::new();
569 decoder.decode(&packet(0, &[0x89, 0x01]));
571 let windows = decoder.decode(&packet(1, &[0x8A, 0x01]));
573 assert!(!windows[0].visible);
574 }
575}