1use crate::cc_data::{CcTriplet, CcType};
13use crate::decode::screen::{
14 Color, EdgeType, FontStyle, Justify, Opacity, PenOffset, PenSize, PrintDirection,
15 ScrollDirection,
16};
17use alloc::string::String;
18use alloc::vec::Vec;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize))]
24#[non_exhaustive]
25pub enum AnchorPoint {
26 #[default]
28 TopLeft,
29 TopCenter,
31 TopRight,
33 MiddleLeft,
35 MiddleCenter,
37 MiddleRight,
39 BottomLeft,
41 BottomCenter,
43 BottomRight,
45}
46
47impl AnchorPoint {
48 #[must_use]
50 pub fn from_bits(v: u8) -> Self {
51 match v & 0x0F {
52 0 => Self::TopLeft,
53 1 => Self::TopCenter,
54 2 => Self::TopRight,
55 3 => Self::MiddleLeft,
56 4 => Self::MiddleCenter,
57 5 => Self::MiddleRight,
58 6 => Self::BottomLeft,
59 7 => Self::BottomCenter,
60 8 => Self::BottomRight,
61 _ => Self::TopLeft,
62 }
63 }
64 #[must_use]
66 pub fn name(&self) -> &'static str {
67 match self {
68 Self::TopLeft => "top_left",
69 Self::TopCenter => "top_center",
70 Self::TopRight => "top_right",
71 Self::MiddleLeft => "middle_left",
72 Self::MiddleCenter => "middle_center",
73 Self::MiddleRight => "middle_right",
74 Self::BottomLeft => "bottom_left",
75 Self::BottomCenter => "bottom_center",
76 Self::BottomRight => "bottom_right",
77 }
78 }
79}
80broadcast_common::impl_spec_display!(AnchorPoint);
81
82const NUM_SERVICES: usize = 6;
85const NUM_WINDOWS: usize = 8;
87const MAX_WINDOW_ROWS: usize = 12;
89const MAX_WINDOW_COLS: usize = 42;
91
92const PACKET_SIZE_ZERO_DATA: usize = 127;
95
96const EXTENDED_SERVICE_ESCAPE: u8 = 7;
99
100const C0_NUL: u8 = 0x00;
102const C0_ETX: u8 = 0x03;
103const C0_BS: u8 = 0x08;
104const C0_FF: u8 = 0x0C;
105const C0_CR: u8 = 0x0D;
106const C0_HCR: u8 = 0x0E;
107const C0_EXT1: u8 = 0x10;
108const C0_P16: u8 = 0x18;
109
110const C1_CW0: u8 = 0x80; const C1_CW7: u8 = 0x87;
113const C1_CLW: u8 = 0x88;
114const C1_DSW: u8 = 0x89;
115const C1_HDW: u8 = 0x8A;
116const C1_TGW: u8 = 0x8B;
117const C1_DLW: u8 = 0x8C;
118const C1_DLY: u8 = 0x8D;
119const C1_DLC: u8 = 0x8E;
120const C1_RST: u8 = 0x8F;
121const C1_SPA: u8 = 0x90;
122const C1_SPC: u8 = 0x91;
123const C1_SPL: u8 = 0x92;
124const C1_SWA: u8 = 0x97;
125const C1_DF0: u8 = 0x98; const C1_DF7: u8 = 0x9F;
127
128const C0_END: u8 = 0x1F;
130const G0_START: u8 = 0x20;
131const G0_END: u8 = 0x7F;
132const C1_START: u8 = 0x80;
133const C1_END: u8 = 0x9F;
134const G1_START: u8 = 0xA0;
135
136const G0_MUSIC_NOTE: u8 = 0x7F;
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
141#[cfg_attr(feature = "serde", derive(serde::Serialize))]
142#[non_exhaustive]
143pub enum WindowState {
144 #[default]
146 Hidden,
147 Visible,
149}
150
151impl WindowState {
152 #[must_use]
154 pub fn name(&self) -> &'static str {
155 match self {
156 Self::Hidden => "hidden",
157 Self::Visible => "visible",
158 }
159 }
160}
161broadcast_common::impl_spec_display!(WindowState);
162
163#[derive(Debug, Clone, PartialEq, Eq)]
169#[cfg_attr(feature = "serde", derive(serde::Serialize))]
170pub struct Window {
171 pub state: WindowState,
173 pub priority: u8,
175 pub anchor_point: AnchorPoint,
177 pub anchor_vertical: u8,
179 pub anchor_horizontal: u8,
181 pub relative_position: bool,
183 pub row_count: u8,
185 pub column_count: u8,
187 pub row_lock: bool,
189 pub column_lock: bool,
191 pub window_style: u8,
193 pub pen_style: u8,
195 pub justify: Justify,
197 pub print_direction: PrintDirection,
199 pub scroll_direction: ScrollDirection,
201 pub word_wrap: bool,
203 pub fill_color: Color,
205 pub fill_opacity: Opacity,
207 pub border_color: Color,
209 pub border_type: EdgeType,
211 pub pen_size: PenSize,
213 pub pen_offset: PenOffset,
215 pub font_style: FontStyle,
217 pub italics: bool,
219 pub underline: bool,
221 pub edge_type: EdgeType,
223 pub fg_color: Color,
225 pub fg_opacity: Opacity,
227 pub bg_color: Color,
229 pub bg_opacity: Opacity,
231 rows: Vec<String>,
233 pen_row: usize,
235 pen_col: usize,
237}
238
239impl Window {
240 fn new() -> Self {
241 Window {
242 state: WindowState::Hidden,
243 priority: 0,
244 anchor_point: AnchorPoint::TopLeft,
245 anchor_vertical: 0,
246 anchor_horizontal: 0,
247 relative_position: false,
248 row_count: 1,
249 column_count: 1,
250 row_lock: false,
251 column_lock: false,
252 window_style: 0,
253 pen_style: 0,
254 justify: Justify::Left,
255 print_direction: PrintDirection::LeftToRight,
256 scroll_direction: ScrollDirection::BottomToTop,
257 word_wrap: false,
258 fill_color: Color::BLACK,
259 fill_opacity: Opacity::Solid,
260 border_color: Color::BLACK,
261 border_type: EdgeType::None,
262 pen_size: PenSize::Standard,
263 pen_offset: PenOffset::Normal,
264 font_style: FontStyle::Default,
265 italics: false,
266 underline: false,
267 edge_type: EdgeType::None,
268 fg_color: Color::WHITE,
269 fg_opacity: Opacity::Solid,
270 bg_color: Color::BLACK,
271 bg_opacity: Opacity::Solid,
272 rows: Vec::new(),
273 pen_row: 0,
274 pen_col: 0,
275 }
276 }
277
278 fn ensure_grid(&mut self) {
279 let rows = (self.row_count as usize).clamp(1, MAX_WINDOW_ROWS);
280 if self.rows.len() != rows {
281 self.rows = alloc::vec![String::new(); rows];
282 }
283 }
284
285 fn clear_text(&mut self) {
286 for r in &mut self.rows {
287 r.clear();
288 }
289 self.pen_row = 0;
290 self.pen_col = 0;
291 }
292
293 fn cols(&self) -> usize {
294 (self.column_count as usize).clamp(1, MAX_WINDOW_COLS)
295 }
296
297 fn put_char(&mut self, ch: char) {
299 self.ensure_grid();
300 let cols = self.cols();
301 if self.pen_row >= self.rows.len() {
302 return;
303 }
304 let row = &mut self.rows[self.pen_row];
306 while row.chars().count() < self.pen_col {
307 row.push(' ');
308 }
309 if self.pen_col < cols {
310 row.push(ch);
311 self.pen_col += 1;
312 }
313 }
314
315 fn back_space(&mut self) {
317 if self.pen_col > 0 {
318 self.pen_col -= 1;
319 if self.pen_row < self.rows.len() {
320 let row = &mut self.rows[self.pen_row];
321 let mut chars: Vec<char> = row.chars().collect();
322 if self.pen_col < chars.len() {
323 chars.truncate(self.pen_col);
324 *row = chars.into_iter().collect();
325 }
326 }
327 }
328 }
329
330 fn carriage_return(&mut self) {
332 self.ensure_grid();
333 self.pen_col = 0;
334 if self.pen_row + 1 < self.rows.len() {
335 self.pen_row += 1;
336 } else if !self.rows.is_empty() {
337 self.rows.remove(0);
339 self.rows.push(String::new());
340 self.pen_row = self.rows.len() - 1;
341 }
342 }
343
344 fn horizontal_cr(&mut self) {
346 self.ensure_grid();
347 if self.pen_row < self.rows.len() {
348 self.rows[self.pen_row].clear();
349 }
350 self.pen_col = 0;
351 }
352
353 fn set_pen_location(&mut self, row: usize, col: usize) {
354 self.ensure_grid();
355 self.pen_row = row.min(self.rows.len().saturating_sub(1));
356 self.pen_col = col.min(self.cols());
357 }
358
359 #[must_use]
362 pub fn text(&self) -> String {
363 let mut lines: Vec<&str> = self.rows.iter().map(|r| r.trim_end()).collect();
366 while lines.last().is_some_and(|l| l.is_empty()) {
367 lines.pop();
368 }
369 lines.join("\n")
370 }
371}
372
373#[derive(Debug, Clone, PartialEq, Eq, Default)]
375#[cfg_attr(feature = "serde", derive(serde::Serialize))]
376struct Service {
377 windows: [Option<Window>; NUM_WINDOWS],
378 current_window: Option<usize>,
380}
381
382impl Service {
383 fn reset(&mut self) {
384 *self = Service::default();
385 }
386
387 fn current(&mut self) -> Option<&mut Window> {
388 let id = self.current_window?;
389 self.windows.get_mut(id)?.as_mut()
390 }
391}
392
393#[derive(Debug, Clone, PartialEq, Eq)]
409#[cfg_attr(feature = "serde", derive(serde::Serialize))]
410pub struct Cea708Decoder {
411 services: [Service; NUM_SERVICES],
412 packet: Vec<u8>,
414 last_seq: Option<u8>,
416}
417
418impl Default for Cea708Decoder {
419 fn default() -> Self {
420 Self::new()
421 }
422}
423
424impl Cea708Decoder {
425 #[must_use]
427 pub fn new() -> Self {
428 Cea708Decoder {
429 services: Default::default(),
430 packet: Vec::new(),
431 last_seq: None,
432 }
433 }
434
435 pub fn reset(&mut self) {
437 for s in &mut self.services {
438 s.reset();
439 }
440 self.packet.clear();
441 self.last_seq = None;
442 }
443
444 pub fn push_triplets<'a, I>(&mut self, triplets: I)
449 where
450 I: IntoIterator<Item = &'a CcTriplet>,
451 {
452 for t in triplets {
453 if !t.cc_valid {
454 continue;
455 }
456 match t.cc_type {
457 CcType::Dtvcc708Start => {
458 self.flush_packet();
460 self.packet.clear();
461 self.packet.push(t.cc_data_1);
462 self.packet.push(t.cc_data_2);
463 }
464 CcType::Dtvcc708Data => {
465 self.packet.push(t.cc_data_1);
466 self.packet.push(t.cc_data_2);
467 }
468 _ => {}
469 }
470 }
471 self.flush_packet();
472 }
473
474 pub fn push_packet(&mut self, ccp: &[u8]) {
477 self.decode_packet(ccp);
478 }
479
480 fn flush_packet(&mut self) {
482 if self.packet.is_empty() {
483 return;
484 }
485 let packet = core::mem::take(&mut self.packet);
486 self.decode_packet(&packet);
487 }
488
489 fn decode_packet(&mut self, ccp: &[u8]) {
491 let Some((&header, rest)) = ccp.split_first() else {
492 return;
493 };
494 let seq = (header >> 6) & 0x03;
495 let size_code = header & 0x3F;
496 let data_size = if size_code == 0 {
497 PACKET_SIZE_ZERO_DATA
498 } else {
499 (size_code as usize) * 2 - 1
500 };
501 if let Some(prev) = self.last_seq
503 && seq != (prev + 1) & 0x03
504 {
505 for s in &mut self.services {
506 s.reset();
507 }
508 }
509 self.last_seq = Some(seq);
510 let end = data_size.min(rest.len());
511 self.decode_service_blocks(&rest[..end]);
512 }
513
514 fn decode_service_blocks(&mut self, mut data: &[u8]) {
516 loop {
517 let Some((&header, rest)) = data.split_first() else {
518 return;
519 };
520 if header == 0 {
522 return;
523 }
524 let mut service_number = u16::from((header >> 5) & 0x07);
525 let block_size = (header & 0x1F) as usize;
526 let mut body = rest;
527 if service_number == u16::from(EXTENDED_SERVICE_ESCAPE) && block_size != 0 {
528 let Some((&ext, after)) = rest.split_first() else {
530 return;
531 };
532 service_number = u16::from(ext & 0x3F);
533 body = after;
534 }
535 if block_size > body.len() {
536 self.dispatch_service(service_number, body);
538 return;
539 }
540 let (block, next) = body.split_at(block_size);
541 self.dispatch_service(service_number, block);
542 data = next;
543 }
544 }
545
546 fn dispatch_service(&mut self, service_number: u16, block: &[u8]) {
547 if service_number == 0 || service_number as usize > NUM_SERVICES {
549 return;
550 }
551 let idx = service_number as usize - 1;
552 Self::interpret(&mut self.services[idx], block);
553 }
554
555 fn interpret(service: &mut Service, block: &[u8]) {
557 let mut i = 0usize;
558 while i < block.len() {
559 let b = block[i];
560 let consumed = match b {
561 0x00..=C0_END => Self::handle_c0(service, &block[i..]),
562 G0_START..=G0_END => {
563 Self::put(service, Self::g0_char(b));
564 1
565 }
566 C1_START..=C1_END => Self::handle_c1(service, &block[i..]),
567 G1_START..=0xFF => {
568 Self::put(service, char::from(b));
570 1
571 }
572 };
573 i += consumed.max(1);
574 }
575 }
576
577 fn handle_c0(service: &mut Service, data: &[u8]) -> usize {
579 let b = data[0];
580 match b {
581 C0_NUL => 1,
582 C0_ETX => 1,
583 C0_BS if let Some(w) = service.current() => {
587 w.back_space();
588 1
589 }
590 C0_FF if let Some(w) = service.current() => {
591 w.clear_text();
592 1
593 }
594 C0_CR if let Some(w) = service.current() => {
595 w.carriage_return();
596 1
597 }
598 C0_HCR if let Some(w) = service.current() => {
599 w.horizontal_cr();
600 1
601 }
602 C0_EXT1 => Self::handle_ext1(service, data),
603 C0_P16 => 3, 0x11..=0x17 => 2,
607 0x19..=0x1F => 3,
608 _ => 1,
609 }
610 }
611
612 fn handle_ext1(service: &mut Service, data: &[u8]) -> usize {
615 let Some(&base) = data.get(1) else {
616 return 1;
617 };
618 match base {
619 0x00..=0x07 => 2,
621 0x08..=0x0F => 3,
622 0x10..=0x17 => 4,
623 0x18..=0x1F => 5,
624 0x20..=0x7F => {
626 Self::put(service, Self::g2_char(base));
627 2
628 }
629 0x80..=0x87 => 6,
631 0x88..=0x8F => 7,
632 0x90..=0x9F => {
633 let n = data.get(2).map_or(0, |d| (d & 0x3F) as usize + 1);
635 3 + n
636 }
637 _ => {
639 Self::put(service, Self::g3_char(base));
640 2
641 }
642 }
643 }
644
645 fn handle_c1(service: &mut Service, data: &[u8]) -> usize {
647 let op = data[0];
648 match op {
649 C1_CW0..=C1_CW7 => {
650 let id = (op - C1_CW0) as usize;
651 if service.windows.get(id).and_then(|w| w.as_ref()).is_some() {
652 service.current_window = Some(id);
653 }
654 1
655 }
656 C1_CLW => Self::window_map_cmd(service, data, WindowMapOp::Clear),
657 C1_DSW => Self::window_map_cmd(service, data, WindowMapOp::Display),
658 C1_HDW => Self::window_map_cmd(service, data, WindowMapOp::Hide),
659 C1_TGW => Self::window_map_cmd(service, data, WindowMapOp::Toggle),
660 C1_DLW => Self::window_map_cmd(service, data, WindowMapOp::Delete),
661 C1_DLY => 2, C1_DLC => 1, C1_RST => {
664 service.reset();
665 1
666 }
667 C1_SPA => Self::set_pen_attributes(service, data),
668 C1_SPC => Self::set_pen_color(service, data),
669 C1_SPL => Self::set_pen_location(service, data),
670 C1_SWA => Self::set_window_attributes(service, data),
671 C1_DF0..=C1_DF7 => Self::define_window(service, data),
672 _ => 1,
674 }
675 }
676
677 fn window_map_cmd(service: &mut Service, data: &[u8], op: WindowMapOp) -> usize {
678 let Some(&map) = data.get(1) else {
679 return 1;
680 };
681 for id in 0..NUM_WINDOWS {
682 if map & (1 << id) == 0 {
683 continue;
684 }
685 match op {
686 WindowMapOp::Clear if let Some(w) = service.windows[id].as_mut() => {
687 w.clear_text();
688 }
689 WindowMapOp::Clear => {}
690 WindowMapOp::Display if let Some(w) = service.windows[id].as_mut() => {
691 w.state = WindowState::Visible;
692 }
693 WindowMapOp::Display => {}
694 WindowMapOp::Hide if let Some(w) = service.windows[id].as_mut() => {
695 w.state = WindowState::Hidden;
696 }
697 WindowMapOp::Hide => {}
698 WindowMapOp::Toggle if let Some(w) = service.windows[id].as_mut() => {
699 w.state = match w.state {
700 WindowState::Visible => WindowState::Hidden,
701 WindowState::Hidden => WindowState::Visible,
702 };
703 }
704 WindowMapOp::Toggle => {}
705 WindowMapOp::Delete => {
706 service.windows[id] = None;
707 if service.current_window == Some(id) {
708 service.current_window = None;
709 }
710 }
711 }
712 }
713 2
714 }
715
716 fn define_window(service: &mut Service, data: &[u8]) -> usize {
718 const TOTAL: usize = 7;
719 if data.len() < TOTAL {
720 return data.len().max(1);
721 }
722 let id = (data[0] - C1_DF0) as usize;
723 let p1 = data[1];
724 let p2 = data[2];
725 let p3 = data[3];
726 let p4 = data[4];
727 let p5 = data[5];
728 let p6 = data[6];
729
730 let creating = service.windows[id].is_none();
731 let w = service.windows[id].get_or_insert_with(Window::new);
732
733 w.priority = p1 & 0x07;
734 w.column_lock = (p1 >> 3) & 0x01 != 0;
735 w.row_lock = (p1 >> 4) & 0x01 != 0;
736 w.state = if (p1 >> 5) & 0x01 != 0 {
737 WindowState::Visible
738 } else {
739 WindowState::Hidden
740 };
741 w.relative_position = (p2 >> 7) & 0x01 != 0;
742 w.anchor_vertical = p2 & 0x7F;
743 w.anchor_horizontal = p3;
744 w.anchor_point = AnchorPoint::from_bits((p4 >> 4) & 0x0F);
745 w.row_count = (p4 & 0x0F) + 1;
746 w.column_count = (p5 & 0x3F) + 1;
747 w.window_style = (p6 >> 3) & 0x07;
748 w.pen_style = p6 & 0x07;
749
750 if creating {
751 apply_window_style(
753 w,
754 if w.window_style == 0 {
755 1
756 } else {
757 w.window_style
758 },
759 );
760 apply_pen_style(w, if w.pen_style == 0 { 1 } else { w.pen_style });
761 w.ensure_grid();
762 w.clear_text();
763 } else {
764 if w.window_style != 0 {
766 apply_window_style(w, w.window_style);
767 }
768 if w.pen_style != 0 {
769 apply_pen_style(w, w.pen_style);
770 }
771 w.ensure_grid();
772 }
773 service.current_window = Some(id);
774 TOTAL
775 }
776
777 fn set_window_attributes(service: &mut Service, data: &[u8]) -> usize {
779 const TOTAL: usize = 5;
780 if data.len() < TOTAL {
781 return data.len().max(1);
782 }
783 let p1 = data[1];
784 let p2 = data[2];
785 let p3 = data[3];
786 let p4 = data[4];
787 if let Some(w) = service.current() {
788 w.fill_opacity = Opacity::from_bits((p1 >> 6) & 0x03);
789 w.fill_color = Color::new((p1 >> 4) & 0x03, (p1 >> 2) & 0x03, p1 & 0x03);
790 let bt_lo = (p2 >> 6) & 0x03;
791 w.border_color = Color::new((p2 >> 4) & 0x03, (p2 >> 2) & 0x03, p2 & 0x03);
792 let bt_hi = (p3 >> 7) & 0x01;
793 w.border_type = EdgeType::from_bits((bt_hi << 2) | bt_lo);
794 w.word_wrap = (p3 >> 6) & 0x01 != 0;
795 w.print_direction = PrintDirection::from_bits((p3 >> 4) & 0x03);
796 w.scroll_direction = ScrollDirection::from_bits((p3 >> 2) & 0x03);
797 w.justify = Justify::from_bits(p3 & 0x03);
798 let _ = p4;
800 }
801 TOTAL
802 }
803
804 fn set_pen_attributes(service: &mut Service, data: &[u8]) -> usize {
806 const TOTAL: usize = 3;
807 if data.len() < TOTAL {
808 return data.len().max(1);
809 }
810 let p1 = data[1];
811 let p2 = data[2];
812 if let Some(w) = service.current() {
813 w.pen_offset = PenOffset::from_bits((p1 >> 2) & 0x03);
814 w.pen_size = PenSize::from_bits(p1 & 0x03);
815 w.italics = (p2 >> 7) & 0x01 != 0;
816 w.underline = (p2 >> 6) & 0x01 != 0;
817 w.edge_type = EdgeType::from_bits((p2 >> 3) & 0x07);
818 w.font_style = FontStyle::from_bits(p2 & 0x07);
819 }
820 TOTAL
821 }
822
823 fn set_pen_color(service: &mut Service, data: &[u8]) -> usize {
825 const TOTAL: usize = 4;
826 if data.len() < TOTAL {
827 return data.len().max(1);
828 }
829 let p1 = data[1];
830 let p2 = data[2];
831 let p3 = data[3];
832 if let Some(w) = service.current() {
833 w.fg_opacity = Opacity::from_bits((p1 >> 6) & 0x03);
834 w.fg_color = Color::new((p1 >> 4) & 0x03, (p1 >> 2) & 0x03, p1 & 0x03);
835 w.bg_opacity = Opacity::from_bits((p2 >> 6) & 0x03);
836 w.bg_color = Color::new((p2 >> 4) & 0x03, (p2 >> 2) & 0x03, p2 & 0x03);
837 w.border_color = Color::new((p3 >> 4) & 0x03, (p3 >> 2) & 0x03, p3 & 0x03);
839 }
840 TOTAL
841 }
842
843 fn set_pen_location(service: &mut Service, data: &[u8]) -> usize {
845 const TOTAL: usize = 3;
846 if data.len() < TOTAL {
847 return data.len().max(1);
848 }
849 let row = (data[1] & 0x0F) as usize;
850 let col = (data[2] & 0x3F) as usize;
851 if let Some(w) = service.current() {
852 w.set_pen_location(row, col);
853 }
854 TOTAL
855 }
856
857 fn put(service: &mut Service, ch: char) {
858 if let Some(w) = service.current() {
859 w.put_char(ch);
860 }
861 }
862
863 fn g0_char(b: u8) -> char {
865 if b == G0_MUSIC_NOTE {
866 '\u{266A}'
867 } else {
868 char::from(b)
869 }
870 }
871
872 fn g2_char(b: u8) -> char {
874 match b {
875 0x20 | 0x21 => ' ', 0x25 => '\u{2026}', 0x2A => '\u{0160}', 0x2C => '\u{0152}', 0x30 => '\u{25A0}', 0x31 => '\u{2018}', 0x32 => '\u{2019}', 0x33 => '\u{201C}', 0x34 => '\u{201D}', 0x35 => '\u{2022}', 0x39 => '\u{2122}', 0x3A => '\u{0161}', 0x3C => '\u{0153}', 0x3D => '\u{2120}', 0x3F => '\u{0178}', 0x76 => '\u{215B}', 0x77 => '\u{215C}', 0x78 => '\u{215D}', 0x79 => '\u{215E}', _ => '_', }
896 }
897
898 fn g3_char(b: u8) -> char {
900 if b == 0xA0 {
901 '\u{1F4FA}' } else {
903 '_'
904 }
905 }
906
907 #[must_use]
910 pub fn windows(&self, service_number: usize) -> &[Option<Window>; NUM_WINDOWS] {
911 const EMPTY: [Option<Window>; NUM_WINDOWS] =
912 [None, None, None, None, None, None, None, None];
913 if service_number == 0 || service_number > NUM_SERVICES {
914 return &EMPTY;
915 }
916 &self.services[service_number - 1].windows
917 }
918
919 #[must_use]
922 pub fn service_text(&self, service_number: usize) -> String {
923 if service_number == 0 || service_number > NUM_SERVICES {
924 return String::new();
925 }
926 let svc = &self.services[service_number - 1];
927 let mut idxs: Vec<usize> = (0..NUM_WINDOWS)
928 .filter(|&i| {
929 svc.windows[i]
930 .as_ref()
931 .is_some_and(|w| w.state == WindowState::Visible)
932 })
933 .collect();
934 idxs.sort_by_key(|&i| {
935 svc.windows[i]
936 .as_ref()
937 .map_or((u8::MAX, i), |w| (w.priority, i))
938 });
939 let mut out = String::new();
940 for i in idxs {
941 if let Some(w) = svc.windows[i].as_ref() {
942 let t = w.text();
943 if t.is_empty() {
944 continue;
945 }
946 if !out.is_empty() {
947 out.push('\n');
948 }
949 out.push_str(&t);
950 }
951 }
952 out
953 }
954}
955
956#[derive(Clone, Copy)]
958enum WindowMapOp {
959 Clear,
960 Display,
961 Hide,
962 Toggle,
963 Delete,
964}
965
966fn apply_window_style(w: &mut Window, id: u8) {
968 w.border_type = EdgeType::None;
971 match id {
972 1 => style(w, Justify::Left, false, Some(Color::BLACK), Opacity::Solid),
973 2 => style(w, Justify::Left, false, None, Opacity::Transparent),
974 3 => style(
975 w,
976 Justify::Center,
977 false,
978 Some(Color::BLACK),
979 Opacity::Solid,
980 ),
981 4 => style(w, Justify::Left, true, Some(Color::BLACK), Opacity::Solid),
982 5 => style(w, Justify::Left, true, None, Opacity::Transparent),
983 6 => style(w, Justify::Center, true, Some(Color::BLACK), Opacity::Solid),
984 7 => {
985 w.justify = Justify::Left;
986 w.word_wrap = false;
987 w.print_direction = PrintDirection::TopToBottom;
988 w.scroll_direction = ScrollDirection::RightToLeft;
989 w.fill_color = Color::BLACK;
990 w.fill_opacity = Opacity::Solid;
991 }
992 _ => {}
993 }
994}
995
996fn style(w: &mut Window, j: Justify, ww: bool, fill: Option<Color>, op: Opacity) {
997 w.justify = j;
998 w.word_wrap = ww;
999 w.print_direction = PrintDirection::LeftToRight;
1000 w.scroll_direction = ScrollDirection::BottomToTop;
1001 w.fill_opacity = op;
1002 if let Some(c) = fill {
1003 w.fill_color = c;
1004 }
1005}
1006
1007fn apply_pen_style(w: &mut Window, id: u8) {
1009 w.pen_size = PenSize::Standard;
1010 w.pen_offset = PenOffset::Normal;
1011 w.italics = false;
1012 w.underline = false;
1013 w.fg_color = Color::WHITE;
1014 w.fg_opacity = Opacity::Solid;
1015 match id {
1016 1 => pen(
1017 w,
1018 FontStyle::Default,
1019 EdgeType::None,
1020 Color::BLACK,
1021 Opacity::Solid,
1022 ),
1023 2 => pen(
1024 w,
1025 FontStyle::MonospacedSerif,
1026 EdgeType::None,
1027 Color::BLACK,
1028 Opacity::Solid,
1029 ),
1030 3 => pen(
1031 w,
1032 FontStyle::ProportionalSerif,
1033 EdgeType::None,
1034 Color::BLACK,
1035 Opacity::Solid,
1036 ),
1037 4 => pen(
1038 w,
1039 FontStyle::MonospacedSansSerif,
1040 EdgeType::None,
1041 Color::BLACK,
1042 Opacity::Solid,
1043 ),
1044 5 => pen(
1045 w,
1046 FontStyle::ProportionalSansSerif,
1047 EdgeType::None,
1048 Color::BLACK,
1049 Opacity::Solid,
1050 ),
1051 6 => pen(
1052 w,
1053 FontStyle::MonospacedSansSerif,
1054 EdgeType::Uniform,
1055 Color::BLACK,
1056 Opacity::Transparent,
1057 ),
1058 7 => pen(
1059 w,
1060 FontStyle::ProportionalSansSerif,
1061 EdgeType::Uniform,
1062 Color::BLACK,
1063 Opacity::Transparent,
1064 ),
1065 _ => {}
1066 }
1067}
1068
1069fn pen(w: &mut Window, font: FontStyle, edge: EdgeType, bg: Color, bg_op: Opacity) {
1070 w.font_style = font;
1071 w.edge_type = edge;
1072 w.bg_color = bg;
1073 w.bg_opacity = bg_op;
1074}
1075
1076#[cfg(test)]
1077mod tests {
1078 use super::*;
1079
1080 fn ccp(svc: u8, cmds: &[u8]) -> Vec<u8> {
1082 let mut sb = alloc::vec![(svc << 5) | (cmds.len() as u8)];
1083 sb.extend_from_slice(cmds);
1084 let size_code = (sb.len().div_ceil(2) + 1) as u8 & 0x3F;
1086 let mut packet = alloc::vec![size_code];
1087 packet.extend_from_slice(&sb);
1088 packet
1089 }
1090
1091 #[test]
1096 fn define_window_worked_example() {
1097 let mut dec = Cea708Decoder::new();
1098 let packet = ccp(1, &[0x9A, 0x38, 0x4A, 0xD1, 0x8B, 0x0F, 0x11]);
1099 dec.push_packet(&packet);
1100 let w = dec.windows(1)[2].as_ref().expect("window 2 defined");
1101 assert_eq!(w.state, WindowState::Visible);
1102 assert!(w.row_lock);
1103 assert!(w.column_lock);
1104 assert_eq!(w.priority, 0);
1105 assert!(!w.relative_position);
1106 assert_eq!(w.anchor_vertical, 74);
1107 assert_eq!(w.anchor_horizontal, 209);
1108 assert_eq!(w.anchor_point, AnchorPoint::BottomRight);
1109 assert_eq!(w.row_count, 12);
1110 assert_eq!(w.column_count, 16);
1111 assert_eq!(w.window_style, 2);
1112 assert_eq!(w.pen_style, 1);
1113 }
1114
1115 #[test]
1118 fn swa_border_type_split() {
1119 let mut dec = Cea708Decoder::new();
1120 let packet = ccp(
1122 1,
1123 &[
1124 0x98, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x64, 0x53, 0x88, 0x22, ],
1127 );
1128 dec.push_packet(&packet);
1129 let w = dec.windows(1)[0].as_ref().expect("window 0");
1130 assert_eq!(w.border_type, EdgeType::RightDropShadow);
1131 }
1132
1133 #[test]
1135 fn decode_text() {
1136 let mut dec = Cea708Decoder::new();
1137 let packet = ccp(
1138 1,
1139 &[
1140 0x98, 0x20, 0x00, 0x00, 0x02, 0x0F, 0x00, b'H', b'i',
1142 ],
1143 );
1144 dec.push_packet(&packet);
1145 assert_eq!(dec.service_text(1), "Hi");
1146 }
1147
1148 #[test]
1150 fn two_services_multi_window() {
1151 let mut dec = Cea708Decoder::new();
1152 let s1_block = [0x98, 0x20, 0x00, 0x00, 0x00, 0x0F, 0x00, b'S', b'1'];
1154 let s2_block = [0x99, 0x20, 0x00, 0x00, 0x00, 0x0F, 0x00, b'S', b'2'];
1156 let mut data = Vec::new();
1157 data.push((1 << 5) | (s1_block.len() as u8));
1158 data.extend_from_slice(&s1_block);
1159 data.push((2 << 5) | (s2_block.len() as u8));
1160 data.extend_from_slice(&s2_block);
1161 let size_code = (data.len().div_ceil(2) + 1) as u8 & 0x3F;
1162 let mut packet = alloc::vec![size_code];
1163 packet.extend_from_slice(&data);
1164 dec.push_packet(&packet);
1165 assert_eq!(dec.service_text(1), "S1");
1166 assert_eq!(dec.service_text(2), "S2");
1167 assert!(dec.windows(1)[0].is_some());
1168 assert!(dec.windows(2)[1].is_some());
1169 }
1170
1171 #[test]
1172 fn carriage_return_rolls_up() {
1173 let mut w = Window::new();
1174 w.row_count = 2;
1175 w.column_count = 10;
1176 w.ensure_grid();
1177 w.put_char('A');
1178 w.carriage_return();
1179 w.put_char('B');
1180 w.carriage_return(); w.put_char('C');
1182 assert_eq!(w.text(), "B\nC");
1183 }
1184
1185 #[test]
1186 fn g0_music_note() {
1187 assert_eq!(Cea708Decoder::g0_char(0x7F), '\u{266A}');
1188 assert_eq!(Cea708Decoder::g0_char(b'A'), 'A');
1189 }
1190
1191 #[test]
1192 fn no_panic_on_arbitrary_input() {
1193 let inputs: &[&[u8]] = &[
1195 &[],
1196 &[0x00],
1197 &[0xFF],
1198 &[0x01, 0x98], &[0x3F, 0x80, 0x90, 0x91, 0x92, 0x97, 0x98], &[0x20, 0xEE, (7 << 5) | 1], &[0x10, 0x9A], &[0x18, 0x00], ];
1204 for inp in inputs {
1205 let mut dec = Cea708Decoder::new();
1206 dec.push_packet(inp);
1207 }
1208 let mut dec = Cea708Decoder::new();
1210 let mut x: u32 = 0x1234_5678;
1211 let mut buf = Vec::new();
1212 for _ in 0..4096 {
1213 x = x.wrapping_mul(1_103_515_245).wrapping_add(12_345);
1214 buf.push((x >> 16) as u8);
1215 }
1216 dec.push_packet(&buf);
1217 let mut dec2 = Cea708Decoder::new();
1219 let triplets: Vec<CcTriplet> = buf
1220 .chunks(2)
1221 .map(|c| CcTriplet {
1222 cc_valid: true,
1223 cc_type: CcType::Dtvcc708Data,
1224 cc_data_1: c[0],
1225 cc_data_2: *c.get(1).unwrap_or(&0),
1226 })
1227 .collect();
1228 dec2.push_triplets(&triplets);
1229 }
1230}