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}
80dvb_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}
161dvb_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 if seq != (prev + 1) & 0x03 {
504 for s in &mut self.services {
505 s.reset();
506 }
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 => {
584 if let Some(w) = service.current() {
585 w.back_space();
586 }
587 1
588 }
589 C0_FF => {
590 if let Some(w) = service.current() {
591 w.clear_text();
592 }
593 1
594 }
595 C0_CR => {
596 if let Some(w) = service.current() {
597 w.carriage_return();
598 }
599 1
600 }
601 C0_HCR => {
602 if let Some(w) = service.current() {
603 w.horizontal_cr();
604 }
605 1
606 }
607 C0_EXT1 => Self::handle_ext1(service, data),
608 C0_P16 => 3, 0x11..=0x17 => 2,
612 0x19..=0x1F => 3,
613 _ => 1,
614 }
615 }
616
617 fn handle_ext1(service: &mut Service, data: &[u8]) -> usize {
620 let Some(&base) = data.get(1) else {
621 return 1;
622 };
623 match base {
624 0x00..=0x07 => 2,
626 0x08..=0x0F => 3,
627 0x10..=0x17 => 4,
628 0x18..=0x1F => 5,
629 0x20..=0x7F => {
631 Self::put(service, Self::g2_char(base));
632 2
633 }
634 0x80..=0x87 => 6,
636 0x88..=0x8F => 7,
637 0x90..=0x9F => {
638 let n = data.get(2).map_or(0, |d| (d & 0x3F) as usize + 1);
640 3 + n
641 }
642 _ => {
644 Self::put(service, Self::g3_char(base));
645 2
646 }
647 }
648 }
649
650 fn handle_c1(service: &mut Service, data: &[u8]) -> usize {
652 let op = data[0];
653 match op {
654 C1_CW0..=C1_CW7 => {
655 let id = (op - C1_CW0) as usize;
656 if service.windows.get(id).and_then(|w| w.as_ref()).is_some() {
657 service.current_window = Some(id);
658 }
659 1
660 }
661 C1_CLW => Self::window_map_cmd(service, data, WindowMapOp::Clear),
662 C1_DSW => Self::window_map_cmd(service, data, WindowMapOp::Display),
663 C1_HDW => Self::window_map_cmd(service, data, WindowMapOp::Hide),
664 C1_TGW => Self::window_map_cmd(service, data, WindowMapOp::Toggle),
665 C1_DLW => Self::window_map_cmd(service, data, WindowMapOp::Delete),
666 C1_DLY => 2, C1_DLC => 1, C1_RST => {
669 service.reset();
670 1
671 }
672 C1_SPA => Self::set_pen_attributes(service, data),
673 C1_SPC => Self::set_pen_color(service, data),
674 C1_SPL => Self::set_pen_location(service, data),
675 C1_SWA => Self::set_window_attributes(service, data),
676 C1_DF0..=C1_DF7 => Self::define_window(service, data),
677 _ => 1,
679 }
680 }
681
682 fn window_map_cmd(service: &mut Service, data: &[u8], op: WindowMapOp) -> usize {
683 let Some(&map) = data.get(1) else {
684 return 1;
685 };
686 for id in 0..NUM_WINDOWS {
687 if map & (1 << id) == 0 {
688 continue;
689 }
690 match op {
691 WindowMapOp::Clear => {
692 if let Some(w) = service.windows[id].as_mut() {
693 w.clear_text();
694 }
695 }
696 WindowMapOp::Display => {
697 if let Some(w) = service.windows[id].as_mut() {
698 w.state = WindowState::Visible;
699 }
700 }
701 WindowMapOp::Hide => {
702 if let Some(w) = service.windows[id].as_mut() {
703 w.state = WindowState::Hidden;
704 }
705 }
706 WindowMapOp::Toggle => {
707 if let Some(w) = service.windows[id].as_mut() {
708 w.state = match w.state {
709 WindowState::Visible => WindowState::Hidden,
710 WindowState::Hidden => WindowState::Visible,
711 };
712 }
713 }
714 WindowMapOp::Delete => {
715 service.windows[id] = None;
716 if service.current_window == Some(id) {
717 service.current_window = None;
718 }
719 }
720 }
721 }
722 2
723 }
724
725 fn define_window(service: &mut Service, data: &[u8]) -> usize {
727 const TOTAL: usize = 7;
728 if data.len() < TOTAL {
729 return data.len().max(1);
730 }
731 let id = (data[0] - C1_DF0) as usize;
732 let p1 = data[1];
733 let p2 = data[2];
734 let p3 = data[3];
735 let p4 = data[4];
736 let p5 = data[5];
737 let p6 = data[6];
738
739 let creating = service.windows[id].is_none();
740 let w = service.windows[id].get_or_insert_with(Window::new);
741
742 w.priority = p1 & 0x07;
743 w.column_lock = (p1 >> 3) & 0x01 != 0;
744 w.row_lock = (p1 >> 4) & 0x01 != 0;
745 w.state = if (p1 >> 5) & 0x01 != 0 {
746 WindowState::Visible
747 } else {
748 WindowState::Hidden
749 };
750 w.relative_position = (p2 >> 7) & 0x01 != 0;
751 w.anchor_vertical = p2 & 0x7F;
752 w.anchor_horizontal = p3;
753 w.anchor_point = AnchorPoint::from_bits((p4 >> 4) & 0x0F);
754 w.row_count = (p4 & 0x0F) + 1;
755 w.column_count = (p5 & 0x3F) + 1;
756 w.window_style = (p6 >> 3) & 0x07;
757 w.pen_style = p6 & 0x07;
758
759 if creating {
760 apply_window_style(
762 w,
763 if w.window_style == 0 {
764 1
765 } else {
766 w.window_style
767 },
768 );
769 apply_pen_style(w, if w.pen_style == 0 { 1 } else { w.pen_style });
770 w.ensure_grid();
771 w.clear_text();
772 } else {
773 if w.window_style != 0 {
775 apply_window_style(w, w.window_style);
776 }
777 if w.pen_style != 0 {
778 apply_pen_style(w, w.pen_style);
779 }
780 w.ensure_grid();
781 }
782 service.current_window = Some(id);
783 TOTAL
784 }
785
786 fn set_window_attributes(service: &mut Service, data: &[u8]) -> usize {
788 const TOTAL: usize = 5;
789 if data.len() < TOTAL {
790 return data.len().max(1);
791 }
792 let p1 = data[1];
793 let p2 = data[2];
794 let p3 = data[3];
795 let p4 = data[4];
796 if let Some(w) = service.current() {
797 w.fill_opacity = Opacity::from_bits((p1 >> 6) & 0x03);
798 w.fill_color = Color::new((p1 >> 4) & 0x03, (p1 >> 2) & 0x03, p1 & 0x03);
799 let bt_lo = (p2 >> 6) & 0x03;
800 w.border_color = Color::new((p2 >> 4) & 0x03, (p2 >> 2) & 0x03, p2 & 0x03);
801 let bt_hi = (p3 >> 7) & 0x01;
802 w.border_type = EdgeType::from_bits((bt_hi << 2) | bt_lo);
803 w.word_wrap = (p3 >> 6) & 0x01 != 0;
804 w.print_direction = PrintDirection::from_bits((p3 >> 4) & 0x03);
805 w.scroll_direction = ScrollDirection::from_bits((p3 >> 2) & 0x03);
806 w.justify = Justify::from_bits(p3 & 0x03);
807 let _ = p4;
809 }
810 TOTAL
811 }
812
813 fn set_pen_attributes(service: &mut Service, data: &[u8]) -> usize {
815 const TOTAL: usize = 3;
816 if data.len() < TOTAL {
817 return data.len().max(1);
818 }
819 let p1 = data[1];
820 let p2 = data[2];
821 if let Some(w) = service.current() {
822 w.pen_offset = PenOffset::from_bits((p1 >> 2) & 0x03);
823 w.pen_size = PenSize::from_bits(p1 & 0x03);
824 w.italics = (p2 >> 7) & 0x01 != 0;
825 w.underline = (p2 >> 6) & 0x01 != 0;
826 w.edge_type = EdgeType::from_bits((p2 >> 3) & 0x07);
827 w.font_style = FontStyle::from_bits(p2 & 0x07);
828 }
829 TOTAL
830 }
831
832 fn set_pen_color(service: &mut Service, data: &[u8]) -> usize {
834 const TOTAL: usize = 4;
835 if data.len() < TOTAL {
836 return data.len().max(1);
837 }
838 let p1 = data[1];
839 let p2 = data[2];
840 let p3 = data[3];
841 if let Some(w) = service.current() {
842 w.fg_opacity = Opacity::from_bits((p1 >> 6) & 0x03);
843 w.fg_color = Color::new((p1 >> 4) & 0x03, (p1 >> 2) & 0x03, p1 & 0x03);
844 w.bg_opacity = Opacity::from_bits((p2 >> 6) & 0x03);
845 w.bg_color = Color::new((p2 >> 4) & 0x03, (p2 >> 2) & 0x03, p2 & 0x03);
846 w.border_color = Color::new((p3 >> 4) & 0x03, (p3 >> 2) & 0x03, p3 & 0x03);
848 }
849 TOTAL
850 }
851
852 fn set_pen_location(service: &mut Service, data: &[u8]) -> usize {
854 const TOTAL: usize = 3;
855 if data.len() < TOTAL {
856 return data.len().max(1);
857 }
858 let row = (data[1] & 0x0F) as usize;
859 let col = (data[2] & 0x3F) as usize;
860 if let Some(w) = service.current() {
861 w.set_pen_location(row, col);
862 }
863 TOTAL
864 }
865
866 fn put(service: &mut Service, ch: char) {
867 if let Some(w) = service.current() {
868 w.put_char(ch);
869 }
870 }
871
872 fn g0_char(b: u8) -> char {
874 if b == G0_MUSIC_NOTE {
875 '\u{266A}'
876 } else {
877 char::from(b)
878 }
879 }
880
881 fn g2_char(b: u8) -> char {
883 match b {
884 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}', _ => '_', }
905 }
906
907 fn g3_char(b: u8) -> char {
909 if b == 0xA0 {
910 '\u{1F4FA}' } else {
912 '_'
913 }
914 }
915
916 #[must_use]
919 pub fn windows(&self, service_number: usize) -> &[Option<Window>; NUM_WINDOWS] {
920 const EMPTY: [Option<Window>; NUM_WINDOWS] =
921 [None, None, None, None, None, None, None, None];
922 if service_number == 0 || service_number > NUM_SERVICES {
923 return &EMPTY;
924 }
925 &self.services[service_number - 1].windows
926 }
927
928 #[must_use]
931 pub fn service_text(&self, service_number: usize) -> String {
932 if service_number == 0 || service_number > NUM_SERVICES {
933 return String::new();
934 }
935 let svc = &self.services[service_number - 1];
936 let mut idxs: Vec<usize> = (0..NUM_WINDOWS)
937 .filter(|&i| {
938 svc.windows[i]
939 .as_ref()
940 .is_some_and(|w| w.state == WindowState::Visible)
941 })
942 .collect();
943 idxs.sort_by_key(|&i| {
944 svc.windows[i]
945 .as_ref()
946 .map_or((u8::MAX, i), |w| (w.priority, i))
947 });
948 let mut out = String::new();
949 for i in idxs {
950 if let Some(w) = svc.windows[i].as_ref() {
951 let t = w.text();
952 if t.is_empty() {
953 continue;
954 }
955 if !out.is_empty() {
956 out.push('\n');
957 }
958 out.push_str(&t);
959 }
960 }
961 out
962 }
963}
964
965#[derive(Clone, Copy)]
967enum WindowMapOp {
968 Clear,
969 Display,
970 Hide,
971 Toggle,
972 Delete,
973}
974
975fn apply_window_style(w: &mut Window, id: u8) {
977 w.border_type = EdgeType::None;
980 match id {
981 1 => style(w, Justify::Left, false, Some(Color::BLACK), Opacity::Solid),
982 2 => style(w, Justify::Left, false, None, Opacity::Transparent),
983 3 => style(
984 w,
985 Justify::Center,
986 false,
987 Some(Color::BLACK),
988 Opacity::Solid,
989 ),
990 4 => style(w, Justify::Left, true, Some(Color::BLACK), Opacity::Solid),
991 5 => style(w, Justify::Left, true, None, Opacity::Transparent),
992 6 => style(w, Justify::Center, true, Some(Color::BLACK), Opacity::Solid),
993 7 => {
994 w.justify = Justify::Left;
995 w.word_wrap = false;
996 w.print_direction = PrintDirection::TopToBottom;
997 w.scroll_direction = ScrollDirection::RightToLeft;
998 w.fill_color = Color::BLACK;
999 w.fill_opacity = Opacity::Solid;
1000 }
1001 _ => {}
1002 }
1003}
1004
1005fn style(w: &mut Window, j: Justify, ww: bool, fill: Option<Color>, op: Opacity) {
1006 w.justify = j;
1007 w.word_wrap = ww;
1008 w.print_direction = PrintDirection::LeftToRight;
1009 w.scroll_direction = ScrollDirection::BottomToTop;
1010 w.fill_opacity = op;
1011 if let Some(c) = fill {
1012 w.fill_color = c;
1013 }
1014}
1015
1016fn apply_pen_style(w: &mut Window, id: u8) {
1018 w.pen_size = PenSize::Standard;
1019 w.pen_offset = PenOffset::Normal;
1020 w.italics = false;
1021 w.underline = false;
1022 w.fg_color = Color::WHITE;
1023 w.fg_opacity = Opacity::Solid;
1024 match id {
1025 1 => pen(
1026 w,
1027 FontStyle::Default,
1028 EdgeType::None,
1029 Color::BLACK,
1030 Opacity::Solid,
1031 ),
1032 2 => pen(
1033 w,
1034 FontStyle::MonospacedSerif,
1035 EdgeType::None,
1036 Color::BLACK,
1037 Opacity::Solid,
1038 ),
1039 3 => pen(
1040 w,
1041 FontStyle::ProportionalSerif,
1042 EdgeType::None,
1043 Color::BLACK,
1044 Opacity::Solid,
1045 ),
1046 4 => pen(
1047 w,
1048 FontStyle::MonospacedSansSerif,
1049 EdgeType::None,
1050 Color::BLACK,
1051 Opacity::Solid,
1052 ),
1053 5 => pen(
1054 w,
1055 FontStyle::ProportionalSansSerif,
1056 EdgeType::None,
1057 Color::BLACK,
1058 Opacity::Solid,
1059 ),
1060 6 => pen(
1061 w,
1062 FontStyle::MonospacedSansSerif,
1063 EdgeType::Uniform,
1064 Color::BLACK,
1065 Opacity::Transparent,
1066 ),
1067 7 => pen(
1068 w,
1069 FontStyle::ProportionalSansSerif,
1070 EdgeType::Uniform,
1071 Color::BLACK,
1072 Opacity::Transparent,
1073 ),
1074 _ => {}
1075 }
1076}
1077
1078fn pen(w: &mut Window, font: FontStyle, edge: EdgeType, bg: Color, bg_op: Opacity) {
1079 w.font_style = font;
1080 w.edge_type = edge;
1081 w.bg_color = bg;
1082 w.bg_opacity = bg_op;
1083}
1084
1085#[cfg(test)]
1086mod tests {
1087 use super::*;
1088
1089 fn ccp(svc: u8, cmds: &[u8]) -> Vec<u8> {
1091 let mut sb = alloc::vec![(svc << 5) | (cmds.len() as u8)];
1092 sb.extend_from_slice(cmds);
1093 let size_code = (sb.len().div_ceil(2) + 1) as u8 & 0x3F;
1095 let mut packet = alloc::vec![size_code];
1096 packet.extend_from_slice(&sb);
1097 packet
1098 }
1099
1100 #[test]
1105 fn define_window_worked_example() {
1106 let mut dec = Cea708Decoder::new();
1107 let packet = ccp(1, &[0x9A, 0x38, 0x4A, 0xD1, 0x8B, 0x0F, 0x11]);
1108 dec.push_packet(&packet);
1109 let w = dec.windows(1)[2].as_ref().expect("window 2 defined");
1110 assert_eq!(w.state, WindowState::Visible);
1111 assert!(w.row_lock);
1112 assert!(w.column_lock);
1113 assert_eq!(w.priority, 0);
1114 assert!(!w.relative_position);
1115 assert_eq!(w.anchor_vertical, 74);
1116 assert_eq!(w.anchor_horizontal, 209);
1117 assert_eq!(w.anchor_point, AnchorPoint::BottomRight);
1118 assert_eq!(w.row_count, 12);
1119 assert_eq!(w.column_count, 16);
1120 assert_eq!(w.window_style, 2);
1121 assert_eq!(w.pen_style, 1);
1122 }
1123
1124 #[test]
1127 fn swa_border_type_split() {
1128 let mut dec = Cea708Decoder::new();
1129 let packet = ccp(
1131 1,
1132 &[
1133 0x98, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x64, 0x53, 0x88, 0x22, ],
1136 );
1137 dec.push_packet(&packet);
1138 let w = dec.windows(1)[0].as_ref().expect("window 0");
1139 assert_eq!(w.border_type, EdgeType::RightDropShadow);
1140 }
1141
1142 #[test]
1144 fn decode_text() {
1145 let mut dec = Cea708Decoder::new();
1146 let packet = ccp(
1147 1,
1148 &[
1149 0x98, 0x20, 0x00, 0x00, 0x02, 0x0F, 0x00, b'H', b'i',
1151 ],
1152 );
1153 dec.push_packet(&packet);
1154 assert_eq!(dec.service_text(1), "Hi");
1155 }
1156
1157 #[test]
1159 fn two_services_multi_window() {
1160 let mut dec = Cea708Decoder::new();
1161 let s1_block = [0x98, 0x20, 0x00, 0x00, 0x00, 0x0F, 0x00, b'S', b'1'];
1163 let s2_block = [0x99, 0x20, 0x00, 0x00, 0x00, 0x0F, 0x00, b'S', b'2'];
1165 let mut data = Vec::new();
1166 data.push((1 << 5) | (s1_block.len() as u8));
1167 data.extend_from_slice(&s1_block);
1168 data.push((2 << 5) | (s2_block.len() as u8));
1169 data.extend_from_slice(&s2_block);
1170 let size_code = (data.len().div_ceil(2) + 1) as u8 & 0x3F;
1171 let mut packet = alloc::vec![size_code];
1172 packet.extend_from_slice(&data);
1173 dec.push_packet(&packet);
1174 assert_eq!(dec.service_text(1), "S1");
1175 assert_eq!(dec.service_text(2), "S2");
1176 assert!(dec.windows(1)[0].is_some());
1177 assert!(dec.windows(2)[1].is_some());
1178 }
1179
1180 #[test]
1181 fn carriage_return_rolls_up() {
1182 let mut w = Window::new();
1183 w.row_count = 2;
1184 w.column_count = 10;
1185 w.ensure_grid();
1186 w.put_char('A');
1187 w.carriage_return();
1188 w.put_char('B');
1189 w.carriage_return(); w.put_char('C');
1191 assert_eq!(w.text(), "B\nC");
1192 }
1193
1194 #[test]
1195 fn g0_music_note() {
1196 assert_eq!(Cea708Decoder::g0_char(0x7F), '\u{266A}');
1197 assert_eq!(Cea708Decoder::g0_char(b'A'), 'A');
1198 }
1199
1200 #[test]
1201 fn no_panic_on_arbitrary_input() {
1202 let inputs: &[&[u8]] = &[
1204 &[],
1205 &[0x00],
1206 &[0xFF],
1207 &[0x01, 0x98], &[0x3F, 0x80, 0x90, 0x91, 0x92, 0x97, 0x98], &[0x20, 0xEE, (7 << 5) | 1], &[0x10, 0x9A], &[0x18, 0x00], ];
1213 for inp in inputs {
1214 let mut dec = Cea708Decoder::new();
1215 dec.push_packet(inp);
1216 }
1217 let mut dec = Cea708Decoder::new();
1219 let mut x: u32 = 0x1234_5678;
1220 let mut buf = Vec::new();
1221 for _ in 0..4096 {
1222 x = x.wrapping_mul(1_103_515_245).wrapping_add(12_345);
1223 buf.push((x >> 16) as u8);
1224 }
1225 dec.push_packet(&buf);
1226 let mut dec2 = Cea708Decoder::new();
1228 let triplets: Vec<CcTriplet> = buf
1229 .chunks(2)
1230 .map(|c| CcTriplet {
1231 cc_valid: true,
1232 cc_type: CcType::Dtvcc708Data,
1233 cc_data_1: c[0],
1234 cc_data_2: *c.get(1).unwrap_or(&0),
1235 })
1236 .collect();
1237 dec2.push_triplets(&triplets);
1238 }
1239}